Compare commits
740 Commits
v0.1.1
...
deepcrayon
Author | SHA1 | Date |
---|---|---|
jebba | a8595591f0 | |
jebba | cd8161a10d | |
jebba | f4f7612655 | |
jebba | e2a3404b06 | |
jebba | afabba64a9 | |
jebba | 473df3d139 | |
jebba | a337163a2d | |
jebba | 65be7edda2 | |
jebba | 0c93f8803d | |
jebba | 8fd633c6d3 | |
jebba | 2771784113 | |
jebba | f05eff6b13 | |
jebba | 7ba8d7f143 | |
jebba | 14c92e8bd5 | |
jebba | 4b352b5bbc | |
jebba | 124717da69 | |
jebba | 2f1c4bc3af | |
jebba | 0ce520d1d5 | |
jebba | 8ef0a2a3d5 | |
jebba | 27c9434383 | |
jebba | fb64efefdc | |
kaladin | 1f6cddd4ab | |
CodeFace | 78c8a9d499 | |
Vitalij Dovhanyč | d8640f4e2f | |
vdovhanyc | d588904fb3 | |
Martin Boehm | e6e6e64351 | |
jsimon | 61c2834002 | |
Martin Boehm | 0ae8ba57a2 | |
Martin Boehm | 8f3106d009 | |
vdovhanych | 099a158f8c | |
Perlover | 37c7f4fbd1 | |
Martin Boehm | db597c1f66 | |
Martin Boehm | dcbcb99055 | |
Martin Boehm | 212b767925 | |
WO | 3fe28d185c | |
Rikard Wissing | d0a1cb29f6 | |
Martin Boehm | 2be5930862 | |
Yusaku Senga | 5fdc26bc14 | |
Martin Boehm | 7e54336e0c | |
Martin Boehm | 7dffe2e0f9 | |
Martin Boehm | d992369426 | |
David Hill | d97b5e14e8 | |
JoHnY | 9df39273ea | |
Pavol Rusnak | 66c072bf25 | |
Jin Eguchi | 505d859f91 | |
Dehumanizer77 | d631bf9265 | |
Martin Kuvandzhiev | 295b630ec8 | |
Dehumanizer77 | b4149946bd | |
Dehumanizer77 | 6981222a43 | |
Peter John Bushnell | bb9fce02cb | |
Jin Eguchi | 077e637093 | |
araarakelyan1985 | 15b88ef23d | |
Martin | 4697d756e0 | |
Martin | 4766110255 | |
Tomas Susanka | 360cac85f6 | |
jackielove4u | 554041c32c | |
CodeFace | d12e6551ea | |
Martin Boehm | 96e8329171 | |
Martin Boehm | f094ee578d | |
Martin Boehm | 00352cb5fe | |
CryptoManiac | c0c2dc4151 | |
Martin Boehm | da1c0d762e | |
Martin Boehm | fc267ed2f4 | |
Martin Boehm | 69d13e0688 | |
Martin Boehm | 248de3cb34 | |
Martin Boehm | 636167c72a | |
Martin Boehm | 24a783be50 | |
Martin Boehm | 579b42cf27 | |
Martin Boehm | 576b8b57b7 | |
Martin Kuvandzhiev | 786047f8c2 | |
Dehumanizer77 | 3ccfd181b7 | |
Dehumanizer77 | 6274f4b3d4 | |
Dehumanizer77 | 2b786c9832 | |
Dehumanizer77 | bc009454d0 | |
Dehumanizer77 | 5e7d0e9f75 | |
Pavol Rusnak | c915f35224 | |
Pavol Rusnak | 3369295e10 | |
hewigovens | 5534372e7c | |
Martin Boehm | fc25200ff8 | |
Martin Boehm | 3d9954bf79 | |
nezero | 214d0144ef | |
Liam Alford | ec79702bab | |
Scotty0448 | e666e7c5a4 | |
JoHnY | 4832205f45 | |
1000101 | b05346b1a1 | |
WO | dcf77a5680 | |
1000101 | 7f1cf09d05 | |
Martin | a1993173ab | |
jackielove4u | bea6b6230f | |
jackielove4u | 72486c606f | |
1000101 | a8ee6aefb0 | |
1000101 | 52cbc7162d | |
Pavol Rusnak | 81ce876d8b | |
Braydon Fuller | 7b70ee0ad0 | |
Braydon Fuller | a83cb7684f | |
Pavol Rusnak | 0f4eadd935 | |
1000101 | e66fa79383 | |
1000101 | f99406e9cf | |
1000101 | be73064223 | |
Panu | 79907e7aa5 | |
1000101 | 2fb1e779c0 | |
1000101 | a530f5612a | |
Martin Boehm | ab285c6b05 | |
Martin Boehm | 17c9080135 | |
1000101 | 791948623e | |
Scotty0448 | af5e8f18ba | |
root | 07ac3c8401 | |
Martin Boehm | 83616bce83 | |
codeface | 22145d0cc2 | |
Dehumanizer77 | 92ae2052c3 | |
Dehumanizer77 | 30149e51d2 | |
Martin Boehm | abb6453fb3 | |
Martin Boehm | eb4e10ac67 | |
1000101 | 5350027e1d | |
1000101 | 7d6c61623e | |
Martin Boehm | 994567aed9 | |
Martin Boehm | dd7964297d | |
Martin Boehm | 3be3bb5c3d | |
Martin Boehm | 0a3ea6e225 | |
Martin Boehm | bc001ce3a3 | |
Martin Boehm | 76324be8ec | |
Martin Boehm | 01d8e48e73 | |
Martin Boehm | ff607bc334 | |
Martin Boehm | e60c320ae7 | |
Martin Boehm | dd2dc6b2ee | |
wakiyamap | b957ed66ab | |
Adam Collier | 3ebe99edb2 | |
v | bad9f992e1 | |
jackielove4u | 707ac28954 | |
Martin Boehm | b6961ca600 | |
Martin Boehm | 5492a51534 | |
Martin Boehm | a7d95a49df | |
Martin Boehm | ee3217aba8 | |
Martin Boehm | c3d58f0649 | |
Martin Boehm | 180b5655d5 | |
hewigovens | 3ba7289587 | |
ciripel | a34ee217b5 | |
WO | 11e9de2ffe | |
Martin Boehm | 81a1acd6f8 | |
Martin Boehm | 997fa661e7 | |
Martin Boehm | 8a926a0799 | |
Martin Boehm | 828e10b629 | |
Scotty0448 | a6fd137283 | |
Dehumanizer77 | f97d23590c | |
Dehumanizer77 | 945827c330 | |
Dehumanizer77 | 42bc7d2ba3 | |
Adam Collier | ddd981a405 | |
Dehumanizer77 | ea6a7e0db6 | |
Scotty0448 | 7eb4675f54 | |
ciripel | 124dee84fa | |
Martin Boehm | 5f1957b4ff | |
Martin Boehm | c43a7a4feb | |
ilmango | e99a8eba65 | |
Martin Boehm | 2a3c5426ca | |
Kirill Fomichev | c2e32b0a25 | |
Martin Boehm | 4338d10dcb | |
TheTrunk | b4bddc8c0e | |
JoHnY | c705600aae | |
Martin Boehm | 48584b3070 | |
David Hill | 395db88a60 | |
David Hill | 8f8ade727c | |
David Hill | 13527bda06 | |
Martin Boehm | d1fd66597b | |
Martin Boehm | 6d1725717e | |
Martin Boehm | 836568b716 | |
Martin Boehm | 7d8ee6bc1d | |
Dehumanizer77 | 0de7475d89 | |
Martin Boehm | e47fd242cf | |
Martin Boehm | 53cc6237a7 | |
Martin Boehm | a6c01534f2 | |
Martin Boehm | f761dbec2a | |
omotenashicoin-project | 2d430aa80b | |
Martin Boehm | 1d0a985c9e | |
Martin Boehm | a4da2f3865 | |
Martin Boehm | 9feccfdb2e | |
Martin Boehm | 1b713308a3 | |
Martin Boehm | 7f46fbab0d | |
Martin Boehm | d583028721 | |
Martin Boehm | 2e37cbb974 | |
Martin Boehm | 00b0a402ea | |
Martin Boehm | fd4181d03f | |
Martin Boehm | 47173774f6 | |
Martin Boehm | 5a2b67bc9a | |
Martin Boehm | 2493c3d1af | |
Martin Boehm | 58f426207e | |
Martin Boehm | 0751ed452c | |
Martin Boehm | 273b880245 | |
judong | d6de5b8048 | |
Martin Boehm | c33ab2b8d5 | |
f4r4 | 7a5881c7c9 | |
WO | 55e1861eae | |
JoHnY | d5b304c76a | |
Martin Boehm | 7e35bac99c | |
JoHnY | 744fd45c06 | |
jackielove4u | bd5726508f | |
Vladyslav Burzakovskyy | 82debaa50e | |
Martin Boehm | 95ac05b280 | |
Martin Boehm | aceadbb10c | |
Vladyslav Burzakovskyy | f0ccab3e01 | |
Vladyslav Burzakovskyy | 9455417e8b | |
Vladyslav Burzakovskyy | 4b63b483e4 | |
Vladyslav Burzakovskyy | 357ad63bde | |
Vladyslav Burzakovskyy | 4ca66f3b1d | |
Vladyslav Burzakovskyy | 87065d13ef | |
Vladyslav Burzakovskyy | 4b564510e0 | |
Vladyslav Burzakovskyy | 5b41a37da6 | |
Vladyslav Burzakovskyy | 8ea853c388 | |
Vladyslav Burzakovskyy | 171b7f9b9d | |
Vladyslav Burzakovskyy | 6f06b549df | |
Vladyslav Burzakovskyy | 729c35334a | |
Dehumanizer77 | a8f436fe4c | |
WO | a89a1fb85a | |
Scotty0448 | 6429290fdc | |
Dali | e4231a2eaa | |
Vladyslav Burzakovskyy | 29af6eb34d | |
Vladyslav Burzakovskyy | acaebd41c0 | |
Vladyslav Burzakovskyy | c7730de36a | |
Vladyslav Burzakovskyy | 2399f245e4 | |
Vladyslav Burzakovskyy | 7f992e9eb8 | |
Martin | 5a604441b4 | |
Martin | 20459a2f46 | |
Vladyslav Burzakovskyy | 94977dc5ef | |
Vladyslav Burzakovskyy | 6919055e30 | |
Vladyslav Burzakovskyy | 918e032bfe | |
Vladyslav Burzakovskyy | d162348d36 | |
Vladyslav Burzakovskyy | db8e0938df | |
Vladyslav Burzakovskyy | 9cec5424e7 | |
Vladyslav Burzakovskyy | c58b1538d6 | |
Vladyslav Burzakovskyy | bc0e95f174 | |
Vladyslav Burzakovskyy | bef572c494 | |
Vladyslav Burzakovskyy | 14c64410f7 | |
Martin Boehm | f2ff7985b1 | |
random.zebra | b7a430574f | |
Martin Boehm | 1ed7b3f2d9 | |
Martin Boehm | 35c9da1ce8 | |
Martin | 1ce7bc1406 | |
Martin Boehm | 225ac85a2a | |
Martin Boehm | 0340cef13c | |
Martin Boehm | 15e2c0bf41 | |
Martin Boehm | 1cec22ecba | |
Vladyslav Burzakovskyy | f6111af5da | |
Jan Hrnko | e2b34afb9c | |
Jan Hrnko | 81ca37e51f | |
Jan Hrnko | beeddef239 | |
Jan Hrnko | cd3cad423a | |
Jan Hrnko | 3ac22a7571 | |
thebevrishot | 76beadef8b | |
Jan Hrnko | 0a177d554c | |
Jan Hrnko | 5c4f1d2674 | |
Jan Hrnko | 684712680f | |
Jan Hrnko | 5c7b1c9a0c | |
Jan Hrnko | 80aa7bc4a4 | |
Jan Hrnko | d092c42e21 | |
Martin Boehm | 6f294a6241 | |
Jan Hrnko | 2a818d8780 | |
Jan Hrnko | f09b8ef683 | |
Martin Boehm | c913a022ef | |
thebevrishot | 630ef1d308 | |
Jan Hrnko | f94878b234 | |
Martin Boehm | 62208b9634 | |
Jan Hrnko | b3367f8f8c | |
Martin Boehm | bf3d822b87 | |
Jan Hrnko | 0baf02c6b8 | |
Jan Hrnko | 0f4f71d2b0 | |
Martin Boehm | da714b5299 | |
Martin Boehm | 5600e0d30a | |
Martin Boehm | e754a6c0fd | |
Jan Hrnko | d26e1fbb3b | |
Liam Alford | 3853520a0f | |
Jan Hrnko | f6ea86e52b | |
Martin Boehm | 7ae81a704b | |
Mykola | c9b72b7aee | |
Mykola | a400ca188a | |
Mykola | c44ce00114 | |
Jan Hrnko | fa2ee739c6 | |
thebevrishot | c3ba9a7b95 | |
WO | 0aadb241fb | |
Jan Hrnko | a7b42f1de0 | |
Martin Boehm | e4c6d23389 | |
Martin Boehm | f7bbffa4c9 | |
Martin Boehm | c45312edf1 | |
Jan Hrnko | dfafd780dc | |
Liam Alford | 4134934031 | |
Jan Hrnko | bb1b909361 | |
Martin Boehm | eb4f049912 | |
Mykola | bd0848dbbe | |
Mykola | ff415ae394 | |
Mykola | 42a208be15 | |
Jan Hrnko | 1c929f2a40 | |
thebevrishot | 21fe8082dd | |
WO | f2e4e67c4d | |
Martin Boehm | 6cfb881a04 | |
Jan Hrnko | bc4b1905f5 | |
Martin Boehm | 262ca3e2e4 | |
Jan Hrnko | f4501e7e1f | |
Min Khang Aung | 6645178782 | |
Scotty0448 | 5d398cc0d7 | |
Martin Boehm | 1c192f6d0b | |
Jan Hrnko | 83b1552dfa | |
Martin Boehm | 4eff57189d | |
Martin Boehm | ac9a721cc6 | |
Jan Hrnko | d3931953d5 | |
Martin Boehm | 8851c649d5 | |
migwi | f28c6bcf61 | |
vlddm | 86a0b5783d | |
Jan Hrnko | 1bc9feb7fe | |
代码脸 | fe24ec2913 | |
atomlab | 28a8641c8e | |
migwi | d4a7fcabd9 | |
Jan Hrnko | 0f39657006 | |
Min Khang Aung | 24725a21a9 | |
TheCreator | 12c4217f94 | |
JoHnY | 8829cdf525 | |
Jan Hrnko | 485887a130 | |
JoHnY | 1d1b02388d | |
Enrique | 00253004ee | |
Martin Boehm | 9d5793a8e3 | |
Jan Hrnko | 1ee09331c9 | |
Scotty0448 | 2d62e3c306 | |
Martin Boehm | a4594bf073 | |
JoHnY | 5431b19cef | |
wakiyamap | c689eedece | |
Jan Hrnko | 431473c30d | |
Martin Boehm | 3a8da67752 | |
Jan Hrnko | d089a84ba9 | |
Migwi Ndung'u | 5ea4bbded6 | |
James Scaur | d6375a19dd | |
WO | 44a0e5823f | |
WakiyamaP | 9f2160a654 | |
Jan Hrnko | 91a26e4d40 | |
Martin Boehm | 094be77ceb | |
Jan Hrnko | e234186f6a | |
Jan Hrnko | 7430fa6051 | |
Jan Hrnko | a986082fda | |
Mohak Shah | 5341b3ebde | |
Vladyslav Burzakovskyy | 4224aab5f2 | |
Martin Boehm | 298ec5ea35 | |
Martin Boehm | be393c6d5d | |
Scotty0448 | a89be9cc07 | |
Martin Boehm | d01fc3d914 | |
Martin Boehm | d450f1d315 | |
Jan Hrnko | 231291cbc6 | |
Martin Boehm | 39f2c73f3e | |
Jan Hrnko | 45f5d115d6 | |
Jan Hrnko | b33b1771d8 | |
David Hill | a6709d523f | |
David Hill | f49cc0719b | |
Panu | 63b4719179 | |
Martin Boehm | 4ba0769433 | |
Jan Hrnko | 8b9fe50677 | |
migwi | 40316727ac | |
David Hill | 6087b985bc | |
TheTrunk | 0739825a6f | |
Scotty0448 | a6de1a97c4 | |
wakiyamap | ee1b71cd15 | |
Martin Boehm | 8b8669f7c9 | |
Migwi Ndung'u | 91691ed7e7 | |
Jan Hrnko | 72143f027c | |
TheTrunk | 785dd0153d | |
Martin Boehm | d37a46f8e9 | |
CodeFace | 4eaeb25a5e | |
CodeFace | 5b2b7ce3d6 | |
Jan Hrnko | b1810dc06b | |
Martin Boehm | 104f6f9a9d | |
Ján Hrnko | d6883a5f35 | |
Martin Boehm | bf461d0737 | |
Martin Boehm | 701348c96d | |
Vladyslav Burzakovskyy | 20eed82e48 | |
Martin Boehm | 6447cdb1b7 | |
Jan Hrnko | 7284c7cbfb | |
Jan Hrnko | 25027ad92e | |
artem | 3464d1cf9b | |
thebevrishot | cbba7ab8c0 | |
Martin Boehm | 49b50f3353 | |
Martin Boehm | 480523a2b3 | |
Martin Boehm | d52bd0352d | |
Martin Boehm | d5f11561ac | |
Martin Boehm | c4487ac94b | |
Martin Boehm | c916d46763 | |
Martin Boehm | 34e5599362 | |
Martin Boehm | d26995a1e4 | |
Martin Boehm | d7d596bf4b | |
Martin Boehm | c409a350c9 | |
Martin Boehm | 5c2b9f763e | |
WO | 950520673d | |
Martin Boehm | bbc6ea4cec | |
Jan Hrnko | 5f6a8ca605 | |
Enrique | bf97a44987 | |
Scotty0448 | e87fb62b1d | |
Sotiris Blad | ce91b9e0f8 | |
kiss1987f4 | a4e3db6fbb | |
Ján Hrnko | 3e67307a5d | |
Martin Boehm | b33414cdba | |
Martin Boehm | 7f63acaaff | |
Petr Kracík | 97fc295ee4 | |
Petr Kracík | ebf6de9dde | |
Martin Boehm | 6f603e2424 | |
Martin Boehm | 6ec0175937 | |
Martin Boehm | 42d3ecdd41 | |
Martin Boehm | 8cd94070ce | |
Martin Boehm | 749e534a26 | |
Martin Boehm | c7b36189cd | |
Martin Boehm | 3f17333be6 | |
Martin Boehm | 9ba294da39 | |
Martin Boehm | 77bea0ecdc | |
Martin Boehm | b6e5055821 | |
Petr Kracík | aa69121cc2 | |
Martin Boehm | bb9bd7fea7 | |
Martin Boehm | 9e98a4eb39 | |
Martin Boehm | f793d3390c | |
Petr Kracík | 3c9a0a3833 | |
Martin Boehm | 0440a1d6bb | |
Petr Kracík | 9a021b6723 | |
Martin Boehm | 8b07783134 | |
Martin Boehm | ce0529c130 | |
Martin Boehm | 61e096b3f9 | |
Martin Boehm | 733c966094 | |
Martin Boehm | 995d5c66b5 | |
Martin Boehm | 5689be20f3 | |
Martin Boehm | 917840d6b2 | |
Martin Boehm | 90d7a7f2da | |
Martin Boehm | 1e5ada2ebf | |
Martin Boehm | 984618a0ed | |
Martin Boehm | 228d40e7a5 | |
Martin Boehm | 239ccdfd78 | |
Martin Boehm | 55e39f0ea4 | |
Martin Boehm | 942c95add8 | |
Martin Boehm | 34475ff5e8 | |
Martin Boehm | 0312db4d9d | |
Martin Boehm | b348a53664 | |
Jan Hrnko | bed1a02a3e | |
JoHnY | 0316ead232 | |
Scotty0448 | 78ffee5d3d | |
motty | dcfb468d76 | |
Jan Hrnko | 9da5f0cc35 | |
Ján Hrnko | 93e4b3f4e1 | |
TheTrunk | 2969b06ce2 | |
TheTrunk | f21ba5573b | |
TheTrunk | da03baf83a | |
CodeFace | 18bea50b69 | |
Sotiris Blad | beb8244570 | |
Martin Boehm | 7c4e8c5750 | |
Martin Boehm | 2d3e7c9612 | |
Petr Kracík | 5322cea4a5 | |
Martin Boehm | 629e07e6a4 | |
Petr Kracík | 1da82a070b | |
Martin Boehm | e8bda6cabe | |
Sotiris Blad | ae4cf6b029 | |
Martin Boehm | 10d08161ba | |
y-chan | 39632a8d39 | |
y-chan | 24b7dca29b | |
y-chan | fc528a7056 | |
y-chan | 591b1937dc | |
y-chan | 5aabc52a62 | |
y-chan | f791467efd | |
y-chan | 09fd01c0a5 | |
y-chan | 4dc5721384 | |
y-chan | 03f910cc30 | |
y-chan | 4a2e7602b1 | |
y-chan | 70ace81011 | |
Petr Kracík | 32885e54e7 | |
Petr Kracík | c34af64f0c | |
Martin Boehm | d23d0a9e4f | |
ilmango | 68fed56d12 | |
wakiyamap | b51229a114 | |
romanornr | 35f3278771 | |
Cronos | 54e5a76896 | |
Martin Boehm | 9642e306ac | |
Martin Boehm | 3ef9426229 | |
Martin Boehm | dffcded306 | |
Martin Boehm | 8fb4772331 | |
Martin Boehm | 230b5e5d32 | |
Petr Kracík | 8c1691be8d | |
Petr Kracík | 8b38d3b7ea | |
Petr Kracík | cd0202891b | |
Petr Kracík | 9abff90701 | |
Martin Boehm | 9f8cf071dd | |
Petr Kracík | 974f31e135 | |
Petr Kracík | 8ae7a61dab | |
Martin Boehm | b367e25194 | |
Martin Boehm | a6d5f4d421 | |
Martin Boehm | add504b57e | |
Martin Boehm | 7ac877f160 | |
Jin Eguchi | 02c65bc67d | |
Cronos | f108559d7c | |
Jin Eguchi | ef6078d14e | |
Cronos | 1392a884ca | |
Martin Boehm | 127d24be06 | |
Martin Boehm | e394350e85 | |
Martin Boehm | 4b0addbe98 | |
Martin Boehm | b227a8e777 | |
Martin Boehm | 3f973bf47d | |
Martin Boehm | 4435dbbfb4 | |
Martin Boehm | 4512a57134 | |
Martin Boehm | b64d76d8f9 | |
Martin Boehm | 47f798dbaa | |
Martin Boehm | 870354bc90 | |
Martin Boehm | c19f6bfb42 | |
Martin Boehm | 4bc196f599 | |
Martin Boehm | 987aec47f9 | |
Martin Boehm | f2dc4a56d8 | |
Yura Pakhuchiy | 827cbcd1d8 | |
Yura Pakhuchiy | 1c290db225 | |
Martin Boehm | c813f76336 | |
Martin Boehm | d2928b3516 | |
Martin Boehm | a8735c460d | |
kiss1987f4 | 954619efe9 | |
Martin Boehm | fa4a11c3a7 | |
Petr Kracík | 9cd35b3616 | |
Petr Kracík | 72f4ad9101 | |
Martin Boehm | 1cd4c207be | |
Martin Boehm | ee4ecc2bb0 | |
Petr Kracík | 57ce874208 | |
Petr Kracík | e6863b0420 | |
Martin Boehm | d734c7d489 | |
Martin Boehm | 35b24b03b9 | |
kiss1987f4 | 12fbdedf6e | |
Martin Boehm | f01273ab10 | |
Petr Kracík | 0bfe1d5558 | |
Petr Kracík | 955209db6b | |
Martin Boehm | c074873452 | |
Martin Boehm | 8c471ed20d | |
Petr Kracík | 713f928a57 | |
Petr Kracík | 98a637e952 | |
Martin Boehm | b8eca9c25e | |
Martin Boehm | ce3c7c5e66 | |
Martin Boehm | 68575b2786 | |
Martin Boehm | 283a039290 | |
Matej Čamaj | c0cebd4ce6 | |
Martin Boehm | 6ac14f163a | |
Martin Boehm | df952ffb05 | |
Martin Boehm | c7e884dd81 | |
Martin Boehm | bae1050ce4 | |
Martin Boehm | 6784ecd6b3 | |
Martin Boehm | 1d8389bb2c | |
Martin Boehm | 846b20c795 | |
Martin Boehm | 133d6f9c59 | |
Martin Boehm | 46e49daa9c | |
Martin Boehm | e7b3421603 | |
codeface | 41433a9684 | |
Martin Boehm | efca04a3fb | |
Martin Boehm | e06ff194de | |
Martin Boehm | 70330273c0 | |
Martin Boehm | 3df1cc81ed | |
Martin Boehm | 3d10d9c2c5 | |
ilmango | 3551c90590 | |
人畜無害 | 52308ad8af | |
Martin Boehm | 3f7ac68865 | |
Martin Boehm | 1540dc940d | |
Gruve_p | 4b15e46602 | |
Martin Boehm | bd89ab8256 | |
Martin Boehm | 1c35c632cb | |
Martin Boehm | e54998dccc | |
Martin Boehm | 39f9f46d3d | |
Martin Boehm | dda96b4a8f | |
Martin Boehm | 7b590d9958 | |
Martin Boehm | 87b778d648 | |
Martin Boehm | 66854715cf | |
Martin Boehm | 881dab35f5 | |
Martin Boehm | ffbbbb0b44 | |
Martin Boehm | 46001a9fa5 | |
Martin Boehm | 22e73c44cc | |
Martin Boehm | 0e7714dfd1 | |
Martin Boehm | c67c8d4008 | |
Martin Boehm | bcd4b8ed4f | |
Martin Boehm | 9382c7a9c0 | |
Martin Boehm | e7fc52760e | |
wlc- | 0d26854c35 | |
Gruve_p | 16558d23c0 | |
Martin Boehm | c7e88ff2be | |
Martin Boehm | e1eadda6bf | |
Petr Kracík | 04d843f593 | |
Petr Kracík | 7042a45ba5 | |
Martin Boehm | 347e1d5967 | |
Martin Boehm | c7808b87d5 | |
Martin Boehm | 88d9e09ad4 | |
Petr Kracík | d41adf0a4e | |
Martin Boehm | 0546a2609d | |
Martin Boehm | 593247c364 | |
Martin Boehm | 3edfebca65 | |
Martin Boehm | b8672a0fff | |
Martin Boehm | f3ec1e6b77 | |
WO | 6460ca3872 | |
WO | d28a0beab6 | |
WO | 47a77c35c5 | |
WO | bee0f8c709 | |
Petr Kracík | 7609ab7c76 | |
Petr Kracík | 166bf74162 | |
Petr Kracík | d314140dde | |
Martin Boehm | 4846af9f60 | |
Martin Boehm | 63fb910ecb | |
Martin Boehm | 6b0a4960fd | |
Jeremiah Buddenhagen | 28649b5d2c | |
Martin Boehm | 273436f109 | |
Martin Boehm | 1ca4a0cfc7 | |
Martin Boehm | ae332547ba | |
Martin Boehm | 64c8ae9a62 | |
thebevrishot | 94253c122f | |
Martin Boehm | 266b0575b6 | |
Martin | b2af4c1242 | |
Petr Kracík | 42686166d8 | |
Petr Kracík | e64c0a52d4 | |
Martin Boehm | 9d3cd3b3e9 | |
Martin Boehm | 57b40ad6dc | |
Martin Boehm | 8f1f1c87ac | |
Martin Boehm | 225830d3e9 | |
Martin Boehm | b670b4fede | |
Martin Boehm | 27dba68319 | |
random.zebra | 818288cd16 | |
Martin | 8efaa4ebc3 | |
Jeremiah Buddenhagen | 87624f44ca | |
Martin Boehm | 986275bb76 | |
Martin Boehm | dafe19cf29 | |
Martin Boehm | 42f6821cfa | |
Martin Boehm | e27821f231 | |
Martin Boehm | 499d65460f | |
WO | e83511469b | |
Martin Boehm | e6d7cea761 | |
Martin Boehm | 06a1e96190 | |
Martin Boehm | 171e15c9f7 | |
Martin | 2975785de7 | |
Petr Kracík | 5b8954b74f | |
Petr Kracík | 024b25dff7 | |
Martin Boehm | f3cbd5e20e | |
Martin Boehm | 828a11b6c3 | |
Martin Boehm | b411ce881e | |
Putta Khunchalee | 8e82b3da0f | |
Martin Boehm | b593643f63 | |
Martin Boehm | d1f30e27cf | |
Martin Boehm | 522e6528d3 | |
FujiCoin | 3cca225c14 | |
Martin Boehm | 9af314f7aa | |
Martin Boehm | 3a8e854384 | |
Martin | 9069c25584 | |
Petr Kracík | 19d6869465 | |
Martin Boehm | 1c29283bf0 | |
Martin Boehm | 05daf85c10 | |
Martin Boehm | ac0c359fbe | |
Martin | aefbc33a01 | |
Martin Boehm | 210652328f | |
rikardwissing | ffa745d390 | |
rikardwissing | 24bedfec26 | |
Martin Boehm | c2a581ea72 | |
Martin Boehm | f109513464 | |
Martin Boehm | 4bd43c5f47 | |
Martin Boehm | 3ca593aff1 | |
Martin Boehm | 8c4fcf4441 | |
Martin Boehm | 341bf331c1 | |
Martin Boehm | d77fb35bb5 | |
Martin Boehm | 51bb4fe109 | |
Martin Boehm | 00c1fd6661 | |
Martin Boehm | 2c3af5129e | |
Martin Boehm | 07108b8c4f | |
Martin | 16c32098f6 | |
Petr Kracík | f8d62b9190 | |
Martin Boehm | 1e8506bdf7 | |
Martin Boehm | 97e0844a4b | |
Martin Boehm | 1b7218530b | |
Martin Boehm | ab077d882c | |
Martin Boehm | cb7a90bcc5 | |
Martin | 2b85054e03 | |
Martin Boehm | d82bd8e7b8 | |
Petr Kracík | 6b97265b18 | |
Martin Boehm | 9518f94714 | |
Martin Boehm | 26f293fc42 | |
Martin Boehm | 44f07734ce | |
Martin Boehm | 2552a429e8 | |
Martin | e64109726e | |
Petr Kracík | 9086b9111e | |
Martin Boehm | 8e57d23d66 | |
Martin Boehm | 4e040cb1f0 | |
Martin Boehm | e24115da83 | |
Martin Boehm | a7b8994419 | |
Martin Boehm | 7da714bec4 | |
Martin Boehm | 81e105dd4f | |
Martin Boehm | e12641ae7d | |
Martin Boehm | 9288a12f1d | |
Martin Boehm | bab500d3f8 | |
Martin Boehm | 2e9f87e39d | |
Martin Boehm | 7edea80209 | |
Martin Boehm | 83f9a525b0 | |
Martin | cbf9b8097b | |
Martin Boehm | 3ff1a86ab1 | |
Petr Kracík | 7ef18d38e8 | |
Martin Boehm | f332c0aa9e | |
Martin Boehm | a04b2b67b5 | |
Martin Boehm | 35a13e0647 | |
Martin Boehm | e9e6b472b6 | |
Martin Boehm | 9c142663ce | |
Martin | 964a7a4b17 | |
wlc- | 23a833a5fd | |
Martin Boehm | d01081fd83 | |
Martin Boehm | 4b39519750 | |
Martin Boehm | 13f7b48ae6 | |
Martin Boehm | 75e2ffa025 | |
Martin Boehm | 5af08584ab | |
Martin Boehm | 70559ab9e0 | |
Martin Boehm | 45e1e32e18 | |
Martin Boehm | 0110fd0cf2 | |
Martin Boehm | 874130ce4b | |
Martin Boehm | d5c80db8f0 | |
Martin Boehm | 9eb022238d | |
Martin Boehm | 0e1725321d | |
Martin Boehm | 55ae22bab1 | |
Martin Boehm | 3e532e9130 | |
Martin Boehm | 5e170b8dd8 | |
Martin Boehm | 4a216fa647 | |
Martin Boehm | ec1647c864 | |
Martin Boehm | 9a04c862d6 | |
Martin Boehm | a08f568353 | |
Martin Boehm | fead52881f | |
Martin Boehm | c96c357013 | |
Martin Boehm | 61177c3750 | |
Martin Boehm | 463eab9d2d | |
Martin Boehm | 8ac57a3d56 | |
Martin Boehm | 1f32a39d16 | |
Martin Boehm | 995d169172 | |
Martin Boehm | 7a990f9b5b | |
Martin Boehm | 8886256d0b | |
Martin Boehm | eb524c2226 | |
Martin Boehm | fad7ea326c | |
Martin Boehm | 089346a4bb | |
Martin Boehm | ab20a14d18 | |
Martin Boehm | 0f6fdf7f21 | |
Martin Boehm | 6072aa5e9e | |
Martin Boehm | 975c98b5b7 | |
Martin Boehm | ef03abcd1c | |
Martin Boehm | 1ac7a7abca | |
Martin Boehm | 4448c57ba8 | |
Martin Boehm | 28b3a4d1b4 | |
Martin Boehm | afb8926e1b | |
Martin Boehm | acb5d63afc | |
Martin Boehm | 1dbe7f42ba | |
Martin Boehm | 6d3e171b71 | |
Martin Boehm | edf7193df4 | |
Martin Boehm | 4a05757321 | |
Martin Boehm | fac728bc51 | |
Martin Boehm | 38ba033654 | |
Martin Boehm | 13acef41d4 | |
Martin Boehm | 9b14964900 |
|
@ -6,8 +6,12 @@ notes.txt
|
|||
debug*
|
||||
.vscode
|
||||
docker/blockbook
|
||||
build
|
||||
!build/templates
|
||||
!build/docker
|
||||
build/pkg-defs
|
||||
build/blockbook
|
||||
build/blockchaincfg.json
|
||||
build/ldb
|
||||
build/sst_dump
|
||||
build/*.deb
|
||||
.bin-image
|
||||
.deb-image
|
||||
.deb-image
|
||||
\.idea/
|
||||
|
|
133
.gitlab-ci.yml
133
.gitlab-ci.yml
|
@ -1,6 +1,7 @@
|
|||
stages:
|
||||
- build
|
||||
- test
|
||||
- backend-deploy-and-test
|
||||
|
||||
build:
|
||||
stage: build
|
||||
|
@ -25,3 +26,135 @@ integration-test:
|
|||
tags:
|
||||
- blockbook
|
||||
script: make test-integration ARGS="-run='TestIntegration/(bcash|bgold|bitcoin|dash|dogecoin|litecoin|vertcoin|zcash)=main/'"
|
||||
|
||||
backend-deploy-and-test-bcash:
|
||||
stage: backend-deploy-and-test
|
||||
only:
|
||||
refs:
|
||||
- master
|
||||
changes:
|
||||
- configs/coins/bcash.json
|
||||
tags:
|
||||
- blockbook
|
||||
script: ./contrib/scripts/backend-deploy-and-test.sh bcash
|
||||
|
||||
backend-deploy-and-test-bgold:
|
||||
stage: backend-deploy-and-test
|
||||
only:
|
||||
refs:
|
||||
- master
|
||||
changes:
|
||||
- configs/coins/bgold.json
|
||||
tags:
|
||||
- blockbook
|
||||
script: ./contrib/scripts/backend-deploy-and-test.sh bgold
|
||||
|
||||
backend-deploy-and-test-bitcoin:
|
||||
stage: backend-deploy-and-test
|
||||
only:
|
||||
refs:
|
||||
- master
|
||||
changes:
|
||||
- configs/coins/bitcoin.json
|
||||
tags:
|
||||
- blockbook
|
||||
script: ./contrib/scripts/backend-deploy-and-test.sh bitcoin
|
||||
|
||||
backend-deploy-and-test-dash:
|
||||
stage: backend-deploy-and-test
|
||||
only:
|
||||
refs:
|
||||
- master
|
||||
changes:
|
||||
- configs/coins/dash.json
|
||||
tags:
|
||||
- blockbook
|
||||
script: ./contrib/scripts/backend-deploy-and-test.sh dash
|
||||
|
||||
backend-deploy-and-test-digibyte:
|
||||
stage: backend-deploy-and-test
|
||||
only:
|
||||
refs:
|
||||
- master
|
||||
changes:
|
||||
- configs/coins/digibyte.json
|
||||
tags:
|
||||
- blockbook
|
||||
script: ./contrib/scripts/backend-deploy-and-test.sh digibyte
|
||||
|
||||
backend-deploy-and-test-dogecoin:
|
||||
stage: backend-deploy-and-test
|
||||
only:
|
||||
refs:
|
||||
- master
|
||||
changes:
|
||||
- configs/coins/dogecoin.json
|
||||
tags:
|
||||
- blockbook
|
||||
script: ./contrib/scripts/backend-deploy-and-test.sh dogecoin
|
||||
|
||||
backend-deploy-and-test-litecoin:
|
||||
stage: backend-deploy-and-test
|
||||
only:
|
||||
refs:
|
||||
- master
|
||||
changes:
|
||||
- configs/coins/litecoin.json
|
||||
tags:
|
||||
- blockbook
|
||||
script: ./contrib/scripts/backend-deploy-and-test.sh litecoin
|
||||
|
||||
backend-deploy-and-test-namecoin:
|
||||
stage: backend-deploy-and-test
|
||||
only:
|
||||
refs:
|
||||
- master
|
||||
changes:
|
||||
- configs/coins/namecoin.json
|
||||
tags:
|
||||
- blockbook
|
||||
script: ./contrib/scripts/backend-deploy-and-test.sh namecoin
|
||||
|
||||
backend-deploy-and-test-vertcoin:
|
||||
stage: backend-deploy-and-test
|
||||
only:
|
||||
refs:
|
||||
- master
|
||||
changes:
|
||||
- configs/coins/vertcoin.json
|
||||
tags:
|
||||
- blockbook
|
||||
script: ./contrib/scripts/backend-deploy-and-test.sh vertcoin
|
||||
|
||||
backend-deploy-and-test-zcash:
|
||||
stage: backend-deploy-and-test
|
||||
only:
|
||||
refs:
|
||||
- master
|
||||
changes:
|
||||
- configs/coins/zcash.json
|
||||
tags:
|
||||
- blockbook
|
||||
script: ./contrib/scripts/backend-deploy-and-test.sh zcash
|
||||
|
||||
backend-deploy-and-test-bitcoin_testnet:
|
||||
stage: backend-deploy-and-test
|
||||
only:
|
||||
refs:
|
||||
- master
|
||||
changes:
|
||||
- configs/coins/bitcoin_testnet.json
|
||||
tags:
|
||||
- blockbook
|
||||
script: ./contrib/scripts/backend-deploy-and-test.sh bitcoin_testnet bitcoin-testnet bitcoin=test testnet3/debug.log
|
||||
|
||||
backend-deploy-and-test-ethereum_testnet_ropsten:
|
||||
stage: backend-deploy-and-test
|
||||
only:
|
||||
refs:
|
||||
- master
|
||||
changes:
|
||||
- configs/coins/ethereum_testnet_ropsten.json
|
||||
tags:
|
||||
- blockbook
|
||||
script: ./contrib/scripts/backend-deploy-and-test.sh ethereum_testnet_ropsten ethereum-testnet-ropsten ethereum=test ethereum_testnet_ropsten.log
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
# Blockbook Contributor Guide
|
||||
|
||||
Blockbook is back-end service for Trezor wallet. Although it is open source, design and development of core packages
|
||||
Blockbook is back-end service for Trezor wallet. Although it is open source, the design and development of the core packages
|
||||
is done by Trezor developers in order to keep Blockbook compatible with Trezor.
|
||||
|
||||
Bug fixes and support for new coins are welcome. Please take note that non-fixing pull requests that change base
|
||||
packages or another coin code will not be accepted. If you will have to change some of existing code, please file
|
||||
Bug fixes and support for new coins are welcome. **Please take note that non-fixing pull requests that change base
|
||||
packages or another coin code will not be accepted.** If you have a need to change some of the existing code, please file
|
||||
an issue and discuss your request with Blockbook maintainers.
|
||||
|
||||
## Development environment
|
||||
|
||||
Instructions to set up your development environment and build Blockbook are described in separated
|
||||
Instructions to set up your development environment and build Blockbook are described in a separate
|
||||
[document](/docs/build.md).
|
||||
|
||||
## How can I contribute?
|
||||
|
@ -25,8 +25,7 @@ updates. Do not leave random "+1" or "I have this too" comments, as they only cl
|
|||
resolving it. However, if you have ways to reproduce the issue or have additional information that may help resolving
|
||||
the issue, please leave a comment.
|
||||
|
||||
Include information about Blockbook instance which is exposed at internal HTTP port. Ports are listed in
|
||||
[port registry](/docs/ports.md). For example execute `curl -k https://localhost:9030` for Bitcoin.
|
||||
Include information about the Blockbook instance, which is shown at the Blockbook status page or returned by API call. For example execute `curl -k https://<server name>:<public port>/api` to get JSON containing details about Blockbook and Backend installation. Ports are listed in the [port registry](/docs/ports.md).
|
||||
|
||||
Also include the steps required to reproduce the problem if possible and applicable. This information will help us
|
||||
review and fix your issue faster. When sending lengthy log-files, consider posting them as a gist
|
||||
|
@ -34,35 +33,35 @@ review and fix your issue faster. When sending lengthy log-files, consider posti
|
|||
|
||||
### Adding coin support
|
||||
|
||||
> **Important notice**: Although we are happy for support of new coins, we do not have enough capacity to run them all
|
||||
> on our infrastructure. We run Blockbook instances only for selected number of coins. If you want to have Blockbook
|
||||
> instance for your coin, you will have to deploy it to your own server.
|
||||
|
||||
Trezor harware wallet supports over 500 coins, see https://trezor.io/coins/. You are free to add support for any of
|
||||
them to Blockbook. Actually implemented coins are listed [here](/docs/ports.md).
|
||||
them to Blockbook. Currently implemented coins are listed [here](/docs/ports.md).
|
||||
|
||||
You should follow few steps bellow to get smooth merge of your PR.
|
||||
|
||||
> Although we are happy for support of new coins we have not enough capacity to run them all on our infrastructure.
|
||||
> Actually we can run Blockbook instances only for coins supported by Trezor wallet. If you want to have Blockbook
|
||||
> instance for your coin, you will have to deploy your own server.
|
||||
You should follow the steps below to get smooth merge of your PR.
|
||||
|
||||
#### Add coin definition
|
||||
|
||||
Coin definitions are stored in JSON files in *configs/coins* directory. They are single source of Blockbook
|
||||
Coin definitions are stored in JSON files in *configs/coins* directory. They are the single source of Blockbook
|
||||
configuration, Blockbook and back-end package definition and build metadata. Since Blockbook supports only single
|
||||
coin index per running instance, every coin (including testnet) must have single definition file.
|
||||
|
||||
All options of coin definition are described in [config.md](/docs/config.md).
|
||||
|
||||
Because most of coins are fork of Bitcoin and they have similar way to install and configure their daemon, we use
|
||||
templates to generate package definition and configuration files during build process. Similarly, there templates for Blockbook
|
||||
templates to generate package definition and configuration files during build process. Similarly, there are templates for Blockbook
|
||||
package. Templates are filled with data from coin definition. Although normally all package definitions are generated automatically
|
||||
during the build process, sometimes there is a reason to see them. You can create them by calling
|
||||
during the build process, sometimes there is a reason to check what was generated. You can create them by calling
|
||||
`go run build/templates/generate.go coin`, where *coin* is name of definition file without .json extension. Files are
|
||||
generated to *build/pkg-defs* directory.
|
||||
|
||||
Good examples of coin configuration are
|
||||
[*configs/coins/bitcoin.json*](configs/coins/bitcoin.json) and
|
||||
[*configs/coins/ethereum.json*](configs/coins/ethereum.json) for Bitcoin-like coins and different coins, respectively.
|
||||
[*configs/coins/ethereum.json*](configs/coins/ethereum.json) for Bitcoin type coins and Ethereum type coins, respectively.
|
||||
|
||||
Usually you have to update only few options that differ from Bitcoin definition. At first there is base information
|
||||
Usually you have to update only a few options that differ from the Bitcoin definition. At first there is base information
|
||||
about coin in section *coin* – name, alias etc. Then update port information in *port* section. We keep port series as
|
||||
listed in [the port registry](/docs/ports.md). Select next port numbers in the series. Port numbers must be unique across all
|
||||
port definitions.
|
||||
|
@ -109,15 +108,15 @@ different concept than Bitcoin.
|
|||
|
||||
##### BlockChain interface
|
||||
|
||||
Type that implements *bchain.BlockChain* interface ensures communication with block chain network. Because
|
||||
it calls node RPCs it usually has suffix RPC.
|
||||
Type that implements *bchain.BlockChain* interface ensures communication with the block chain network. Because
|
||||
it calls node RPCs, it usually has suffix RPC.
|
||||
|
||||
Initialization of object is separated into two stages. At first there is called factory method (details described
|
||||
in next section) and then *bchain.BlockChain.Initialize()* method. Separated initialization method allows you call
|
||||
in the next section) and then *bchain.BlockChain.Initialize()* method. Separated initialization method allows you call
|
||||
inherited methods during initialization. However it is common practice override fields of embedded structure in factory
|
||||
method.
|
||||
|
||||
During initialization, there is usually loaded chain information, registered message queue callback and created mempool
|
||||
Initialization routine usually loads chain information, registers message queue callback and creates mempool
|
||||
and parser objects.
|
||||
|
||||
BitcoinRPC uses *btc.RPCMarshaller* ([btc/codec.go](/bchain/coins/btc/codec.go)) in order to distinguish API version of
|
||||
|
@ -136,7 +135,7 @@ must correspond to *coin.name* in coin definition file (see above).
|
|||
Configuration passed to factory method is coin specific. For types that embed *btc.BitcoinRPC,* configuration must
|
||||
contain at least fields referred in *btc.Configuration* ([btc/bitcoinrpc.go](/bchain/coins/btc/bitcoinrpc.go)).
|
||||
|
||||
For types that embed base struct it is common practise call factory method of embedded type in order to
|
||||
For types that embed base struct it is common practice to call factory method of the embedded type in order to
|
||||
create & initialize it. It is much more robust than simple struct composition.
|
||||
|
||||
For example see [zec/zcashrpc.go](/bchain/coins/zec/zcashrpc.go).
|
||||
|
@ -146,7 +145,7 @@ For example see [zec/zcashrpc.go](/bchain/coins/zec/zcashrpc.go).
|
|||
Type that implements *bchain.BlockChainParser* interface ensures parsing and conversions of block chain data. It is
|
||||
initialized by *bchain.BlockChain* during initialization.
|
||||
|
||||
There are several groups of methods defined in *bchian.BlockChainParser*:
|
||||
There are several groups of methods defined in *bchain.BlockChainParser*:
|
||||
|
||||
* *GetAddrDescFromVout* and *GetAddrDescFromAddress* – Convert transaction addresses to *Address Descriptor* that is used as database ID.
|
||||
Most of coins use output script as *Address Descriptor*.
|
||||
|
@ -168,9 +167,9 @@ different approach for address representation than Bitcoin.
|
|||
|
||||
#### Add tests
|
||||
|
||||
Add unit tests and integration tests. PR without passing tests won't be accepted.
|
||||
Tests are described [here](/docs/testing.md).
|
||||
Add unit tests and integration tests. **Pull requests without passing tests will not be accepted**.
|
||||
How to implement tests is described [here](/docs/testing.md).
|
||||
|
||||
#### Deploy public server
|
||||
|
||||
Deploy Blockbook server on public IP address. Blockbook maintainers will check implementation before merging.
|
||||
Deploy Blockbook server on public IP address. Blockbook maintainers will check your implementation before merging.
|
||||
|
|
|
@ -1,219 +0,0 @@
|
|||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/Groestlcoin/go-groestl-hash"
|
||||
packages = ["groestl","hash"]
|
||||
revision = "790653ac190c4029ee200e82a8f21b5d1afaf7d6"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/beorn7/perks"
|
||||
packages = ["quantile"]
|
||||
revision = "3a771d992973f24aa725d07868b467d1ddfceafb"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/bsm/go-vlq"
|
||||
packages = ["."]
|
||||
revision = "ec6e8d4f5f4ec0f6e808ffc7f4dcc7516d4d7d49"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/btcsuite/btcd"
|
||||
packages = ["blockchain","btcec","chaincfg","chaincfg/chainhash","database","txscript","wire"]
|
||||
revision = "2be2f12b358dc57d70b8f501b00be450192efbc3"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/btcsuite/btclog"
|
||||
packages = ["."]
|
||||
revision = "84c8d2346e9fc8c7b947e243b9c24e6df9fd206a"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/btcsuite/btcutil"
|
||||
packages = [".","base58","bech32"]
|
||||
revision = "501929d3d046174c3d39f0ea54ece471aa17238c"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/deckarep/golang-set"
|
||||
packages = ["."]
|
||||
revision = "1d4478f51bed434f1dadf96dcd9b43aabac66795"
|
||||
version = "v1.7"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/ethereum/go-ethereum"
|
||||
packages = [".","common","common/hexutil","common/math","core/types","crypto","crypto/secp256k1","crypto/sha3","ethclient","ethdb","log","metrics","p2p/netutil","params","rlp","rpc","trie"]
|
||||
revision = "89451f7c382ad2185987ee369f16416f89c28a7d"
|
||||
version = "v1.8.15"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/go-stack/stack"
|
||||
packages = ["."]
|
||||
revision = "259ab82a6cad3992b4e21ff5cac294ccb06474bc"
|
||||
version = "v1.7.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/gobuffalo/packr"
|
||||
packages = ["."]
|
||||
revision = "5a2cbb54c4e7d482e3f518c56f1f86f133d5204f"
|
||||
version = "v1.13.7"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/gogo/protobuf"
|
||||
packages = ["proto"]
|
||||
revision = "1adfc126b41513cc696b209667c8656ea7aac67c"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/golang/glog"
|
||||
packages = ["."]
|
||||
revision = "23def4e6c14b4da8ac2ed8007337bc5eb5007998"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/golang/protobuf"
|
||||
packages = ["proto"]
|
||||
revision = "925541529c1fa6821df4e44ce2723319eb2be768"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/golang/snappy"
|
||||
packages = ["."]
|
||||
revision = "553a641470496b2327abcac10b36396bd98e45c9"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/gorilla/websocket"
|
||||
packages = ["."]
|
||||
revision = "ea4d1f681babbce9545c9c5f3d5194a789c89f5b"
|
||||
version = "v1.2.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/jakm/bchutil"
|
||||
packages = ["."]
|
||||
revision = "5a273ca8a96628732c07ff5cfb9f3e7d965241e8"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/jakm/btcutil"
|
||||
packages = [".","base58","bech32","chaincfg","txscript"]
|
||||
revision = "224b76333062172edefdeb502123fdda12205f76"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/juju/errors"
|
||||
packages = ["."]
|
||||
revision = "c7d06af17c68cd34c835053720b21f6549d9b0ee"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/martinboehm/golang-socketio"
|
||||
packages = [".","protocol","transport"]
|
||||
revision = "f60b0a8befde091474a624a8ffd81ee9912957b3"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/matttproud/golang_protobuf_extensions"
|
||||
packages = ["pbutil"]
|
||||
revision = "3247c84500bff8d9fb6d579d800f20b3e091582c"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/mr-tron/base58"
|
||||
packages = ["base58"]
|
||||
revision = "c1bdf7c52f59d6685ca597b9955a443ff95eeee6"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/pebbe/zmq4"
|
||||
packages = ["."]
|
||||
revision = "5b443b6471cea4b4f9f85025530c04c93233f76a"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/pkg/errors"
|
||||
packages = ["."]
|
||||
revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
|
||||
version = "v0.8.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/prometheus/client_golang"
|
||||
packages = ["prometheus","prometheus/promhttp"]
|
||||
revision = "c5b7fccd204277076155f10851dad72b76a49317"
|
||||
version = "v0.8.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/prometheus/client_model"
|
||||
packages = ["go"]
|
||||
revision = "99fa1f4be8e564e8a6b613da7fa6f46c9edafc6c"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/prometheus/common"
|
||||
packages = ["expfmt","internal/bitbucket.org/ww/goautoneg","model"]
|
||||
revision = "d0f7cd64bda49e08b22ae8a730aa57aa0db125d6"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/prometheus/procfs"
|
||||
packages = [".","internal/util","nfs","xfs"]
|
||||
revision = "8b1c2da0d56deffdbb9e48d4414b4e674bd8083e"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/rs/cors"
|
||||
packages = ["."]
|
||||
revision = "feef513b9575b32f84bafa580aad89b011259019"
|
||||
version = "v1.3.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/schancel/cashaddr-converter"
|
||||
packages = ["address","baseconv","cashaddress","legacy"]
|
||||
revision = "0a38f5822f795dc3727b4caacc298e02938d9eb1"
|
||||
version = "v9"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/syndtr/goleveldb"
|
||||
packages = ["leveldb","leveldb/cache","leveldb/comparer","leveldb/errors","leveldb/filter","leveldb/iterator","leveldb/journal","leveldb/memdb","leveldb/opt","leveldb/storage","leveldb/table","leveldb/util"]
|
||||
revision = "714f901b98fdb3aa954b4193d8cbd64a28d80cad"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/tecbot/gorocksdb"
|
||||
packages = ["."]
|
||||
revision = "214b6b7bc0f06812ab5602fdc502a3e619916f38"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/crypto"
|
||||
packages = ["ripemd160"]
|
||||
revision = "d6449816ce06963d9d136eee5a56fca5b0616e7e"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/net"
|
||||
packages = ["websocket"]
|
||||
revision = "61147c48b25b599e5b561d2e9c4f3e1ef489ca41"
|
||||
|
||||
[[projects]]
|
||||
branch = "v2"
|
||||
name = "gopkg.in/karalabe/cookiejar.v2"
|
||||
packages = ["collections/prque"]
|
||||
revision = "8dcd6a7f4951f6ff3ee9cbb919a06d8925822e57"
|
||||
|
||||
[[projects]]
|
||||
branch = "v2"
|
||||
name = "gopkg.in/natefinch/npipe.v2"
|
||||
packages = ["."]
|
||||
revision = "c1b8fa8bdccecb0b8db834ee0b92fdbcfa606dd6"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "3a7b863bec3806ba9460f3961e2d6e8ff4b77e5f66d196b1f79bd37fa6bf8d82"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
74
Gopkg.toml
74
Gopkg.toml
|
@ -1,74 +0,0 @@
|
|||
|
||||
# Gopkg.toml example
|
||||
#
|
||||
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
|
||||
# for detailed Gopkg.toml documentation.
|
||||
#
|
||||
# required = ["github.com/user/thing/cmd/thing"]
|
||||
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project"
|
||||
# version = "1.0.0"
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project2"
|
||||
# branch = "dev"
|
||||
# source = "github.com/myfork/project2"
|
||||
#
|
||||
# [[override]]
|
||||
# name = "github.com/x/y"
|
||||
# version = "2.4.0"
|
||||
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/bsm/go-vlq"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/btcsuite/btcd"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/btcsuite/btcutil"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/golang/glog"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/gorilla/mux"
|
||||
version = "1.6.1"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/juju/errors"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/martinboehm/golang-socketio"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/pebbe/zmq4"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/prometheus/client_golang"
|
||||
version = "0.8.0"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/tecbot/gorocksdb"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/ethereum/go-ethereum"
|
||||
version = "1.8.2"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/golang/protobuf"
|
||||
version = "1.0.0"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/cpacia/bchutil"
|
23
Makefile
23
Makefile
|
@ -1,8 +1,9 @@
|
|||
BIN_IMAGE = blockbook-build
|
||||
DEB_IMAGE = blockbook-build-deb
|
||||
PACKAGER = $(shell id -u):$(shell id -g)
|
||||
BASE_IMAGE = $$(awk -F= '$$1=="ID" { print $$2 ;}' /etc/os-release):$$(awk -F= '$$1=="VERSION_ID" { print $$2 ;}' /etc/os-release | tr -d '"')
|
||||
NO_CACHE = false
|
||||
UPDATE_VENDOR = 1
|
||||
TCMALLOC =
|
||||
ARGS ?=
|
||||
|
||||
TARGETS=$(subst .json,, $(shell ls configs/coins))
|
||||
|
@ -10,28 +11,28 @@ TARGETS=$(subst .json,, $(shell ls configs/coins))
|
|||
.PHONY: build build-debug test deb
|
||||
|
||||
build: .bin-image
|
||||
docker run -t --rm -e PACKAGER=$(PACKAGER) -e UPDATE_VENDOR=$(UPDATE_VENDOR) -v $(CURDIR):/src -v $(CURDIR)/build:/out $(BIN_IMAGE) make build ARGS="$(ARGS)"
|
||||
docker run -t --rm -e PACKAGER=$(PACKAGER) -v "$(CURDIR):/src" -v "$(CURDIR)/build:/out" $(BIN_IMAGE) make build ARGS="$(ARGS)"
|
||||
|
||||
build-debug: .bin-image
|
||||
docker run -t --rm -e PACKAGER=$(PACKAGER) -e UPDATE_VENDOR=$(UPDATE_VENDOR) -v $(CURDIR):/src -v $(CURDIR)/build:/out $(BIN_IMAGE) make build-debug ARGS="$(ARGS)"
|
||||
docker run -t --rm -e PACKAGER=$(PACKAGER) -v "$(CURDIR):/src" -v "$(CURDIR)/build:/out" $(BIN_IMAGE) make build-debug ARGS="$(ARGS)"
|
||||
|
||||
test: .bin-image
|
||||
docker run -t --rm -e PACKAGER=$(PACKAGER) -e UPDATE_VENDOR=$(UPDATE_VENDOR) -v $(CURDIR):/src --network="host" $(BIN_IMAGE) make test ARGS="$(ARGS)"
|
||||
docker run -t --rm -e PACKAGER=$(PACKAGER) -v "$(CURDIR):/src" --network="host" $(BIN_IMAGE) make test ARGS="$(ARGS)"
|
||||
|
||||
test-integration: .bin-image
|
||||
docker run -t --rm -e PACKAGER=$(PACKAGER) -e UPDATE_VENDOR=$(UPDATE_VENDOR) -v $(CURDIR):/src --network="host" $(BIN_IMAGE) make test-integration ARGS="$(ARGS)"
|
||||
docker run -t --rm -e PACKAGER=$(PACKAGER) -v "$(CURDIR):/src" --network="host" $(BIN_IMAGE) make test-integration ARGS="$(ARGS)"
|
||||
|
||||
test-all: .bin-image
|
||||
docker run -t --rm -e PACKAGER=$(PACKAGER) -e UPDATE_VENDOR=$(UPDATE_VENDOR) -v $(CURDIR):/src --network="host" $(BIN_IMAGE) make test-all ARGS="$(ARGS)"
|
||||
docker run -t --rm -e PACKAGER=$(PACKAGER) -v "$(CURDIR):/src" --network="host" $(BIN_IMAGE) make test-all ARGS="$(ARGS)"
|
||||
|
||||
deb-backend-%: .deb-image
|
||||
docker run -t --rm -e PACKAGER=$(PACKAGER) -e UPDATE_VENDOR=$(UPDATE_VENDOR) -v $(CURDIR):/src -v $(CURDIR)/build:/out $(DEB_IMAGE) /build/build-deb.sh backend $* $(ARGS)
|
||||
docker run -t --rm -e PACKAGER=$(PACKAGER) -v "$(CURDIR):/src" -v "$(CURDIR)/build:/out" $(DEB_IMAGE) /build/build-deb.sh backend $* $(ARGS)
|
||||
|
||||
deb-blockbook-%: .deb-image
|
||||
docker run -t --rm -e PACKAGER=$(PACKAGER) -e UPDATE_VENDOR=$(UPDATE_VENDOR) -v $(CURDIR):/src -v $(CURDIR)/build:/out $(DEB_IMAGE) /build/build-deb.sh blockbook $* $(ARGS)
|
||||
docker run -t --rm -e PACKAGER=$(PACKAGER) -v "$(CURDIR):/src" -v "$(CURDIR)/build:/out" $(DEB_IMAGE) /build/build-deb.sh blockbook $* $(ARGS)
|
||||
|
||||
deb-%: .deb-image
|
||||
docker run -t --rm -e PACKAGER=$(PACKAGER) -e UPDATE_VENDOR=$(UPDATE_VENDOR) -v $(CURDIR):/src -v $(CURDIR)/build:/out $(DEB_IMAGE) /build/build-deb.sh all $* $(ARGS)
|
||||
docker run -t --rm -e PACKAGER=$(PACKAGER) -v "$(CURDIR):/src" -v "$(CURDIR)/build:/out" $(DEB_IMAGE) /build/build-deb.sh all $* $(ARGS)
|
||||
|
||||
deb-blockbook-all: clean-deb $(addprefix deb-blockbook-, $(TARGETS))
|
||||
|
||||
|
@ -44,8 +45,8 @@ build-images: clean-images
|
|||
|
||||
.bin-image:
|
||||
@if [ $$(build/tools/image_status.sh $(BIN_IMAGE):latest build/docker) != "ok" ]; then \
|
||||
echo "Building image $(BIN_IMAGE)..."; \
|
||||
docker build --no-cache=$(NO_CACHE) -t $(BIN_IMAGE) build/docker/bin; \
|
||||
echo "Building image $(BIN_IMAGE) from $(BASE_IMAGE)"; \
|
||||
docker build --no-cache=$(NO_CACHE) --build-arg TCMALLOC=$(TCMALLOC) --build-arg BASE_IMAGE=$(BASE_IMAGE) -t $(BIN_IMAGE) build/docker/bin; \
|
||||
else \
|
||||
echo "Image $(BIN_IMAGE) is up to date"; \
|
||||
fi
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
[![Go Report Card](https://goreportcard.com/badge/trezor/blockbook)](https://goreportcard.com/report/trezor/blockbook)
|
||||
|
||||
# Blockbook
|
||||
|
||||
**Blockbook** is back-end service for Trezor wallet. Main features of **Blockbook** are:
|
||||
|
||||
- index of addresses and address balances of the connected block chain
|
||||
- fast searches in the indexes
|
||||
- simple blockchain explorer
|
||||
- websocket, API and legacy Bitcore Insight compatible socket.io interfaces
|
||||
- support of multiple coins (Bitcoin and Ethereum type), with easy extensibility for other coins
|
||||
- scripts for easy creation of debian packages for backend and blockbook
|
||||
|
||||
## Build and installation instructions
|
||||
|
||||
Officially supported platform is **Debian Linux** and **AMD64** architecture.
|
||||
|
||||
Memory and disk requirements for initial synchronization of **Bitcoin mainnet** are around 32 GB RAM and over 180 GB of disk space. After initial synchronization, fully synchronized instance uses about 10 GB RAM.
|
||||
Other coins should have lower requirements, depending on the size of their block chain. Note that fast SSD disks are highly
|
||||
recommended.
|
||||
|
||||
User installation guide is [here](https://wiki.trezor.io/User_manual:Running_a_local_instance_of_Trezor_Wallet_backend_(Blockbook)).
|
||||
|
||||
Developer build guide is [here](/docs/build.md).
|
||||
|
||||
Contribution guide is [here](CONTRIBUTING.md).
|
||||
|
||||
## Implemented coins
|
||||
|
||||
Blockbook currently supports over 30 coins. The Trezor team implemented
|
||||
|
||||
- Bitcoin, Bitcoin Cash, Zcash, Dash, Litecoin, Bitcoin Gold, Ethereum, Ethereum Classic, Dogecoin, Namecoin, Vertcoin, DigiByte, Liquid
|
||||
|
||||
the rest of coins were implemented by the community.
|
||||
|
||||
Testnets for some coins are also supported, for example:
|
||||
- Bitcoin Testnet, Bitcoin Cash Testnet, ZCash Testnet, Ethereum Testnet Ropsten
|
||||
|
||||
List of all implemented coins is in [the registry of ports](/docs/ports.md).
|
||||
|
||||
## Common issues when running Blockbook or implementing additional coins
|
||||
|
||||
#### Out of memory when doing initial synchronization
|
||||
|
||||
How to reduce memory footprint of the initial sync:
|
||||
|
||||
- disable rocksdb cache by parameter `-dbcache=0`, the default size is 500MB
|
||||
- run blockbook with parameter `-workers=1`. This disables bulk import mode, which caches a lot of data in memory (not in rocksdb cache). It will run about twice as slowly but especially for smaller blockchains it is no problem at all.
|
||||
|
||||
Please add your experience to this [issue](https://github.com/trezor/blockbook/issues/43).
|
||||
|
||||
#### Error `internalState: database is in inconsistent state and cannot be used`
|
||||
|
||||
Blockbook was killed during the initial import, most commonly by OOM killer. By default, Blockbook performs the initial import in bulk import mode, which for performance reasons does not store all the data immediately to the database. If Blockbook is killed during this phase, the database is left in an inconsistent state.
|
||||
|
||||
See above how to reduce the memory footprint, delete the database files and run the import again.
|
||||
|
||||
Check [this](https://github.com/trezor/blockbook/issues/89) or [this](https://github.com/trezor/blockbook/issues/147) issue for more info.
|
||||
|
||||
#### Running on Ubuntu
|
||||
|
||||
[This issue](https://github.com/trezor/blockbook/issues/45) discusses how to run Blockbook on Ubuntu. If you have some additional experience with Blockbook on Ubuntu, please add it to [this issue](https://github.com/trezor/blockbook/issues/45).
|
||||
|
||||
#### My coin implementation is reporting parse errors when importing blockchain
|
||||
|
||||
Your coin's block/transaction data may not be compatible with `BitcoinParser` `ParseBlock`/`ParseTx`, which is used by default. In that case, implement your coin in a similar way we used in case of [zcash](https://github.com/trezor/blockbook/tree/master/bchain/coins/zec) and some other coins. The principle is not to parse the block/transaction data in Blockbook but instead to get parsed transactions as json from the backend.
|
||||
|
||||
## Data storage in RocksDB
|
||||
|
||||
Blockbook stores data the key-value store RocksDB. Database format is described [here](/docs/rocksdb.md).
|
||||
|
||||
## API
|
||||
|
||||
Blockbook API is described [here](/docs/api.md).
|
67
README.md
67
README.md
|
@ -1,46 +1,57 @@
|
|||
[![Go Report Card](https://goreportcard.com/badge/trezor/blockbook)](https://goreportcard.com/report/trezor/blockbook)
|
||||
# Fork
|
||||
Fork of Trezor Blockbook.
|
||||
|
||||
# Blockbook
|
||||
|
||||
> **WARNING: Blockbook is currently in the state of heavy development. We may implement at any time backwards incompatible changes that require full reindexation of the database. Also, do not expect this documentation to be always up to date.**
|
||||
The differences:
|
||||
|
||||
**Blockbook** is back-end service for Trezor wallet. Main features of **Blockbook** are:
|
||||
* Just for Ethereum.
|
||||
|
||||
- create missing indexes in the blockchain - index of addresses and address balances
|
||||
- allow fast searches in the index of addresses
|
||||
- implement parts Insight socket.io interface as required by Trezor wallet
|
||||
- support of multiple coins
|
||||
- simple blockchain explorer for implemented coins
|
||||
- scripts for easy creation of debian packages for backend and blockbook
|
||||
* Use existing `geth --full` server.
|
||||
|
||||
## Build and installation instructions
|
||||
* Don't require `backend-*` package.
|
||||
|
||||
Officially supported platform is **Debian Linux** and **AMD64** architecture.
|
||||
* Minimal UI, dark theme.
|
||||
|
||||
Memory and disk requirements for initial synchronization of **Bitcoin mainnet** are around 32 GB RAM and over 150 GB of disk size. After initial synchronization, fully synchronized instance takes around 10 GB RAM.
|
||||
Other coins should have lower requirements depending on size of their block chain. Note that fast SSD disks are highly
|
||||
recommended.
|
||||
* Listen only on localhost, no SSL.
|
||||
|
||||
User installation guide is [here](https://wiki.trezor.io/User_manual:Running_a_local_instance_of_Trezor_Wallet_backend_(Blockbook)).
|
||||
* Use spacecruft repos, not github.
|
||||
|
||||
Developer build guide is [here](/docs/build.md).
|
||||
* Don't use CDN.
|
||||
|
||||
Contribution guide is [here](CONTRIBUTING.md).
|
||||
# Install
|
||||
|
||||
# Implemented coins
|
||||
```
|
||||
# Install docker, etc.
|
||||
git clone https://spacecruft.org/spacecruft/blockbook
|
||||
cd blockbook
|
||||
make deb-blockbook-ethereum
|
||||
dpkg -i build/blockbook-ethereum_0.3.4_amd64.deb
|
||||
```
|
||||
|
||||
The most significant coins implemented by Blockbook are:
|
||||
- Bitcoin, Bcash, Bgold, ZCash, Dash, Litecoin
|
||||
Edit config:
|
||||
```
|
||||
vim /opt/coins/blockbook/ethereum/config/blockchaincfg.json
|
||||
```
|
||||
|
||||
Incomplete, experimental support is for:
|
||||
- Ethereum, Ethereum Classic
|
||||
XXX Hardcoded into systemd script, set `geth` node:
|
||||
|
||||
Testnets for some coins are also supported, for example:
|
||||
- Bitcoin Testnet, Bcash Testnet, ZCash Testnet, Ethereum Testnet Ropsten
|
||||
```
|
||||
vim /lib/systemd/system/blockbook-ethereum.service
|
||||
systemctl daemon-reload
|
||||
```
|
||||
|
||||
List of all implemented coins is in [the registry of ports](/docs/ports.md).
|
||||
Start:
|
||||
```
|
||||
systemctl start blockbook-ethereum.service
|
||||
```
|
||||
|
||||
# Data storage in RocksDB
|
||||
Logs:
|
||||
```
|
||||
tail -f /opt/coins/blockbook/ethereum/logs/blockbook.INFO
|
||||
```
|
||||
|
||||
Blockbook stores data the key-value store RocksDB. Database format is described [here](/docs/rocksdb.md).
|
||||
# Upstream
|
||||
Fork of Trezor Blockbook. See `README-upstream.md`.
|
||||
|
||||
* https://github.com/trezor/blockbook
|
||||
|
||||
|
|
|
@ -21,8 +21,9 @@ func init() {
|
|||
panic(err)
|
||||
}
|
||||
if tosLink, err := box.MustString("tos_link"); err == nil {
|
||||
tosLink = strings.TrimSpace(tosLink)
|
||||
if _, err := url.ParseRequestURI(tosLink); err == nil {
|
||||
Text.TOSLink = strings.TrimSpace(tosLink)
|
||||
Text.TOSLink = tosLink
|
||||
} else {
|
||||
panic(fmt.Sprint("tos_link is not valid URL:", err.Error()))
|
||||
}
|
||||
|
|
432
api/types.go
432
api/types.go
|
@ -1,13 +1,43 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"blockbook/bchain"
|
||||
"blockbook/common"
|
||||
"blockbook/db"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"math/big"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/eth"
|
||||
"spacecruft.org/spacecruft/blockbook/common"
|
||||
"spacecruft.org/spacecruft/blockbook/db"
|
||||
)
|
||||
|
||||
const maxUint32 = ^uint32(0)
|
||||
const maxInt = int(^uint(0) >> 1)
|
||||
const maxInt64 = int64(^uint64(0) >> 1)
|
||||
|
||||
// AccountDetails specifies what data returns GetAddress and GetXpub calls
|
||||
type AccountDetails int
|
||||
|
||||
const (
|
||||
// AccountDetailsBasic - only that address is indexed and some basic info
|
||||
AccountDetailsBasic AccountDetails = iota
|
||||
// AccountDetailsTokens - basic info + tokens
|
||||
AccountDetailsTokens
|
||||
// AccountDetailsTokenBalances - basic info + token with balance
|
||||
AccountDetailsTokenBalances
|
||||
// AccountDetailsTxidHistory - basic + token balances + txids, subject to paging
|
||||
AccountDetailsTxidHistory
|
||||
// AccountDetailsTxHistoryLight - basic + tokens + easily obtained tx data (not requiring requests to backend), subject to paging
|
||||
AccountDetailsTxHistoryLight
|
||||
// AccountDetailsTxHistory - basic + tokens + full tx data, subject to paging
|
||||
AccountDetailsTxHistory
|
||||
)
|
||||
|
||||
// ErrUnsupportedXpub is returned when coin type does not support xpub address derivation or provided string is not an xpub
|
||||
var ErrUnsupportedXpub = errors.New("XPUB not supported")
|
||||
|
||||
// APIError extends error by information if the error details should be returned to the end user
|
||||
type APIError struct {
|
||||
Text string
|
||||
|
@ -26,99 +56,316 @@ func NewAPIError(s string, public bool) error {
|
|||
}
|
||||
}
|
||||
|
||||
// ScriptSig contains input script
|
||||
type ScriptSig struct {
|
||||
Hex string `json:"hex"`
|
||||
Asm string `json:"asm,omitempty"`
|
||||
// Amount is datatype holding amounts
|
||||
type Amount big.Int
|
||||
|
||||
// IsZeroBigInt if big int has zero value
|
||||
func IsZeroBigInt(b *big.Int) bool {
|
||||
return len(b.Bits()) == 0
|
||||
}
|
||||
|
||||
// MarshalJSON Amount serialization
|
||||
func (a *Amount) MarshalJSON() (out []byte, err error) {
|
||||
if a == nil {
|
||||
return []byte(`"0"`), nil
|
||||
}
|
||||
return []byte(`"` + (*big.Int)(a).String() + `"`), nil
|
||||
}
|
||||
|
||||
func (a *Amount) String() string {
|
||||
if a == nil {
|
||||
return ""
|
||||
}
|
||||
return (*big.Int)(a).String()
|
||||
}
|
||||
|
||||
// DecimalString returns amount with decimal point placed according to parameter d
|
||||
func (a *Amount) DecimalString(d int) string {
|
||||
return bchain.AmountToDecimalString((*big.Int)(a), d)
|
||||
}
|
||||
|
||||
// AsBigInt returns big.Int type for the Amount (empty if Amount is nil)
|
||||
func (a *Amount) AsBigInt() big.Int {
|
||||
if a == nil {
|
||||
return *new(big.Int)
|
||||
}
|
||||
return big.Int(*a)
|
||||
}
|
||||
|
||||
// AsInt64 returns Amount as int64 (0 if Amount is nil).
|
||||
// It is used only for legacy interfaces (socket.io)
|
||||
// and generally not recommended to use for possible loss of precision.
|
||||
func (a *Amount) AsInt64() int64 {
|
||||
if a == nil {
|
||||
return 0
|
||||
}
|
||||
return (*big.Int)(a).Int64()
|
||||
}
|
||||
|
||||
// Vin contains information about single transaction input
|
||||
type Vin struct {
|
||||
Txid string `json:"txid"`
|
||||
Vout uint32 `json:"vout"`
|
||||
Sequence int64 `json:"sequence,omitempty"`
|
||||
N int `json:"n"`
|
||||
ScriptSig ScriptSig `json:"scriptSig"`
|
||||
AddrDesc bchain.AddressDescriptor `json:"-"`
|
||||
Addresses []string `json:"addresses"`
|
||||
Searchable bool `json:"-"`
|
||||
Value string `json:"value"`
|
||||
ValueSat big.Int `json:"-"`
|
||||
}
|
||||
|
||||
// ScriptPubKey contains output script and addresses derived from it
|
||||
type ScriptPubKey struct {
|
||||
Hex string `json:"hex"`
|
||||
Asm string `json:"asm,omitempty"`
|
||||
AddrDesc bchain.AddressDescriptor `json:"-"`
|
||||
Addresses []string `json:"addresses"`
|
||||
Searchable bool `json:"-"`
|
||||
Type string `json:"type,omitempty"`
|
||||
Txid string `json:"txid,omitempty"`
|
||||
Vout uint32 `json:"vout,omitempty"`
|
||||
Sequence int64 `json:"sequence,omitempty"`
|
||||
N int `json:"n"`
|
||||
AddrDesc bchain.AddressDescriptor `json:"-"`
|
||||
Addresses []string `json:"addresses,omitempty"`
|
||||
IsAddress bool `json:"isAddress"`
|
||||
ValueSat *Amount `json:"value,omitempty"`
|
||||
Hex string `json:"hex,omitempty"`
|
||||
Asm string `json:"asm,omitempty"`
|
||||
Coinbase string `json:"coinbase,omitempty"`
|
||||
}
|
||||
|
||||
// Vout contains information about single transaction output
|
||||
type Vout struct {
|
||||
Value string `json:"value"`
|
||||
ValueSat big.Int `json:"-"`
|
||||
N int `json:"n"`
|
||||
ScriptPubKey ScriptPubKey `json:"scriptPubKey"`
|
||||
Spent bool `json:"spent"`
|
||||
SpentTxID string `json:"spentTxId,omitempty"`
|
||||
SpentIndex int `json:"spentIndex,omitempty"`
|
||||
SpentHeight int `json:"spentHeight,omitempty"`
|
||||
ValueSat *Amount `json:"value,omitempty"`
|
||||
N int `json:"n"`
|
||||
Spent bool `json:"spent,omitempty"`
|
||||
SpentTxID string `json:"spentTxId,omitempty"`
|
||||
SpentIndex int `json:"spentIndex,omitempty"`
|
||||
SpentHeight int `json:"spentHeight,omitempty"`
|
||||
Hex string `json:"hex,omitempty"`
|
||||
Asm string `json:"asm,omitempty"`
|
||||
AddrDesc bchain.AddressDescriptor `json:"-"`
|
||||
Addresses []string `json:"addresses"`
|
||||
IsAddress bool `json:"isAddress"`
|
||||
Type string `json:"type,omitempty"`
|
||||
}
|
||||
|
||||
// TokenType specifies type of token
|
||||
type TokenType string
|
||||
|
||||
// ERC20TokenType is Ethereum ERC20 token
|
||||
const ERC20TokenType TokenType = "ERC20"
|
||||
|
||||
// XPUBAddressTokenType is address derived from xpub
|
||||
const XPUBAddressTokenType TokenType = "XPUBAddress"
|
||||
|
||||
// Token contains info about tokens held by an address
|
||||
type Token struct {
|
||||
Type TokenType `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Path string `json:"path,omitempty"`
|
||||
Contract string `json:"contract,omitempty"`
|
||||
Transfers int `json:"transfers"`
|
||||
Symbol string `json:"symbol,omitempty"`
|
||||
Decimals int `json:"decimals,omitempty"`
|
||||
BalanceSat *Amount `json:"balance,omitempty"`
|
||||
TotalReceivedSat *Amount `json:"totalReceived,omitempty"`
|
||||
TotalSentSat *Amount `json:"totalSent,omitempty"`
|
||||
ContractIndex string `json:"-"`
|
||||
}
|
||||
|
||||
// TokenTransfer contains info about a token transfer done in a transaction
|
||||
type TokenTransfer struct {
|
||||
Type TokenType `json:"type"`
|
||||
From string `json:"from"`
|
||||
To string `json:"to"`
|
||||
Token string `json:"token"`
|
||||
Name string `json:"name"`
|
||||
Symbol string `json:"symbol"`
|
||||
Decimals int `json:"decimals"`
|
||||
Value *Amount `json:"value"`
|
||||
}
|
||||
|
||||
// EthereumSpecific contains ethereum specific transaction data
|
||||
type EthereumSpecific struct {
|
||||
Status eth.TxStatus `json:"status"` // 1 OK, 0 Fail, -1 pending
|
||||
Nonce uint64 `json:"nonce"`
|
||||
GasLimit *big.Int `json:"gasLimit"`
|
||||
GasUsed *big.Int `json:"gasUsed"`
|
||||
GasPrice *Amount `json:"gasPrice"`
|
||||
Data string `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// Tx holds information about a transaction
|
||||
type Tx struct {
|
||||
Txid string `json:"txid"`
|
||||
Version int32 `json:"version,omitempty"`
|
||||
Locktime uint32 `json:"locktime,omitempty"`
|
||||
Vin []Vin `json:"vin"`
|
||||
Vout []Vout `json:"vout"`
|
||||
Blockhash string `json:"blockhash,omitempty"`
|
||||
Blockheight int `json:"blockheight"`
|
||||
Confirmations uint32 `json:"confirmations"`
|
||||
Time int64 `json:"time,omitempty"`
|
||||
Blocktime int64 `json:"blocktime"`
|
||||
ValueOut string `json:"valueOut"`
|
||||
ValueOutSat big.Int `json:"-"`
|
||||
Size int `json:"size,omitempty"`
|
||||
ValueIn string `json:"valueIn"`
|
||||
ValueInSat big.Int `json:"-"`
|
||||
Fees string `json:"fees"`
|
||||
FeesSat big.Int `json:"-"`
|
||||
Hex string `json:"hex"`
|
||||
Txid string `json:"txid"`
|
||||
Version int32 `json:"version,omitempty"`
|
||||
Locktime uint32 `json:"lockTime,omitempty"`
|
||||
Vin []Vin `json:"vin"`
|
||||
Vout []Vout `json:"vout"`
|
||||
Blockhash string `json:"blockHash,omitempty"`
|
||||
Blockheight int `json:"blockHeight"`
|
||||
Confirmations uint32 `json:"confirmations"`
|
||||
Blocktime int64 `json:"blockTime"`
|
||||
Size int `json:"size,omitempty"`
|
||||
ValueOutSat *Amount `json:"value"`
|
||||
ValueInSat *Amount `json:"valueIn,omitempty"`
|
||||
FeesSat *Amount `json:"fees,omitempty"`
|
||||
Hex string `json:"hex,omitempty"`
|
||||
Rbf bool `json:"rbf,omitempty"`
|
||||
CoinSpecificData json.RawMessage `json:"coinSpecificData,omitempty"`
|
||||
TokenTransfers []TokenTransfer `json:"tokenTransfers,omitempty"`
|
||||
EthereumSpecific *EthereumSpecific `json:"ethereumSpecific,omitempty"`
|
||||
}
|
||||
|
||||
// FeeStats contains detailed block fee statistics
|
||||
type FeeStats struct {
|
||||
TxCount int `json:"txCount"`
|
||||
TotalFeesSat *Amount `json:"totalFeesSat"`
|
||||
AverageFeePerKb int64 `json:"averageFeePerKb"`
|
||||
DecilesFeePerKb [11]int64 `json:"decilesFeePerKb"`
|
||||
}
|
||||
|
||||
// Paging contains information about paging for address, blocks and block
|
||||
type Paging struct {
|
||||
Page int `json:"page"`
|
||||
TotalPages int `json:"totalPages"`
|
||||
ItemsOnPage int `json:"itemsOnPage"`
|
||||
Page int `json:"page,omitempty"`
|
||||
TotalPages int `json:"totalPages,omitempty"`
|
||||
ItemsOnPage int `json:"itemsOnPage,omitempty"`
|
||||
}
|
||||
|
||||
// TokensToReturn specifies what tokens are returned by GetAddress and GetXpubAddress
|
||||
type TokensToReturn int
|
||||
|
||||
const (
|
||||
// AddressFilterVoutOff disables filtering of transactions by vout
|
||||
AddressFilterVoutOff = -1
|
||||
// AddressFilterVoutInputs specifies that only txs where the address is as input are returned
|
||||
AddressFilterVoutInputs = -2
|
||||
// AddressFilterVoutOutputs specifies that only txs where the address is as output are returned
|
||||
AddressFilterVoutOutputs = -3
|
||||
// AddressFilterVoutQueryNotNecessary signals that query for transactions is not necessary as there are no transactions for specified contract filter
|
||||
AddressFilterVoutQueryNotNecessary = -4
|
||||
|
||||
// TokensToReturnNonzeroBalance - return only tokens with nonzero balance
|
||||
TokensToReturnNonzeroBalance TokensToReturn = 0
|
||||
// TokensToReturnUsed - return tokens with some transfers (even if they have zero balance now)
|
||||
TokensToReturnUsed TokensToReturn = 1
|
||||
// TokensToReturnDerived - return all derived tokens
|
||||
TokensToReturnDerived TokensToReturn = 2
|
||||
)
|
||||
|
||||
// AddressFilter is used to filter data returned from GetAddress api method
|
||||
type AddressFilter struct {
|
||||
Vout int
|
||||
Contract string
|
||||
FromHeight uint32
|
||||
ToHeight uint32
|
||||
TokensToReturn TokensToReturn
|
||||
// OnlyConfirmed set to true will ignore mempool transactions; mempool is also ignored if FromHeight/ToHeight filter is specified
|
||||
OnlyConfirmed bool
|
||||
}
|
||||
|
||||
// Address holds information about address and its transactions
|
||||
type Address struct {
|
||||
Paging
|
||||
AddrStr string `json:"addrStr"`
|
||||
Balance string `json:"balance"`
|
||||
TotalReceived string `json:"totalReceived"`
|
||||
TotalSent string `json:"totalSent"`
|
||||
UnconfirmedBalance string `json:"unconfirmedBalance"`
|
||||
UnconfirmedTxApperances int `json:"unconfirmedTxApperances"`
|
||||
TxApperances int `json:"txApperances"`
|
||||
Transactions []*Tx `json:"txs,omitempty"`
|
||||
Txids []string `json:"transactions,omitempty"`
|
||||
AddrStr string `json:"address"`
|
||||
BalanceSat *Amount `json:"balance"`
|
||||
TotalReceivedSat *Amount `json:"totalReceived,omitempty"`
|
||||
TotalSentSat *Amount `json:"totalSent,omitempty"`
|
||||
UnconfirmedBalanceSat *Amount `json:"unconfirmedBalance"`
|
||||
UnconfirmedTxs int `json:"unconfirmedTxs"`
|
||||
Txs int `json:"txs"`
|
||||
NonTokenTxs int `json:"nonTokenTxs,omitempty"`
|
||||
Transactions []*Tx `json:"transactions,omitempty"`
|
||||
Txids []string `json:"txids,omitempty"`
|
||||
Nonce string `json:"nonce,omitempty"`
|
||||
UsedTokens int `json:"usedTokens,omitempty"`
|
||||
Tokens []Token `json:"tokens,omitempty"`
|
||||
Erc20Contract *bchain.Erc20Contract `json:"erc20Contract,omitempty"`
|
||||
// helpers for explorer
|
||||
Filter string `json:"-"`
|
||||
XPubAddresses map[string]struct{} `json:"-"`
|
||||
}
|
||||
|
||||
// AddressUtxo holds information about address and its transactions
|
||||
type AddressUtxo struct {
|
||||
// Utxo is one unspent transaction output
|
||||
type Utxo struct {
|
||||
Txid string `json:"txid"`
|
||||
Vout uint32 `json:"vout"`
|
||||
Amount string `json:"amount"`
|
||||
AmountSat big.Int `json:"satoshis"`
|
||||
Vout int32 `json:"vout"`
|
||||
AmountSat *Amount `json:"value"`
|
||||
Height int `json:"height,omitempty"`
|
||||
Confirmations int `json:"confirmations"`
|
||||
Address string `json:"address,omitempty"`
|
||||
Path string `json:"path,omitempty"`
|
||||
Locktime uint32 `json:"lockTime,omitempty"`
|
||||
Coinbase bool `json:"coinbase,omitempty"`
|
||||
}
|
||||
|
||||
// Utxos is array of Utxo
|
||||
type Utxos []Utxo
|
||||
|
||||
func (a Utxos) Len() int { return len(a) }
|
||||
func (a Utxos) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a Utxos) Less(i, j int) bool {
|
||||
// sort in reverse order, unconfirmed (height==0) utxos on top
|
||||
hi := a[i].Height
|
||||
hj := a[j].Height
|
||||
if hi == 0 {
|
||||
hi = maxInt
|
||||
}
|
||||
if hj == 0 {
|
||||
hj = maxInt
|
||||
}
|
||||
return hi >= hj
|
||||
}
|
||||
|
||||
// BalanceHistory contains info about one point in time of balance history
|
||||
type BalanceHistory struct {
|
||||
Time uint32 `json:"time"`
|
||||
Txs uint32 `json:"txs"`
|
||||
ReceivedSat *Amount `json:"received"`
|
||||
SentSat *Amount `json:"sent"`
|
||||
SentToSelfSat *Amount `json:"sentToSelf"`
|
||||
FiatRates map[string]float64 `json:"rates,omitempty"`
|
||||
Txid string `json:"txid,omitempty"`
|
||||
}
|
||||
|
||||
// BalanceHistories is array of BalanceHistory
|
||||
type BalanceHistories []BalanceHistory
|
||||
|
||||
func (a BalanceHistories) Len() int { return len(a) }
|
||||
func (a BalanceHistories) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a BalanceHistories) Less(i, j int) bool {
|
||||
ti := a[i].Time
|
||||
tj := a[j].Time
|
||||
if ti == tj {
|
||||
return a[i].Txid < a[j].Txid
|
||||
}
|
||||
return ti < tj
|
||||
}
|
||||
|
||||
// SortAndAggregate sums BalanceHistories to groups defined by parameter groupByTime
|
||||
func (a BalanceHistories) SortAndAggregate(groupByTime uint32) BalanceHistories {
|
||||
bhs := make(BalanceHistories, 0)
|
||||
if len(a) > 0 {
|
||||
bha := BalanceHistory{
|
||||
ReceivedSat: &Amount{},
|
||||
SentSat: &Amount{},
|
||||
SentToSelfSat: &Amount{},
|
||||
}
|
||||
sort.Sort(a)
|
||||
for i := range a {
|
||||
bh := &a[i]
|
||||
time := bh.Time - bh.Time%groupByTime
|
||||
if bha.Time != time {
|
||||
if bha.Time != 0 {
|
||||
// in aggregate, do not return txid as it could multiple of them
|
||||
bha.Txid = ""
|
||||
bhs = append(bhs, bha)
|
||||
}
|
||||
bha = BalanceHistory{
|
||||
Time: time,
|
||||
ReceivedSat: &Amount{},
|
||||
SentSat: &Amount{},
|
||||
SentToSelfSat: &Amount{},
|
||||
}
|
||||
}
|
||||
if bha.Txid != bh.Txid {
|
||||
bha.Txs += bh.Txs
|
||||
bha.Txid = bh.Txid
|
||||
}
|
||||
(*big.Int)(bha.ReceivedSat).Add((*big.Int)(bha.ReceivedSat), (*big.Int)(bh.ReceivedSat))
|
||||
(*big.Int)(bha.SentSat).Add((*big.Int)(bha.SentSat), (*big.Int)(bh.SentSat))
|
||||
(*big.Int)(bha.SentToSelfSat).Add((*big.Int)(bha.SentToSelfSat), (*big.Int)(bh.SentToSelfSat))
|
||||
}
|
||||
if bha.Txs > 0 {
|
||||
bha.Txid = ""
|
||||
bhs = append(bhs, bha)
|
||||
}
|
||||
}
|
||||
return bhs
|
||||
}
|
||||
|
||||
// Blocks is list of blocks with paging information
|
||||
|
@ -127,11 +374,28 @@ type Blocks struct {
|
|||
Blocks []db.BlockInfo `json:"blocks"`
|
||||
}
|
||||
|
||||
// BlockInfo contains extended block header data and a list of block txids
|
||||
type BlockInfo struct {
|
||||
Hash string `json:"hash"`
|
||||
Prev string `json:"previousBlockHash,omitempty"`
|
||||
Next string `json:"nextBlockHash,omitempty"`
|
||||
Height uint32 `json:"height"`
|
||||
Confirmations int `json:"confirmations"`
|
||||
Size int `json:"size"`
|
||||
Time int64 `json:"time,omitempty"`
|
||||
Version common.JSONNumber `json:"version"`
|
||||
MerkleRoot string `json:"merkleRoot"`
|
||||
Nonce string `json:"nonce"`
|
||||
Bits string `json:"bits"`
|
||||
Difficulty string `json:"difficulty"`
|
||||
Txids []string `json:"tx,omitempty"`
|
||||
}
|
||||
|
||||
// Block contains information about block
|
||||
type Block struct {
|
||||
Paging
|
||||
bchain.BlockInfo
|
||||
TxCount int `json:"TxCount"`
|
||||
BlockInfo
|
||||
TxCount int `json:"txCount"`
|
||||
Transactions []*Tx `json:"txs,omitempty"`
|
||||
}
|
||||
|
||||
|
@ -140,16 +404,17 @@ type BlockbookInfo struct {
|
|||
Coin string `json:"coin"`
|
||||
Host string `json:"host"`
|
||||
Version string `json:"version"`
|
||||
GitCommit string `json:"gitcommit"`
|
||||
BuildTime string `json:"buildtime"`
|
||||
GitCommit string `json:"gitCommit"`
|
||||
BuildTime string `json:"buildTime"`
|
||||
SyncMode bool `json:"syncMode"`
|
||||
InitialSync bool `json:"initialsync"`
|
||||
InitialSync bool `json:"initialSync"`
|
||||
InSync bool `json:"inSync"`
|
||||
BestHeight uint32 `json:"bestHeight"`
|
||||
LastBlockTime time.Time `json:"lastBlockTime"`
|
||||
InSyncMempool bool `json:"inSyncMempool"`
|
||||
LastMempoolTime time.Time `json:"lastMempoolTime"`
|
||||
MempoolSize int `json:"mempoolSize"`
|
||||
Decimals int `json:"decimals"`
|
||||
DbSize int64 `json:"dbSize"`
|
||||
DbSizeFromColumns int64 `json:"dbSizeFromColumns,omitempty"`
|
||||
DbColumns []common.InternalStateColumn `json:"dbColumns,omitempty"`
|
||||
|
@ -158,6 +423,19 @@ type BlockbookInfo struct {
|
|||
|
||||
// SystemInfo contains information about the running blockbook and backend instance
|
||||
type SystemInfo struct {
|
||||
Blockbook *BlockbookInfo `json:"blockbook"`
|
||||
Backend *bchain.ChainInfo `json:"backend"`
|
||||
Blockbook *BlockbookInfo `json:"blockbook"`
|
||||
Backend *common.BackendInfo `json:"backend"`
|
||||
}
|
||||
|
||||
// MempoolTxid contains information about a transaction in mempool
|
||||
type MempoolTxid struct {
|
||||
Time int64 `json:"time"`
|
||||
Txid string `json:"txid"`
|
||||
}
|
||||
|
||||
// MempoolTxids contains a list of mempool txids with paging information
|
||||
type MempoolTxids struct {
|
||||
Paging
|
||||
Mempool []MempoolTxid `json:"mempool"`
|
||||
MempoolSize int `json:"mempoolSize"`
|
||||
}
|
||||
|
|
|
@ -0,0 +1,174 @@
|
|||
// +build unittest
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAmount_MarshalJSON(t *testing.T) {
|
||||
type amounts struct {
|
||||
A1 Amount `json:"a1"`
|
||||
A2 Amount `json:"a2,omitempty"`
|
||||
PA1 *Amount `json:"pa1"`
|
||||
PA2 *Amount `json:"pa2,omitempty"`
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
a amounts
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
want: `{"a1":"0","a2":"0","pa1":null}`,
|
||||
},
|
||||
{
|
||||
name: "1",
|
||||
a: amounts{
|
||||
A1: (Amount)(*big.NewInt(123456)),
|
||||
A2: (Amount)(*big.NewInt(787901)),
|
||||
PA1: (*Amount)(big.NewInt(234567)),
|
||||
PA2: (*Amount)(big.NewInt(890123)),
|
||||
},
|
||||
want: `{"a1":"123456","a2":"787901","pa1":"234567","pa2":"890123"}`,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
b, err := json.Marshal(&tt.a)
|
||||
if err != nil {
|
||||
t.Errorf("json.Marshal() error = %v", err)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(string(b), tt.want) {
|
||||
t.Errorf("json.Marshal() = %v, want %v", string(b), tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBalanceHistories_SortAndAggregate(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
a BalanceHistories
|
||||
groupByTime uint32
|
||||
want BalanceHistories
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
a: []BalanceHistory{},
|
||||
groupByTime: 3600,
|
||||
want: []BalanceHistory{},
|
||||
},
|
||||
{
|
||||
name: "one",
|
||||
a: []BalanceHistory{
|
||||
{
|
||||
ReceivedSat: (*Amount)(big.NewInt(1)),
|
||||
SentSat: (*Amount)(big.NewInt(2)),
|
||||
SentToSelfSat: (*Amount)(big.NewInt(1)),
|
||||
Time: 1521514812,
|
||||
Txid: "00b2c06055e5e90e9c82bd4181fde310104391a7fa4f289b1704e5d90caa3840",
|
||||
Txs: 1,
|
||||
},
|
||||
},
|
||||
groupByTime: 3600,
|
||||
want: []BalanceHistory{
|
||||
{
|
||||
ReceivedSat: (*Amount)(big.NewInt(1)),
|
||||
SentSat: (*Amount)(big.NewInt(2)),
|
||||
SentToSelfSat: (*Amount)(big.NewInt(1)),
|
||||
Time: 1521514800,
|
||||
Txs: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "aggregate",
|
||||
a: []BalanceHistory{
|
||||
{
|
||||
ReceivedSat: (*Amount)(big.NewInt(1)),
|
||||
SentSat: (*Amount)(big.NewInt(2)),
|
||||
SentToSelfSat: (*Amount)(big.NewInt(0)),
|
||||
Time: 1521504812,
|
||||
Txid: "0011223344556677889900112233445566778899001122334455667788990011",
|
||||
Txs: 1,
|
||||
},
|
||||
{
|
||||
ReceivedSat: (*Amount)(big.NewInt(3)),
|
||||
SentSat: (*Amount)(big.NewInt(4)),
|
||||
SentToSelfSat: (*Amount)(big.NewInt(2)),
|
||||
Time: 1521504812,
|
||||
Txid: "00b2c06055e5e90e9c82bd4181fde310104391a7fa4f289b1704e5d90caa3840",
|
||||
Txs: 1,
|
||||
},
|
||||
{
|
||||
ReceivedSat: (*Amount)(big.NewInt(5)),
|
||||
SentSat: (*Amount)(big.NewInt(6)),
|
||||
SentToSelfSat: (*Amount)(big.NewInt(3)),
|
||||
Time: 1521514812,
|
||||
Txid: "00b2c06055e5e90e9c82bd4181fde310104391a7fa4f289b1704e5d90caa3840",
|
||||
Txs: 1,
|
||||
},
|
||||
{
|
||||
ReceivedSat: (*Amount)(big.NewInt(7)),
|
||||
SentSat: (*Amount)(big.NewInt(8)),
|
||||
SentToSelfSat: (*Amount)(big.NewInt(3)),
|
||||
Time: 1521504812,
|
||||
Txid: "00b2c06055e5e90e9c82bd4181fde310104391a7fa4f289b1704e5d90caa3840",
|
||||
Txs: 1,
|
||||
},
|
||||
{
|
||||
ReceivedSat: (*Amount)(big.NewInt(9)),
|
||||
SentSat: (*Amount)(big.NewInt(10)),
|
||||
SentToSelfSat: (*Amount)(big.NewInt(5)),
|
||||
Time: 1521534812,
|
||||
Txid: "0011223344556677889900112233445566778899001122334455667788990011",
|
||||
Txs: 1,
|
||||
},
|
||||
{
|
||||
ReceivedSat: (*Amount)(big.NewInt(11)),
|
||||
SentSat: (*Amount)(big.NewInt(12)),
|
||||
SentToSelfSat: (*Amount)(big.NewInt(6)),
|
||||
Time: 1521534812,
|
||||
Txid: "1122334455667788990011223344556677889900112233445566778899001100",
|
||||
Txs: 1,
|
||||
},
|
||||
},
|
||||
groupByTime: 3600,
|
||||
want: []BalanceHistory{
|
||||
{
|
||||
ReceivedSat: (*Amount)(big.NewInt(11)),
|
||||
SentSat: (*Amount)(big.NewInt(14)),
|
||||
SentToSelfSat: (*Amount)(big.NewInt(5)),
|
||||
Time: 1521504000,
|
||||
Txs: 2,
|
||||
},
|
||||
{
|
||||
ReceivedSat: (*Amount)(big.NewInt(5)),
|
||||
SentSat: (*Amount)(big.NewInt(6)),
|
||||
SentToSelfSat: (*Amount)(big.NewInt(3)),
|
||||
Time: 1521514800,
|
||||
Txs: 1,
|
||||
},
|
||||
{
|
||||
ReceivedSat: (*Amount)(big.NewInt(20)),
|
||||
SentSat: (*Amount)(big.NewInt(22)),
|
||||
SentToSelfSat: (*Amount)(big.NewInt(11)),
|
||||
Time: 1521532800,
|
||||
Txs: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := tt.a.SortAndAggregate(tt.groupByTime); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("BalanceHistories.SortAndAggregate() = %+v, want %+v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,221 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
)
|
||||
|
||||
// ScriptSigV1 is used for legacy api v1
|
||||
type ScriptSigV1 struct {
|
||||
Hex string `json:"hex,omitempty"`
|
||||
Asm string `json:"asm,omitempty"`
|
||||
}
|
||||
|
||||
// VinV1 is used for legacy api v1
|
||||
type VinV1 struct {
|
||||
Txid string `json:"txid"`
|
||||
Vout uint32 `json:"vout"`
|
||||
Sequence int64 `json:"sequence,omitempty"`
|
||||
N int `json:"n"`
|
||||
ScriptSig ScriptSigV1 `json:"scriptSig"`
|
||||
AddrDesc bchain.AddressDescriptor `json:"-"`
|
||||
Addresses []string `json:"addresses"`
|
||||
IsAddress bool `json:"-"`
|
||||
Value string `json:"value"`
|
||||
ValueSat big.Int `json:"-"`
|
||||
}
|
||||
|
||||
// ScriptPubKeyV1 is used for legacy api v1
|
||||
type ScriptPubKeyV1 struct {
|
||||
Hex string `json:"hex,omitempty"`
|
||||
Asm string `json:"asm,omitempty"`
|
||||
AddrDesc bchain.AddressDescriptor `json:"-"`
|
||||
Addresses []string `json:"addresses"`
|
||||
IsAddress bool `json:"-"`
|
||||
Type string `json:"type,omitempty"`
|
||||
}
|
||||
|
||||
// VoutV1 is used for legacy api v1
|
||||
type VoutV1 struct {
|
||||
Value string `json:"value"`
|
||||
ValueSat big.Int `json:"-"`
|
||||
N int `json:"n"`
|
||||
ScriptPubKey ScriptPubKeyV1 `json:"scriptPubKey"`
|
||||
Spent bool `json:"spent"`
|
||||
SpentTxID string `json:"spentTxId,omitempty"`
|
||||
SpentIndex int `json:"spentIndex,omitempty"`
|
||||
SpentHeight int `json:"spentHeight,omitempty"`
|
||||
}
|
||||
|
||||
// TxV1 is used for legacy api v1
|
||||
type TxV1 struct {
|
||||
Txid string `json:"txid"`
|
||||
Version int32 `json:"version,omitempty"`
|
||||
Locktime uint32 `json:"locktime,omitempty"`
|
||||
Vin []VinV1 `json:"vin"`
|
||||
Vout []VoutV1 `json:"vout"`
|
||||
Blockhash string `json:"blockhash,omitempty"`
|
||||
Blockheight int `json:"blockheight"`
|
||||
Confirmations uint32 `json:"confirmations"`
|
||||
Time int64 `json:"time,omitempty"`
|
||||
Blocktime int64 `json:"blocktime"`
|
||||
ValueOut string `json:"valueOut"`
|
||||
ValueOutSat big.Int `json:"-"`
|
||||
Size int `json:"size,omitempty"`
|
||||
ValueIn string `json:"valueIn"`
|
||||
ValueInSat big.Int `json:"-"`
|
||||
Fees string `json:"fees"`
|
||||
FeesSat big.Int `json:"-"`
|
||||
Hex string `json:"hex"`
|
||||
}
|
||||
|
||||
// AddressV1 is used for legacy api v1
|
||||
type AddressV1 struct {
|
||||
Paging
|
||||
AddrStr string `json:"addrStr"`
|
||||
Balance string `json:"balance"`
|
||||
TotalReceived string `json:"totalReceived"`
|
||||
TotalSent string `json:"totalSent"`
|
||||
UnconfirmedBalance string `json:"unconfirmedBalance"`
|
||||
UnconfirmedTxApperances int `json:"unconfirmedTxApperances"`
|
||||
TxApperances int `json:"txApperances"`
|
||||
Transactions []*TxV1 `json:"txs,omitempty"`
|
||||
Txids []string `json:"transactions,omitempty"`
|
||||
}
|
||||
|
||||
// AddressUtxoV1 is used for legacy api v1
|
||||
type AddressUtxoV1 struct {
|
||||
Txid string `json:"txid"`
|
||||
Vout uint32 `json:"vout"`
|
||||
Amount string `json:"amount"`
|
||||
AmountSat big.Int `json:"satoshis"`
|
||||
Height int `json:"height,omitempty"`
|
||||
Confirmations int `json:"confirmations"`
|
||||
}
|
||||
|
||||
// BlockV1 contains information about block
|
||||
type BlockV1 struct {
|
||||
Paging
|
||||
BlockInfo
|
||||
TxCount int `json:"txCount"`
|
||||
Transactions []*TxV1 `json:"txs,omitempty"`
|
||||
}
|
||||
|
||||
// TxToV1 converts Tx to TxV1
|
||||
func (w *Worker) TxToV1(tx *Tx) *TxV1 {
|
||||
d := w.chainParser.AmountDecimals()
|
||||
vinV1 := make([]VinV1, len(tx.Vin))
|
||||
for i := range tx.Vin {
|
||||
v := &tx.Vin[i]
|
||||
vinV1[i] = VinV1{
|
||||
AddrDesc: v.AddrDesc,
|
||||
Addresses: v.Addresses,
|
||||
N: v.N,
|
||||
ScriptSig: ScriptSigV1{
|
||||
Asm: v.Asm,
|
||||
Hex: v.Hex,
|
||||
},
|
||||
IsAddress: v.IsAddress,
|
||||
Sequence: v.Sequence,
|
||||
Txid: v.Txid,
|
||||
Value: v.ValueSat.DecimalString(d),
|
||||
ValueSat: v.ValueSat.AsBigInt(),
|
||||
Vout: v.Vout,
|
||||
}
|
||||
}
|
||||
voutV1 := make([]VoutV1, len(tx.Vout))
|
||||
for i := range tx.Vout {
|
||||
v := &tx.Vout[i]
|
||||
voutV1[i] = VoutV1{
|
||||
N: v.N,
|
||||
ScriptPubKey: ScriptPubKeyV1{
|
||||
AddrDesc: v.AddrDesc,
|
||||
Addresses: v.Addresses,
|
||||
Asm: v.Asm,
|
||||
Hex: v.Hex,
|
||||
IsAddress: v.IsAddress,
|
||||
Type: v.Type,
|
||||
},
|
||||
Spent: v.Spent,
|
||||
SpentHeight: v.SpentHeight,
|
||||
SpentIndex: v.SpentIndex,
|
||||
SpentTxID: v.SpentTxID,
|
||||
Value: v.ValueSat.DecimalString(d),
|
||||
ValueSat: v.ValueSat.AsBigInt(),
|
||||
}
|
||||
}
|
||||
return &TxV1{
|
||||
Blockhash: tx.Blockhash,
|
||||
Blockheight: tx.Blockheight,
|
||||
Blocktime: tx.Blocktime,
|
||||
Confirmations: tx.Confirmations,
|
||||
Fees: tx.FeesSat.DecimalString(d),
|
||||
FeesSat: tx.FeesSat.AsBigInt(),
|
||||
Hex: tx.Hex,
|
||||
Locktime: tx.Locktime,
|
||||
Size: tx.Size,
|
||||
Time: tx.Blocktime,
|
||||
Txid: tx.Txid,
|
||||
ValueIn: tx.ValueInSat.DecimalString(d),
|
||||
ValueInSat: tx.ValueInSat.AsBigInt(),
|
||||
ValueOut: tx.ValueOutSat.DecimalString(d),
|
||||
ValueOutSat: tx.ValueOutSat.AsBigInt(),
|
||||
Version: tx.Version,
|
||||
Vin: vinV1,
|
||||
Vout: voutV1,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Worker) transactionsToV1(txs []*Tx) []*TxV1 {
|
||||
v1 := make([]*TxV1, len(txs))
|
||||
for i := range txs {
|
||||
v1[i] = w.TxToV1(txs[i])
|
||||
}
|
||||
return v1
|
||||
}
|
||||
|
||||
// AddressToV1 converts Address to AddressV1
|
||||
func (w *Worker) AddressToV1(a *Address) *AddressV1 {
|
||||
d := w.chainParser.AmountDecimals()
|
||||
return &AddressV1{
|
||||
AddrStr: a.AddrStr,
|
||||
Balance: a.BalanceSat.DecimalString(d),
|
||||
Paging: a.Paging,
|
||||
TotalReceived: a.TotalReceivedSat.DecimalString(d),
|
||||
TotalSent: a.TotalSentSat.DecimalString(d),
|
||||
Transactions: w.transactionsToV1(a.Transactions),
|
||||
TxApperances: a.Txs,
|
||||
Txids: a.Txids,
|
||||
UnconfirmedBalance: a.UnconfirmedBalanceSat.DecimalString(d),
|
||||
UnconfirmedTxApperances: a.UnconfirmedTxs,
|
||||
}
|
||||
}
|
||||
|
||||
// AddressUtxoToV1 converts []AddressUtxo to []AddressUtxoV1
|
||||
func (w *Worker) AddressUtxoToV1(au Utxos) []AddressUtxoV1 {
|
||||
d := w.chainParser.AmountDecimals()
|
||||
v1 := make([]AddressUtxoV1, len(au))
|
||||
for i := range au {
|
||||
utxo := &au[i]
|
||||
v1[i] = AddressUtxoV1{
|
||||
AmountSat: utxo.AmountSat.AsBigInt(),
|
||||
Amount: utxo.AmountSat.DecimalString(d),
|
||||
Confirmations: utxo.Confirmations,
|
||||
Height: utxo.Height,
|
||||
Txid: utxo.Txid,
|
||||
Vout: uint32(utxo.Vout),
|
||||
}
|
||||
}
|
||||
return v1
|
||||
}
|
||||
|
||||
// BlockToV1 converts Address to Address1
|
||||
func (w *Worker) BlockToV1(b *Block) *BlockV1 {
|
||||
return &BlockV1{
|
||||
BlockInfo: b.BlockInfo,
|
||||
Paging: b.Paging,
|
||||
Transactions: w.transactionsToV1(b.Transactions),
|
||||
TxCount: b.TxCount,
|
||||
}
|
||||
}
|
1805
api/worker.go
1805
api/worker.go
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,637 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/juju/errors"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
"spacecruft.org/spacecruft/blockbook/db"
|
||||
)
|
||||
|
||||
const defaultAddressesGap = 20
|
||||
const maxAddressesGap = 10000
|
||||
|
||||
const txInput = 1
|
||||
const txOutput = 2
|
||||
|
||||
const xpubCacheSize = 512
|
||||
const xpubCacheExpirationSeconds = 7200
|
||||
|
||||
var cachedXpubs = make(map[string]xpubData)
|
||||
var cachedXpubsMux sync.Mutex
|
||||
|
||||
type xpubTxid struct {
|
||||
txid string
|
||||
height uint32
|
||||
inputOutput byte
|
||||
}
|
||||
|
||||
type xpubTxids []xpubTxid
|
||||
|
||||
func (a xpubTxids) Len() int { return len(a) }
|
||||
func (a xpubTxids) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a xpubTxids) Less(i, j int) bool {
|
||||
// if the heights are equal, make inputs less than outputs
|
||||
hi := a[i].height
|
||||
hj := a[j].height
|
||||
if hi == hj {
|
||||
return (a[i].inputOutput & txInput) >= (a[j].inputOutput & txInput)
|
||||
}
|
||||
return hi > hj
|
||||
}
|
||||
|
||||
type xpubAddress struct {
|
||||
addrDesc bchain.AddressDescriptor
|
||||
balance *db.AddrBalance
|
||||
txs uint32
|
||||
maxHeight uint32
|
||||
complete bool
|
||||
txids xpubTxids
|
||||
}
|
||||
|
||||
type xpubData struct {
|
||||
gap int
|
||||
accessed int64
|
||||
basePath string
|
||||
dataHeight uint32
|
||||
dataHash string
|
||||
txCountEstimate uint32
|
||||
sentSat big.Int
|
||||
balanceSat big.Int
|
||||
addresses []xpubAddress
|
||||
changeAddresses []xpubAddress
|
||||
}
|
||||
|
||||
func (w *Worker) xpubGetAddressTxids(addrDesc bchain.AddressDescriptor, mempool bool, fromHeight, toHeight uint32, maxResults int) ([]xpubTxid, bool, error) {
|
||||
var err error
|
||||
complete := true
|
||||
txs := make([]xpubTxid, 0, 4)
|
||||
var callback db.GetTransactionsCallback
|
||||
callback = func(txid string, height uint32, indexes []int32) error {
|
||||
// take all txs in the last found block even if it exceeds maxResults
|
||||
if len(txs) >= maxResults && txs[len(txs)-1].height != height {
|
||||
complete = false
|
||||
return &db.StopIteration{}
|
||||
}
|
||||
inputOutput := byte(0)
|
||||
for _, index := range indexes {
|
||||
if index < 0 {
|
||||
inputOutput |= txInput
|
||||
} else {
|
||||
inputOutput |= txOutput
|
||||
}
|
||||
}
|
||||
txs = append(txs, xpubTxid{txid, height, inputOutput})
|
||||
return nil
|
||||
}
|
||||
if mempool {
|
||||
uniqueTxs := make(map[string]int)
|
||||
o, err := w.mempool.GetAddrDescTransactions(addrDesc)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
for _, m := range o {
|
||||
if l, found := uniqueTxs[m.Txid]; !found {
|
||||
l = len(txs)
|
||||
callback(m.Txid, 0, []int32{m.Vout})
|
||||
if len(txs) > l {
|
||||
uniqueTxs[m.Txid] = l
|
||||
}
|
||||
} else {
|
||||
if m.Vout < 0 {
|
||||
txs[l].inputOutput |= txInput
|
||||
} else {
|
||||
txs[l].inputOutput |= txOutput
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
err = w.db.GetAddrDescTransactions(addrDesc, fromHeight, toHeight, callback)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
}
|
||||
return txs, complete, nil
|
||||
}
|
||||
|
||||
func (w *Worker) xpubCheckAndLoadTxids(ad *xpubAddress, filter *AddressFilter, maxHeight uint32, maxResults int) error {
|
||||
// skip if not used
|
||||
if ad.balance == nil {
|
||||
return nil
|
||||
}
|
||||
// if completely loaded, check if there are not some new txs and load if necessary
|
||||
if ad.complete {
|
||||
if ad.balance.Txs != ad.txs {
|
||||
newTxids, _, err := w.xpubGetAddressTxids(ad.addrDesc, false, ad.maxHeight+1, maxHeight, maxInt)
|
||||
if err == nil {
|
||||
ad.txids = append(newTxids, ad.txids...)
|
||||
ad.maxHeight = maxHeight
|
||||
ad.txs = uint32(len(ad.txids))
|
||||
if ad.txs != ad.balance.Txs {
|
||||
glog.Warning("xpubCheckAndLoadTxids inconsistency ", ad.addrDesc, ", ad.txs=", ad.txs, ", ad.balance.Txs=", ad.balance.Txs)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
// load all txids to get paging correctly
|
||||
newTxids, complete, err := w.xpubGetAddressTxids(ad.addrDesc, false, 0, maxHeight, maxInt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ad.txids = newTxids
|
||||
ad.complete = complete
|
||||
ad.maxHeight = maxHeight
|
||||
if complete {
|
||||
ad.txs = uint32(len(ad.txids))
|
||||
if ad.txs != ad.balance.Txs {
|
||||
glog.Warning("xpubCheckAndLoadTxids inconsistency ", ad.addrDesc, ", ad.txs=", ad.txs, ", ad.balance.Txs=", ad.balance.Txs)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Worker) xpubDerivedAddressBalance(data *xpubData, ad *xpubAddress) (bool, error) {
|
||||
var err error
|
||||
if ad.balance, err = w.db.GetAddrDescBalance(ad.addrDesc, db.AddressBalanceDetailUTXO); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if ad.balance != nil {
|
||||
data.txCountEstimate += ad.balance.Txs
|
||||
data.sentSat.Add(&data.sentSat, &ad.balance.SentSat)
|
||||
data.balanceSat.Add(&data.balanceSat, &ad.balance.BalanceSat)
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (w *Worker) xpubScanAddresses(xpub string, data *xpubData, addresses []xpubAddress, gap int, change int, minDerivedIndex int, fork bool) (int, []xpubAddress, error) {
|
||||
// rescan known addresses
|
||||
lastUsed := 0
|
||||
for i := range addresses {
|
||||
ad := &addresses[i]
|
||||
if fork {
|
||||
// reset the cached data
|
||||
ad.txs = 0
|
||||
ad.maxHeight = 0
|
||||
ad.complete = false
|
||||
ad.txids = nil
|
||||
}
|
||||
used, err := w.xpubDerivedAddressBalance(data, ad)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
if used {
|
||||
lastUsed = i
|
||||
}
|
||||
}
|
||||
// derive new addresses as necessary
|
||||
missing := len(addresses) - lastUsed
|
||||
for missing < gap {
|
||||
from := len(addresses)
|
||||
to := from + gap - missing
|
||||
if to < minDerivedIndex {
|
||||
to = minDerivedIndex
|
||||
}
|
||||
descriptors, err := w.chainParser.DeriveAddressDescriptorsFromTo(xpub, uint32(change), uint32(from), uint32(to))
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
for i, a := range descriptors {
|
||||
ad := xpubAddress{addrDesc: a}
|
||||
used, err := w.xpubDerivedAddressBalance(data, &ad)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
if used {
|
||||
lastUsed = i + from
|
||||
}
|
||||
addresses = append(addresses, ad)
|
||||
}
|
||||
missing = len(addresses) - lastUsed
|
||||
}
|
||||
return lastUsed, addresses, nil
|
||||
}
|
||||
|
||||
func (w *Worker) tokenFromXpubAddress(data *xpubData, ad *xpubAddress, changeIndex int, index int, option AccountDetails) Token {
|
||||
a, _, _ := w.chainParser.GetAddressesFromAddrDesc(ad.addrDesc)
|
||||
var address string
|
||||
if len(a) > 0 {
|
||||
address = a[0]
|
||||
}
|
||||
var balance, totalReceived, totalSent *big.Int
|
||||
var transfers int
|
||||
if ad.balance != nil {
|
||||
transfers = int(ad.balance.Txs)
|
||||
if option >= AccountDetailsTokenBalances {
|
||||
balance = &ad.balance.BalanceSat
|
||||
totalSent = &ad.balance.SentSat
|
||||
totalReceived = ad.balance.ReceivedSat()
|
||||
}
|
||||
}
|
||||
return Token{
|
||||
Type: XPUBAddressTokenType,
|
||||
Name: address,
|
||||
Decimals: w.chainParser.AmountDecimals(),
|
||||
BalanceSat: (*Amount)(balance),
|
||||
TotalReceivedSat: (*Amount)(totalReceived),
|
||||
TotalSentSat: (*Amount)(totalSent),
|
||||
Transfers: transfers,
|
||||
Path: fmt.Sprintf("%s/%d/%d", data.basePath, changeIndex, index),
|
||||
}
|
||||
}
|
||||
|
||||
func evictXpubCacheItems() {
|
||||
var oldestKey string
|
||||
oldest := maxInt64
|
||||
now := time.Now().Unix()
|
||||
count := 0
|
||||
for k, v := range cachedXpubs {
|
||||
if v.accessed+xpubCacheExpirationSeconds < now {
|
||||
delete(cachedXpubs, k)
|
||||
count++
|
||||
}
|
||||
if v.accessed < oldest {
|
||||
oldestKey = k
|
||||
oldest = v.accessed
|
||||
}
|
||||
}
|
||||
if oldestKey != "" && oldest+xpubCacheExpirationSeconds >= now {
|
||||
delete(cachedXpubs, oldestKey)
|
||||
count++
|
||||
}
|
||||
glog.Info("Evicted ", count, " items from xpub cache, oldest item accessed at ", time.Unix(oldest, 0), ", cache size ", len(cachedXpubs))
|
||||
}
|
||||
|
||||
func (w *Worker) getXpubData(xpub string, page int, txsOnPage int, option AccountDetails, filter *AddressFilter, gap int) (*xpubData, uint32, error) {
|
||||
if w.chainType != bchain.ChainBitcoinType {
|
||||
return nil, 0, ErrUnsupportedXpub
|
||||
}
|
||||
var (
|
||||
err error
|
||||
bestheight uint32
|
||||
besthash string
|
||||
)
|
||||
if gap <= 0 {
|
||||
gap = defaultAddressesGap
|
||||
} else if gap > maxAddressesGap {
|
||||
// limit the maximum gap to protect against unreasonably big values that could cause high load of the server
|
||||
gap = maxAddressesGap
|
||||
}
|
||||
// gap is increased one as there must be gap of empty addresses before the derivation is stopped
|
||||
gap++
|
||||
var processedHash string
|
||||
cachedXpubsMux.Lock()
|
||||
data, found := cachedXpubs[xpub]
|
||||
cachedXpubsMux.Unlock()
|
||||
// to load all data for xpub may take some time, do it in a loop to process a possible new block
|
||||
for {
|
||||
bestheight, besthash, err = w.db.GetBestBlock()
|
||||
if err != nil {
|
||||
return nil, 0, errors.Annotatef(err, "GetBestBlock")
|
||||
}
|
||||
if besthash == processedHash {
|
||||
break
|
||||
}
|
||||
fork := false
|
||||
if !found || data.gap != gap {
|
||||
data = xpubData{gap: gap}
|
||||
data.basePath, err = w.chainParser.DerivationBasePath(xpub)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
} else {
|
||||
hash, err := w.db.GetBlockHash(data.dataHeight)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
if hash != data.dataHash {
|
||||
// in case of for reset all cached data
|
||||
fork = true
|
||||
}
|
||||
}
|
||||
processedHash = besthash
|
||||
if data.dataHeight < bestheight || fork {
|
||||
data.dataHeight = bestheight
|
||||
data.dataHash = besthash
|
||||
data.balanceSat = *new(big.Int)
|
||||
data.sentSat = *new(big.Int)
|
||||
data.txCountEstimate = 0
|
||||
var lastUsedIndex int
|
||||
lastUsedIndex, data.addresses, err = w.xpubScanAddresses(xpub, &data, data.addresses, gap, 0, 0, fork)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
_, data.changeAddresses, err = w.xpubScanAddresses(xpub, &data, data.changeAddresses, gap, 1, lastUsedIndex, fork)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
}
|
||||
if option >= AccountDetailsTxidHistory {
|
||||
for _, da := range [][]xpubAddress{data.addresses, data.changeAddresses} {
|
||||
for i := range da {
|
||||
if err = w.xpubCheckAndLoadTxids(&da[i], filter, bestheight, (page+1)*txsOnPage); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
data.accessed = time.Now().Unix()
|
||||
cachedXpubsMux.Lock()
|
||||
if len(cachedXpubs) >= xpubCacheSize {
|
||||
evictXpubCacheItems()
|
||||
}
|
||||
cachedXpubs[xpub] = data
|
||||
cachedXpubsMux.Unlock()
|
||||
return &data, bestheight, nil
|
||||
}
|
||||
|
||||
// GetXpubAddress computes address value and gets transactions for given address
|
||||
func (w *Worker) GetXpubAddress(xpub string, page int, txsOnPage int, option AccountDetails, filter *AddressFilter, gap int) (*Address, error) {
|
||||
start := time.Now()
|
||||
page--
|
||||
if page < 0 {
|
||||
page = 0
|
||||
}
|
||||
type mempoolMap struct {
|
||||
tx *Tx
|
||||
inputOutput byte
|
||||
}
|
||||
var (
|
||||
txc xpubTxids
|
||||
txmMap map[string]*Tx
|
||||
txCount int
|
||||
txs []*Tx
|
||||
txids []string
|
||||
pg Paging
|
||||
filtered bool
|
||||
err error
|
||||
uBalSat big.Int
|
||||
unconfirmedTxs int
|
||||
)
|
||||
data, bestheight, err := w.getXpubData(xpub, page, txsOnPage, option, filter, gap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// setup filtering of txids
|
||||
var txidFilter func(txid *xpubTxid, ad *xpubAddress) bool
|
||||
if !(filter.FromHeight == 0 && filter.ToHeight == 0 && filter.Vout == AddressFilterVoutOff) {
|
||||
toHeight := maxUint32
|
||||
if filter.ToHeight != 0 {
|
||||
toHeight = filter.ToHeight
|
||||
}
|
||||
txidFilter = func(txid *xpubTxid, ad *xpubAddress) bool {
|
||||
if txid.height < filter.FromHeight || txid.height > toHeight {
|
||||
return false
|
||||
}
|
||||
if filter.Vout != AddressFilterVoutOff {
|
||||
if filter.Vout == AddressFilterVoutInputs && txid.inputOutput&txInput == 0 ||
|
||||
filter.Vout == AddressFilterVoutOutputs && txid.inputOutput&txOutput == 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
filtered = true
|
||||
}
|
||||
// process mempool, only if ToHeight is not specified
|
||||
if filter.ToHeight == 0 && !filter.OnlyConfirmed {
|
||||
txmMap = make(map[string]*Tx)
|
||||
mempoolEntries := make(bchain.MempoolTxidEntries, 0)
|
||||
for _, da := range [][]xpubAddress{data.addresses, data.changeAddresses} {
|
||||
for i := range da {
|
||||
ad := &da[i]
|
||||
newTxids, _, err := w.xpubGetAddressTxids(ad.addrDesc, true, 0, 0, maxInt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, txid := range newTxids {
|
||||
// the same tx can have multiple addresses from the same xpub, get it from backend it only once
|
||||
tx, foundTx := txmMap[txid.txid]
|
||||
if !foundTx {
|
||||
tx, err = w.GetTransaction(txid.txid, false, true)
|
||||
// mempool transaction may fail
|
||||
if err != nil || tx == nil {
|
||||
glog.Warning("GetTransaction in mempool: ", err)
|
||||
continue
|
||||
}
|
||||
txmMap[txid.txid] = tx
|
||||
}
|
||||
// skip already confirmed txs, mempool may be out of sync
|
||||
if tx.Confirmations == 0 {
|
||||
if !foundTx {
|
||||
unconfirmedTxs++
|
||||
}
|
||||
uBalSat.Add(&uBalSat, tx.getAddrVoutValue(ad.addrDesc))
|
||||
uBalSat.Sub(&uBalSat, tx.getAddrVinValue(ad.addrDesc))
|
||||
// mempool txs are returned only on the first page, uniquely and filtered
|
||||
if page == 0 && !foundTx && (txidFilter == nil || txidFilter(&txid, ad)) {
|
||||
mempoolEntries = append(mempoolEntries, bchain.MempoolTxidEntry{Txid: txid.txid, Time: uint32(tx.Blocktime)})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// sort the entries by time descending
|
||||
sort.Sort(mempoolEntries)
|
||||
for _, entry := range mempoolEntries {
|
||||
if option == AccountDetailsTxidHistory {
|
||||
txids = append(txids, entry.Txid)
|
||||
} else if option >= AccountDetailsTxHistoryLight {
|
||||
txs = append(txs, txmMap[entry.Txid])
|
||||
}
|
||||
}
|
||||
}
|
||||
if option >= AccountDetailsTxidHistory {
|
||||
txcMap := make(map[string]bool)
|
||||
txc = make(xpubTxids, 0, 32)
|
||||
for _, da := range [][]xpubAddress{data.addresses, data.changeAddresses} {
|
||||
for i := range da {
|
||||
ad := &da[i]
|
||||
for _, txid := range ad.txids {
|
||||
added, foundTx := txcMap[txid.txid]
|
||||
// count txs regardless of filter but only once
|
||||
if !foundTx {
|
||||
txCount++
|
||||
}
|
||||
// add tx only once
|
||||
if !added {
|
||||
add := txidFilter == nil || txidFilter(&txid, ad)
|
||||
txcMap[txid.txid] = add
|
||||
if add {
|
||||
txc = append(txc, txid)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
sort.Stable(txc)
|
||||
txCount = len(txcMap)
|
||||
totalResults := txCount
|
||||
if filtered {
|
||||
totalResults = -1
|
||||
}
|
||||
var from, to int
|
||||
pg, from, to, page = computePaging(len(txc), page, txsOnPage)
|
||||
if len(txc) >= txsOnPage {
|
||||
if totalResults < 0 {
|
||||
pg.TotalPages = -1
|
||||
} else {
|
||||
pg, _, _, _ = computePaging(totalResults, page, txsOnPage)
|
||||
}
|
||||
}
|
||||
// get confirmed transactions
|
||||
for i := from; i < to; i++ {
|
||||
xpubTxid := &txc[i]
|
||||
if option == AccountDetailsTxidHistory {
|
||||
txids = append(txids, xpubTxid.txid)
|
||||
} else {
|
||||
tx, err := w.txFromTxid(xpubTxid.txid, bestheight, option, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
txs = append(txs, tx)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
txCount = int(data.txCountEstimate)
|
||||
}
|
||||
usedTokens := 0
|
||||
var tokens []Token
|
||||
var xpubAddresses map[string]struct{}
|
||||
if option > AccountDetailsBasic {
|
||||
tokens = make([]Token, 0, 4)
|
||||
xpubAddresses = make(map[string]struct{})
|
||||
}
|
||||
for ci, da := range [][]xpubAddress{data.addresses, data.changeAddresses} {
|
||||
for i := range da {
|
||||
ad := &da[i]
|
||||
if ad.balance != nil {
|
||||
usedTokens++
|
||||
}
|
||||
if option > AccountDetailsBasic {
|
||||
token := w.tokenFromXpubAddress(data, ad, ci, i, option)
|
||||
if filter.TokensToReturn == TokensToReturnDerived ||
|
||||
filter.TokensToReturn == TokensToReturnUsed && ad.balance != nil ||
|
||||
filter.TokensToReturn == TokensToReturnNonzeroBalance && ad.balance != nil && !IsZeroBigInt(&ad.balance.BalanceSat) {
|
||||
tokens = append(tokens, token)
|
||||
}
|
||||
xpubAddresses[token.Name] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
var totalReceived big.Int
|
||||
totalReceived.Add(&data.balanceSat, &data.sentSat)
|
||||
addr := Address{
|
||||
Paging: pg,
|
||||
AddrStr: xpub,
|
||||
BalanceSat: (*Amount)(&data.balanceSat),
|
||||
TotalReceivedSat: (*Amount)(&totalReceived),
|
||||
TotalSentSat: (*Amount)(&data.sentSat),
|
||||
Txs: txCount,
|
||||
UnconfirmedBalanceSat: (*Amount)(&uBalSat),
|
||||
UnconfirmedTxs: unconfirmedTxs,
|
||||
Transactions: txs,
|
||||
Txids: txids,
|
||||
UsedTokens: usedTokens,
|
||||
Tokens: tokens,
|
||||
XPubAddresses: xpubAddresses,
|
||||
}
|
||||
glog.Info("GetXpubAddress ", xpub[:16], ", ", len(data.addresses)+len(data.changeAddresses), " derived addresses, ", txCount, " confirmed txs, finished in ", time.Since(start))
|
||||
return &addr, nil
|
||||
}
|
||||
|
||||
// GetXpubUtxo returns unspent outputs for given xpub
|
||||
func (w *Worker) GetXpubUtxo(xpub string, onlyConfirmed bool, gap int) (Utxos, error) {
|
||||
start := time.Now()
|
||||
data, _, err := w.getXpubData(xpub, 0, 1, AccountDetailsBasic, &AddressFilter{
|
||||
Vout: AddressFilterVoutOff,
|
||||
OnlyConfirmed: onlyConfirmed,
|
||||
}, gap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r := make(Utxos, 0, 8)
|
||||
for ci, da := range [][]xpubAddress{data.addresses, data.changeAddresses} {
|
||||
for i := range da {
|
||||
ad := &da[i]
|
||||
onlyMempool := false
|
||||
if ad.balance == nil {
|
||||
if onlyConfirmed {
|
||||
continue
|
||||
}
|
||||
onlyMempool = true
|
||||
}
|
||||
utxos, err := w.getAddrDescUtxo(ad.addrDesc, ad.balance, onlyConfirmed, onlyMempool)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(utxos) > 0 {
|
||||
t := w.tokenFromXpubAddress(data, ad, ci, i, AccountDetailsTokens)
|
||||
for j := range utxos {
|
||||
a := &utxos[j]
|
||||
a.Address = t.Name
|
||||
a.Path = t.Path
|
||||
}
|
||||
r = append(r, utxos...)
|
||||
}
|
||||
}
|
||||
}
|
||||
sort.Stable(r)
|
||||
glog.Info("GetXpubUtxo ", xpub[:16], ", ", len(r), " utxos, finished in ", time.Since(start))
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// GetXpubBalanceHistory returns history of balance for given xpub
|
||||
func (w *Worker) GetXpubBalanceHistory(xpub string, fromTimestamp, toTimestamp int64, currencies []string, gap int, groupBy uint32) (BalanceHistories, error) {
|
||||
bhs := make(BalanceHistories, 0)
|
||||
start := time.Now()
|
||||
fromUnix, fromHeight, toUnix, toHeight := w.balanceHistoryHeightsFromTo(fromTimestamp, toTimestamp)
|
||||
if fromHeight >= toHeight {
|
||||
return bhs, nil
|
||||
}
|
||||
data, _, err := w.getXpubData(xpub, 0, 1, AccountDetailsTxidHistory, &AddressFilter{
|
||||
Vout: AddressFilterVoutOff,
|
||||
OnlyConfirmed: true,
|
||||
FromHeight: fromHeight,
|
||||
ToHeight: toHeight,
|
||||
}, gap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
selfAddrDesc := make(map[string]struct{})
|
||||
for _, da := range [][]xpubAddress{data.addresses, data.changeAddresses} {
|
||||
for i := range da {
|
||||
selfAddrDesc[string(da[i].addrDesc)] = struct{}{}
|
||||
}
|
||||
}
|
||||
for _, da := range [][]xpubAddress{data.addresses, data.changeAddresses} {
|
||||
for i := range da {
|
||||
ad := &da[i]
|
||||
txids := ad.txids
|
||||
for txi := len(txids) - 1; txi >= 0; txi-- {
|
||||
bh, err := w.balanceHistoryForTxid(ad.addrDesc, txids[txi].txid, fromUnix, toUnix, selfAddrDesc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if bh != nil {
|
||||
bhs = append(bhs, *bh)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
bha := bhs.SortAndAggregate(groupBy)
|
||||
err = w.setFiatRateToBalanceHistories(bha, currencies)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
glog.Info("GetUtxoBalanceHistory ", xpub[:16], ", blocks ", fromHeight, "-", toHeight, ", count ", len(bha), ", finished in ", time.Since(start))
|
||||
return bha, nil
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
package bchain
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
// BaseChain is base type for bchain.BlockChain
|
||||
type BaseChain struct {
|
||||
Parser BlockChainParser
|
||||
Testnet bool
|
||||
Network string
|
||||
}
|
||||
|
||||
// TODO more bchain.BlockChain methods
|
||||
|
||||
// GetChainParser returns BlockChainParser
|
||||
func (b *BaseChain) GetChainParser() BlockChainParser {
|
||||
return b.Parser
|
||||
}
|
||||
|
||||
// IsTestnet returns true if the blockchain is testnet
|
||||
func (b *BaseChain) IsTestnet() bool {
|
||||
return b.Testnet
|
||||
}
|
||||
|
||||
// GetNetworkName returns network name
|
||||
func (b *BaseChain) GetNetworkName() string {
|
||||
return b.Network
|
||||
}
|
||||
|
||||
// GetMempoolEntry is not supported by default
|
||||
func (b *BaseChain) GetMempoolEntry(txid string) (*MempoolEntry, error) {
|
||||
return nil, errors.New("GetMempoolEntry: not supported")
|
||||
}
|
||||
|
||||
// EthereumTypeGetBalance is not supported
|
||||
func (b *BaseChain) EthereumTypeGetBalance(addrDesc AddressDescriptor) (*big.Int, error) {
|
||||
return nil, errors.New("Not supported")
|
||||
}
|
||||
|
||||
// EthereumTypeGetNonce is not supported
|
||||
func (b *BaseChain) EthereumTypeGetNonce(addrDesc AddressDescriptor) (uint64, error) {
|
||||
return 0, errors.New("Not supported")
|
||||
}
|
||||
|
||||
// EthereumTypeEstimateGas is not supported
|
||||
func (b *BaseChain) EthereumTypeEstimateGas(params map[string]interface{}) (uint64, error) {
|
||||
return 0, errors.New("Not supported")
|
||||
}
|
||||
|
||||
// EthereumTypeGetErc20ContractInfo is not supported
|
||||
func (b *BaseChain) EthereumTypeGetErc20ContractInfo(contractDesc AddressDescriptor) (*Erc20Contract, error) {
|
||||
return nil, errors.New("Not supported")
|
||||
}
|
||||
|
||||
// EthereumTypeGetErc20ContractBalance is not supported
|
||||
func (b *BaseChain) EthereumTypeGetErc20ContractBalance(addrDesc, contractDesc AddressDescriptor) (*big.Int, error) {
|
||||
return nil, errors.New("Not supported")
|
||||
}
|
|
@ -0,0 +1,136 @@
|
|||
package bchain
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type addrIndex struct {
|
||||
addrDesc string
|
||||
n int32
|
||||
}
|
||||
|
||||
type txEntry struct {
|
||||
addrIndexes []addrIndex
|
||||
time uint32
|
||||
}
|
||||
|
||||
type txidio struct {
|
||||
txid string
|
||||
io []addrIndex
|
||||
}
|
||||
|
||||
// BaseMempool is mempool base handle
|
||||
type BaseMempool struct {
|
||||
chain BlockChain
|
||||
mux sync.Mutex
|
||||
txEntries map[string]txEntry
|
||||
addrDescToTx map[string][]Outpoint
|
||||
OnNewTxAddr OnNewTxAddrFunc
|
||||
OnNewTx OnNewTxFunc
|
||||
}
|
||||
|
||||
// GetTransactions returns slice of mempool transactions for given address
|
||||
func (m *BaseMempool) GetTransactions(address string) ([]Outpoint, error) {
|
||||
parser := m.chain.GetChainParser()
|
||||
addrDesc, err := parser.GetAddrDescFromAddress(address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m.GetAddrDescTransactions(addrDesc)
|
||||
}
|
||||
|
||||
// GetAddrDescTransactions returns slice of mempool transactions for given address descriptor, in reverse order
|
||||
func (m *BaseMempool) GetAddrDescTransactions(addrDesc AddressDescriptor) ([]Outpoint, error) {
|
||||
m.mux.Lock()
|
||||
defer m.mux.Unlock()
|
||||
outpoints := m.addrDescToTx[string(addrDesc)]
|
||||
rv := make([]Outpoint, len(outpoints))
|
||||
for i, j := len(outpoints)-1, 0; i >= 0; i-- {
|
||||
rv[j] = outpoints[i]
|
||||
j++
|
||||
}
|
||||
return rv, nil
|
||||
}
|
||||
|
||||
func (a MempoolTxidEntries) Len() int { return len(a) }
|
||||
func (a MempoolTxidEntries) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a MempoolTxidEntries) Less(i, j int) bool {
|
||||
// if the Time is equal, sort by txid to make the order defined
|
||||
hi := a[i].Time
|
||||
hj := a[j].Time
|
||||
if hi == hj {
|
||||
return a[i].Txid > a[j].Txid
|
||||
}
|
||||
// order in reverse
|
||||
return hi > hj
|
||||
}
|
||||
|
||||
// removeEntryFromMempool removes entry from mempool structs. The caller is responsible for locking!
|
||||
func (m *BaseMempool) removeEntryFromMempool(txid string, entry txEntry) {
|
||||
delete(m.txEntries, txid)
|
||||
for _, si := range entry.addrIndexes {
|
||||
outpoints, found := m.addrDescToTx[si.addrDesc]
|
||||
if found {
|
||||
newOutpoints := make([]Outpoint, 0, len(outpoints)-1)
|
||||
for _, o := range outpoints {
|
||||
if o.Txid != txid {
|
||||
newOutpoints = append(newOutpoints, o)
|
||||
}
|
||||
}
|
||||
if len(newOutpoints) > 0 {
|
||||
m.addrDescToTx[si.addrDesc] = newOutpoints
|
||||
} else {
|
||||
delete(m.addrDescToTx, si.addrDesc)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetAllEntries returns all mempool entries sorted by fist seen time in descending order
|
||||
func (m *BaseMempool) GetAllEntries() MempoolTxidEntries {
|
||||
i := 0
|
||||
m.mux.Lock()
|
||||
entries := make(MempoolTxidEntries, len(m.txEntries))
|
||||
for txid, entry := range m.txEntries {
|
||||
entries[i] = MempoolTxidEntry{
|
||||
Txid: txid,
|
||||
Time: entry.time,
|
||||
}
|
||||
i++
|
||||
}
|
||||
m.mux.Unlock()
|
||||
sort.Sort(entries)
|
||||
return entries
|
||||
}
|
||||
|
||||
// GetTransactionTime returns first seen time of a transaction
|
||||
func (m *BaseMempool) GetTransactionTime(txid string) uint32 {
|
||||
m.mux.Lock()
|
||||
e, found := m.txEntries[txid]
|
||||
m.mux.Unlock()
|
||||
if !found {
|
||||
return 0
|
||||
}
|
||||
return e.time
|
||||
}
|
||||
|
||||
func (m *BaseMempool) txToMempoolTx(tx *Tx) *MempoolTx {
|
||||
mtx := MempoolTx{
|
||||
Hex: tx.Hex,
|
||||
Blocktime: time.Now().Unix(),
|
||||
LockTime: tx.LockTime,
|
||||
Txid: tx.Txid,
|
||||
Version: tx.Version,
|
||||
Vout: tx.Vout,
|
||||
CoinSpecificData: tx.CoinSpecificData,
|
||||
}
|
||||
mtx.Vin = make([]MempoolVin, len(tx.Vin))
|
||||
for i, vin := range tx.Vin {
|
||||
mtx.Vin[i] = MempoolVin{
|
||||
Vin: vin,
|
||||
}
|
||||
}
|
||||
return &mtx
|
||||
}
|
|
@ -7,7 +7,9 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"github.com/golang/glog"
|
||||
"github.com/juju/errors"
|
||||
"spacecruft.org/spacecruft/blockbook/common"
|
||||
)
|
||||
|
||||
// BaseParser implements data parsing/handling functionality base for all other parsers
|
||||
|
@ -26,18 +28,32 @@ func (p *BaseParser) ParseTx(b []byte) (*Tx, error) {
|
|||
return nil, errors.New("ParseTx: not implemented")
|
||||
}
|
||||
|
||||
// GetAddrDescForUnknownInput returns nil AddressDescriptor
|
||||
func (p *BaseParser) GetAddrDescForUnknownInput(tx *Tx, input int) AddressDescriptor {
|
||||
var iTxid string
|
||||
if len(tx.Vin) > input {
|
||||
iTxid = tx.Vin[input].Txid
|
||||
}
|
||||
glog.Warningf("tx %v, input tx %v not found in txAddresses", tx.Txid, iTxid)
|
||||
return nil
|
||||
}
|
||||
|
||||
const zeros = "0000000000000000000000000000000000000000"
|
||||
|
||||
// AmountToBigInt converts amount in json.Number (string) to big.Int
|
||||
// AmountToBigInt converts amount in common.JSONNumber (string) to big.Int
|
||||
// it uses string operations to avoid problems with rounding
|
||||
func (p *BaseParser) AmountToBigInt(n json.Number) (big.Int, error) {
|
||||
func (p *BaseParser) AmountToBigInt(n common.JSONNumber) (big.Int, error) {
|
||||
var r big.Int
|
||||
s := string(n)
|
||||
i := strings.IndexByte(s, '.')
|
||||
d := p.AmountDecimalPoint
|
||||
if d > len(zeros) {
|
||||
d = len(zeros)
|
||||
}
|
||||
if i == -1 {
|
||||
s = s + zeros[:p.AmountDecimalPoint]
|
||||
s = s + zeros[:d]
|
||||
} else {
|
||||
z := p.AmountDecimalPoint - len(s) + i + 1
|
||||
z := d - len(s) + i + 1
|
||||
if z > 0 {
|
||||
s = s[:i] + s[i+1:] + zeros[:z]
|
||||
} else {
|
||||
|
@ -50,18 +66,24 @@ func (p *BaseParser) AmountToBigInt(n json.Number) (big.Int, error) {
|
|||
return r, nil
|
||||
}
|
||||
|
||||
// AmountToDecimalString converts amount in big.Int to string with decimal point in the correct place
|
||||
func (p *BaseParser) AmountToDecimalString(a *big.Int) string {
|
||||
// AmountToDecimalString converts amount in big.Int to string with decimal point in the place defined by the parameter d
|
||||
func AmountToDecimalString(a *big.Int, d int) string {
|
||||
if a == nil {
|
||||
return ""
|
||||
}
|
||||
n := a.String()
|
||||
var s string
|
||||
if n[0] == '-' {
|
||||
n = n[1:]
|
||||
s = "-"
|
||||
}
|
||||
if len(n) <= p.AmountDecimalPoint {
|
||||
n = zeros[:p.AmountDecimalPoint-len(n)+1] + n
|
||||
if d > len(zeros) {
|
||||
d = len(zeros)
|
||||
}
|
||||
i := len(n) - p.AmountDecimalPoint
|
||||
if len(n) <= d {
|
||||
n = zeros[:d-len(n)+1] + n
|
||||
}
|
||||
i := len(n) - d
|
||||
ad := strings.TrimRight(n[i:], "0")
|
||||
if len(ad) > 0 {
|
||||
n = n[:i] + "." + ad
|
||||
|
@ -71,6 +93,16 @@ func (p *BaseParser) AmountToDecimalString(a *big.Int) string {
|
|||
return s + n
|
||||
}
|
||||
|
||||
// AmountToDecimalString converts amount in big.Int to string with decimal point in the correct place
|
||||
func (p *BaseParser) AmountToDecimalString(a *big.Int) string {
|
||||
return AmountToDecimalString(a, p.AmountDecimalPoint)
|
||||
}
|
||||
|
||||
// AmountDecimals returns number of decimal places in amounts
|
||||
func (p *BaseParser) AmountDecimals() int {
|
||||
return p.AmountDecimalPoint
|
||||
}
|
||||
|
||||
// ParseTxFromJson parses JSON message containing transaction and returns Tx struct
|
||||
func (p *BaseParser) ParseTxFromJson(msg json.RawMessage) (*Tx, error) {
|
||||
var tx Tx
|
||||
|
@ -125,9 +157,14 @@ func (p *BaseParser) UnpackBlockHash(buf []byte) (string, error) {
|
|||
return hex.EncodeToString(buf), nil
|
||||
}
|
||||
|
||||
// IsUTXOChain returns true if the block chain is UTXO type, otherwise false
|
||||
func (p *BaseParser) IsUTXOChain() bool {
|
||||
return true
|
||||
// GetChainType is type of the blockchain, default is ChainBitcoinType
|
||||
func (p *BaseParser) GetChainType() ChainType {
|
||||
return ChainBitcoinType
|
||||
}
|
||||
|
||||
// MinimumCoinbaseConfirmations returns minimum number of confirmations a coinbase transaction must have before it can be spent
|
||||
func (p *BaseParser) MinimumCoinbaseConfirmations() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
// PackTx packs transaction to byte array using protobuf
|
||||
|
@ -139,8 +176,9 @@ func (p *BaseParser) PackTx(tx *Tx, height uint32, blockTime int64) ([]byte, err
|
|||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "Vin %v Hex %v", i, vi.ScriptSig.Hex)
|
||||
}
|
||||
// coinbase txs do not have Vin.txid
|
||||
itxid, err := p.PackTxid(vi.Txid)
|
||||
if err != nil {
|
||||
if err != nil && err != ErrTxidMissing {
|
||||
return nil, errors.Annotatef(err, "Vin %v Txid %v", i, vi.Txid)
|
||||
}
|
||||
pti[i] = &ProtoTransaction_VinType{
|
||||
|
@ -171,6 +209,7 @@ func (p *BaseParser) PackTx(tx *Tx, height uint32, blockTime int64) ([]byte, err
|
|||
Locktime: tx.LockTime,
|
||||
Vin: pti,
|
||||
Vout: pto,
|
||||
Version: tx.Version,
|
||||
}
|
||||
if pt.Hex, err = hex.DecodeString(tx.Hex); err != nil {
|
||||
return nil, errors.Annotatef(err, "Hex %v", tx.Hex)
|
||||
|
@ -230,6 +269,33 @@ func (p *BaseParser) UnpackTx(buf []byte) (*Tx, uint32, error) {
|
|||
Txid: txid,
|
||||
Vin: vin,
|
||||
Vout: vout,
|
||||
Version: pt.Version,
|
||||
}
|
||||
return &tx, pt.Height, nil
|
||||
}
|
||||
|
||||
// IsAddrDescIndexable returns true if AddressDescriptor should be added to index
|
||||
// by default all AddressDescriptors are indexable
|
||||
func (p *BaseParser) IsAddrDescIndexable(addrDesc AddressDescriptor) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// DerivationBasePath is unsupported
|
||||
func (p *BaseParser) DerivationBasePath(xpub string) (string, error) {
|
||||
return "", errors.New("Not supported")
|
||||
}
|
||||
|
||||
// DeriveAddressDescriptors is unsupported
|
||||
func (p *BaseParser) DeriveAddressDescriptors(xpub string, change uint32, indexes []uint32) ([]AddressDescriptor, error) {
|
||||
return nil, errors.New("Not supported")
|
||||
}
|
||||
|
||||
// DeriveAddressDescriptorsFromTo is unsupported
|
||||
func (p *BaseParser) DeriveAddressDescriptorsFromTo(xpub string, change uint32, fromIndex uint32, toIndex uint32) ([]AddressDescriptor, error) {
|
||||
return nil, errors.New("Not supported")
|
||||
}
|
||||
|
||||
// EthereumTypeGetErc20FromTx is unsupported
|
||||
func (p *BaseParser) EthereumTypeGetErc20FromTx(tx *Tx) ([]Erc20Transfer, error) {
|
||||
return nil, errors.New("Not supported")
|
||||
}
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
// +build unittest
|
||||
|
||||
package bchain
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"spacecruft.org/spacecruft/blockbook/common"
|
||||
)
|
||||
|
||||
func NewBaseParser(adp int) *BaseParser {
|
||||
|
@ -27,7 +30,8 @@ var amounts = []struct {
|
|||
{big.NewInt(-8), "-0.00000008", 8, "!"},
|
||||
{big.NewInt(-89012345678), "-890.12345678", 8, "!"},
|
||||
{big.NewInt(-12345), "-0.00012345", 8, "!"},
|
||||
{big.NewInt(12345678), "0.123456789012", 8, "0.12345678"}, // test of truncation of too many decimal places
|
||||
{big.NewInt(12345678), "0.123456789012", 8, "0.12345678"}, // test of truncation of too many decimal places
|
||||
{big.NewInt(12345678), "0.0000000000000000000000000000000012345678", 1234, "!"}, // test of too big number decimal places
|
||||
}
|
||||
|
||||
func TestBaseParser_AmountToDecimalString(t *testing.T) {
|
||||
|
@ -43,7 +47,7 @@ func TestBaseParser_AmountToDecimalString(t *testing.T) {
|
|||
func TestBaseParser_AmountToBigInt(t *testing.T) {
|
||||
for _, tt := range amounts {
|
||||
t.Run(tt.s, func(t *testing.T) {
|
||||
got, err := NewBaseParser(tt.adp).AmountToBigInt(json.Number(tt.s))
|
||||
got, err := NewBaseParser(tt.adp).AmountToBigInt(common.JSONNumber(tt.s))
|
||||
if err != nil {
|
||||
t.Errorf("BaseParser.AmountToBigInt() error = %v", err)
|
||||
return
|
||||
|
|
|
@ -1,33 +1,42 @@
|
|||
package bch
|
||||
|
||||
import (
|
||||
"blockbook/bchain"
|
||||
"blockbook/bchain/coins/btc"
|
||||
"fmt"
|
||||
|
||||
"github.com/jakm/bchutil"
|
||||
"github.com/jakm/btcutil"
|
||||
"github.com/jakm/btcutil/chaincfg"
|
||||
"github.com/jakm/btcutil/txscript"
|
||||
"github.com/martinboehm/bchutil"
|
||||
"github.com/martinboehm/btcutil"
|
||||
"github.com/martinboehm/btcutil/chaincfg"
|
||||
"github.com/martinboehm/btcutil/txscript"
|
||||
"github.com/schancel/cashaddr-converter/address"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
|
||||
)
|
||||
|
||||
// AddressFormat type is used to specify different formats of address
|
||||
type AddressFormat = uint8
|
||||
|
||||
const (
|
||||
// Legacy AddressFormat is the same as Bitcoin
|
||||
Legacy AddressFormat = iota
|
||||
// CashAddr AddressFormat is new Bitcoin Cash standard
|
||||
CashAddr
|
||||
)
|
||||
|
||||
const (
|
||||
// MainNetPrefix is CashAddr prefix for mainnet
|
||||
MainNetPrefix = "bitcoincash:"
|
||||
// TestNetPrefix is CashAddr prefix for testnet
|
||||
TestNetPrefix = "bchtest:"
|
||||
// RegTestPrefix is CashAddr prefix for regtest
|
||||
RegTestPrefix = "bchreg:"
|
||||
)
|
||||
|
||||
var (
|
||||
// MainNetParams are parser parameters for mainnet
|
||||
MainNetParams chaincfg.Params
|
||||
// TestNetParams are parser parameters for testnet
|
||||
TestNetParams chaincfg.Params
|
||||
// RegtestParams are parser parameters for regtest
|
||||
RegtestParams chaincfg.Params
|
||||
)
|
||||
|
||||
|
@ -62,13 +71,7 @@ func NewBCashParser(params *chaincfg.Params, c *btc.Configuration) (*BCashParser
|
|||
return nil, fmt.Errorf("Unknown address format: %s", c.AddressFormat)
|
||||
}
|
||||
p := &BCashParser{
|
||||
BitcoinParser: &btc.BitcoinParser{
|
||||
BaseParser: &bchain.BaseParser{
|
||||
BlockAddressesToKeep: c.BlockAddressesToKeep,
|
||||
AmountDecimalPoint: 8,
|
||||
},
|
||||
Params: params,
|
||||
},
|
||||
BitcoinParser: btc.NewBitcoinParser(params, c),
|
||||
AddressFormat: format,
|
||||
}
|
||||
p.OutputScriptToAddressesFunc = p.outputScriptToAddresses
|
||||
|
@ -118,17 +121,16 @@ func (p *BCashParser) addressToOutputScript(address string) ([]byte, error) {
|
|||
return nil, err
|
||||
}
|
||||
return script, nil
|
||||
} else {
|
||||
da, err := btcutil.DecodeAddress(address, p.Params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
script, err := txscript.PayToAddrScript(da)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return script, nil
|
||||
}
|
||||
da, err := btcutil.DecodeAddress(address, p.Params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
script, err := txscript.PayToAddrScript(da)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return script, nil
|
||||
}
|
||||
|
||||
func isCashAddr(addr string) bool {
|
||||
|
@ -149,7 +151,7 @@ func isCashAddr(addr string) bool {
|
|||
func (p *BCashParser) outputScriptToAddresses(script []byte) ([]string, bool, error) {
|
||||
// convert possible P2PK script to P2PK, which bchutil can process
|
||||
var err error
|
||||
script, err = txscript.ConvertP2PKtoP2PKH(script)
|
||||
script, err = txscript.ConvertP2PKtoP2PKH(p.Params.Base58CksumHasher, script)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
@ -158,14 +160,13 @@ func (p *BCashParser) outputScriptToAddresses(script []byte) ([]string, bool, er
|
|||
// do not return unknown script type error as error
|
||||
if err.Error() == "unknown script type" {
|
||||
// try OP_RETURN script
|
||||
or := btc.TryParseOPReturn(script)
|
||||
or := p.TryParseOPReturn(script)
|
||||
if or != "" {
|
||||
return []string{or}, false, nil
|
||||
}
|
||||
return []string{}, false, nil
|
||||
} else {
|
||||
return nil, false, err
|
||||
}
|
||||
return nil, false, err
|
||||
}
|
||||
// EncodeAddress returns CashAddr address
|
||||
addr := a.EncodeAddress()
|
||||
|
|
|
@ -3,15 +3,15 @@
|
|||
package bch
|
||||
|
||||
import (
|
||||
"blockbook/bchain"
|
||||
"blockbook/bchain/coins/btc"
|
||||
"encoding/hex"
|
||||
"math/big"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/jakm/btcutil/chaincfg"
|
||||
"github.com/martinboehm/btcutil/chaincfg"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
package bch
|
||||
|
||||
import (
|
||||
"blockbook/bchain"
|
||||
"blockbook/bchain/coins/btc"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"math/big"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/jakm/bchutil"
|
||||
"github.com/juju/errors"
|
||||
"github.com/martinboehm/bchutil"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
|
||||
)
|
||||
|
||||
// BCashRPC is an interface to JSON-RPC bitcoind service.
|
||||
|
@ -27,16 +27,18 @@ func NewBCashRPC(config json.RawMessage, pushHandler func(bchain.NotificationTyp
|
|||
s := &BCashRPC{
|
||||
b.(*btc.BitcoinRPC),
|
||||
}
|
||||
s.ChainConfig.SupportsEstimateSmartFee = false
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// Initialize initializes BCashRPC instance.
|
||||
func (b *BCashRPC) Initialize() error {
|
||||
chainName, err := b.GetChainInfoAndInitializeMempool(b)
|
||||
ci, err := b.GetChainInfo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
chainName := ci.Chain
|
||||
|
||||
params := GetChainParams(chainName)
|
||||
|
||||
|
@ -157,14 +159,27 @@ func (b *BCashRPC) GetBlockFull(hash string) (*bchain.Block, error) {
|
|||
return nil, errors.New("Not implemented")
|
||||
}
|
||||
|
||||
// EstimateSmartFee returns fee estimation.
|
||||
func (b *BCashRPC) EstimateSmartFee(blocks int, conservative bool) (big.Int, error) {
|
||||
glog.V(1).Info("rpc: estimatesmartfee ", blocks)
|
||||
func isErrBlockNotFound(err *bchain.RPCError) bool {
|
||||
return err.Message == "Block not found" ||
|
||||
err.Message == "Block height out of range"
|
||||
}
|
||||
|
||||
// EstimateFee returns fee estimation
|
||||
func (b *BCashRPC) EstimateFee(blocks int) (big.Int, error) {
|
||||
// from version BitcoinABC version 0.19.1 EstimateFee does not support parameter Blocks
|
||||
if b.ChainConfig.CoinShortcut == "BCHSV" {
|
||||
return b.BitcoinRPC.EstimateFee(blocks)
|
||||
}
|
||||
|
||||
glog.V(1).Info("rpc: estimatefee ", blocks)
|
||||
|
||||
res := btc.ResEstimateFee{}
|
||||
req := struct {
|
||||
Method string `json:"method"`
|
||||
}{
|
||||
Method: "estimatefee",
|
||||
}
|
||||
|
||||
res := btc.ResEstimateSmartFee{}
|
||||
req := cmdEstimateSmartFee{Method: "estimatesmartfee"}
|
||||
req.Params.Blocks = blocks
|
||||
// conservative param is omitted
|
||||
err := b.Call(&req, &res)
|
||||
|
||||
var r big.Int
|
||||
|
@ -174,14 +189,15 @@ func (b *BCashRPC) EstimateSmartFee(blocks int, conservative bool) (big.Int, err
|
|||
if res.Error != nil {
|
||||
return r, res.Error
|
||||
}
|
||||
r, err = b.Parser.AmountToBigInt(res.Result.Feerate)
|
||||
r, err = b.Parser.AmountToBigInt(res.Result)
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func isErrBlockNotFound(err *bchain.RPCError) bool {
|
||||
return err.Message == "Block not found" ||
|
||||
err.Message == "Block height out of range"
|
||||
// EstimateSmartFee returns fee estimation
|
||||
func (b *BCashRPC) EstimateSmartFee(blocks int, conservative bool) (big.Int, error) {
|
||||
// EstimateSmartFee is not supported by bcash
|
||||
return b.EstimateFee(blocks)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
package bellcoin
|
||||
|
||||
import (
|
||||
"github.com/martinboehm/btcd/wire"
|
||||
"github.com/martinboehm/btcutil/chaincfg"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
|
||||
)
|
||||
|
||||
// magic numbers
|
||||
const (
|
||||
MainnetMagic wire.BitcoinNet = 0xbebacefa
|
||||
TestnetMagic wire.BitcoinNet = 0x0709110b
|
||||
)
|
||||
|
||||
// chain parameters
|
||||
var (
|
||||
MainNetParams chaincfg.Params
|
||||
TestNetParams chaincfg.Params
|
||||
)
|
||||
|
||||
func init() {
|
||||
MainNetParams = chaincfg.MainNetParams
|
||||
MainNetParams.Net = MainnetMagic
|
||||
MainNetParams.PubKeyHashAddrID = []byte{25}
|
||||
MainNetParams.ScriptHashAddrID = []byte{85}
|
||||
MainNetParams.Bech32HRPSegwit = "bm"
|
||||
|
||||
TestNetParams = chaincfg.TestNet3Params
|
||||
TestNetParams.Net = TestnetMagic
|
||||
TestNetParams.PubKeyHashAddrID = []byte{111}
|
||||
TestNetParams.ScriptHashAddrID = []byte{196}
|
||||
TestNetParams.Bech32HRPSegwit = "bt"
|
||||
}
|
||||
|
||||
// BellcoinParser handle
|
||||
type BellcoinParser struct {
|
||||
*btc.BitcoinParser
|
||||
}
|
||||
|
||||
// NewBellcoinParser returns new BellcoinParser instance
|
||||
func NewBellcoinParser(params *chaincfg.Params, c *btc.Configuration) *BellcoinParser {
|
||||
return &BellcoinParser{BitcoinParser: btc.NewBitcoinParser(params, c)}
|
||||
}
|
||||
|
||||
// GetChainParams contains network parameters for the main Bellcoin network,
|
||||
// and the test Bellcoin network
|
||||
func GetChainParams(chain string) *chaincfg.Params {
|
||||
if !chaincfg.IsRegistered(&MainNetParams) {
|
||||
err := chaincfg.Register(&MainNetParams)
|
||||
if err == nil {
|
||||
err = chaincfg.Register(&TestNetParams)
|
||||
}
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
switch chain {
|
||||
case "test":
|
||||
return &TestNetParams
|
||||
default:
|
||||
return &MainNetParams
|
||||
}
|
||||
}
|
|
@ -0,0 +1,327 @@
|
|||
// +build unittest
|
||||
|
||||
package bellcoin
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"math/big"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/martinboehm/btcutil/chaincfg"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
c := m.Run()
|
||||
chaincfg.ResetParams()
|
||||
os.Exit(c)
|
||||
}
|
||||
|
||||
func Test_GetAddrDescFromAddress_Mainnet(t *testing.T) {
|
||||
type args struct {
|
||||
address string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "P2PKH1",
|
||||
args: args{address: "BSQmMGWpjwP5Lu8feSypSaPFiTTrC3EdEx"},
|
||||
want: "76a914f0e2aff6730b53b9986a5db8ca17c59426134a0988ac",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "P2PKH2",
|
||||
args: args{address: "BLpETLYW1vs8csYSoYeCvPwsiCSTJUjx6T"},
|
||||
want: "76a914b3822026c7f758b43a0882d7f2cbfa954702e45688ac",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "P2SH1",
|
||||
args: args{address: "bNn9Y9YfgNUHXopXJEesS9M8noJzzrWTmP"},
|
||||
want: "a9146e3c881d51d62a668362a5bba28be438f9c548e287",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "P2SH2",
|
||||
args: args{address: "bXCT73hNPSwCeVbpvo3wJJU3erRjawUGSu"},
|
||||
want: "a914ca962f788569443b33ec673208ccdcfaac6873b487",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "witness_v0_keyhash",
|
||||
args: args{address: "bm1q3msdh3npg5ufvwm2sxltxcet6hll9tjzkzxzqt"},
|
||||
want: "00148ee0dbc6614538963b6a81beb3632bd5fff2ae42",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "witness_v0_scripthashx",
|
||||
args: args{address: "bm1q0uvgqxwqt0u4esawwcff7652jreqye30kmp4sj5dtydee50pze0sjx6wn5"},
|
||||
want: "00207f188019c05bf95cc3ae76129f6a8a90f202662fb6c3584a8d591b9cd1e1165f",
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
parser := NewBellcoinParser(GetChainParams("main"), &btc.Configuration{})
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := parser.GetAddrDescFromAddress(tt.args.address)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("GetAddrDescFromAddress() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
h := hex.EncodeToString(got)
|
||||
if !reflect.DeepEqual(h, tt.want) {
|
||||
t.Errorf("GetAddrDescFromAddress() = %v, want %v", h, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_GetAddressesFromAddrDesc(t *testing.T) {
|
||||
type args struct {
|
||||
script string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []string
|
||||
want2 bool
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "P2PKH",
|
||||
args: args{script: "76a914f0e2aff6730b53b9986a5db8ca17c59426134a0988ac"},
|
||||
want: []string{"BSQmMGWpjwP5Lu8feSypSaPFiTTrC3EdEx"},
|
||||
want2: true,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "P2SH",
|
||||
args: args{script: "a9146e3c881d51d62a668362a5bba28be438f9c548e287"},
|
||||
want: []string{"bNn9Y9YfgNUHXopXJEesS9M8noJzzrWTmP"},
|
||||
want2: true,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "P2WPKH",
|
||||
args: args{script: "00148ee0dbc6614538963b6a81beb3632bd5fff2ae42"},
|
||||
want: []string{"bm1q3msdh3npg5ufvwm2sxltxcet6hll9tjzkzxzqt"},
|
||||
want2: true,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "P2WSH",
|
||||
args: args{script: "00207f188019c05bf95cc3ae76129f6a8a90f202662fb6c3584a8d591b9cd1e1165f"},
|
||||
want: []string{"bm1q0uvgqxwqt0u4esawwcff7652jreqye30kmp4sj5dtydee50pze0sjx6wn5"},
|
||||
want2: true,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "OP_RETURN ascii",
|
||||
args: args{script: "6a0461686f6a"},
|
||||
want: []string{"OP_RETURN (ahoj)"},
|
||||
want2: false,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "OP_RETURN hex",
|
||||
args: args{script: "6a072020f1686f6a20"},
|
||||
want: []string{"OP_RETURN 2020f1686f6a20"},
|
||||
want2: false,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
parser := NewBellcoinParser(GetChainParams("main"), &btc.Configuration{})
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
b, _ := hex.DecodeString(tt.args.script)
|
||||
got, got2, err := parser.GetAddressesFromAddrDesc(b)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("outputScriptToAddresses() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("GetAddressesFromAddrDesc() = %v, want %v", got, tt.want)
|
||||
}
|
||||
if !reflect.DeepEqual(got2, tt.want2) {
|
||||
t.Errorf("GetAddressesFromAddrDesc() = %v, want %v", got2, tt.want2)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
testTx1 bchain.Tx
|
||||
|
||||
testTxPacked1 = "0003238c8bc5aac144020000000001026f3631c4db09d48354d12cd2e780b2aa0f92198fca7d044a6b073e9a06b4135d0200000017160014dfc67c36c26ea8036042c56b8ce5d5027d1fb81ffeffffffac13cfbe663797aced3134006adec863e5b3f9480ab65a4dda112de875f8dd6e010000006a473044022066e4d5f99fec12f076d7d912d1e530e4b950d23cc3de2b8127f98109f510c9d502207a74f2e7a753262fdd29756193493d47ead7880871cbc6c55cc6d20e229c0c1201210294fc5b3928335caddc7f4c536e0db85c736cbc7c164de0319976da90b65d288ffeffffff05f4c16020000000001976a91416e20046e69c396aed18c6663ddcbbf1dde9082f88ac0aa47113000000001976a91426fa5e6c4e579058d3f4d1dd83d99e291d4dc0c588acbcb11a00000000001976a9145a53bd436b5c19a42b1518ef18443e8403bcaeed88ac0c04693b0000000017a9140576053f982117afcff024013ed61270189c4fbc87358bad07000000001976a914e00ec357dfee124ac68b3c10dcf82e2ed0993ccc88ac02483045022100ed4b0e9b140850951ffbc10349e3ac56a18b80c600a77b95cfac274e10228eb602207c876b9b134e63b8a01ba28720dc4c1c2c67bb7e547fb71d31440cd365c674260121027aa4243e82c73c9c15d544a0b61a828eed1128464952bfbdc9235d4380f2767d008a230300"
|
||||
)
|
||||
|
||||
func init() {
|
||||
testTx1 = bchain.Tx{
|
||||
Hex: "020000000001026f3631c4db09d48354d12cd2e780b2aa0f92198fca7d044a6b073e9a06b4135d0200000017160014dfc67c36c26ea8036042c56b8ce5d5027d1fb81ffeffffffac13cfbe663797aced3134006adec863e5b3f9480ab65a4dda112de875f8dd6e010000006a473044022066e4d5f99fec12f076d7d912d1e530e4b950d23cc3de2b8127f98109f510c9d502207a74f2e7a753262fdd29756193493d47ead7880871cbc6c55cc6d20e229c0c1201210294fc5b3928335caddc7f4c536e0db85c736cbc7c164de0319976da90b65d288ffeffffff05f4c16020000000001976a91416e20046e69c396aed18c6663ddcbbf1dde9082f88ac0aa47113000000001976a91426fa5e6c4e579058d3f4d1dd83d99e291d4dc0c588acbcb11a00000000001976a9145a53bd436b5c19a42b1518ef18443e8403bcaeed88ac0c04693b0000000017a9140576053f982117afcff024013ed61270189c4fbc87358bad07000000001976a914e00ec357dfee124ac68b3c10dcf82e2ed0993ccc88ac02483045022100ed4b0e9b140850951ffbc10349e3ac56a18b80c600a77b95cfac274e10228eb602207c876b9b134e63b8a01ba28720dc4c1c2c67bb7e547fb71d31440cd365c674260121027aa4243e82c73c9c15d544a0b61a828eed1128464952bfbdc9235d4380f2767d008a230300",
|
||||
Blocktime: 1549095010,
|
||||
Txid: "e7ef52bbf3d9cb1ca5dfdb02eabf108e2b0b7757b009d1cfb24a06e4126e67f2",
|
||||
LockTime: 205706,
|
||||
Version: 2,
|
||||
Vin: []bchain.Vin{
|
||||
{
|
||||
ScriptSig: bchain.ScriptSig{
|
||||
Hex: "160014dfc67c36c26ea8036042c56b8ce5d5027d1fb81f",
|
||||
},
|
||||
Txid: "5d13b4069a3e076b4a047dca8f19920faab280e7d22cd15483d409dbc431366f",
|
||||
Vout: 2,
|
||||
Sequence: 4294967294,
|
||||
},
|
||||
{
|
||||
ScriptSig: bchain.ScriptSig{
|
||||
Hex: "473044022066e4d5f99fec12f076d7d912d1e530e4b950d23cc3de2b8127f98109f510c9d502207a74f2e7a753262fdd29756193493d47ead7880871cbc6c55cc6d20e229c0c1201210294fc5b3928335caddc7f4c536e0db85c736cbc7c164de0319976da90b65d288f",
|
||||
},
|
||||
Txid: "6eddf875e82d11da4d5ab60a48f9b3e563c8de6a003431edac973766becf13ac",
|
||||
Vout: 1,
|
||||
Sequence: 4294967294,
|
||||
},
|
||||
},
|
||||
Vout: []bchain.Vout{
|
||||
{
|
||||
ValueSat: *big.NewInt(543212020),
|
||||
N: 0,
|
||||
ScriptPubKey: bchain.ScriptPubKey{
|
||||
Hex: "76a91416e20046e69c396aed18c6663ddcbbf1dde9082f88ac",
|
||||
Addresses: []string{
|
||||
"B6Y5DmPr1LPUP95YDEnX6FbJHERVipkRcg",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ValueSat: *big.NewInt(326214666),
|
||||
N: 1,
|
||||
ScriptPubKey: bchain.ScriptPubKey{
|
||||
Hex: "76a91426fa5e6c4e579058d3f4d1dd83d99e291d4dc0c588ac",
|
||||
Addresses: []string{
|
||||
"B81BDwJTasemPgnHBxQ67wX2WV48b2XmEc",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ValueSat: *big.NewInt(1749436),
|
||||
N: 2,
|
||||
ScriptPubKey: bchain.ScriptPubKey{
|
||||
Hex: "76a9145a53bd436b5c19a42b1518ef18443e8403bcaeed88ac",
|
||||
Addresses: []string{
|
||||
"BCggkHk8ZTVd9T4yseNmURj6w56XY47EUG",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ValueSat: *big.NewInt(996738060),
|
||||
N: 3,
|
||||
ScriptPubKey: bchain.ScriptPubKey{
|
||||
Hex: "a9140576053f982117afcff024013ed61270189c4fbc87",
|
||||
Addresses: []string{
|
||||
"bDE9TQ3W5zF4ej8hLwYVdK5w8n5zhd9Qxj",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ValueSat: *big.NewInt(128813877),
|
||||
N: 4,
|
||||
ScriptPubKey: bchain.ScriptPubKey{
|
||||
Hex: "76a914e00ec357dfee124ac68b3c10dcf82e2ed0993ccc88ac",
|
||||
Addresses: []string{
|
||||
"BQsnfSXNonZZMs1G6ndrVLbUJAK5tFG2bN",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func Test_PackTx(t *testing.T) {
|
||||
type args struct {
|
||||
tx bchain.Tx
|
||||
height uint32
|
||||
blockTime int64
|
||||
parser *BellcoinParser
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "Bellcoin-1",
|
||||
args: args{
|
||||
tx: testTx1,
|
||||
height: 205708,
|
||||
blockTime: 1549095010,
|
||||
parser: NewBellcoinParser(GetChainParams("main"), &btc.Configuration{}),
|
||||
},
|
||||
want: testTxPacked1,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := tt.args.parser.PackTx(&tt.args.tx, tt.args.height, tt.args.blockTime)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("packTx() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
h := hex.EncodeToString(got)
|
||||
if !reflect.DeepEqual(h, tt.want) {
|
||||
t.Errorf("packTx() = %v, want %v", h, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_UnpackTx(t *testing.T) {
|
||||
type args struct {
|
||||
packedTx string
|
||||
parser *BellcoinParser
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *bchain.Tx
|
||||
want1 uint32
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "Bellcoin-1",
|
||||
args: args{
|
||||
packedTx: testTxPacked1,
|
||||
parser: NewBellcoinParser(GetChainParams("main"), &btc.Configuration{}),
|
||||
},
|
||||
want: &testTx1,
|
||||
want1: 205708,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
b, _ := hex.DecodeString(tt.args.packedTx)
|
||||
got, got1, err := tt.args.parser.UnpackTx(b)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("unpackTx() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("unpackTx() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
if got1 != tt.want1 {
|
||||
t.Errorf("unpackTx() got1 = %v, want %v", got1, tt.want1)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
package bellcoin
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
|
||||
)
|
||||
|
||||
// BellcoinRPC is an interface to JSON-RPC bitcoind service.
|
||||
type BellcoinRPC struct {
|
||||
*btc.BitcoinRPC
|
||||
}
|
||||
|
||||
// NewBellcoinRPC returns new BellcoinRPC instance.
|
||||
func NewBellcoinRPC(config json.RawMessage, pushHandler func(bchain.NotificationType)) (bchain.BlockChain, error) {
|
||||
b, err := btc.NewBitcoinRPC(config, pushHandler)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s := &BellcoinRPC{
|
||||
b.(*btc.BitcoinRPC),
|
||||
}
|
||||
s.RPCMarshaler = btc.JSONMarshalerV2{}
|
||||
s.ChainConfig.SupportsEstimateFee = false
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// Initialize initializes BellcoinRPC instance.
|
||||
func (b *BellcoinRPC) Initialize() error {
|
||||
ci, err := b.GetChainInfo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
chainName := ci.Chain
|
||||
|
||||
glog.Info("Chain name ", chainName)
|
||||
params := GetChainParams(chainName)
|
||||
|
||||
// always create parser
|
||||
b.Parser = NewBellcoinParser(params, b.ChainConfig)
|
||||
|
||||
// parameters for getInfo request
|
||||
if params.Net == MainnetMagic {
|
||||
b.Testnet = false
|
||||
b.Network = "livenet"
|
||||
} else {
|
||||
b.Testnet = true
|
||||
b.Network = "testnet"
|
||||
}
|
||||
|
||||
glog.Info("rpc: block chain ", params.Name)
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
package bitcore
|
||||
|
||||
import (
|
||||
"github.com/martinboehm/btcd/wire"
|
||||
"github.com/martinboehm/btcutil/chaincfg"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
|
||||
)
|
||||
|
||||
const (
|
||||
MainnetMagic wire.BitcoinNet = 0xf9beb4d9
|
||||
TestnetMagic wire.BitcoinNet = 0xfdd2c8f1
|
||||
RegtestMagic wire.BitcoinNet = 0xfabfb5da
|
||||
)
|
||||
|
||||
var (
|
||||
MainNetParams chaincfg.Params
|
||||
TestNetParams chaincfg.Params
|
||||
)
|
||||
|
||||
func init() {
|
||||
MainNetParams = chaincfg.MainNetParams
|
||||
MainNetParams.Net = MainnetMagic
|
||||
MainNetParams.PubKeyHashAddrID = []byte{3}
|
||||
MainNetParams.ScriptHashAddrID = []byte{125}
|
||||
MainNetParams.Bech32HRPSegwit = "btx"
|
||||
|
||||
TestNetParams = chaincfg.TestNet3Params
|
||||
TestNetParams.Net = TestnetMagic
|
||||
TestNetParams.PubKeyHashAddrID = []byte{111}
|
||||
TestNetParams.ScriptHashAddrID = []byte{196}
|
||||
TestNetParams.Bech32HRPSegwit = "tbtx"
|
||||
|
||||
err := chaincfg.Register(&MainNetParams)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// BitcoreParser handle
|
||||
type BitcoreParser struct {
|
||||
*btc.BitcoinParser
|
||||
baseparser *bchain.BaseParser
|
||||
}
|
||||
|
||||
// NewBitcoreParser returns new BitcoreParser instance
|
||||
func NewBitcoreParser(params *chaincfg.Params, c *btc.Configuration) *BitcoreParser {
|
||||
return &BitcoreParser{
|
||||
BitcoinParser: btc.NewBitcoinParser(params, c),
|
||||
baseparser: &bchain.BaseParser{},
|
||||
}
|
||||
}
|
||||
|
||||
// GetChainParams contains network parameters for the main Bitcore network,
|
||||
// and the test Bitcore network
|
||||
func GetChainParams(chain string) *chaincfg.Params {
|
||||
if !chaincfg.IsRegistered(&MainNetParams) {
|
||||
err := chaincfg.Register(&MainNetParams)
|
||||
if err == nil {
|
||||
err = chaincfg.Register(&TestNetParams)
|
||||
}
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
switch chain {
|
||||
case "test":
|
||||
return &TestNetParams
|
||||
default:
|
||||
return &MainNetParams
|
||||
}
|
||||
}
|
||||
|
||||
// PackTx packs transaction to byte array using protobuf
|
||||
func (p *BitcoreParser) PackTx(tx *bchain.Tx, height uint32, blockTime int64) ([]byte, error) {
|
||||
return p.baseparser.PackTx(tx, height, blockTime)
|
||||
}
|
||||
|
||||
// UnpackTx unpacks transaction from protobuf byte array
|
||||
func (p *BitcoreParser) UnpackTx(buf []byte) (*bchain.Tx, uint32, error) {
|
||||
return p.baseparser.UnpackTx(buf)
|
||||
}
|
|
@ -0,0 +1,209 @@
|
|||
// +build unittest
|
||||
|
||||
package bitcore
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"math/big"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/martinboehm/btcutil/chaincfg"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
c := m.Run()
|
||||
chaincfg.ResetParams()
|
||||
os.Exit(c)
|
||||
}
|
||||
|
||||
func Test_GetAddrDescFromAddress_Mainnet(t *testing.T) {
|
||||
type args struct {
|
||||
address string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "pubkeyhash1",
|
||||
args: args{address: "2HbJfcGD6NTm318VeBjfd2hLf44hHkzHVV"},
|
||||
want: "76a9143236327ebad3be5e336777bb3562439720f38dc488ac",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "pubkeyhash2",
|
||||
args: args{address: "2XFmLkpyBzJncnnkALQ3qnMqSqUdqcBdc4"},
|
||||
want: "76a914c815e7f760bbd5f109d58e848cf78ba808d972e088ac",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "scripthash1",
|
||||
args: args{address: "scUVfntFvnyTHRCvBwUNyKGVFLiBb2iHVK"},
|
||||
want: "a914c7ec567ef583a96a74c02980cc42f728cc987c3287",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "scripthash2",
|
||||
args: args{address: "sVeAXe1CMWVAuq5174hG49QRfkBp4GFvAu"},
|
||||
want: "a9147cf7a3a6b1305871ff5f0f064aaa634880ff67ab87",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "witness_v0_keyhash",
|
||||
args: args{address: "btx1qnfkmarp8pe8q05690zd48qma3gmp0pp66gqsv3"},
|
||||
want: "00149a6dbe8c270e4e07d345789b53837d8a3617843a",
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
parser := NewBitcoreParser(GetChainParams("main"), &btc.Configuration{})
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := parser.GetAddrDescFromAddress(tt.args.address)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("GetAddrDescFromAddress() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
h := hex.EncodeToString(got)
|
||||
if !reflect.DeepEqual(h, tt.want) {
|
||||
t.Errorf("GetAddrDescFromAddress() = %v, want %v", h, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
testTx1 bchain.Tx
|
||||
|
||||
testTxPacked1 = "0a20fcd4f2e45787a33571bc9b2ce939d6e8e51fa053296de9240f05455702bd954012e2010200000001f69bd1fd76e52a426f21332e3b7cfbc3350eacbd21c6e0c11a7ae11919803ef0010000006b483045022100d1fa62b9d7860a03e1dcd4734fe42457cb508ebb49e896d7a77748d997d09fba022005f1657b39451afe97076d8667fe5f6f18ca76391521ab84d09d5b82137d933b0121035aaf032f13761f27465467dc73f1998a80dd4d85a6353d2832a7244d7b591d3effffffff02a87322b3010000001976a914d0c320db3fbd0abe2b6fe31a3bca4fed8ce8669588ac94b94f37000000001976a9145584ee07090af59938e991c9d8e9e945c99a449f88ac0000000018858a8ce205200028f9f3133299010a001220f03e801919e17a1ac1e0c621bdac0e35c3fb7c3b2e33216f422ae576fdd19bf61801226b483045022100d1fa62b9d7860a03e1dcd4734fe42457cb508ebb49e896d7a77748d997d09fba022005f1657b39451afe97076d8667fe5f6f18ca76391521ab84d09d5b82137d933b0121035aaf032f13761f27465467dc73f1998a80dd4d85a6353d2832a7244d7b591d3e28ffffffff0f3a480a0501b32273a810001a1976a914d0c320db3fbd0abe2b6fe31a3bca4fed8ce8669588ac22223259336546797741414673617039757139726942474143684e326858356a6e7268753a470a04374fb99410011a1976a9145584ee07090af59938e991c9d8e9e945c99a449f88ac2222324c6f7a646b704450723562356b6a66445042315a76454c597735734475684139594002"
|
||||
)
|
||||
|
||||
func init() {
|
||||
testTx1 = bchain.Tx{
|
||||
Hex: "0200000001f69bd1fd76e52a426f21332e3b7cfbc3350eacbd21c6e0c11a7ae11919803ef0010000006b483045022100d1fa62b9d7860a03e1dcd4734fe42457cb508ebb49e896d7a77748d997d09fba022005f1657b39451afe97076d8667fe5f6f18ca76391521ab84d09d5b82137d933b0121035aaf032f13761f27465467dc73f1998a80dd4d85a6353d2832a7244d7b591d3effffffff02a87322b3010000001976a914d0c320db3fbd0abe2b6fe31a3bca4fed8ce8669588ac94b94f37000000001976a9145584ee07090af59938e991c9d8e9e945c99a449f88ac00000000",
|
||||
Blocktime: 1547896069,
|
||||
Time: 1547896069,
|
||||
Txid: "fcd4f2e45787a33571bc9b2ce939d6e8e51fa053296de9240f05455702bd9540",
|
||||
LockTime: 0,
|
||||
Version: 2,
|
||||
Vin: []bchain.Vin{
|
||||
{
|
||||
ScriptSig: bchain.ScriptSig{
|
||||
Hex: "483045022100d1fa62b9d7860a03e1dcd4734fe42457cb508ebb49e896d7a77748d997d09fba022005f1657b39451afe97076d8667fe5f6f18ca76391521ab84d09d5b82137d933b0121035aaf032f13761f27465467dc73f1998a80dd4d85a6353d2832a7244d7b591d3e",
|
||||
},
|
||||
Txid: "f03e801919e17a1ac1e0c621bdac0e35c3fb7c3b2e33216f422ae576fdd19bf6",
|
||||
Vout: 1,
|
||||
Sequence: 4294967295,
|
||||
},
|
||||
},
|
||||
Vout: []bchain.Vout{
|
||||
{
|
||||
ValueSat: *big.NewInt(7300346792),
|
||||
N: 0,
|
||||
ScriptPubKey: bchain.ScriptPubKey{
|
||||
Hex: "76a914d0c320db3fbd0abe2b6fe31a3bca4fed8ce8669588ac",
|
||||
Addresses: []string{
|
||||
"2Y3eFywAAFsap9uq9riBGAChN2hX5jnrhu",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ValueSat: *big.NewInt(927971732),
|
||||
N: 1,
|
||||
ScriptPubKey: bchain.ScriptPubKey{
|
||||
Hex: "76a9145584ee07090af59938e991c9d8e9e945c99a449f88ac",
|
||||
Addresses: []string{
|
||||
"2LozdkpDPr5b5kjfDPB1ZvELYw5sDuhA9Y",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func Test_PackTx(t *testing.T) {
|
||||
type args struct {
|
||||
tx bchain.Tx
|
||||
height uint32
|
||||
blockTime int64
|
||||
parser *BitcoreParser
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "bitcore-1",
|
||||
args: args{
|
||||
tx: testTx1,
|
||||
height: 326137,
|
||||
blockTime: 1547896069,
|
||||
parser: NewBitcoreParser(GetChainParams("main"), &btc.Configuration{}),
|
||||
},
|
||||
want: testTxPacked1,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := tt.args.parser.PackTx(&tt.args.tx, tt.args.height, tt.args.blockTime)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("packTx() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
h := hex.EncodeToString(got)
|
||||
if !reflect.DeepEqual(h, tt.want) {
|
||||
t.Errorf("packTx() = %v, want %v", h, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_UnpackTx(t *testing.T) {
|
||||
type args struct {
|
||||
packedTx string
|
||||
parser *BitcoreParser
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *bchain.Tx
|
||||
want1 uint32
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "bitcore-1",
|
||||
args: args{
|
||||
packedTx: testTxPacked1,
|
||||
parser: NewBitcoreParser(GetChainParams("main"), &btc.Configuration{}),
|
||||
},
|
||||
want: &testTx1,
|
||||
want1: 326137,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
b, _ := hex.DecodeString(tt.args.packedTx)
|
||||
got, got1, err := tt.args.parser.UnpackTx(b)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("unpackTx() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("unpackTx() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
if got1 != tt.want1 {
|
||||
t.Errorf("unpackTx() got1 = %v, want %v", got1, tt.want1)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
package bitcore
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/juju/errors"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
|
||||
)
|
||||
|
||||
// BitcoreRPC is an interface to JSON-RPC bitcoind service.
|
||||
type BitcoreRPC struct {
|
||||
*btc.BitcoinRPC
|
||||
}
|
||||
|
||||
// NewBitcoreRPC returns new BitcoreRPC instance.
|
||||
func NewBitcoreRPC(config json.RawMessage, pushHandler func(bchain.NotificationType)) (bchain.BlockChain, error) {
|
||||
b, err := btc.NewBitcoinRPC(config, pushHandler)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s := &BitcoreRPC{
|
||||
b.(*btc.BitcoinRPC),
|
||||
}
|
||||
s.RPCMarshaler = btc.JSONMarshalerV2{}
|
||||
s.ChainConfig.SupportsEstimateFee = false
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// Initialize initializes BitcoreRPC instance.
|
||||
func (b *BitcoreRPC) Initialize() error {
|
||||
ci, err := b.GetChainInfo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
chainName := ci.Chain
|
||||
glog.Info("Chain name ", chainName)
|
||||
params := GetChainParams(chainName)
|
||||
|
||||
// always create parser
|
||||
b.Parser = NewBitcoreParser(params, b.ChainConfig)
|
||||
|
||||
// parameters for getInfo request
|
||||
if params.Net == MainnetMagic {
|
||||
b.Testnet = false
|
||||
b.Network = "livenet"
|
||||
} else {
|
||||
b.Testnet = true
|
||||
b.Network = "testnet"
|
||||
}
|
||||
|
||||
glog.Info("rpc: block chain ", params.Name)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetBlock returns block with given hash.
|
||||
|
||||
func (f *BitcoreRPC) GetBlock(hash string, height uint32) (*bchain.Block, error) {
|
||||
var err error
|
||||
if hash == "" {
|
||||
hash, err = f.GetBlockHash(height)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if !f.ParseBlocks {
|
||||
return f.GetBlockFull(hash)
|
||||
}
|
||||
// optimization
|
||||
if height > 0 {
|
||||
return f.GetBlockWithoutHeader(hash, height)
|
||||
}
|
||||
header, err := f.GetBlockHeader(hash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data, err := f.GetBlockRaw(hash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
block, err := f.Parser.ParseBlock(data)
|
||||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "hash %v", hash)
|
||||
}
|
||||
block.BlockHeader = *header
|
||||
return block, nil
|
||||
}
|
||||
|
||||
// GetBlockFull returns block with given hash
|
||||
func (f *BitcoreRPC) GetBlockFull(hash string) (*bchain.Block, error) {
|
||||
glog.V(1).Info("rpc: getblock (verbosity=2) ", hash)
|
||||
|
||||
res := btc.ResGetBlockFull{}
|
||||
req := btc.CmdGetBlock{Method: "getblock"}
|
||||
req.Params.BlockHash = hash
|
||||
req.Params.Verbosity = 2
|
||||
err := f.Call(&req, &res)
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "hash %v", hash)
|
||||
}
|
||||
if res.Error != nil {
|
||||
if btc.IsErrBlockNotFound(res.Error) {
|
||||
return nil, bchain.ErrBlockNotFound
|
||||
}
|
||||
return nil, errors.Annotatef(res.Error, "hash %v", hash)
|
||||
}
|
||||
|
||||
for i := range res.Result.Txs {
|
||||
tx := &res.Result.Txs[i]
|
||||
for j := range tx.Vout {
|
||||
vout := &tx.Vout[j]
|
||||
// convert vout.JsonValue to big.Int and clear it, it is only temporary value used for unmarshal
|
||||
vout.ValueSat, err = f.Parser.AmountToBigInt(vout.JsonValue)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vout.JsonValue = ""
|
||||
}
|
||||
}
|
||||
|
||||
return &res.Result, nil
|
||||
}
|
||||
|
||||
// GetTransactionForMempool returns a transaction by the transaction ID.
|
||||
// It could be optimized for mempool, i.e. without block time and confirmations
|
||||
func (f *BitcoreRPC) GetTransactionForMempool(txid string) (*bchain.Tx, error) {
|
||||
return f.GetTransaction(txid)
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
package bitzeny
|
||||
|
||||
import (
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
|
||||
|
||||
"github.com/martinboehm/btcd/wire"
|
||||
"github.com/martinboehm/btcutil/chaincfg"
|
||||
)
|
||||
|
||||
// magic numbers
|
||||
const (
|
||||
MainnetMagic wire.BitcoinNet = 0xf9bea5da
|
||||
TestnetMagic wire.BitcoinNet = 0x594e4559
|
||||
)
|
||||
|
||||
// chain parameters
|
||||
var (
|
||||
MainNetParams chaincfg.Params
|
||||
TestNetParams chaincfg.Params
|
||||
)
|
||||
|
||||
func init() {
|
||||
MainNetParams = chaincfg.MainNetParams
|
||||
MainNetParams.Net = MainnetMagic
|
||||
MainNetParams.PubKeyHashAddrID = []byte{81}
|
||||
MainNetParams.ScriptHashAddrID = []byte{5}
|
||||
MainNetParams.Bech32HRPSegwit = "bz"
|
||||
|
||||
TestNetParams = chaincfg.TestNet3Params
|
||||
TestNetParams.Net = TestnetMagic
|
||||
TestNetParams.PubKeyHashAddrID = []byte{111}
|
||||
TestNetParams.ScriptHashAddrID = []byte{196}
|
||||
TestNetParams.Bech32HRPSegwit = "tz"
|
||||
}
|
||||
|
||||
// BitZenyParser handle
|
||||
type BitZenyParser struct {
|
||||
*btc.BitcoinParser
|
||||
}
|
||||
|
||||
// NewBitZenyParser returns new BitZenyParser instance
|
||||
func NewBitZenyParser(params *chaincfg.Params, c *btc.Configuration) *BitZenyParser {
|
||||
return &BitZenyParser{BitcoinParser: btc.NewBitcoinParser(params, c)}
|
||||
}
|
||||
|
||||
// GetChainParams contains network parameters for the main BitZeny network,
|
||||
// and the test BitZeny network
|
||||
func GetChainParams(chain string) *chaincfg.Params {
|
||||
if !chaincfg.IsRegistered(&MainNetParams) {
|
||||
err := chaincfg.Register(&MainNetParams)
|
||||
if err == nil {
|
||||
err = chaincfg.Register(&TestNetParams)
|
||||
}
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
switch chain {
|
||||
case "test":
|
||||
return &TestNetParams
|
||||
default:
|
||||
return &MainNetParams
|
||||
}
|
||||
}
|
|
@ -0,0 +1,290 @@
|
|||
// +build unittest
|
||||
|
||||
package bitzeny
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"math/big"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
|
||||
|
||||
"github.com/martinboehm/btcutil/chaincfg"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
c := m.Run()
|
||||
chaincfg.ResetParams()
|
||||
os.Exit(c)
|
||||
}
|
||||
|
||||
func Test_GetAddrDescFromAddress_Mainnet(t *testing.T) {
|
||||
type args struct {
|
||||
address string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "P2PKH1",
|
||||
args: args{address: "Zw74N1RSU2xV3a7SBERBiCP11fMwX5yvMu"},
|
||||
want: "76a914d8658ca5c406149071687d370d1d22d972d2f88488ac",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "P2PKH2",
|
||||
args: args{address: "ZiSn1vTSxGu2kFcnkjjm7bYGhT5BVAVfEG"},
|
||||
want: "76a9144d869697281ad18370313122795e56dfdc3a331388ac",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "P2SH1",
|
||||
args: args{address: "3CZ3357bm1K81StpEDQtEH3ho3ULx19nc8"},
|
||||
want: "a9147726fc1144eae1b7bd301d87d0a7f846cadb591887",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "P2SH2",
|
||||
args: args{address: "3M1AjZEuBzScbd9pchiGJSVT4yNfwzSmXP"},
|
||||
want: "a914d3d93b5d7f57b94a4fecde93d4489f2b423fd3c287",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "witness_v0_keyhash",
|
||||
args: args{address: "bz1q7rfrdacyyfwx8gppd8ah9hka8npgqsm44prfnd"},
|
||||
want: "0014f0d236f704225c63a02169fb72dedd3cc2804375",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "witness_v0_scripthashx",
|
||||
args: args{address: "bz1qd2mspe6m2wpztw4q2mccyvyess6569eu59sfvf0u0vdmdwltr5lse8d7sw"},
|
||||
want: "00206ab700e75b538225baa056f182309984354d173ca1609625fc7b1bb6bbeb1d3f",
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
parser := NewBitZenyParser(GetChainParams("main"), &btc.Configuration{})
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := parser.GetAddrDescFromAddress(tt.args.address)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("GetAddrDescFromAddress() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
h := hex.EncodeToString(got)
|
||||
if !reflect.DeepEqual(h, tt.want) {
|
||||
t.Errorf("GetAddrDescFromAddress() = %v, want %v", h, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_GetAddressesFromAddrDesc(t *testing.T) {
|
||||
type args struct {
|
||||
script string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []string
|
||||
want2 bool
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "P2PKH",
|
||||
args: args{script: "76a914d8658ca5c406149071687d370d1d22d972d2f88488ac"},
|
||||
want: []string{"Zw74N1RSU2xV3a7SBERBiCP11fMwX5yvMu"},
|
||||
want2: true,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "P2SH",
|
||||
args: args{script: "a9147726fc1144eae1b7bd301d87d0a7f846cadb591887"},
|
||||
want: []string{"3CZ3357bm1K81StpEDQtEH3ho3ULx19nc8"},
|
||||
want2: true,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "P2WPKH",
|
||||
args: args{script: "0014f0d236f704225c63a02169fb72dedd3cc2804375"},
|
||||
want: []string{"bz1q7rfrdacyyfwx8gppd8ah9hka8npgqsm44prfnd"},
|
||||
want2: true,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "P2WSH",
|
||||
args: args{script: "00206ab700e75b538225baa056f182309984354d173ca1609625fc7b1bb6bbeb1d3f"},
|
||||
want: []string{"bz1qd2mspe6m2wpztw4q2mccyvyess6569eu59sfvf0u0vdmdwltr5lse8d7sw"},
|
||||
want2: true,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "OP_RETURN ascii",
|
||||
args: args{script: "6a0461686f6a"},
|
||||
want: []string{"OP_RETURN (ahoj)"},
|
||||
want2: false,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "OP_RETURN hex",
|
||||
args: args{script: "6a072020f1686f6a20"},
|
||||
want: []string{"OP_RETURN 2020f1686f6a20"},
|
||||
want2: false,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
parser := NewBitZenyParser(GetChainParams("main"), &btc.Configuration{})
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
b, _ := hex.DecodeString(tt.args.script)
|
||||
got, got2, err := parser.GetAddressesFromAddrDesc(b)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("outputScriptToAddresses() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("GetAddressesFromAddrDesc() = %v, want %v", got, tt.want)
|
||||
}
|
||||
if !reflect.DeepEqual(got2, tt.want2) {
|
||||
t.Errorf("GetAddressesFromAddrDesc() = %v, want %v", got2, tt.want2)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
testTx1 bchain.Tx
|
||||
|
||||
testTxPacked1 = "001c3f1a8be6859d3e0100000001aef422fb91cd91e556966fed4121ac44017a761d71385596536bb447ae05213e000000006a47304402202341ac4297925257dc72eb418a069c45e76f7070340e27501f6308cc7eff45f802204a347915adceff5f6fc9b8075d95d47887d46b344c7bbc8066d315931b189ad001210228c2520812b7f8c63e7a088c61b6348b22fa0c98812e736a1fd896bc828d3c65feffffff028041f13d000000001976a91478379ea136bb5783b675cd11e412bf0703995aeb88aca9983141000000001976a9144d869697281ad18370313122795e56dfdc3a331388ac193f1c00"
|
||||
)
|
||||
|
||||
func init() {
|
||||
testTx1 = bchain.Tx{
|
||||
Hex: "0100000001aef422fb91cd91e556966fed4121ac44017a761d71385596536bb447ae05213e000000006a47304402202341ac4297925257dc72eb418a069c45e76f7070340e27501f6308cc7eff45f802204a347915adceff5f6fc9b8075d95d47887d46b344c7bbc8066d315931b189ad001210228c2520812b7f8c63e7a088c61b6348b22fa0c98812e736a1fd896bc828d3c65feffffff028041f13d000000001976a91478379ea136bb5783b675cd11e412bf0703995aeb88aca9983141000000001976a9144d869697281ad18370313122795e56dfdc3a331388ac193f1c00",
|
||||
Blocktime: 1583392607,
|
||||
Txid: "f81c34b300961877328c3aaa7cd5e69068457868309fbf1e92544e3a6a915bcb",
|
||||
LockTime: 1851161,
|
||||
Version: 1,
|
||||
Vin: []bchain.Vin{
|
||||
{
|
||||
ScriptSig: bchain.ScriptSig{
|
||||
Hex: "47304402202341ac4297925257dc72eb418a069c45e76f7070340e27501f6308cc7eff45f802204a347915adceff5f6fc9b8075d95d47887d46b344c7bbc8066d315931b189ad001210228c2520812b7f8c63e7a088c61b6348b22fa0c98812e736a1fd896bc828d3c65",
|
||||
},
|
||||
Txid: "3e2105ae47b46b53965538711d767a0144ac2141ed6f9656e591cd91fb22f4ae",
|
||||
Vout: 0,
|
||||
Sequence: 4294967294,
|
||||
},
|
||||
},
|
||||
Vout: []bchain.Vout{
|
||||
{
|
||||
ValueSat: *big.NewInt(1039221120),
|
||||
N: 0,
|
||||
ScriptPubKey: bchain.ScriptPubKey{
|
||||
Hex: "76a91478379ea136bb5783b675cd11e412bf0703995aeb88ac",
|
||||
Addresses: []string{
|
||||
"ZnLWULVbAzjy1TSKxGnpkomeeaEDTHk5Nj",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ValueSat: *big.NewInt(1093769385),
|
||||
N: 1,
|
||||
ScriptPubKey: bchain.ScriptPubKey{
|
||||
Hex: "76a9144d869697281ad18370313122795e56dfdc3a331388ac",
|
||||
Addresses: []string{
|
||||
"ZiSn1vTSxGu2kFcnkjjm7bYGhT5BVAVfEG",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func Test_PackTx(t *testing.T) {
|
||||
type args struct {
|
||||
tx bchain.Tx
|
||||
height uint32
|
||||
blockTime int64
|
||||
parser *BitZenyParser
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "BitZeny-1",
|
||||
args: args{
|
||||
tx: testTx1,
|
||||
height: 1851162,
|
||||
blockTime: 1583392607,
|
||||
parser: NewBitZenyParser(GetChainParams("main"), &btc.Configuration{}),
|
||||
},
|
||||
want: testTxPacked1,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := tt.args.parser.PackTx(&tt.args.tx, tt.args.height, tt.args.blockTime)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("packTx() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
h := hex.EncodeToString(got)
|
||||
if !reflect.DeepEqual(h, tt.want) {
|
||||
t.Errorf("packTx() = %v, want %v", h, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_UnpackTx(t *testing.T) {
|
||||
type args struct {
|
||||
packedTx string
|
||||
parser *BitZenyParser
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *bchain.Tx
|
||||
want1 uint32
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "BitZeny-1",
|
||||
args: args{
|
||||
packedTx: testTxPacked1,
|
||||
parser: NewBitZenyParser(GetChainParams("main"), &btc.Configuration{}),
|
||||
},
|
||||
want: &testTx1,
|
||||
want1: 1851162,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
b, _ := hex.DecodeString(tt.args.packedTx)
|
||||
got, got1, err := tt.args.parser.UnpackTx(b)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("unpackTx() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("unpackTx() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
if got1 != tt.want1 {
|
||||
t.Errorf("unpackTx() got1 = %v, want %v", got1, tt.want1)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
package bitzeny
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
|
||||
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
// BitZenyRPC is an interface to JSON-RPC bitcoind service.
|
||||
type BitZenyRPC struct {
|
||||
*btc.BitcoinRPC
|
||||
}
|
||||
|
||||
// NewBitZenyRPC returns new BitZenyRPC instance.
|
||||
func NewBitZenyRPC(config json.RawMessage, pushHandler func(bchain.NotificationType)) (bchain.BlockChain, error) {
|
||||
b, err := btc.NewBitcoinRPC(config, pushHandler)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s := &BitZenyRPC{
|
||||
b.(*btc.BitcoinRPC),
|
||||
}
|
||||
s.RPCMarshaler = btc.JSONMarshalerV2{}
|
||||
s.ChainConfig.SupportsEstimateFee = false
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// Initialize initializes BitZenyRPC instance.
|
||||
func (b *BitZenyRPC) Initialize() error {
|
||||
ci, err := b.GetChainInfo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
chainName := ci.Chain
|
||||
|
||||
glog.Info("Chain name ", chainName)
|
||||
params := GetChainParams(chainName)
|
||||
|
||||
// always create parser
|
||||
b.Parser = NewBitZenyParser(params, b.ChainConfig)
|
||||
|
||||
// parameters for getInfo request
|
||||
if params.Net == MainnetMagic {
|
||||
b.Testnet = false
|
||||
b.Network = "livenet"
|
||||
} else {
|
||||
b.Testnet = true
|
||||
b.Network = "testnet"
|
||||
}
|
||||
|
||||
glog.Info("rpc: block chain ", params.Name)
|
||||
|
||||
return nil
|
||||
}
|
|
@ -1,23 +1,6 @@
|
|||
package coins
|
||||
|
||||
import (
|
||||
"blockbook/bchain"
|
||||
"blockbook/bchain/coins/bch"
|
||||
"blockbook/bchain/coins/btc"
|
||||
"blockbook/bchain/coins/btg"
|
||||
"blockbook/bchain/coins/dash"
|
||||
"blockbook/bchain/coins/digibyte"
|
||||
"blockbook/bchain/coins/dogecoin"
|
||||
"blockbook/bchain/coins/eth"
|
||||
"blockbook/bchain/coins/gamecredits"
|
||||
"blockbook/bchain/coins/grs"
|
||||
"blockbook/bchain/coins/litecoin"
|
||||
"blockbook/bchain/coins/monacoin"
|
||||
"blockbook/bchain/coins/myriad"
|
||||
"blockbook/bchain/coins/namecoin"
|
||||
"blockbook/bchain/coins/vertcoin"
|
||||
"blockbook/bchain/coins/zec"
|
||||
"blockbook/common"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
@ -27,26 +10,76 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/juju/errors"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/bch"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/bellcoin"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/bitcore"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/bitzeny"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/btg"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/cpuchain"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/dash"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/dcr"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/deeponion"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/digibyte"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/divi"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/dogecoin"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/eth"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/firo"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/flo"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/fujicoin"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/gamecredits"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/grs"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/koto"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/liquid"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/litecoin"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/monacoin"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/monetaryunit"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/myriad"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/namecoin"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/nuls"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/omotenashicoin"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/pivx"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/polis"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/qtum"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/ravencoin"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/ritocoin"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/snowgem"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/trezarcoin"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/unobtanium"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/vertcoin"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/viacoin"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/vipstarcoin"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/zec"
|
||||
"spacecruft.org/spacecruft/blockbook/common"
|
||||
)
|
||||
|
||||
type blockChainFactory func(config json.RawMessage, pushHandler func(bchain.NotificationType)) (bchain.BlockChain, error)
|
||||
|
||||
// BlockChainFactories is a map of constructors of coin RPC interfaces
|
||||
var BlockChainFactories = make(map[string]blockChainFactory)
|
||||
|
||||
func init() {
|
||||
BlockChainFactories["Bitcoin"] = btc.NewBitcoinRPC
|
||||
BlockChainFactories["Testnet"] = btc.NewBitcoinRPC
|
||||
BlockChainFactories["Signet"] = btc.NewBitcoinRPC
|
||||
BlockChainFactories["Zcash"] = zec.NewZCashRPC
|
||||
BlockChainFactories["Zcash Testnet"] = zec.NewZCashRPC
|
||||
BlockChainFactories["Ethereum"] = eth.NewEthereumRPC
|
||||
BlockChainFactories["Ethereum Classic"] = eth.NewEthereumRPC
|
||||
BlockChainFactories["Ethereum Testnet Ropsten"] = eth.NewEthereumRPC
|
||||
BlockChainFactories["Ethereum Testnet Goerli"] = eth.NewEthereumRPC
|
||||
BlockChainFactories["Bcash"] = bch.NewBCashRPC
|
||||
BlockChainFactories["Bcash Testnet"] = bch.NewBCashRPC
|
||||
BlockChainFactories["Bgold"] = btg.NewBGoldRPC
|
||||
BlockChainFactories["Bgold Testnet"] = btg.NewBGoldRPC
|
||||
BlockChainFactories["Dash"] = dash.NewDashRPC
|
||||
BlockChainFactories["Dash Testnet"] = dash.NewDashRPC
|
||||
BlockChainFactories["Decred"] = dcr.NewDecredRPC
|
||||
BlockChainFactories["Decred Testnet"] = dcr.NewDecredRPC
|
||||
BlockChainFactories["GameCredits"] = gamecredits.NewGameCreditsRPC
|
||||
BlockChainFactories["Koto"] = koto.NewKotoRPC
|
||||
BlockChainFactories["Koto Testnet"] = koto.NewKotoRPC
|
||||
BlockChainFactories["Litecoin"] = litecoin.NewLitecoinRPC
|
||||
BlockChainFactories["Litecoin Testnet"] = litecoin.NewLitecoinRPC
|
||||
BlockChainFactories["Dogecoin"] = dogecoin.NewDogecoinRPC
|
||||
|
@ -55,10 +88,38 @@ func init() {
|
|||
BlockChainFactories["Namecoin"] = namecoin.NewNamecoinRPC
|
||||
BlockChainFactories["Monacoin"] = monacoin.NewMonacoinRPC
|
||||
BlockChainFactories["Monacoin Testnet"] = monacoin.NewMonacoinRPC
|
||||
BlockChainFactories["MonetaryUnit"] = monetaryunit.NewMonetaryUnitRPC
|
||||
BlockChainFactories["DigiByte"] = digibyte.NewDigiByteRPC
|
||||
BlockChainFactories["DigiByte Testnet"] = digibyte.NewDigiByteRPC
|
||||
BlockChainFactories["Myriad"] = myriad.NewMyriadRPC
|
||||
BlockChainFactories["Liquid"] = liquid.NewLiquidRPC
|
||||
BlockChainFactories["Groestlcoin"] = grs.NewGroestlcoinRPC
|
||||
BlockChainFactories["Groestlcoin Testnet"] = grs.NewGroestlcoinRPC
|
||||
BlockChainFactories["PIVX"] = pivx.NewPivXRPC
|
||||
BlockChainFactories["PIVX Testnet"] = pivx.NewPivXRPC
|
||||
BlockChainFactories["Polis"] = polis.NewPolisRPC
|
||||
BlockChainFactories["Firo"] = firo.NewFiroRPC
|
||||
BlockChainFactories["Fujicoin"] = fujicoin.NewFujicoinRPC
|
||||
BlockChainFactories["Flo"] = flo.NewFloRPC
|
||||
BlockChainFactories["Bellcoin"] = bellcoin.NewBellcoinRPC
|
||||
BlockChainFactories["Qtum"] = qtum.NewQtumRPC
|
||||
BlockChainFactories["Viacoin"] = viacoin.NewViacoinRPC
|
||||
BlockChainFactories["Qtum Testnet"] = qtum.NewQtumRPC
|
||||
BlockChainFactories["NULS"] = nuls.NewNulsRPC
|
||||
BlockChainFactories["VIPSTARCOIN"] = vipstarcoin.NewVIPSTARCOINRPC
|
||||
BlockChainFactories["ZelCash"] = zec.NewZCashRPC
|
||||
BlockChainFactories["Ravencoin"] = ravencoin.NewRavencoinRPC
|
||||
BlockChainFactories["Ritocoin"] = ritocoin.NewRitocoinRPC
|
||||
BlockChainFactories["Divi"] = divi.NewDiviRPC
|
||||
BlockChainFactories["CPUchain"] = cpuchain.NewCPUchainRPC
|
||||
BlockChainFactories["Unobtanium"] = unobtanium.NewUnobtaniumRPC
|
||||
BlockChainFactories["DeepOnion"] = deeponion.NewDeepOnionRPC
|
||||
BlockChainFactories["SnowGem"] = snowgem.NewSnowGemRPC
|
||||
BlockChainFactories["Bitcore"] = bitcore.NewBitcoreRPC
|
||||
BlockChainFactories["Omotenashicoin"] = omotenashicoin.NewOmotenashiCoinRPC
|
||||
BlockChainFactories["Omotenashicoin Testnet"] = omotenashicoin.NewOmotenashiCoinRPC
|
||||
BlockChainFactories["BitZeny"] = bitzeny.NewBitZenyRPC
|
||||
BlockChainFactories["Trezarcoin"] = trezarcoin.NewTrezarcoinRPC
|
||||
}
|
||||
|
||||
// GetCoinNameFromConfig gets coin name and coin shortcut from config file
|
||||
|
@ -79,30 +140,34 @@ func GetCoinNameFromConfig(configfile string) (string, string, string, error) {
|
|||
return cn.CoinName, cn.CoinShortcut, cn.CoinLabel, nil
|
||||
}
|
||||
|
||||
// NewBlockChain creates bchain.BlockChain of type defined by parameter coin
|
||||
func NewBlockChain(coin string, configfile string, pushHandler func(bchain.NotificationType), metrics *common.Metrics) (bchain.BlockChain, error) {
|
||||
// NewBlockChain creates bchain.BlockChain and bchain.Mempool for the coin passed by the parameter coin
|
||||
func NewBlockChain(coin string, configfile string, pushHandler func(bchain.NotificationType), metrics *common.Metrics) (bchain.BlockChain, bchain.Mempool, error) {
|
||||
data, err := ioutil.ReadFile(configfile)
|
||||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "Error reading file %v", configfile)
|
||||
return nil, nil, errors.Annotatef(err, "Error reading file %v", configfile)
|
||||
}
|
||||
var config json.RawMessage
|
||||
err = json.Unmarshal(data, &config)
|
||||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "Error parsing file %v", configfile)
|
||||
return nil, nil, errors.Annotatef(err, "Error parsing file %v", configfile)
|
||||
}
|
||||
bcf, ok := BlockChainFactories[coin]
|
||||
if !ok {
|
||||
return nil, errors.New(fmt.Sprint("Unsupported coin '", coin, "'. Must be one of ", reflect.ValueOf(BlockChainFactories).MapKeys()))
|
||||
return nil, nil, errors.New(fmt.Sprint("Unsupported coin '", coin, "'. Must be one of ", reflect.ValueOf(BlockChainFactories).MapKeys()))
|
||||
}
|
||||
bc, err := bcf(config, pushHandler)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
err = bc.Initialize()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
return &blockChainWithMetrics{b: bc, m: metrics}, nil
|
||||
mempool, err := bc.CreateMempool(bc)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return &blockChainWithMetrics{b: bc, m: metrics}, &mempoolWithMetrics{mempool: mempool, m: metrics}, nil
|
||||
}
|
||||
|
||||
type blockChainWithMetrics struct {
|
||||
|
@ -113,7 +178,7 @@ type blockChainWithMetrics struct {
|
|||
func (c *blockChainWithMetrics) observeRPCLatency(method string, start time.Time, err error) {
|
||||
var e string
|
||||
if err != nil {
|
||||
e = err.Error()
|
||||
e = "failure"
|
||||
}
|
||||
c.m.RPCLatency.With(common.Labels{"method": method, "error": e}).Observe(float64(time.Since(start)) / 1e6) // in milliseconds
|
||||
}
|
||||
|
@ -122,6 +187,14 @@ func (c *blockChainWithMetrics) Initialize() error {
|
|||
return c.b.Initialize()
|
||||
}
|
||||
|
||||
func (c *blockChainWithMetrics) CreateMempool(chain bchain.BlockChain) (bchain.Mempool, error) {
|
||||
return c.b.CreateMempool(chain)
|
||||
}
|
||||
|
||||
func (c *blockChainWithMetrics) InitializeMempool(addrDescForOutpoint bchain.AddrDescForOutpointFunc, onNewTxAddr bchain.OnNewTxAddrFunc, onNewTx bchain.OnNewTxFunc) error {
|
||||
return c.b.InitializeMempool(addrDescForOutpoint, onNewTxAddr, onNewTx)
|
||||
}
|
||||
|
||||
func (c *blockChainWithMetrics) Shutdown(ctx context.Context) error {
|
||||
return c.b.Shutdown(ctx)
|
||||
}
|
||||
|
@ -177,9 +250,9 @@ func (c *blockChainWithMetrics) GetBlockInfo(hash string) (v *bchain.BlockInfo,
|
|||
return c.b.GetBlockInfo(hash)
|
||||
}
|
||||
|
||||
func (c *blockChainWithMetrics) GetMempool() (v []string, err error) {
|
||||
defer func(s time.Time) { c.observeRPCLatency("GetMempool", s, err) }(time.Now())
|
||||
return c.b.GetMempool()
|
||||
func (c *blockChainWithMetrics) GetMempoolTransactions() (v []string, err error) {
|
||||
defer func(s time.Time) { c.observeRPCLatency("GetMempoolTransactions", s, err) }(time.Now())
|
||||
return c.b.GetMempoolTransactions()
|
||||
}
|
||||
|
||||
func (c *blockChainWithMetrics) GetTransaction(txid string) (v *bchain.Tx, err error) {
|
||||
|
@ -187,9 +260,9 @@ func (c *blockChainWithMetrics) GetTransaction(txid string) (v *bchain.Tx, err e
|
|||
return c.b.GetTransaction(txid)
|
||||
}
|
||||
|
||||
func (c *blockChainWithMetrics) GetTransactionSpecific(txid string) (v json.RawMessage, err error) {
|
||||
func (c *blockChainWithMetrics) GetTransactionSpecific(tx *bchain.Tx) (v json.RawMessage, err error) {
|
||||
defer func(s time.Time) { c.observeRPCLatency("GetTransactionSpecific", s, err) }(time.Now())
|
||||
return c.b.GetTransactionSpecific(txid)
|
||||
return c.b.GetTransactionSpecific(tx)
|
||||
}
|
||||
|
||||
func (c *blockChainWithMetrics) GetTransactionForMempool(txid string) (v *bchain.Tx, err error) {
|
||||
|
@ -212,25 +285,6 @@ func (c *blockChainWithMetrics) SendRawTransaction(tx string) (v string, err err
|
|||
return c.b.SendRawTransaction(tx)
|
||||
}
|
||||
|
||||
func (c *blockChainWithMetrics) ResyncMempool(onNewTxAddr bchain.OnNewTxAddrFunc) (count int, err error) {
|
||||
defer func(s time.Time) { c.observeRPCLatency("ResyncMempool", s, err) }(time.Now())
|
||||
count, err = c.b.ResyncMempool(onNewTxAddr)
|
||||
if err == nil {
|
||||
c.m.MempoolSize.Set(float64(count))
|
||||
}
|
||||
return count, err
|
||||
}
|
||||
|
||||
func (c *blockChainWithMetrics) GetMempoolTransactions(address string) (v []string, err error) {
|
||||
defer func(s time.Time) { c.observeRPCLatency("GetMempoolTransactions", s, err) }(time.Now())
|
||||
return c.b.GetMempoolTransactions(address)
|
||||
}
|
||||
|
||||
func (c *blockChainWithMetrics) GetMempoolTransactionsForAddrDesc(addrDesc bchain.AddressDescriptor) (v []string, err error) {
|
||||
defer func(s time.Time) { c.observeRPCLatency("GetMempoolTransactionsForAddrDesc", s, err) }(time.Now())
|
||||
return c.b.GetMempoolTransactionsForAddrDesc(addrDesc)
|
||||
}
|
||||
|
||||
func (c *blockChainWithMetrics) GetMempoolEntry(txid string) (v *bchain.MempoolEntry, err error) {
|
||||
defer func(s time.Time) { c.observeRPCLatency("GetMempoolEntry", s, err) }(time.Now())
|
||||
return c.b.GetMempoolEntry(txid)
|
||||
|
@ -239,3 +293,69 @@ func (c *blockChainWithMetrics) GetMempoolEntry(txid string) (v *bchain.MempoolE
|
|||
func (c *blockChainWithMetrics) GetChainParser() bchain.BlockChainParser {
|
||||
return c.b.GetChainParser()
|
||||
}
|
||||
|
||||
func (c *blockChainWithMetrics) EthereumTypeGetBalance(addrDesc bchain.AddressDescriptor) (v *big.Int, err error) {
|
||||
defer func(s time.Time) { c.observeRPCLatency("EthereumTypeGetBalance", s, err) }(time.Now())
|
||||
return c.b.EthereumTypeGetBalance(addrDesc)
|
||||
}
|
||||
|
||||
func (c *blockChainWithMetrics) EthereumTypeGetNonce(addrDesc bchain.AddressDescriptor) (v uint64, err error) {
|
||||
defer func(s time.Time) { c.observeRPCLatency("EthereumTypeGetNonce", s, err) }(time.Now())
|
||||
return c.b.EthereumTypeGetNonce(addrDesc)
|
||||
}
|
||||
|
||||
func (c *blockChainWithMetrics) EthereumTypeEstimateGas(params map[string]interface{}) (v uint64, err error) {
|
||||
defer func(s time.Time) { c.observeRPCLatency("EthereumTypeEstimateGas", s, err) }(time.Now())
|
||||
return c.b.EthereumTypeEstimateGas(params)
|
||||
}
|
||||
|
||||
func (c *blockChainWithMetrics) EthereumTypeGetErc20ContractInfo(contractDesc bchain.AddressDescriptor) (v *bchain.Erc20Contract, err error) {
|
||||
defer func(s time.Time) { c.observeRPCLatency("EthereumTypeGetErc20ContractInfo", s, err) }(time.Now())
|
||||
return c.b.EthereumTypeGetErc20ContractInfo(contractDesc)
|
||||
}
|
||||
|
||||
func (c *blockChainWithMetrics) EthereumTypeGetErc20ContractBalance(addrDesc, contractDesc bchain.AddressDescriptor) (v *big.Int, err error) {
|
||||
defer func(s time.Time) { c.observeRPCLatency("EthereumTypeGetErc20ContractInfo", s, err) }(time.Now())
|
||||
return c.b.EthereumTypeGetErc20ContractBalance(addrDesc, contractDesc)
|
||||
}
|
||||
|
||||
type mempoolWithMetrics struct {
|
||||
mempool bchain.Mempool
|
||||
m *common.Metrics
|
||||
}
|
||||
|
||||
func (c *mempoolWithMetrics) observeRPCLatency(method string, start time.Time, err error) {
|
||||
var e string
|
||||
if err != nil {
|
||||
e = "failure"
|
||||
}
|
||||
c.m.RPCLatency.With(common.Labels{"method": method, "error": e}).Observe(float64(time.Since(start)) / 1e6) // in milliseconds
|
||||
}
|
||||
|
||||
func (c *mempoolWithMetrics) Resync() (count int, err error) {
|
||||
defer func(s time.Time) { c.observeRPCLatency("ResyncMempool", s, err) }(time.Now())
|
||||
count, err = c.mempool.Resync()
|
||||
if err == nil {
|
||||
c.m.MempoolSize.Set(float64(count))
|
||||
}
|
||||
return count, err
|
||||
}
|
||||
|
||||
func (c *mempoolWithMetrics) GetTransactions(address string) (v []bchain.Outpoint, err error) {
|
||||
defer func(s time.Time) { c.observeRPCLatency("GetMempoolTransactions", s, err) }(time.Now())
|
||||
return c.mempool.GetTransactions(address)
|
||||
}
|
||||
|
||||
func (c *mempoolWithMetrics) GetAddrDescTransactions(addrDesc bchain.AddressDescriptor) (v []bchain.Outpoint, err error) {
|
||||
defer func(s time.Time) { c.observeRPCLatency("GetMempoolTransactionsForAddrDesc", s, err) }(time.Now())
|
||||
return c.mempool.GetAddrDescTransactions(addrDesc)
|
||||
}
|
||||
|
||||
func (c *mempoolWithMetrics) GetAllEntries() (v bchain.MempoolTxidEntries) {
|
||||
defer func(s time.Time) { c.observeRPCLatency("GetAllEntries", s, nil) }(time.Now())
|
||||
return c.mempool.GetAllEntries()
|
||||
}
|
||||
|
||||
func (c *mempoolWithMetrics) GetTransactionTime(txid string) uint32 {
|
||||
return c.mempool.GetTransactionTime(txid)
|
||||
}
|
||||
|
|
|
@ -1,28 +1,52 @@
|
|||
package btc
|
||||
|
||||
import (
|
||||
"blockbook/bchain"
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"math/big"
|
||||
"strconv"
|
||||
|
||||
vlq "github.com/bsm/go-vlq"
|
||||
"github.com/btcsuite/btcd/blockchain"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/jakm/btcutil"
|
||||
"github.com/jakm/btcutil/chaincfg"
|
||||
"github.com/jakm/btcutil/txscript"
|
||||
"github.com/juju/errors"
|
||||
"github.com/martinboehm/btcd/blockchain"
|
||||
"github.com/martinboehm/btcd/wire"
|
||||
"github.com/martinboehm/btcutil"
|
||||
"github.com/martinboehm/btcutil/chaincfg"
|
||||
"github.com/martinboehm/btcutil/hdkeychain"
|
||||
"github.com/martinboehm/btcutil/txscript"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
)
|
||||
|
||||
// temp params for signet(wait btcd commit)
|
||||
// magic numbers
|
||||
const (
|
||||
SignetMagic wire.BitcoinNet = 0x6a70c7f0
|
||||
)
|
||||
|
||||
// chain parameters
|
||||
var (
|
||||
SigNetParams chaincfg.Params
|
||||
)
|
||||
|
||||
func init() {
|
||||
SigNetParams = chaincfg.TestNet3Params
|
||||
SigNetParams.Net = SignetMagic
|
||||
}
|
||||
|
||||
// OutputScriptToAddressesFunc converts ScriptPubKey to bitcoin addresses
|
||||
type OutputScriptToAddressesFunc func(script []byte) ([]string, bool, error)
|
||||
|
||||
// BitcoinParser handle
|
||||
type BitcoinParser struct {
|
||||
*bchain.BaseParser
|
||||
Params *chaincfg.Params
|
||||
OutputScriptToAddressesFunc OutputScriptToAddressesFunc
|
||||
Params *chaincfg.Params
|
||||
OutputScriptToAddressesFunc OutputScriptToAddressesFunc
|
||||
XPubMagic uint32
|
||||
XPubMagicSegwitP2sh uint32
|
||||
XPubMagicSegwitNative uint32
|
||||
Slip44 uint32
|
||||
minimumCoinbaseConfirmations int
|
||||
}
|
||||
|
||||
// NewBitcoinParser returns new BitcoinParser instance
|
||||
|
@ -32,7 +56,12 @@ func NewBitcoinParser(params *chaincfg.Params, c *Configuration) *BitcoinParser
|
|||
BlockAddressesToKeep: c.BlockAddressesToKeep,
|
||||
AmountDecimalPoint: 8,
|
||||
},
|
||||
Params: params,
|
||||
Params: params,
|
||||
XPubMagic: c.XPubMagic,
|
||||
XPubMagicSegwitP2sh: c.XPubMagicSegwitP2sh,
|
||||
XPubMagicSegwitNative: c.XPubMagicSegwitNative,
|
||||
Slip44: c.Slip44,
|
||||
minimumCoinbaseConfirmations: c.MinimumCoinbaseConfirmations,
|
||||
}
|
||||
p.OutputScriptToAddressesFunc = p.outputScriptToAddresses
|
||||
return p
|
||||
|
@ -50,6 +79,8 @@ func GetChainParams(chain string) *chaincfg.Params {
|
|||
return &chaincfg.TestNet3Params
|
||||
case "regtest":
|
||||
return &chaincfg.RegressionNetParams
|
||||
case "signet":
|
||||
return &SigNetParams
|
||||
}
|
||||
return &chaincfg.MainNetParams
|
||||
}
|
||||
|
@ -62,7 +93,7 @@ func (p *BitcoinParser) GetAddrDescFromVout(output *bchain.Vout) (bchain.Address
|
|||
}
|
||||
// convert possible P2PK script to P2PKH
|
||||
// so that all transactions by given public key are indexed together
|
||||
return txscript.ConvertP2PKtoP2PKH(ad)
|
||||
return txscript.ConvertP2PKtoP2PKH(p.Params.Base58CksumHasher, ad)
|
||||
}
|
||||
|
||||
// GetAddrDescFromAddress returns internal address representation (descriptor) of given address
|
||||
|
@ -80,6 +111,15 @@ func (p *BitcoinParser) GetScriptFromAddrDesc(addrDesc bchain.AddressDescriptor)
|
|||
return addrDesc, nil
|
||||
}
|
||||
|
||||
// IsAddrDescIndexable returns true if AddressDescriptor should be added to index
|
||||
// empty or OP_RETURN scripts are not indexed
|
||||
func (p *BitcoinParser) IsAddrDescIndexable(addrDesc bchain.AddressDescriptor) bool {
|
||||
if len(addrDesc) == 0 || addrDesc[0] == txscript.OP_RETURN {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// addressToOutputScript converts bitcoin address to ScriptPubKey
|
||||
func (p *BitcoinParser) addressToOutputScript(address string) ([]byte, error) {
|
||||
da, err := btcutil.DecodeAddress(address, p.Params)
|
||||
|
@ -94,7 +134,7 @@ func (p *BitcoinParser) addressToOutputScript(address string) ([]byte, error) {
|
|||
}
|
||||
|
||||
// TryParseOPReturn tries to process OP_RETURN script and return its string representation
|
||||
func TryParseOPReturn(script []byte) string {
|
||||
func (p *BitcoinParser) TryParseOPReturn(script []byte) string {
|
||||
if len(script) > 1 && script[0] == txscript.OP_RETURN {
|
||||
// trying 2 variants of OP_RETURN data
|
||||
// 1) OP_RETURN OP_PUSHDATA1 <datalen> <data>
|
||||
|
@ -113,6 +153,13 @@ func TryParseOPReturn(script []byte) string {
|
|||
data = script[2:]
|
||||
}
|
||||
if l == len(data) {
|
||||
var ed string
|
||||
|
||||
ed = p.tryParseOmni(data)
|
||||
if ed != "" {
|
||||
return ed
|
||||
}
|
||||
|
||||
isASCII := true
|
||||
for _, c := range data {
|
||||
if c < 32 || c > 127 {
|
||||
|
@ -120,7 +167,6 @@ func TryParseOPReturn(script []byte) string {
|
|||
break
|
||||
}
|
||||
}
|
||||
var ed string
|
||||
if isASCII {
|
||||
ed = "(" + string(data) + ")"
|
||||
} else {
|
||||
|
@ -132,6 +178,39 @@ func TryParseOPReturn(script []byte) string {
|
|||
return ""
|
||||
}
|
||||
|
||||
var omniCurrencyMap = map[uint32]string{
|
||||
1: "Omni",
|
||||
2: "Test Omni",
|
||||
31: "TetherUS",
|
||||
}
|
||||
|
||||
// tryParseOmni tries to extract Omni simple send transaction from script
|
||||
func (p *BitcoinParser) tryParseOmni(data []byte) string {
|
||||
|
||||
// currently only simple send transaction version 0 is supported, see
|
||||
// https://github.com/OmniLayer/spec#transfer-coins-simple-send
|
||||
if len(data) != 20 || data[0] != 'o' {
|
||||
return ""
|
||||
}
|
||||
// omni (4) <tx_version> (2) <tx_type> (2)
|
||||
omniHeader := []byte{'o', 'm', 'n', 'i', 0, 0, 0, 0}
|
||||
if bytes.Compare(data[0:8], omniHeader) != 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
currencyID := binary.BigEndian.Uint32(data[8:12])
|
||||
currency, ok := omniCurrencyMap[currencyID]
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
amount := new(big.Int)
|
||||
amount.SetBytes(data[12:])
|
||||
amountStr := p.AmountToDecimalString(amount)
|
||||
|
||||
ed := "OMNI Simple Send: " + amountStr + " " + currency + " (#" + strconv.Itoa(int(currencyID)) + ")"
|
||||
return ed
|
||||
}
|
||||
|
||||
// outputScriptToAddresses converts ScriptPubKey to addresses with a flag that the addresses are searchable
|
||||
func (p *BitcoinParser) outputScriptToAddresses(script []byte) ([]string, bool, error) {
|
||||
sc, addresses, _, err := txscript.ExtractPkScriptAddrs(script, p.Params)
|
||||
|
@ -146,7 +225,7 @@ func (p *BitcoinParser) outputScriptToAddresses(script []byte) ([]string, bool,
|
|||
if sc == txscript.PubKeyHashTy || sc == txscript.WitnessV0PubKeyHashTy || sc == txscript.ScriptHashTy || sc == txscript.WitnessV0ScriptHashTy {
|
||||
s = true
|
||||
} else if len(rv) == 0 {
|
||||
or := TryParseOPReturn(script)
|
||||
or := p.TryParseOPReturn(script)
|
||||
if or != "" {
|
||||
rv = []string{or}
|
||||
}
|
||||
|
@ -154,6 +233,7 @@ func (p *BitcoinParser) outputScriptToAddresses(script []byte) ([]string, bool,
|
|||
return rv, s, nil
|
||||
}
|
||||
|
||||
// TxFromMsgTx converts bitcoin wire Tx to bchain.Tx
|
||||
func (p *BitcoinParser) TxFromMsgTx(t *wire.MsgTx, parseAddresses bool) bchain.Tx {
|
||||
vin := make([]bchain.Vin, len(t.TxIn))
|
||||
for i, in := range t.TxIn {
|
||||
|
@ -265,3 +345,109 @@ func (p *BitcoinParser) UnpackTx(buf []byte) (*bchain.Tx, uint32, error) {
|
|||
|
||||
return tx, height, nil
|
||||
}
|
||||
|
||||
// MinimumCoinbaseConfirmations returns minimum number of confirmations a coinbase transaction must have before it can be spent
|
||||
func (p *BitcoinParser) MinimumCoinbaseConfirmations() int {
|
||||
return p.minimumCoinbaseConfirmations
|
||||
}
|
||||
|
||||
func (p *BitcoinParser) addrDescFromExtKey(extKey *hdkeychain.ExtendedKey) (bchain.AddressDescriptor, error) {
|
||||
var a btcutil.Address
|
||||
var err error
|
||||
if extKey.Version() == p.XPubMagicSegwitP2sh {
|
||||
// redeemScript <witness version: OP_0><len pubKeyHash: 20><20-byte-pubKeyHash>
|
||||
pubKeyHash := btcutil.Hash160(extKey.PubKeyBytes())
|
||||
redeemScript := make([]byte, len(pubKeyHash)+2)
|
||||
redeemScript[0] = 0
|
||||
redeemScript[1] = byte(len(pubKeyHash))
|
||||
copy(redeemScript[2:], pubKeyHash)
|
||||
hash := btcutil.Hash160(redeemScript)
|
||||
a, err = btcutil.NewAddressScriptHashFromHash(hash, p.Params)
|
||||
} else if extKey.Version() == p.XPubMagicSegwitNative {
|
||||
a, err = btcutil.NewAddressWitnessPubKeyHash(btcutil.Hash160(extKey.PubKeyBytes()), p.Params)
|
||||
} else {
|
||||
// default to P2PKH address
|
||||
a, err = extKey.Address(p.Params)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return txscript.PayToAddrScript(a)
|
||||
}
|
||||
|
||||
// DeriveAddressDescriptors derives address descriptors from given xpub for listed indexes
|
||||
func (p *BitcoinParser) DeriveAddressDescriptors(xpub string, change uint32, indexes []uint32) ([]bchain.AddressDescriptor, error) {
|
||||
extKey, err := hdkeychain.NewKeyFromString(xpub, p.Params.Base58CksumHasher)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
changeExtKey, err := extKey.Child(change)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ad := make([]bchain.AddressDescriptor, len(indexes))
|
||||
for i, index := range indexes {
|
||||
indexExtKey, err := changeExtKey.Child(index)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ad[i], err = p.addrDescFromExtKey(indexExtKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return ad, nil
|
||||
}
|
||||
|
||||
// DeriveAddressDescriptorsFromTo derives address descriptors from given xpub for addresses in index range
|
||||
func (p *BitcoinParser) DeriveAddressDescriptorsFromTo(xpub string, change uint32, fromIndex uint32, toIndex uint32) ([]bchain.AddressDescriptor, error) {
|
||||
if toIndex <= fromIndex {
|
||||
return nil, errors.New("toIndex<=fromIndex")
|
||||
}
|
||||
extKey, err := hdkeychain.NewKeyFromString(xpub, p.Params.Base58CksumHasher)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
changeExtKey, err := extKey.Child(change)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ad := make([]bchain.AddressDescriptor, toIndex-fromIndex)
|
||||
for index := fromIndex; index < toIndex; index++ {
|
||||
indexExtKey, err := changeExtKey.Child(index)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ad[index-fromIndex], err = p.addrDescFromExtKey(indexExtKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return ad, nil
|
||||
}
|
||||
|
||||
// DerivationBasePath returns base path of xpub
|
||||
func (p *BitcoinParser) DerivationBasePath(xpub string) (string, error) {
|
||||
extKey, err := hdkeychain.NewKeyFromString(xpub, p.Params.Base58CksumHasher)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var c, bip string
|
||||
cn := extKey.ChildNum()
|
||||
if cn >= 0x80000000 {
|
||||
cn -= 0x80000000
|
||||
c = "'"
|
||||
}
|
||||
c = strconv.Itoa(int(cn)) + c
|
||||
if extKey.Depth() != 3 {
|
||||
return "unknown/" + c, nil
|
||||
}
|
||||
if extKey.Version() == p.XPubMagicSegwitP2sh {
|
||||
bip = "49"
|
||||
} else if extKey.Version() == p.XPubMagicSegwitNative {
|
||||
bip = "84"
|
||||
} else {
|
||||
bip = "44"
|
||||
}
|
||||
return "m/" + bip + "'/" + strconv.Itoa(int(p.Slip44)) + "'/" + c, nil
|
||||
}
|
||||
|
|
|
@ -3,14 +3,14 @@
|
|||
package btc
|
||||
|
||||
import (
|
||||
"blockbook/bchain"
|
||||
"encoding/hex"
|
||||
"math/big"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/jakm/btcutil/chaincfg"
|
||||
"github.com/martinboehm/btcutil/chaincfg"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
|
@ -19,7 +19,7 @@ func TestMain(m *testing.M) {
|
|||
os.Exit(c)
|
||||
}
|
||||
|
||||
func Test_GetAddrDescFromAddress(t *testing.T) {
|
||||
func TestGetAddrDescFromAddress(t *testing.T) {
|
||||
type args struct {
|
||||
address string
|
||||
}
|
||||
|
@ -77,7 +77,7 @@ func Test_GetAddrDescFromAddress(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func Test_GetAddrDescFromVout(t *testing.T) {
|
||||
func TestGetAddrDescFromVout(t *testing.T) {
|
||||
type args struct {
|
||||
vout bchain.Vout
|
||||
}
|
||||
|
@ -141,7 +141,7 @@ func Test_GetAddrDescFromVout(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func Test_GetAddressesFromAddrDesc(t *testing.T) {
|
||||
func TestGetAddressesFromAddrDesc(t *testing.T) {
|
||||
type args struct {
|
||||
script string
|
||||
}
|
||||
|
@ -215,6 +215,27 @@ func Test_GetAddressesFromAddrDesc(t *testing.T) {
|
|||
want2: false,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "OP_RETURN omni simple send tether",
|
||||
args: args{script: "6a146f6d6e69000000000000001f00000709bb647351"},
|
||||
want: []string{"OMNI Simple Send: 77383.80022609 TetherUS (#31)"},
|
||||
want2: false,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "OP_RETURN omni simple send not supported coin",
|
||||
args: args{script: "6a146f6d6e69000000000000000300000709bb647351"},
|
||||
want: []string{"OP_RETURN 6f6d6e69000000000000000300000709bb647351"},
|
||||
want2: false,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "OP_RETURN omni not supported version",
|
||||
args: args{script: "6a146f6d6e69010000000000000300000709bb647351"},
|
||||
want: []string{"OP_RETURN 6f6d6e69010000000000000300000709bb647351"},
|
||||
want2: false,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
parser := NewBitcoinParser(GetChainParams("main"), &Configuration{})
|
||||
|
@ -238,10 +259,11 @@ func Test_GetAddressesFromAddrDesc(t *testing.T) {
|
|||
}
|
||||
|
||||
var (
|
||||
testTx1, testTx2 bchain.Tx
|
||||
testTx1, testTx2, testTx3 bchain.Tx
|
||||
|
||||
testTxPacked1 = "0001e2408ba8d7af5401000000017f9a22c9cbf54bd902400df746f138f37bcf5b4d93eb755820e974ba43ed5f42040000006a4730440220037f4ed5427cde81d55b9b6a2fd08c8a25090c2c2fff3a75c1a57625ca8a7118022076c702fe55969fa08137f71afd4851c48e31082dd3c40c919c92cdbc826758d30121029f6da5623c9f9b68a9baf9c1bc7511df88fa34c6c2f71f7c62f2f03ff48dca80feffffff019c9700000000000017a9146144d57c8aff48492c9dfb914e120b20bad72d6f8773d00700"
|
||||
testTxPacked2 = "0007c91a899ab7da6a010000000001019d64f0c72a0d206001decbffaa722eb1044534c74eee7a5df8318e42a4323ec10000000017160014550da1f5d25a9dae2eafd6902b4194c4c6500af6ffffffff02809698000000000017a914cd668d781ece600efa4b2404dc91fd26b8b8aed8870553d7360000000017a914246655bdbd54c7e477d0ea2375e86e0db2b8f80a8702473044022076aba4ad559616905fa51d4ddd357fc1fdb428d40cb388e042cdd1da4a1b7357022011916f90c712ead9a66d5f058252efd280439ad8956a967e95d437d246710bc9012102a80a5964c5612bb769ef73147b2cf3c149bc0fd4ecb02f8097629c94ab013ffd00000000"
|
||||
testTxPacked3 = "00003d818bfda9aa3e02000000000102deb1999a857ab0a13d6b12fbd95ea75b409edde5f2ff747507ce42d9986a8b9d0000000000fdffffff9fd2d3361e203b2375eba6438efbef5b3075531e7e583c7cc76b7294fe7f22980000000000fdffffff02a0860100000000001600148091746745464e7555c31e9a5afceac14a02978ae7fc1c0000000000160014565ea9ff4589d3e05ba149ae6e257752bfdc2a1e0247304402207d67d320a8e813f986b35e9791935fcb736754812b7038686f5de6cfdcda99cd02201c3bb2c178e0056016437ecfe365a7eef84aa9d293ebdc566177af82e22fcdd3012103abb30c1bbe878b07b58dc169b1d061d48c60be8107f632a59778b38bf7ceea5a02473044022044f54a478cfe086e870cb026c9dcd4e14e63778bef569a4d55a6332725cd9a9802202f0e94c04e6f328fc64ad9efe552888c299750d1b8d033324825a3ff29920e030121036fcd433428aa7dc65c4f5408fa31f208c54fe4b4c6c1ae9c39a825ed4f1ac039813d0000"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
@ -314,9 +336,57 @@ func init() {
|
|||
},
|
||||
},
|
||||
}
|
||||
|
||||
testTx3 = bchain.Tx{
|
||||
Hex: "02000000000102deb1999a857ab0a13d6b12fbd95ea75b409edde5f2ff747507ce42d9986a8b9d0000000000fdffffff9fd2d3361e203b2375eba6438efbef5b3075531e7e583c7cc76b7294fe7f22980000000000fdffffff02a0860100000000001600148091746745464e7555c31e9a5afceac14a02978ae7fc1c0000000000160014565ea9ff4589d3e05ba149ae6e257752bfdc2a1e0247304402207d67d320a8e813f986b35e9791935fcb736754812b7038686f5de6cfdcda99cd02201c3bb2c178e0056016437ecfe365a7eef84aa9d293ebdc566177af82e22fcdd3012103abb30c1bbe878b07b58dc169b1d061d48c60be8107f632a59778b38bf7ceea5a02473044022044f54a478cfe086e870cb026c9dcd4e14e63778bef569a4d55a6332725cd9a9802202f0e94c04e6f328fc64ad9efe552888c299750d1b8d033324825a3ff29920e030121036fcd433428aa7dc65c4f5408fa31f208c54fe4b4c6c1ae9c39a825ed4f1ac039813d0000",
|
||||
Blocktime: 1607805599,
|
||||
Txid: "24551a58a1d1fb89d7052e2bbac7cb69a7825ee1e39439befbec8c32148cf735",
|
||||
LockTime: 15745,
|
||||
Version: 2,
|
||||
Vin: []bchain.Vin{
|
||||
{
|
||||
ScriptSig: bchain.ScriptSig{
|
||||
Hex: "",
|
||||
},
|
||||
Txid: "9d8b6a98d942ce077574fff2e5dd9e405ba75ed9fb126b3da1b07a859a99b1de",
|
||||
Vout: 0,
|
||||
Sequence: 4294967293,
|
||||
},
|
||||
{
|
||||
ScriptSig: bchain.ScriptSig{
|
||||
Hex: "",
|
||||
},
|
||||
Txid: "98227ffe94726bc77c3c587e1e5375305beffb8e43a6eb75233b201e36d3d29f",
|
||||
Vout: 0,
|
||||
Sequence: 4294967293,
|
||||
},
|
||||
},
|
||||
Vout: []bchain.Vout{
|
||||
{
|
||||
ValueSat: *big.NewInt(100000),
|
||||
N: 0,
|
||||
ScriptPubKey: bchain.ScriptPubKey{
|
||||
Hex: "00148091746745464e7555c31e9a5afceac14a02978a",
|
||||
Addresses: []string{
|
||||
"tb1qszghge69ge8824wrr6d94l82c99q99u2ccgv5w",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ValueSat: *big.NewInt(1899751),
|
||||
N: 1,
|
||||
ScriptPubKey: bchain.ScriptPubKey{
|
||||
Hex: "0014565ea9ff4589d3e05ba149ae6e257752bfdc2a1e",
|
||||
Addresses: []string{
|
||||
"tb1q2e02nl6938f7qkapfxhxufth22lac2s792vsxp",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func Test_PackTx(t *testing.T) {
|
||||
func TestPackTx(t *testing.T) {
|
||||
type args struct {
|
||||
tx bchain.Tx
|
||||
height uint32
|
||||
|
@ -351,6 +421,17 @@ func Test_PackTx(t *testing.T) {
|
|||
want: testTxPacked2,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "signet-1",
|
||||
args: args{
|
||||
tx: testTx3,
|
||||
height: 15745,
|
||||
blockTime: 1607805599,
|
||||
parser: NewBitcoinParser(GetChainParams("signet"), &Configuration{}),
|
||||
},
|
||||
want: testTxPacked3,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
@ -367,7 +448,7 @@ func Test_PackTx(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func Test_UnpackTx(t *testing.T) {
|
||||
func TestUnpackTx(t *testing.T) {
|
||||
type args struct {
|
||||
packedTx string
|
||||
parser *BitcoinParser
|
||||
|
@ -399,6 +480,16 @@ func Test_UnpackTx(t *testing.T) {
|
|||
want1: 510234,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "signet-1",
|
||||
args: args{
|
||||
packedTx: testTxPacked3,
|
||||
parser: NewBitcoinParser(GetChainParams("signet"), &Configuration{}),
|
||||
},
|
||||
want: &testTx3,
|
||||
want1: 15745,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
@ -417,3 +508,245 @@ func Test_UnpackTx(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeriveAddressDescriptors(t *testing.T) {
|
||||
btcMainParser := NewBitcoinParser(GetChainParams("main"), &Configuration{XPubMagic: 76067358, XPubMagicSegwitP2sh: 77429938, XPubMagicSegwitNative: 78792518})
|
||||
type args struct {
|
||||
xpub string
|
||||
change uint32
|
||||
indexes []uint32
|
||||
parser *BitcoinParser
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "m/44'/0'/0'",
|
||||
args: args{
|
||||
xpub: "xpub6BosfCnifzxcFwrSzQiqu2DBVTshkCXacvNsWGYJVVhhawA7d4R5WSWGFNbi8Aw6ZRc1brxMyWMzG3DSSSSoekkudhUd9yLb6qx39T9nMdj",
|
||||
change: 0,
|
||||
indexes: []uint32{0, 1234},
|
||||
parser: btcMainParser,
|
||||
},
|
||||
want: []string{"1LqBGSKuX5yYUonjxT5qGfpUsXKYYWeabA", "1P9w11dXAmG3QBjKLAvCsek8izs1iR2iFi"},
|
||||
},
|
||||
{
|
||||
name: "m/49'/0'/0'",
|
||||
args: args{
|
||||
xpub: "ypub6Ww3ibxVfGzLrAH1PNcjyAWenMTbbAosGNB6VvmSEgytSER9azLDWCxoJwW7Ke7icmizBMXrzBx9979FfaHxHcrArf3zbeJJJUZPf663zsP",
|
||||
change: 0,
|
||||
indexes: []uint32{0, 1234},
|
||||
parser: btcMainParser,
|
||||
},
|
||||
want: []string{"37VucYSaXLCAsxYyAPfbSi9eh4iEcbShgf", "367meFzJ9KqDLm9PX6U8Z8RdmkSNBuxX8T"},
|
||||
},
|
||||
{
|
||||
name: "m/84'/0'/0'",
|
||||
args: args{
|
||||
xpub: "zpub6rFR7y4Q2AijBEqTUquhVz398htDFrtymD9xYYfG1m4wAcvPhXNfE3EfH1r1ADqtfSdVCToUG868RvUUkgDKf31mGDtKsAYz2oz2AGutZYs",
|
||||
change: 0,
|
||||
indexes: []uint32{0, 1234},
|
||||
parser: btcMainParser,
|
||||
},
|
||||
want: []string{"bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu", "bc1q4nm6g46ujzyjaeusralaz2nfv2rf04jjfyamkw"},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := tt.args.parser.DeriveAddressDescriptors(tt.args.xpub, tt.args.change, tt.args.indexes)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("DeriveAddressDescriptorsFromTo() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
gotAddresses := make([]string, len(got))
|
||||
for i, ad := range got {
|
||||
aa, _, err := tt.args.parser.GetAddressesFromAddrDesc(ad)
|
||||
if err != nil || len(aa) != 1 {
|
||||
t.Errorf("DeriveAddressDescriptorsFromTo() got incorrect address descriptor %v, error %v", ad, err)
|
||||
return
|
||||
}
|
||||
gotAddresses[i] = aa[0]
|
||||
}
|
||||
if !reflect.DeepEqual(gotAddresses, tt.want) {
|
||||
t.Errorf("DeriveAddressDescriptorsFromTo() = %v, want %v", gotAddresses, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeriveAddressDescriptorsFromTo(t *testing.T) {
|
||||
btcMainParser := NewBitcoinParser(GetChainParams("main"), &Configuration{XPubMagic: 76067358, XPubMagicSegwitP2sh: 77429938, XPubMagicSegwitNative: 78792518})
|
||||
btcTestnetsParser := NewBitcoinParser(GetChainParams("test"), &Configuration{XPubMagic: 70617039, XPubMagicSegwitP2sh: 71979618, XPubMagicSegwitNative: 73342198})
|
||||
type args struct {
|
||||
xpub string
|
||||
change uint32
|
||||
fromIndex uint32
|
||||
toIndex uint32
|
||||
parser *BitcoinParser
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "m/44'/0'/0'",
|
||||
args: args{
|
||||
xpub: "xpub6BosfCnifzxcFwrSzQiqu2DBVTshkCXacvNsWGYJVVhhawA7d4R5WSWGFNbi8Aw6ZRc1brxMyWMzG3DSSSSoekkudhUd9yLb6qx39T9nMdj",
|
||||
change: 0,
|
||||
fromIndex: 0,
|
||||
toIndex: 1,
|
||||
parser: btcMainParser,
|
||||
},
|
||||
want: []string{"1LqBGSKuX5yYUonjxT5qGfpUsXKYYWeabA"},
|
||||
},
|
||||
{
|
||||
name: "m/49'/0'/0'",
|
||||
args: args{
|
||||
xpub: "ypub6Ww3ibxVfGzLrAH1PNcjyAWenMTbbAosGNB6VvmSEgytSER9azLDWCxoJwW7Ke7icmizBMXrzBx9979FfaHxHcrArf3zbeJJJUZPf663zsP",
|
||||
change: 0,
|
||||
fromIndex: 0,
|
||||
toIndex: 1,
|
||||
parser: btcMainParser,
|
||||
},
|
||||
want: []string{"37VucYSaXLCAsxYyAPfbSi9eh4iEcbShgf"},
|
||||
},
|
||||
{
|
||||
name: "m/84'/0'/0'",
|
||||
args: args{
|
||||
xpub: "zpub6rFR7y4Q2AijBEqTUquhVz398htDFrtymD9xYYfG1m4wAcvPhXNfE3EfH1r1ADqtfSdVCToUG868RvUUkgDKf31mGDtKsAYz2oz2AGutZYs",
|
||||
change: 0,
|
||||
fromIndex: 0,
|
||||
toIndex: 1,
|
||||
parser: btcMainParser,
|
||||
},
|
||||
want: []string{"bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu"},
|
||||
},
|
||||
{
|
||||
name: "m/49'/1'/0'",
|
||||
args: args{
|
||||
xpub: "upub5DR1Mg5nykixzYjFXWW5GghAU7dDqoPVJ2jrqFbL8sJ7Hs7jn69MP7KBnnmxn88GeZtnH8PRKV9w5MMSFX8AdEAoXY8Qd8BJPoXtpMeHMxJ",
|
||||
change: 0,
|
||||
fromIndex: 0,
|
||||
toIndex: 10,
|
||||
parser: btcTestnetsParser,
|
||||
},
|
||||
want: []string{"2N4Q5FhU2497BryFfUgbqkAJE87aKHUhXMp", "2Mt7P2BAfE922zmfXrdcYTLyR7GUvbwSEns", "2N6aUMgQk8y1zvoq6FeWFyotyj75WY9BGsu", "2NA7tbZWM9BcRwBuebKSQe2xbhhF1paJwBM", "2N8RZMzvrUUnpLmvACX9ysmJ2MX3GK5jcQM", "2MvUUSiQZDSqyeSdofKX9KrSCio1nANPDTe", "2NBXaWu1HazjoUVgrXgcKNoBLhtkkD9Gmet", "2N791Ttf89tMVw2maj86E1Y3VgxD9Mc7PU7", "2NCJmwEq8GJm8t8GWWyBXAfpw7F2qZEVP5Y", "2NEgW71hWKer2XCSA8ZCC2VnWpB77L6bk68"},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := tt.args.parser.DeriveAddressDescriptorsFromTo(tt.args.xpub, tt.args.change, tt.args.fromIndex, tt.args.toIndex)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("DeriveAddressDescriptorsFromTo() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
gotAddresses := make([]string, len(got))
|
||||
for i, ad := range got {
|
||||
aa, _, err := tt.args.parser.GetAddressesFromAddrDesc(ad)
|
||||
if err != nil || len(aa) != 1 {
|
||||
t.Errorf("DeriveAddressDescriptorsFromTo() got incorrect address descriptor %v, error %v", ad, err)
|
||||
return
|
||||
}
|
||||
gotAddresses[i] = aa[0]
|
||||
}
|
||||
if !reflect.DeepEqual(gotAddresses, tt.want) {
|
||||
t.Errorf("DeriveAddressDescriptorsFromTo() = %v, want %v", gotAddresses, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDeriveAddressDescriptorsFromToXpub(b *testing.B) {
|
||||
btcMainParser := NewBitcoinParser(GetChainParams("main"), &Configuration{XPubMagic: 76067358, XPubMagicSegwitP2sh: 77429938, XPubMagicSegwitNative: 78792518})
|
||||
for i := 0; i < b.N; i++ {
|
||||
btcMainParser.DeriveAddressDescriptorsFromTo("xpub6BosfCnifzxcFwrSzQiqu2DBVTshkCXacvNsWGYJVVhhawA7d4R5WSWGFNbi8Aw6ZRc1brxMyWMzG3DSSSSoekkudhUd9yLb6qx39T9nMdj", 1, 0, 100)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDeriveAddressDescriptorsFromToYpub(b *testing.B) {
|
||||
btcMainParser := NewBitcoinParser(GetChainParams("main"), &Configuration{XPubMagic: 76067358, XPubMagicSegwitP2sh: 77429938, XPubMagicSegwitNative: 78792518})
|
||||
for i := 0; i < b.N; i++ {
|
||||
btcMainParser.DeriveAddressDescriptorsFromTo("ypub6Ww3ibxVfGzLrAH1PNcjyAWenMTbbAosGNB6VvmSEgytSER9azLDWCxoJwW7Ke7icmizBMXrzBx9979FfaHxHcrArf3zbeJJJUZPf663zsP", 1, 0, 100)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDeriveAddressDescriptorsFromToZpub(b *testing.B) {
|
||||
btcMainParser := NewBitcoinParser(GetChainParams("main"), &Configuration{XPubMagic: 76067358, XPubMagicSegwitP2sh: 77429938, XPubMagicSegwitNative: 78792518})
|
||||
for i := 0; i < b.N; i++ {
|
||||
btcMainParser.DeriveAddressDescriptorsFromTo("zpub6rFR7y4Q2AijBEqTUquhVz398htDFrtymD9xYYfG1m4wAcvPhXNfE3EfH1r1ADqtfSdVCToUG868RvUUkgDKf31mGDtKsAYz2oz2AGutZYs", 1, 0, 100)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBitcoinParser_DerivationBasePath(t *testing.T) {
|
||||
btcMainParser := NewBitcoinParser(GetChainParams("main"), &Configuration{XPubMagic: 76067358, XPubMagicSegwitP2sh: 77429938, XPubMagicSegwitNative: 78792518, Slip44: 0})
|
||||
btcTestnetsParser := NewBitcoinParser(GetChainParams("test"), &Configuration{XPubMagic: 70617039, XPubMagicSegwitP2sh: 71979618, XPubMagicSegwitNative: 73342198, Slip44: 1})
|
||||
zecMainParser := NewBitcoinParser(GetChainParams("main"), &Configuration{XPubMagic: 76067358, Slip44: 133})
|
||||
type args struct {
|
||||
xpub string
|
||||
parser *BitcoinParser
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "m/84'/0'/0'",
|
||||
args: args{
|
||||
xpub: "zpub6rFR7y4Q2AijBEqTUquhVz398htDFrtymD9xYYfG1m4wAcvPhXNfE3EfH1r1ADqtfSdVCToUG868RvUUkgDKf31mGDtKsAYz2oz2AGutZYs",
|
||||
parser: btcMainParser,
|
||||
},
|
||||
want: "m/84'/0'/0'",
|
||||
},
|
||||
{
|
||||
name: "m/49'/0'/55 - not hardened account",
|
||||
args: args{
|
||||
xpub: "ypub6XKbB5DJRAbW4TRJLp4uXQXG3ob5BtByXsNZFBjq9qcbzrczjVXfCz5cEo1SFDexmeWRnbCMDaRgaW4m9d2nBaa8FvUQCu3n9G1UBR8WhbT",
|
||||
parser: btcMainParser,
|
||||
},
|
||||
want: "m/49'/0'/55",
|
||||
},
|
||||
{
|
||||
name: "m/49'/0' - incomplete path, without account",
|
||||
args: args{
|
||||
xpub: "ypub6UzM8PUqxcSoqC9gumfoiFhE8Qt84HbGpCD4eVJfJAojXTVtBxeddvTWJGJhGoaVBNJLmEgMdLXHgaLVJa4xEvk2tcokkdZhFdkxMLUE9sB",
|
||||
parser: btcMainParser,
|
||||
},
|
||||
want: "unknown/0'",
|
||||
},
|
||||
{
|
||||
name: "m/49'/1'/0'",
|
||||
args: args{
|
||||
xpub: "upub5DR1Mg5nykixzYjFXWW5GghAU7dDqoPVJ2jrqFbL8sJ7Hs7jn69MP7KBnnmxn88GeZtnH8PRKV9w5MMSFX8AdEAoXY8Qd8BJPoXtpMeHMxJ",
|
||||
parser: btcTestnetsParser,
|
||||
},
|
||||
want: "m/49'/1'/0'",
|
||||
},
|
||||
{
|
||||
name: "m/44'/133'/12'",
|
||||
args: args{
|
||||
xpub: "xpub6CQdEahwhKRTLYpP6cyb7ZaGb3r4tVdyPX6dC1PfrNuByrCkWDgUkmpD28UdV9QccKgY1ZiAbGv1Fakcg2LxdFVSTNKHcjdRjqhjPK8Trkb",
|
||||
parser: zecMainParser,
|
||||
},
|
||||
want: "m/44'/133'/12'",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := tt.args.parser.DerivationBasePath(tt.args.xpub)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("BitcoinParser.DerivationBasePath() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("BitcoinParser.DerivationBasePath() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package btc
|
||||
|
||||
import (
|
||||
"blockbook/bchain"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
|
@ -11,24 +10,24 @@ import (
|
|||
"math/big"
|
||||
"net"
|
||||
"net/http"
|
||||
"runtime/debug"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/juju/errors"
|
||||
"github.com/martinboehm/btcd/wire"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
"spacecruft.org/spacecruft/blockbook/common"
|
||||
)
|
||||
|
||||
// BitcoinRPC is an interface to JSON-RPC bitcoind service.
|
||||
type BitcoinRPC struct {
|
||||
*bchain.BaseChain
|
||||
client http.Client
|
||||
rpcURL string
|
||||
user string
|
||||
password string
|
||||
Parser bchain.BlockChainParser
|
||||
Testnet bool
|
||||
Network string
|
||||
Mempool *bchain.UTXOMempool
|
||||
Mempool *bchain.MempoolBitcoinType
|
||||
ParseBlocks bool
|
||||
pushHandler func(bchain.NotificationType)
|
||||
mq *bchain.MQ
|
||||
|
@ -36,22 +35,30 @@ type BitcoinRPC struct {
|
|||
RPCMarshaler RPCMarshaler
|
||||
}
|
||||
|
||||
// Configuration represents json config file
|
||||
type Configuration struct {
|
||||
CoinName string `json:"coin_name"`
|
||||
CoinShortcut string `json:"coin_shortcut"`
|
||||
RPCURL string `json:"rpc_url"`
|
||||
RPCUser string `json:"rpc_user"`
|
||||
RPCPass string `json:"rpc_pass"`
|
||||
RPCTimeout int `json:"rpc_timeout"`
|
||||
Parse bool `json:"parse"`
|
||||
MessageQueueBinding string `json:"message_queue_binding"`
|
||||
Subversion string `json:"subversion"`
|
||||
BlockAddressesToKeep int `json:"block_addresses_to_keep"`
|
||||
MempoolWorkers int `json:"mempool_workers"`
|
||||
MempoolSubWorkers int `json:"mempool_sub_workers"`
|
||||
AddressFormat string `json:"address_format"`
|
||||
SupportsEstimateFee bool `json:"supports_estimate_fee"`
|
||||
SupportsEstimateSmartFee bool `json:"supports_estimate_smart_fee"`
|
||||
CoinName string `json:"coin_name"`
|
||||
CoinShortcut string `json:"coin_shortcut"`
|
||||
RPCURL string `json:"rpc_url"`
|
||||
RPCUser string `json:"rpc_user"`
|
||||
RPCPass string `json:"rpc_pass"`
|
||||
RPCTimeout int `json:"rpc_timeout"`
|
||||
Parse bool `json:"parse"`
|
||||
MessageQueueBinding string `json:"message_queue_binding"`
|
||||
Subversion string `json:"subversion"`
|
||||
BlockAddressesToKeep int `json:"block_addresses_to_keep"`
|
||||
MempoolWorkers int `json:"mempool_workers"`
|
||||
MempoolSubWorkers int `json:"mempool_sub_workers"`
|
||||
AddressFormat string `json:"address_format"`
|
||||
SupportsEstimateFee bool `json:"supports_estimate_fee"`
|
||||
SupportsEstimateSmartFee bool `json:"supports_estimate_smart_fee"`
|
||||
XPubMagic uint32 `json:"xpub_magic,omitempty"`
|
||||
XPubMagicSegwitP2sh uint32 `json:"xpub_magic_segwit_p2sh,omitempty"`
|
||||
XPubMagicSegwitNative uint32 `json:"xpub_magic_segwit_native,omitempty"`
|
||||
Slip44 uint32 `json:"slip44,omitempty"`
|
||||
AlternativeEstimateFee string `json:"alternative_estimate_fee,omitempty"`
|
||||
AlternativeEstimateFeeParams string `json:"alternative_estimate_fee_params,omitempty"`
|
||||
MinimumCoinbaseConfirmations int `json:"minimumCoinbaseConfirmations,omitempty"`
|
||||
}
|
||||
|
||||
// NewBitcoinRPC returns new BitcoinRPC instance.
|
||||
|
@ -66,6 +73,10 @@ func NewBitcoinRPC(config json.RawMessage, pushHandler func(bchain.NotificationT
|
|||
if c.BlockAddressesToKeep < 100 {
|
||||
c.BlockAddressesToKeep = 100
|
||||
}
|
||||
// default MinimumCoinbaseConfirmations is 100
|
||||
if c.MinimumCoinbaseConfirmations == 0 {
|
||||
c.MinimumCoinbaseConfirmations = 100
|
||||
}
|
||||
// at least 1 mempool worker/subworker for synchronous mempool synchronization
|
||||
if c.MempoolWorkers < 1 {
|
||||
c.MempoolWorkers = 1
|
||||
|
@ -84,6 +95,7 @@ func NewBitcoinRPC(config json.RawMessage, pushHandler func(bchain.NotificationT
|
|||
}
|
||||
|
||||
s := &BitcoinRPC{
|
||||
BaseChain: &bchain.BaseChain{},
|
||||
client: http.Client{Timeout: time.Duration(c.RPCTimeout) * time.Second, Transport: transport},
|
||||
rpcURL: c.RPCURL,
|
||||
user: c.RPCUser,
|
||||
|
@ -97,37 +109,15 @@ func NewBitcoinRPC(config json.RawMessage, pushHandler func(bchain.NotificationT
|
|||
return s, nil
|
||||
}
|
||||
|
||||
// GetChainInfoAndInitializeMempool is called by Initialize and reused by other coins
|
||||
// it contacts the blockchain rpc interface for the first time
|
||||
// and if successful it connects to ZeroMQ and creates mempool handler
|
||||
func (b *BitcoinRPC) GetChainInfoAndInitializeMempool(bc bchain.BlockChain) (string, error) {
|
||||
// try to connect to block chain and get some info
|
||||
ci, err := bc.GetChainInfo()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
chainName := ci.Chain
|
||||
|
||||
mq, err := bchain.NewMQ(b.ChainConfig.MessageQueueBinding, b.pushHandler)
|
||||
if err != nil {
|
||||
glog.Error("mq: ", err)
|
||||
return "", err
|
||||
}
|
||||
b.mq = mq
|
||||
|
||||
b.Mempool = bchain.NewUTXOMempool(bc, b.ChainConfig.MempoolWorkers, b.ChainConfig.MempoolSubWorkers)
|
||||
|
||||
return chainName, nil
|
||||
}
|
||||
|
||||
// Initialize initializes BitcoinRPC instance.
|
||||
func (b *BitcoinRPC) Initialize() error {
|
||||
b.ChainConfig.SupportsEstimateFee = false
|
||||
|
||||
chainName, err := b.GetChainInfoAndInitializeMempool(b)
|
||||
ci, err := b.GetChainInfo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
chainName := ci.Chain
|
||||
|
||||
params := GetChainParams(chainName)
|
||||
|
||||
|
@ -145,9 +135,45 @@ func (b *BitcoinRPC) Initialize() error {
|
|||
|
||||
glog.Info("rpc: block chain ", params.Name)
|
||||
|
||||
if b.ChainConfig.AlternativeEstimateFee == "whatthefee" {
|
||||
if err = InitWhatTheFee(b, b.ChainConfig.AlternativeEstimateFeeParams); err != nil {
|
||||
glog.Error("InitWhatTheFee error ", err, " Reverting to default estimateFee functionality")
|
||||
// disable AlternativeEstimateFee logic
|
||||
b.ChainConfig.AlternativeEstimateFee = ""
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateMempool creates mempool if not already created, however does not initialize it
|
||||
func (b *BitcoinRPC) CreateMempool(chain bchain.BlockChain) (bchain.Mempool, error) {
|
||||
if b.Mempool == nil {
|
||||
b.Mempool = bchain.NewMempoolBitcoinType(chain, b.ChainConfig.MempoolWorkers, b.ChainConfig.MempoolSubWorkers)
|
||||
}
|
||||
return b.Mempool, nil
|
||||
}
|
||||
|
||||
// InitializeMempool creates ZeroMQ subscription and sets AddrDescForOutpointFunc to the Mempool
|
||||
func (b *BitcoinRPC) InitializeMempool(addrDescForOutpoint bchain.AddrDescForOutpointFunc, onNewTxAddr bchain.OnNewTxAddrFunc, onNewTx bchain.OnNewTxFunc) error {
|
||||
if b.Mempool == nil {
|
||||
return errors.New("Mempool not created")
|
||||
}
|
||||
b.Mempool.AddrDescForOutpoint = addrDescForOutpoint
|
||||
b.Mempool.OnNewTxAddr = onNewTxAddr
|
||||
b.Mempool.OnNewTx = onNewTx
|
||||
if b.mq == nil {
|
||||
mq, err := bchain.NewMQ(b.ChainConfig.MessageQueueBinding, b.pushHandler)
|
||||
if err != nil {
|
||||
glog.Error("mq: ", err)
|
||||
return err
|
||||
}
|
||||
b.mq = mq
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Shutdown ZeroMQ and other resources
|
||||
func (b *BitcoinRPC) Shutdown(ctx context.Context) error {
|
||||
if b.mq != nil {
|
||||
if err := b.mq.Shutdown(ctx); err != nil {
|
||||
|
@ -158,18 +184,12 @@ func (b *BitcoinRPC) Shutdown(ctx context.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (b *BitcoinRPC) IsTestnet() bool {
|
||||
return b.Testnet
|
||||
}
|
||||
|
||||
func (b *BitcoinRPC) GetNetworkName() string {
|
||||
return b.Network
|
||||
}
|
||||
|
||||
// GetCoinName returns the coin name
|
||||
func (b *BitcoinRPC) GetCoinName() string {
|
||||
return b.ChainConfig.CoinName
|
||||
}
|
||||
|
||||
// GetSubversion returns the backend subversion
|
||||
func (b *BitcoinRPC) GetSubversion() string {
|
||||
return b.ChainConfig.Subversion
|
||||
}
|
||||
|
@ -219,13 +239,13 @@ type CmdGetBlockChainInfo struct {
|
|||
type ResGetBlockChainInfo struct {
|
||||
Error *bchain.RPCError `json:"error"`
|
||||
Result struct {
|
||||
Chain string `json:"chain"`
|
||||
Blocks int `json:"blocks"`
|
||||
Headers int `json:"headers"`
|
||||
Bestblockhash string `json:"bestblockhash"`
|
||||
Difficulty json.Number `json:"difficulty"`
|
||||
SizeOnDisk int64 `json:"size_on_disk"`
|
||||
Warnings string `json:"warnings"`
|
||||
Chain string `json:"chain"`
|
||||
Blocks int `json:"blocks"`
|
||||
Headers int `json:"headers"`
|
||||
Bestblockhash string `json:"bestblockhash"`
|
||||
Difficulty common.JSONNumber `json:"difficulty"`
|
||||
SizeOnDisk int64 `json:"size_on_disk"`
|
||||
Warnings string `json:"warnings"`
|
||||
} `json:"result"`
|
||||
}
|
||||
|
||||
|
@ -238,11 +258,11 @@ type CmdGetNetworkInfo struct {
|
|||
type ResGetNetworkInfo struct {
|
||||
Error *bchain.RPCError `json:"error"`
|
||||
Result struct {
|
||||
Version json.Number `json:"version"`
|
||||
Subversion json.Number `json:"subversion"`
|
||||
ProtocolVersion json.Number `json:"protocolversion"`
|
||||
Timeoffset float64 `json:"timeoffset"`
|
||||
Warnings string `json:"warnings"`
|
||||
Version common.JSONNumber `json:"version"`
|
||||
Subversion common.JSONNumber `json:"subversion"`
|
||||
ProtocolVersion common.JSONNumber `json:"protocolversion"`
|
||||
Timeoffset float64 `json:"timeoffset"`
|
||||
Warnings string `json:"warnings"`
|
||||
} `json:"result"`
|
||||
}
|
||||
|
||||
|
@ -340,8 +360,8 @@ type CmdEstimateSmartFee struct {
|
|||
type ResEstimateSmartFee struct {
|
||||
Error *bchain.RPCError `json:"error"`
|
||||
Result struct {
|
||||
Feerate json.Number `json:"feerate"`
|
||||
Blocks int `json:"blocks"`
|
||||
Feerate common.JSONNumber `json:"feerate"`
|
||||
Blocks int `json:"blocks"`
|
||||
} `json:"result"`
|
||||
}
|
||||
|
||||
|
@ -355,8 +375,8 @@ type CmdEstimateFee struct {
|
|||
}
|
||||
|
||||
type ResEstimateFee struct {
|
||||
Error *bchain.RPCError `json:"error"`
|
||||
Result json.Number `json:"result"`
|
||||
Error *bchain.RPCError `json:"error"`
|
||||
Result common.JSONNumber `json:"result"`
|
||||
}
|
||||
|
||||
// sendrawtransaction
|
||||
|
@ -462,7 +482,8 @@ func (b *BitcoinRPC) GetChainInfo() (*bchain.ChainInfo, error) {
|
|||
return rv, nil
|
||||
}
|
||||
|
||||
func isErrBlockNotFound(err *bchain.RPCError) bool {
|
||||
// IsErrBlockNotFound returns true if error means block was not found
|
||||
func IsErrBlockNotFound(err *bchain.RPCError) bool {
|
||||
return err.Message == "Block not found" ||
|
||||
err.Message == "Block height out of range"
|
||||
}
|
||||
|
@ -480,7 +501,7 @@ func (b *BitcoinRPC) GetBlockHash(height uint32) (string, error) {
|
|||
return "", errors.Annotatef(err, "height %v", height)
|
||||
}
|
||||
if res.Error != nil {
|
||||
if isErrBlockNotFound(res.Error) {
|
||||
if IsErrBlockNotFound(res.Error) {
|
||||
return "", bchain.ErrBlockNotFound
|
||||
}
|
||||
return "", errors.Annotatef(res.Error, "height %v", height)
|
||||
|
@ -502,7 +523,7 @@ func (b *BitcoinRPC) GetBlockHeader(hash string) (*bchain.BlockHeader, error) {
|
|||
return nil, errors.Annotatef(err, "hash %v", hash)
|
||||
}
|
||||
if res.Error != nil {
|
||||
if isErrBlockNotFound(res.Error) {
|
||||
if IsErrBlockNotFound(res.Error) {
|
||||
return nil, bchain.ErrBlockNotFound
|
||||
}
|
||||
return nil, errors.Annotatef(res.Error, "hash %v", hash)
|
||||
|
@ -556,7 +577,7 @@ func (b *BitcoinRPC) GetBlockInfo(hash string) (*bchain.BlockInfo, error) {
|
|||
return nil, errors.Annotatef(err, "hash %v", hash)
|
||||
}
|
||||
if res.Error != nil {
|
||||
if isErrBlockNotFound(res.Error) {
|
||||
if IsErrBlockNotFound(res.Error) {
|
||||
return nil, bchain.ErrBlockNotFound
|
||||
}
|
||||
return nil, errors.Annotatef(res.Error, "hash %v", hash)
|
||||
|
@ -580,7 +601,7 @@ func (b *BitcoinRPC) GetBlockWithoutHeader(hash string, height uint32) (*bchain.
|
|||
return block, nil
|
||||
}
|
||||
|
||||
// GetBlockRaw returns block with given hash as bytes.
|
||||
// GetBlockRaw returns block with given hash as bytes
|
||||
func (b *BitcoinRPC) GetBlockRaw(hash string) ([]byte, error) {
|
||||
glog.V(1).Info("rpc: getblock (verbosity=0) ", hash)
|
||||
|
||||
|
@ -594,7 +615,7 @@ func (b *BitcoinRPC) GetBlockRaw(hash string) ([]byte, error) {
|
|||
return nil, errors.Annotatef(err, "hash %v", hash)
|
||||
}
|
||||
if res.Error != nil {
|
||||
if isErrBlockNotFound(res.Error) {
|
||||
if IsErrBlockNotFound(res.Error) {
|
||||
return nil, bchain.ErrBlockNotFound
|
||||
}
|
||||
return nil, errors.Annotatef(res.Error, "hash %v", hash)
|
||||
|
@ -602,7 +623,7 @@ func (b *BitcoinRPC) GetBlockRaw(hash string) ([]byte, error) {
|
|||
return hex.DecodeString(res.Result)
|
||||
}
|
||||
|
||||
// GetBlockFull returns block with given hash.
|
||||
// GetBlockFull returns block with given hash
|
||||
func (b *BitcoinRPC) GetBlockFull(hash string) (*bchain.Block, error) {
|
||||
glog.V(1).Info("rpc: getblock (verbosity=2) ", hash)
|
||||
|
||||
|
@ -616,16 +637,30 @@ func (b *BitcoinRPC) GetBlockFull(hash string) (*bchain.Block, error) {
|
|||
return nil, errors.Annotatef(err, "hash %v", hash)
|
||||
}
|
||||
if res.Error != nil {
|
||||
if isErrBlockNotFound(res.Error) {
|
||||
if IsErrBlockNotFound(res.Error) {
|
||||
return nil, bchain.ErrBlockNotFound
|
||||
}
|
||||
return nil, errors.Annotatef(res.Error, "hash %v", hash)
|
||||
}
|
||||
|
||||
for i := range res.Result.Txs {
|
||||
tx := &res.Result.Txs[i]
|
||||
for j := range tx.Vout {
|
||||
vout := &tx.Vout[j]
|
||||
// convert vout.JsonValue to big.Int and clear it, it is only temporary value used for unmarshal
|
||||
vout.ValueSat, err = b.Parser.AmountToBigInt(vout.JsonValue)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vout.JsonValue = ""
|
||||
}
|
||||
}
|
||||
|
||||
return &res.Result, nil
|
||||
}
|
||||
|
||||
// GetMempool returns transactions in mempool.
|
||||
func (b *BitcoinRPC) GetMempool() ([]string, error) {
|
||||
// GetMempoolTransactions returns transactions in mempool
|
||||
func (b *BitcoinRPC) GetMempoolTransactions() ([]string, error) {
|
||||
glog.V(1).Info("rpc: getrawmempool")
|
||||
|
||||
res := ResGetMempool{}
|
||||
|
@ -641,7 +676,15 @@ func (b *BitcoinRPC) GetMempool() ([]string, error) {
|
|||
return res.Result, nil
|
||||
}
|
||||
|
||||
// GetTransactionForMempool returns a transaction by the transaction ID.
|
||||
// IsMissingTx return true if error means missing tx
|
||||
func IsMissingTx(err *bchain.RPCError) bool {
|
||||
if err.Code == -5 { // "No such mempool or blockchain transaction"
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GetTransactionForMempool returns a transaction by the transaction ID
|
||||
// It could be optimized for mempool, i.e. without block time and confirmations
|
||||
func (b *BitcoinRPC) GetTransactionForMempool(txid string) (*bchain.Tx, error) {
|
||||
glog.V(1).Info("rpc: getrawtransaction nonverbose ", txid)
|
||||
|
@ -655,6 +698,9 @@ func (b *BitcoinRPC) GetTransactionForMempool(txid string) (*bchain.Tx, error) {
|
|||
return nil, errors.Annotatef(err, "txid %v", txid)
|
||||
}
|
||||
if res.Error != nil {
|
||||
if IsMissingTx(res.Error) {
|
||||
return nil, bchain.ErrTxNotFound
|
||||
}
|
||||
return nil, errors.Annotatef(res.Error, "txid %v", txid)
|
||||
}
|
||||
data, err := hex.DecodeString(res.Result)
|
||||
|
@ -668,13 +714,14 @@ func (b *BitcoinRPC) GetTransactionForMempool(txid string) (*bchain.Tx, error) {
|
|||
return tx, nil
|
||||
}
|
||||
|
||||
// GetTransaction returns a transaction by the transaction ID.
|
||||
// GetTransaction returns a transaction by the transaction ID
|
||||
func (b *BitcoinRPC) GetTransaction(txid string) (*bchain.Tx, error) {
|
||||
r, err := b.GetTransactionSpecific(txid)
|
||||
r, err := b.getRawTransaction(txid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tx, err := b.Parser.ParseTxFromJson(r)
|
||||
tx.CoinSpecificData = r
|
||||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "txid %v", txid)
|
||||
}
|
||||
|
@ -682,7 +729,15 @@ func (b *BitcoinRPC) GetTransaction(txid string) (*bchain.Tx, error) {
|
|||
}
|
||||
|
||||
// GetTransactionSpecific returns json as returned by backend, with all coin specific data
|
||||
func (b *BitcoinRPC) GetTransactionSpecific(txid string) (json.RawMessage, error) {
|
||||
func (b *BitcoinRPC) GetTransactionSpecific(tx *bchain.Tx) (json.RawMessage, error) {
|
||||
if csd, ok := tx.CoinSpecificData.(json.RawMessage); ok {
|
||||
return csd, nil
|
||||
}
|
||||
return b.getRawTransaction(tx.Txid)
|
||||
}
|
||||
|
||||
// getRawTransaction returns json as returned by backend, with all coin specific data
|
||||
func (b *BitcoinRPC) getRawTransaction(txid string) (json.RawMessage, error) {
|
||||
glog.V(1).Info("rpc: getrawtransaction ", txid)
|
||||
|
||||
res := ResGetRawTransaction{}
|
||||
|
@ -695,28 +750,14 @@ func (b *BitcoinRPC) GetTransactionSpecific(txid string) (json.RawMessage, error
|
|||
return nil, errors.Annotatef(err, "txid %v", txid)
|
||||
}
|
||||
if res.Error != nil {
|
||||
if IsMissingTx(res.Error) {
|
||||
return nil, bchain.ErrTxNotFound
|
||||
}
|
||||
return nil, errors.Annotatef(res.Error, "txid %v", txid)
|
||||
}
|
||||
return res.Result, nil
|
||||
}
|
||||
|
||||
// ResyncMempool gets mempool transactions and maps output scripts to transactions.
|
||||
// ResyncMempool is not reentrant, it should be called from a single thread.
|
||||
// It returns number of transactions in mempool
|
||||
func (b *BitcoinRPC) ResyncMempool(onNewTxAddr bchain.OnNewTxAddrFunc) (int, error) {
|
||||
return b.Mempool.Resync(onNewTxAddr)
|
||||
}
|
||||
|
||||
// GetMempoolTransactions returns slice of mempool transactions for given address
|
||||
func (b *BitcoinRPC) GetMempoolTransactions(address string) ([]string, error) {
|
||||
return b.Mempool.GetTransactions(address)
|
||||
}
|
||||
|
||||
// GetMempoolTransactionsForAddrDesc returns slice of mempool transactions for given address descriptor
|
||||
func (b *BitcoinRPC) GetMempoolTransactionsForAddrDesc(addrDesc bchain.AddressDescriptor) ([]string, error) {
|
||||
return b.Mempool.GetAddrDescTransactions(addrDesc)
|
||||
}
|
||||
|
||||
// EstimateSmartFee returns fee estimation
|
||||
func (b *BitcoinRPC) EstimateSmartFee(blocks int, conservative bool) (big.Int, error) {
|
||||
// use EstimateFee if EstimateSmartFee is not supported
|
||||
|
@ -778,7 +819,7 @@ func (b *BitcoinRPC) EstimateFee(blocks int) (big.Int, error) {
|
|||
return r, nil
|
||||
}
|
||||
|
||||
// SendRawTransaction sends raw transaction.
|
||||
// SendRawTransaction sends raw transaction
|
||||
func (b *BitcoinRPC) SendRawTransaction(tx string) (string, error) {
|
||||
glog.V(1).Info("rpc: sendrawtransaction")
|
||||
|
||||
|
@ -828,6 +869,7 @@ func safeDecodeResponse(body io.ReadCloser, res interface{}) (err error) {
|
|||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
glog.Error("unmarshal json recovered from panic: ", r, "; data: ", string(data))
|
||||
debug.PrintStack()
|
||||
if len(data) > 0 && len(data) < 2048 {
|
||||
err = errors.Errorf("Error: %v", string(data))
|
||||
} else {
|
||||
|
@ -842,6 +884,7 @@ func safeDecodeResponse(body io.ReadCloser, res interface{}) (err error) {
|
|||
return json.Unmarshal(data, &res)
|
||||
}
|
||||
|
||||
// Call calls Backend RPC interface, using RPCMarshaler interface to marshall the request
|
||||
func (b *BitcoinRPC) Call(req interface{}, res interface{}) error {
|
||||
httpData, err := b.RPCMarshaler.Marshal(req)
|
||||
if err != nil {
|
||||
|
@ -872,8 +915,3 @@ func (b *BitcoinRPC) Call(req interface{}, res interface{}) error {
|
|||
}
|
||||
return safeDecodeResponse(httpRes.Body, &res)
|
||||
}
|
||||
|
||||
// GetChainParser returns BlockChainParser
|
||||
func (b *BitcoinRPC) GetChainParser() bchain.BlockChainParser {
|
||||
return b.Parser
|
||||
}
|
||||
|
|
|
@ -6,12 +6,15 @@ import (
|
|||
"reflect"
|
||||
)
|
||||
|
||||
// RPCMarshaler is used for marshalling requests to Bitcoin Type RPC interfaces
|
||||
type RPCMarshaler interface {
|
||||
Marshal(v interface{}) ([]byte, error)
|
||||
}
|
||||
|
||||
// JSONMarshalerV2 is used for marshalling requests to newer Bitcoin Type RPC interfaces
|
||||
type JSONMarshalerV2 struct{}
|
||||
|
||||
// Marshal converts struct passed by parameter to JSON
|
||||
func (JSONMarshalerV2) Marshal(v interface{}) ([]byte, error) {
|
||||
d, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
|
@ -20,10 +23,13 @@ func (JSONMarshalerV2) Marshal(v interface{}) ([]byte, error) {
|
|||
return d, nil
|
||||
}
|
||||
|
||||
var InvalidValue = errors.New("Invalid value to marshal")
|
||||
// ErrInvalidValue is error returned by JSONMarshalerV1.Marshal if the passed structure contains invalid value
|
||||
var ErrInvalidValue = errors.New("Invalid value to marshal")
|
||||
|
||||
// JSONMarshalerV1 is used for marshalling requests to legacy Bitcoin Type RPC interfaces
|
||||
type JSONMarshalerV1 struct{}
|
||||
|
||||
// Marshal converts struct passed by parameter to JSON
|
||||
func (JSONMarshalerV1) Marshal(v interface{}) ([]byte, error) {
|
||||
u := cmdUntypedParams{}
|
||||
|
||||
|
@ -50,7 +56,7 @@ func (JSONMarshalerV1) Marshal(v interface{}) ([]byte, error) {
|
|||
|
||||
f := v.FieldByName("Method")
|
||||
if !f.IsValid() || f.Kind() != reflect.String {
|
||||
return nil, InvalidValue
|
||||
return nil, ErrInvalidValue
|
||||
}
|
||||
u.Method = f.String()
|
||||
|
||||
|
@ -69,7 +75,7 @@ func (JSONMarshalerV1) Marshal(v interface{}) ([]byte, error) {
|
|||
arr[i] = f.Field(i).Interface()
|
||||
}
|
||||
default:
|
||||
return nil, InvalidValue
|
||||
return nil, ErrInvalidValue
|
||||
}
|
||||
u.Params = arr
|
||||
}
|
||||
|
|
|
@ -0,0 +1,156 @@
|
|||
package btc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/juju/errors"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
)
|
||||
|
||||
// https://whatthefee.io returns
|
||||
// {"index": [3, 6, 9, 12, 18, 24, 36, 48, 72, 96, 144],
|
||||
// "columns": ["0.0500", "0.2000", "0.5000", "0.8000", "0.9500"],
|
||||
// "data": [[60, 180, 280, 400, 440], [20, 120, 180, 380, 440],
|
||||
// [0, 120, 160, 360, 420], [0, 80, 160, 300, 380], [0, 20, 120, 220, 360],
|
||||
// [0, 20, 100, 180, 300], [0, 0, 80, 140, 240], [0, 0, 60, 100, 180],
|
||||
// [0, 0, 40, 60, 140], [0, 0, 20, 20, 60], [0, 0, 0, 0, 20]]}
|
||||
|
||||
type whatTheFeeServiceResult struct {
|
||||
Index []int `json:"index"`
|
||||
Columns []string `json:"columns"`
|
||||
Data [][]int `json:"data"`
|
||||
}
|
||||
|
||||
type whatTheFeeParams struct {
|
||||
URL string `json:"url"`
|
||||
PeriodSeconds int `periodSeconds:"url"`
|
||||
}
|
||||
|
||||
type whatTheFeeFee struct {
|
||||
blocks int
|
||||
feesPerKB []int
|
||||
}
|
||||
|
||||
type whatTheFeeData struct {
|
||||
params whatTheFeeParams
|
||||
probabilities []string
|
||||
fees []whatTheFeeFee
|
||||
lastSync time.Time
|
||||
chain bchain.BlockChain
|
||||
mux sync.Mutex
|
||||
}
|
||||
|
||||
var whatTheFee whatTheFeeData
|
||||
|
||||
// InitWhatTheFee initializes https://whatthefee.io handler
|
||||
func InitWhatTheFee(chain bchain.BlockChain, params string) error {
|
||||
err := json.Unmarshal([]byte(params), &whatTheFee.params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if whatTheFee.params.URL == "" || whatTheFee.params.PeriodSeconds == 0 {
|
||||
return errors.New("Missing parameters")
|
||||
}
|
||||
whatTheFee.chain = chain
|
||||
go whatTheFeeDownloader()
|
||||
return nil
|
||||
}
|
||||
|
||||
func whatTheFeeDownloader() {
|
||||
period := time.Duration(whatTheFee.params.PeriodSeconds) * time.Second
|
||||
timer := time.NewTimer(period)
|
||||
counter := 0
|
||||
for {
|
||||
var data whatTheFeeServiceResult
|
||||
err := whatTheFeeGetData(&data)
|
||||
if err != nil {
|
||||
glog.Error("whatTheFeeGetData ", err)
|
||||
} else {
|
||||
if whatTheFeeProcessData(&data) {
|
||||
if counter%60 == 0 {
|
||||
whatTheFeeCompareToDefault()
|
||||
}
|
||||
counter++
|
||||
}
|
||||
}
|
||||
<-timer.C
|
||||
timer.Reset(period)
|
||||
}
|
||||
}
|
||||
|
||||
func whatTheFeeProcessData(data *whatTheFeeServiceResult) bool {
|
||||
if len(data.Index) == 0 || len(data.Index) != len(data.Data) || len(data.Columns) == 0 {
|
||||
glog.Errorf("invalid data %+v", data)
|
||||
return false
|
||||
}
|
||||
whatTheFee.mux.Lock()
|
||||
defer whatTheFee.mux.Unlock()
|
||||
whatTheFee.probabilities = data.Columns
|
||||
whatTheFee.fees = make([]whatTheFeeFee, len(data.Index))
|
||||
for i, blocks := range data.Index {
|
||||
if len(data.Columns) != len(data.Data[i]) {
|
||||
glog.Errorf("invalid data %+v", data)
|
||||
return false
|
||||
}
|
||||
fees := make([]int, len(data.Columns))
|
||||
for j, l := range data.Data[i] {
|
||||
fees[j] = int(1000 * math.Exp(float64(l)/100))
|
||||
}
|
||||
whatTheFee.fees[i] = whatTheFeeFee{
|
||||
blocks: blocks,
|
||||
feesPerKB: fees,
|
||||
}
|
||||
}
|
||||
whatTheFee.lastSync = time.Now()
|
||||
glog.Infof("%+v", whatTheFee.fees)
|
||||
return true
|
||||
}
|
||||
|
||||
func whatTheFeeGetData(res interface{}) error {
|
||||
var httpData []byte
|
||||
httpReq, err := http.NewRequest("GET", whatTheFee.params.URL, bytes.NewBuffer(httpData))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
httpRes, err := http.DefaultClient.Do(httpReq)
|
||||
if httpRes != nil {
|
||||
defer httpRes.Body.Close()
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if httpRes.StatusCode != 200 {
|
||||
return errors.New("whatthefee.io returned status " + strconv.Itoa(httpRes.StatusCode))
|
||||
}
|
||||
return safeDecodeResponse(httpRes.Body, &res)
|
||||
}
|
||||
|
||||
func whatTheFeeCompareToDefault() {
|
||||
output := ""
|
||||
for _, fee := range whatTheFee.fees {
|
||||
output += fmt.Sprint(fee.blocks, ",")
|
||||
for _, wtf := range fee.feesPerKB {
|
||||
output += fmt.Sprint(wtf, ",")
|
||||
}
|
||||
conservative, err := whatTheFee.chain.EstimateSmartFee(fee.blocks, true)
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
return
|
||||
}
|
||||
economical, err := whatTheFee.chain.EstimateSmartFee(fee.blocks, false)
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
return
|
||||
}
|
||||
output += fmt.Sprint(conservative.String(), ",", economical.String(), "\n")
|
||||
}
|
||||
glog.Info("whatTheFeeCompareToDefault\n", output)
|
||||
}
|
|
@ -1,25 +1,29 @@
|
|||
package btg
|
||||
|
||||
import (
|
||||
"blockbook/bchain"
|
||||
"blockbook/bchain/coins/btc"
|
||||
"blockbook/bchain/coins/utils"
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/jakm/btcutil/chaincfg"
|
||||
"github.com/martinboehm/btcd/chaincfg/chainhash"
|
||||
"github.com/martinboehm/btcd/wire"
|
||||
"github.com/martinboehm/btcutil/chaincfg"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
// MainnetMagic is mainnet network constant
|
||||
MainnetMagic wire.BitcoinNet = 0x446d47e1
|
||||
// TestnetMagic is testnet network constant
|
||||
TestnetMagic wire.BitcoinNet = 0x456e48e2
|
||||
)
|
||||
|
||||
var (
|
||||
// MainNetParams are parser parameters for mainnet
|
||||
MainNetParams chaincfg.Params
|
||||
// TestNetParams are parser parameters for testnet
|
||||
TestNetParams chaincfg.Params
|
||||
)
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
package btg
|
||||
|
||||
import (
|
||||
"blockbook/bchain/coins/btc"
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
|
@ -12,7 +11,8 @@ import (
|
|||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/jakm/btcutil/chaincfg"
|
||||
"github.com/martinboehm/btcutil/chaincfg"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
|
@ -28,7 +28,7 @@ type testBlock struct {
|
|||
}
|
||||
|
||||
var testParseBlockTxs = map[int]testBlock{
|
||||
104000: testBlock{
|
||||
104000: {
|
||||
size: 15776,
|
||||
time: 1295705889,
|
||||
txs: []string{
|
||||
|
@ -81,7 +81,7 @@ var testParseBlockTxs = map[int]testBlock{
|
|||
"33ad36d79d63b575c7532c516f16b19541f5c637caf7073beb7ddf604c3f39cc",
|
||||
},
|
||||
},
|
||||
532144: testBlock{
|
||||
532144: {
|
||||
size: 12198,
|
||||
time: 1528372417,
|
||||
txs: []string{
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
package btg
|
||||
|
||||
import (
|
||||
"blockbook/bchain"
|
||||
"blockbook/bchain/coins/btc"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
|
||||
)
|
||||
|
||||
// BGoldRPC is an interface to JSON-RPC bitcoind service.
|
||||
|
@ -13,7 +13,7 @@ type BGoldRPC struct {
|
|||
*btc.BitcoinRPC
|
||||
}
|
||||
|
||||
// NewBCashRPC returns new BGoldRPC instance.
|
||||
// NewBGoldRPC returns new BGoldRPC instance.
|
||||
func NewBGoldRPC(config json.RawMessage, pushHandler func(bchain.NotificationType)) (bchain.BlockChain, error) {
|
||||
b, err := btc.NewBitcoinRPC(config, pushHandler)
|
||||
if err != nil {
|
||||
|
@ -29,10 +29,11 @@ func NewBGoldRPC(config json.RawMessage, pushHandler func(bchain.NotificationTyp
|
|||
|
||||
// Initialize initializes BGoldRPC instance.
|
||||
func (b *BGoldRPC) Initialize() error {
|
||||
chainName, err := b.GetChainInfoAndInitializeMempool(b)
|
||||
ci, err := b.GetChainInfo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
chainName := ci.Chain
|
||||
|
||||
params := GetChainParams(chainName)
|
||||
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
package cpuchain
|
||||
|
||||
import (
|
||||
"github.com/martinboehm/btcd/wire"
|
||||
"github.com/martinboehm/btcutil/chaincfg"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
|
||||
)
|
||||
|
||||
// magic numbers
|
||||
const (
|
||||
MainnetMagic wire.BitcoinNet = 0xefbeadde
|
||||
TestnetMagic wire.BitcoinNet = 0x0cb0cefa
|
||||
)
|
||||
|
||||
// chain parameters
|
||||
var (
|
||||
MainNetParams chaincfg.Params
|
||||
TestNetParams chaincfg.Params
|
||||
)
|
||||
|
||||
func init() {
|
||||
MainNetParams = chaincfg.MainNetParams
|
||||
MainNetParams.Net = MainnetMagic
|
||||
MainNetParams.PubKeyHashAddrID = []byte{28}
|
||||
MainNetParams.ScriptHashAddrID = []byte{30}
|
||||
MainNetParams.Bech32HRPSegwit = "cpu"
|
||||
|
||||
TestNetParams = chaincfg.TestNet3Params
|
||||
TestNetParams.Net = TestnetMagic
|
||||
TestNetParams.PubKeyHashAddrID = []byte{111}
|
||||
TestNetParams.ScriptHashAddrID = []byte{196}
|
||||
TestNetParams.Bech32HRPSegwit = "tcpu"
|
||||
}
|
||||
|
||||
// CPUchainParser handle
|
||||
type CPUchainParser struct {
|
||||
*btc.BitcoinParser
|
||||
}
|
||||
|
||||
// NewCPUchainParser returns new CPUchainParser instance
|
||||
func NewCPUchainParser(params *chaincfg.Params, c *btc.Configuration) *CPUchainParser {
|
||||
return &CPUchainParser{BitcoinParser: btc.NewBitcoinParser(params, c)}
|
||||
}
|
||||
|
||||
// GetChainParams contains network parameters for the main CPUchain network,
|
||||
// and the test CPUchain network
|
||||
func GetChainParams(chain string) *chaincfg.Params {
|
||||
if !chaincfg.IsRegistered(&MainNetParams) {
|
||||
err := chaincfg.Register(&MainNetParams)
|
||||
if err == nil {
|
||||
err = chaincfg.Register(&TestNetParams)
|
||||
}
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
switch chain {
|
||||
case "test":
|
||||
return &TestNetParams
|
||||
default:
|
||||
return &MainNetParams
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
package cpuchain
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
|
||||
)
|
||||
|
||||
// CPUchainRPC is an interface to JSON-RPC bitcoind service.
|
||||
type CPUchainRPC struct {
|
||||
*btc.BitcoinRPC
|
||||
}
|
||||
|
||||
// NewCPUchainRPC returns new CPUchainRPC instance.
|
||||
func NewCPUchainRPC(config json.RawMessage, pushHandler func(bchain.NotificationType)) (bchain.BlockChain, error) {
|
||||
b, err := btc.NewBitcoinRPC(config, pushHandler)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s := &CPUchainRPC{
|
||||
b.(*btc.BitcoinRPC),
|
||||
}
|
||||
s.RPCMarshaler = btc.JSONMarshalerV2{}
|
||||
s.ChainConfig.SupportsEstimateFee = false
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// Initialize initializes CPUchainRPC instance.
|
||||
func (b *CPUchainRPC) Initialize() error {
|
||||
ci, err := b.GetChainInfo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
chainName := ci.Chain
|
||||
|
||||
glog.Info("Chain name ", chainName)
|
||||
params := GetChainParams(chainName)
|
||||
|
||||
// always create parser
|
||||
b.Parser = NewCPUchainParser(params, b.ChainConfig)
|
||||
|
||||
// parameters for getInfo request
|
||||
if params.Net == MainnetMagic {
|
||||
b.Testnet = false
|
||||
b.Network = "livenet"
|
||||
} else {
|
||||
b.Testnet = true
|
||||
b.Network = "testnet"
|
||||
}
|
||||
|
||||
glog.Info("rpc: block chain ", params.Name)
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
00000020d5add6362e89ca36f34c908e030bb2467ab919cd7a961b2616aa756606000000dd88d513ccaf848da5a42d5b7af930f8ead36ed3b42b8c4b8c6b6b94ec512c95bf946a5d27980a1d10001de501010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff200362380104bf946a5d086000232f000000000d2f6e6f64655374726174756d2f00000000030000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf980010b27010000001976a914697ab38c1db7d672f5dbedff8936c290f906742c88ac80f0fa02000000001976a914fdcd08e6cb3451c95057130f94ef88b78a70b88e88ac0120000000000000000000000000000000000000000000000000000000000000000000000000
|
|
@ -0,0 +1 @@
|
|||
0000002010588a0622de0f5b07b03f65fc661efbcbc262c46218efe9924eb8b1040000000beb93211c662280d24e8b18edc2d5d63b13e91be4c905db2d997d7293ee6f30f3966a5d9ceb0a1deeeef9e702010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff200369380104f3966a5d0860007697000000000d2f6e6f64655374726174756d2f00000000030000000000000000266a24aa21a9ede177006e83279e577b138ece75a323e0fa913da994727d1166dbee18c5bf20b9cb751027010000001976a914697ab38c1db7d672f5dbedff8936c290f906742c88ac9afefa02000000001976a914fdcd08e6cb3451c95057130f94ef88b78a70b88e88ac01200000000000000000000000000000000000000000000000000000000000000000000000000200000003837669f694a699355c53550cb151a939e9626cabefaae45c98ef1a493986ee2e000000006a473044022035f1d62eb19f6b444170b1bc0db9d5133cd514403f289d7a1505a34be872882f02200420fc3a6b3b127b6ac12e516d32c6bb8e08733c1932c3b29e36538e2713d337012103eda5ae5990c8bf9155a14df837f42398f895d5a3c94d8ee7b51034d177b044f2feffffffc73fb3c2a22b1f4f960971c40502cdf37d54648ad43381324dd1ee2d5f065831020000006a473044022043ce48c511bbe514b44cd3c531259c382fe535b6ea405dcb42720cf3ddcbbadb02203693f81b0de34b7a6479babd0c74a5c907858d876b617cc031cdcae585a9c63e0121025e6cd086754c0264cecbab789fae6ab7e2b551030fb88d2bef5b32c1fd99ca02feffffffce4d953b655a1978c1b39653602514b436f6478f1eac9b816462ac4a448435e5000000006b483045022100e0f55692f19b2498fd4116f7e25cb80be29cf6f5198a2f7161cb3cf920984d370220524b0f63aff32f1f7364bc7393b6bbca956ed954eb9089d1279afc66d86108ca012103a8986d67d7c01ca1c02d0e86cc7b1b0c7a172c9cffd7729bc308d6fa8110b6aefeffffff046636021b000000001976a914add8a0089511e14059d632c7bd980e8db1be62fd88ac8efafe3c000000001976a914317ea248669f7ef786ef867ce90214cba9edef8a88ac303bae3b000000001976a9141abe5eab04d3dc3b7deeb16fe3ba3674205a907588ac9e192e72000000001976a914cd6d6c32dee3d00c9c1afcc3f93ce9a4f30333a188ac67380100
|
|
@ -1,21 +1,27 @@
|
|||
package dash
|
||||
|
||||
import (
|
||||
"blockbook/bchain/coins/btc"
|
||||
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/jakm/btcutil/chaincfg"
|
||||
"github.com/martinboehm/btcd/wire"
|
||||
"github.com/martinboehm/btcutil/chaincfg"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
|
||||
)
|
||||
|
||||
const (
|
||||
// MainnetMagic is mainnet network constant
|
||||
MainnetMagic wire.BitcoinNet = 0xbd6b0cbf
|
||||
// TestnetMagic is testnet network constant
|
||||
TestnetMagic wire.BitcoinNet = 0xffcae2ce
|
||||
// RegtestMagic is regtest network constant
|
||||
RegtestMagic wire.BitcoinNet = 0xdcb7c1fc
|
||||
)
|
||||
|
||||
var (
|
||||
// MainNetParams are parser parameters for mainnet
|
||||
MainNetParams chaincfg.Params
|
||||
// TestNetParams are parser parameters for testnet
|
||||
TestNetParams chaincfg.Params
|
||||
// RegtestParams are parser parameters for regtest
|
||||
RegtestParams chaincfg.Params
|
||||
)
|
||||
|
||||
|
@ -45,11 +51,15 @@ func init() {
|
|||
// DashParser handle
|
||||
type DashParser struct {
|
||||
*btc.BitcoinParser
|
||||
baseparser *bchain.BaseParser
|
||||
}
|
||||
|
||||
// NewDashParser returns new DashParser instance
|
||||
func NewDashParser(params *chaincfg.Params, c *btc.Configuration) *DashParser {
|
||||
return &DashParser{BitcoinParser: btc.NewBitcoinParser(params, c)}
|
||||
return &DashParser{
|
||||
BitcoinParser: btc.NewBitcoinParser(params, c),
|
||||
baseparser: &bchain.BaseParser{},
|
||||
}
|
||||
}
|
||||
|
||||
// GetChainParams contains network parameters for the main Dash network,
|
||||
|
@ -77,3 +87,13 @@ func GetChainParams(chain string) *chaincfg.Params {
|
|||
return &MainNetParams
|
||||
}
|
||||
}
|
||||
|
||||
// PackTx packs transaction to byte array using protobuf
|
||||
func (p *DashParser) PackTx(tx *bchain.Tx, height uint32, blockTime int64) ([]byte, error) {
|
||||
return p.baseparser.PackTx(tx, height, blockTime)
|
||||
}
|
||||
|
||||
// UnpackTx unpacks transaction from protobuf byte array
|
||||
func (p *DashParser) UnpackTx(buf []byte) (*bchain.Tx, uint32, error) {
|
||||
return p.baseparser.UnpackTx(buf)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,334 @@
|
|||
// +build unittest
|
||||
|
||||
package dash
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
|
||||
)
|
||||
|
||||
type testBlock struct {
|
||||
size int
|
||||
time int64
|
||||
txs []string
|
||||
}
|
||||
|
||||
var testParseBlockTxs = map[int]testBlock{
|
||||
500001: {
|
||||
size: 6521,
|
||||
time: 1468043164,
|
||||
txs: []string{
|
||||
"6d45c761892eb07b7651140aa42901b03cb501a82585fb360ff8f155d46727b0",
|
||||
"5f6cbefca8a48cccd40805013e5f6c602e0d35c3511ccb7a2ae25e51dd95d38f",
|
||||
"0eb37faa8b2f24c68c8e4a3009a051caded005d5f13f0dc216ff6422256f6b7b",
|
||||
"2565cfacb7f8cbc73dc1053b06ca527d9798bf6bf29a778cb5924a17dd167a39",
|
||||
"85f93911e8a4d8d9bbf3de009a666ed594d62aa41a34a9e3763058067e64f084",
|
||||
"46712f0f32a392c71df443798e120d0f40eb93063631a992b3dedd4d4afc04e4",
|
||||
"1790efaa05caad7ab546ef479041c3cb1cb9bca7b9cc7992566d2e2344701167",
|
||||
},
|
||||
},
|
||||
// last block without special transactions, valid for bitcoin parser
|
||||
1028159: {
|
||||
size: 8608,
|
||||
time: 1551246608,
|
||||
txs: []string{
|
||||
"a800f5b2dde5d48bda08d9d6fc5647c41cec902ce690a5a2be0665e6acf77c35",
|
||||
"981d6668e65b70fcd97ddd68319f3c5e5163e510cc0ed479be5667bf1782f036",
|
||||
"b9fd19d37ec97d038da2ccad9414ef311275d5fd3762bdec3e76f535e2295f4c",
|
||||
"1b4051d02c9919ef8d482cadf6ca2002442d9436b444923cd295fe56009ec52b",
|
||||
"6f1ec8472624b8e7481024ee8b228086b9b32606790e94f161589d3fe2b3a826",
|
||||
"b12c512803d733f3f7afce846e18c6a46c713533cdb18a13392cbda88866523c",
|
||||
"69e6d67946ed660b440c8e457933ae594ce60acdbe17ded091ce0ac6f41ed186",
|
||||
"755c3b7cad9b569def3f69f897da2ee7732ee2e0a165965512680b4fe9086e12",
|
||||
"63de772ff400789c2c3ad9be653817bebf92551e139d80c8e735bb0610865500",
|
||||
"1566bd9bb2413c63412a13d16c7017814363f668d8b3bfe66a5478734e73f010",
|
||||
"d7f441b0abca7df6530a0620661c839244bb6f26e1b4a53b783fb3acc1f5f42a",
|
||||
"ec7762d0e02a87e311b128662db8ef4161dcc9d9f2831250c7366eed98fc744b",
|
||||
"b977669bfc0ac3b9ca9de7512fda564a69fe49dde8a286fcb7ea99147db54b5f",
|
||||
},
|
||||
},
|
||||
// first block with special transactions, invalid for bitcoin parser
|
||||
// 1028160: {
|
||||
// size: 2347,
|
||||
// time: 1551246710,
|
||||
// txs: []string{
|
||||
// "71d6975e3b79b52baf26c3269896a34f3bedfb04561c692ffa31f64dada1f9c4",
|
||||
// "ed732a404cdfd4e0475a7a016200b7eef191f2c9de0ffdef8a20091c0499299c",
|
||||
// "99d0613f82ea1f928bbc98318665adbdf5b40d206bd487fe77d542e86c903f55",
|
||||
// "05cbf334a563468d0e378c56b43fb5254ee9c0e35ca8fab5cb242ebd825ae97b",
|
||||
// "ee91fae7be36b3b81bc60992e904e1ae91e7dffdd5751ccaef557ba62ea80a4f",
|
||||
// },
|
||||
// },
|
||||
}
|
||||
|
||||
func helperLoadBlock(t *testing.T, height int) []byte {
|
||||
name := fmt.Sprintf("block_dump.%d", height)
|
||||
path := filepath.Join("testdata", name)
|
||||
|
||||
d, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
d = bytes.TrimSpace(d)
|
||||
|
||||
b := make([]byte, hex.DecodedLen(len(d)))
|
||||
_, err = hex.Decode(b, d)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
func TestParseBlock(t *testing.T) {
|
||||
p := NewDashParser(GetChainParams("main"), &btc.Configuration{})
|
||||
|
||||
for height, tb := range testParseBlockTxs {
|
||||
b := helperLoadBlock(t, height)
|
||||
|
||||
blk, err := p.ParseBlock(b)
|
||||
if err != nil {
|
||||
t.Errorf("ParseBlock() error %v", err)
|
||||
}
|
||||
|
||||
if blk.Size != tb.size {
|
||||
t.Errorf("ParseBlock() block size: got %d, want %d", blk.Size, tb.size)
|
||||
}
|
||||
|
||||
if blk.Time != tb.time {
|
||||
t.Errorf("ParseBlock() block time: got %d, want %d", blk.Time, tb.time)
|
||||
}
|
||||
|
||||
if len(blk.Txs) != len(tb.txs) {
|
||||
t.Errorf("ParseBlock() number of transactions: got %d, want %d", len(blk.Txs), len(tb.txs))
|
||||
}
|
||||
|
||||
for ti, tx := range tb.txs {
|
||||
if blk.Txs[ti].Txid != tx {
|
||||
t.Errorf("ParseBlock() transaction %d: got %s, want %s", ti, blk.Txs[ti].Txid, tx)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
testTx1 = bchain.Tx{
|
||||
Blocktime: 1551246710,
|
||||
Confirmations: 0,
|
||||
Hex: "0100000001f85264d11a747bdba77d411e5e4a3d35e3aeb5843b34a95234a2121ac65496bd000000006b483045022100dfa158fbd9773fab4f6f329c807e040af0c3a40967cbe01667169b914ed5ad960220061c5876364caa3e3c9c990ad2b4cc8b1a53d4f954dbda8434b0e67cc8348ff6012103093865e1e132b33a2a5ed01c79d2edba3473826a66cb26b8311bfa42749c2190ffffffff02ec3f8a2a010000001976a91470dcef2a22575d7a8f0779fb1d6cdd48135bd22788ac3116491d000000001976a91471348f7780e955a2a60eba17ecc4c826ebc23a9888ac00000000",
|
||||
LockTime: 0,
|
||||
Time: 1551246710,
|
||||
Txid: "ed732a404cdfd4e0475a7a016200b7eef191f2c9de0ffdef8a20091c0499299c",
|
||||
Version: 1,
|
||||
Vin: []bchain.Vin{
|
||||
{
|
||||
Txid: "bd9654c61a12a23452a9343b84b5aee3353d4a5e1e417da7db7b741ad16452f8",
|
||||
Vout: 0,
|
||||
ScriptSig: bchain.ScriptSig{
|
||||
Hex: "483045022100dfa158fbd9773fab4f6f329c807e040af0c3a40967cbe01667169b914ed5ad960220061c5876364caa3e3c9c990ad2b4cc8b1a53d4f954dbda8434b0e67cc8348ff6012103093865e1e132b33a2a5ed01c79d2edba3473826a66cb26b8311bfa42749c2190",
|
||||
},
|
||||
Sequence: 4294967295,
|
||||
},
|
||||
},
|
||||
Vout: []bchain.Vout{
|
||||
{
|
||||
N: 0,
|
||||
ScriptPubKey: bchain.ScriptPubKey{
|
||||
Addresses: []string{"XkycBX1ykVXXs92pAi6ZQwZPEre9kSHHKH"},
|
||||
Hex: "76a91470dcef2a22575d7a8f0779fb1d6cdd48135bd22788ac",
|
||||
},
|
||||
ValueSat: *big.NewInt(5008670700),
|
||||
},
|
||||
{
|
||||
N: 1,
|
||||
ScriptPubKey: bchain.ScriptPubKey{
|
||||
Addresses: []string{"Xm1R9thKBm2EZKZevXsmMX4DVwQQuTohZu"},
|
||||
Hex: "76a91471348f7780e955a2a60eba17ecc4c826ebc23a9888ac",
|
||||
},
|
||||
ValueSat: *big.NewInt(491329073),
|
||||
},
|
||||
},
|
||||
}
|
||||
testTxPacked1 = "0a20ed732a404cdfd4e0475a7a016200b7eef191f2c9de0ffdef8a20091c0499299c12e2010100000001f85264d11a747bdba77d411e5e4a3d35e3aeb5843b34a95234a2121ac65496bd000000006b483045022100dfa158fbd9773fab4f6f329c807e040af0c3a40967cbe01667169b914ed5ad960220061c5876364caa3e3c9c990ad2b4cc8b1a53d4f954dbda8434b0e67cc8348ff6012103093865e1e132b33a2a5ed01c79d2edba3473826a66cb26b8311bfa42749c2190ffffffff02ec3f8a2a010000001976a91470dcef2a22575d7a8f0779fb1d6cdd48135bd22788ac3116491d000000001976a91471348f7780e955a2a60eba17ecc4c826ebc23a9888ac0000000018f6cad8e305200028c0e03e3299010a001220bd9654c61a12a23452a9343b84b5aee3353d4a5e1e417da7db7b741ad16452f81800226b483045022100dfa158fbd9773fab4f6f329c807e040af0c3a40967cbe01667169b914ed5ad960220061c5876364caa3e3c9c990ad2b4cc8b1a53d4f954dbda8434b0e67cc8348ff6012103093865e1e132b33a2a5ed01c79d2edba3473826a66cb26b8311bfa42749c219028ffffffff0f3a480a05012a8a3fec10001a1976a91470dcef2a22575d7a8f0779fb1d6cdd48135bd22788ac2222586b7963425831796b565858733932704169365a51775a50457265396b5348484b483a470a041d49163110011a1976a91471348f7780e955a2a60eba17ecc4c826ebc23a9888ac2222586d31523974684b426d32455a4b5a657658736d4d5834445677515175546f685a754001"
|
||||
|
||||
testTx2 = bchain.Tx{
|
||||
Blocktime: 1551246710,
|
||||
Confirmations: 0,
|
||||
Hex: "03000500010000000000000000000000000000000000000000000000000000000000000000ffffffff170340b00f1291af3c09542bc8349901000000002f4e614effffffff024181f809000000001976a9146a341485a9444b35dc9cb90d24e7483de7d37e0088ac3581f809000000001976a9140d1156f6026bf975ea3553b03fb534d0959c294c88ac0000000026010040b00f000000000000000000000000000000000000000000000000000000000000000000",
|
||||
LockTime: 0,
|
||||
Time: 1551246710,
|
||||
Txid: "71d6975e3b79b52baf26c3269896a34f3bedfb04561c692ffa31f64dada1f9c4",
|
||||
Version: 3,
|
||||
Vin: []bchain.Vin{
|
||||
{
|
||||
Coinbase: "0340b00f1291af3c09542bc8349901000000002f4e614e",
|
||||
Sequence: 4294967295,
|
||||
},
|
||||
},
|
||||
Vout: []bchain.Vout{
|
||||
{
|
||||
N: 0,
|
||||
ScriptPubKey: bchain.ScriptPubKey{
|
||||
Addresses: []string{"XkNPrBSJtrHZUvUqb3JF4g5rMB3uzaJfEL"},
|
||||
Hex: "76a9146a341485a9444b35dc9cb90d24e7483de7d37e0088ac",
|
||||
},
|
||||
ValueSat: *big.NewInt(167280961),
|
||||
},
|
||||
{
|
||||
N: 1,
|
||||
ScriptPubKey: bchain.ScriptPubKey{
|
||||
Addresses: []string{"XbswPXhcLqm5AN5gwcTTyiUGSP2YndWwk9"},
|
||||
Hex: "76a9140d1156f6026bf975ea3553b03fb534d0959c294c88ac",
|
||||
},
|
||||
ValueSat: *big.NewInt(167280949),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
testTxPacked2 = "0a2071d6975e3b79b52baf26c3269896a34f3bedfb04561c692ffa31f64dada1f9c412b50103000500010000000000000000000000000000000000000000000000000000000000000000ffffffff170340b00f1291af3c09542bc8349901000000002f4e614effffffff024181f809000000001976a9146a341485a9444b35dc9cb90d24e7483de7d37e0088ac3581f809000000001976a9140d1156f6026bf975ea3553b03fb534d0959c294c88ac0000000026010040b00f00000000000000000000000000000000000000000000000000000000000000000018f6cad8e305200028c0e03e32380a2e30333430623030663132393161663363303935343262633833343939303130303030303030303266346536313465180028ffffffff0f3a470a0409f8814110001a1976a9146a341485a9444b35dc9cb90d24e7483de7d37e0088ac2222586b4e507242534a7472485a5576557162334a46346735724d4233757a614a66454c3a470a0409f8813510011a1976a9140d1156f6026bf975ea3553b03fb534d0959c294c88ac222258627377505868634c716d35414e35677763545479695547535032596e6457776b394003"
|
||||
)
|
||||
|
||||
func TestBaseParser_ParseTxFromJson(t *testing.T) {
|
||||
p := NewDashParser(GetChainParams("main"), &btc.Configuration{})
|
||||
tests := []struct {
|
||||
name string
|
||||
msg string
|
||||
want *bchain.Tx
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "normal tx",
|
||||
msg: `{"hex":"0100000001f85264d11a747bdba77d411e5e4a3d35e3aeb5843b34a95234a2121ac65496bd000000006b483045022100dfa158fbd9773fab4f6f329c807e040af0c3a40967cbe01667169b914ed5ad960220061c5876364caa3e3c9c990ad2b4cc8b1a53d4f954dbda8434b0e67cc8348ff6012103093865e1e132b33a2a5ed01c79d2edba3473826a66cb26b8311bfa42749c2190ffffffff02ec3f8a2a010000001976a91470dcef2a22575d7a8f0779fb1d6cdd48135bd22788ac3116491d000000001976a91471348f7780e955a2a60eba17ecc4c826ebc23a9888ac00000000","txid":"ed732a404cdfd4e0475a7a016200b7eef191f2c9de0ffdef8a20091c0499299c","size":226,"version":1,"type":0,"locktime":0,"vin":[{"txid":"bd9654c61a12a23452a9343b84b5aee3353d4a5e1e417da7db7b741ad16452f8","vout":0,"scriptSig":{"asm":"3045022100dfa158fbd9773fab4f6f329c807e040af0c3a40967cbe01667169b914ed5ad960220061c5876364caa3e3c9c990ad2b4cc8b1a53d4f954dbda8434b0e67cc8348ff6[ALL]03093865e1e132b33a2a5ed01c79d2edba3473826a66cb26b8311bfa42749c2190","hex":"483045022100dfa158fbd9773fab4f6f329c807e040af0c3a40967cbe01667169b914ed5ad960220061c5876364caa3e3c9c990ad2b4cc8b1a53d4f954dbda8434b0e67cc8348ff6012103093865e1e132b33a2a5ed01c79d2edba3473826a66cb26b8311bfa42749c2190"},"value":55.00000000,"valueSat":5500000000,"address":"Xgcv4bKAXaWf5sjX9KR49L98jeMwNgeXWh","sequence":4294967295}],"vout":[{"value":50.08670700,"valueSat":5008670700,"n":0,"scriptPubKey":{"asm":"OP_DUPOP_HASH16070dcef2a22575d7a8f0779fb1d6cdd48135bd227OP_EQUALVERIFYOP_CHECKSIG","hex":"76a91470dcef2a22575d7a8f0779fb1d6cdd48135bd22788ac","reqSigs":1,"type":"pubkeyhash","addresses":["XkycBX1ykVXXs92pAi6ZQwZPEre9kSHHKH"]}},{"value":4.91329073,"valueSat":491329073,"n":1,"scriptPubKey":{"asm":"OP_DUPOP_HASH16071348f7780e955a2a60eba17ecc4c826ebc23a98OP_EQUALVERIFYOP_CHECKSIG","hex":"76a91471348f7780e955a2a60eba17ecc4c826ebc23a9888ac","reqSigs":1,"type":"pubkeyhash","addresses":["Xm1R9thKBm2EZKZevXsmMX4DVwQQuTohZu"]}}],"blockhash":"000000000000002099caaf1a877911d99a5980ede9b981280eecb291afedf87b","height":1028160,"confirmations":0,"time":1551246710,"blocktime":1551246710,"instantlock":false}`,
|
||||
want: &testTx1,
|
||||
},
|
||||
{
|
||||
name: "special tx - DIP2",
|
||||
msg: `{"hex":"03000500010000000000000000000000000000000000000000000000000000000000000000ffffffff170340b00f1291af3c09542bc8349901000000002f4e614effffffff024181f809000000001976a9146a341485a9444b35dc9cb90d24e7483de7d37e0088ac3581f809000000001976a9140d1156f6026bf975ea3553b03fb534d0959c294c88ac0000000026010040b00f000000000000000000000000000000000000000000000000000000000000000000","txid":"71d6975e3b79b52baf26c3269896a34f3bedfb04561c692ffa31f64dada1f9c4","size":181,"version":3,"type":5,"locktime":0,"vin":[{"coinbase":"0340b00f1291af3c09542bc8349901000000002f4e614e","sequence":4294967295}],"vout":[{"value":1.67280961,"valueSat":167280961,"n":0,"scriptPubKey":{"asm":"OP_DUPOP_HASH1606a341485a9444b35dc9cb90d24e7483de7d37e00OP_EQUALVERIFYOP_CHECKSIG","hex":"76a9146a341485a9444b35dc9cb90d24e7483de7d37e0088ac","reqSigs":1,"type":"pubkeyhash","addresses":["XkNPrBSJtrHZUvUqb3JF4g5rMB3uzaJfEL"]}},{"value":1.67280949,"valueSat":167280949,"n":1,"scriptPubKey":{"asm":"OP_DUPOP_HASH1600d1156f6026bf975ea3553b03fb534d0959c294cOP_EQUALVERIFYOP_CHECKSIG","hex":"76a9140d1156f6026bf975ea3553b03fb534d0959c294c88ac","reqSigs":1,"type":"pubkeyhash","addresses":["XbswPXhcLqm5AN5gwcTTyiUGSP2YndWwk9"]}}],"extraPayloadSize":38,"extraPayload":"010040b00f000000000000000000000000000000000000000000000000000000000000000000","cbTx":{"version":1,"height":1028160,"merkleRootMNList":"0000000000000000000000000000000000000000000000000000000000000000"},"blockhash":"000000000000002099caaf1a877911d99a5980ede9b981280eecb291afedf87b","height":1028160,"confirmations":0,"time":1551246710,"blocktime":1551246710,"instantlock":false}`,
|
||||
want: &testTx2,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := p.ParseTxFromJson([]byte(tt.msg))
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("DashParser.ParseTxFromJson() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("DashParser.ParseTxFromJson() = %+v, want %+v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_PackTx(t *testing.T) {
|
||||
type args struct {
|
||||
tx bchain.Tx
|
||||
height uint32
|
||||
blockTime int64
|
||||
parser *DashParser
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "dash-1",
|
||||
args: args{
|
||||
tx: testTx1,
|
||||
height: 1028160,
|
||||
blockTime: 1551246710,
|
||||
parser: NewDashParser(GetChainParams("main"), &btc.Configuration{}),
|
||||
},
|
||||
want: testTxPacked1,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "dash-2",
|
||||
args: args{
|
||||
tx: testTx2,
|
||||
height: 1028160,
|
||||
blockTime: 1551246710,
|
||||
parser: NewDashParser(GetChainParams("main"), &btc.Configuration{}),
|
||||
},
|
||||
want: testTxPacked2,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := tt.args.parser.PackTx(&tt.args.tx, tt.args.height, tt.args.blockTime)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("packTx() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
h := hex.EncodeToString(got)
|
||||
if !reflect.DeepEqual(h, tt.want) {
|
||||
t.Errorf("packTx() = %v, want %v", h, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_UnpackTx(t *testing.T) {
|
||||
type args struct {
|
||||
packedTx string
|
||||
parser *DashParser
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *bchain.Tx
|
||||
want1 uint32
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "dash-1",
|
||||
args: args{
|
||||
packedTx: testTxPacked1,
|
||||
parser: NewDashParser(GetChainParams("main"), &btc.Configuration{}),
|
||||
},
|
||||
want: &testTx1,
|
||||
want1: 1028160,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "dash-2",
|
||||
args: args{
|
||||
packedTx: testTxPacked2,
|
||||
parser: NewDashParser(GetChainParams("main"), &btc.Configuration{}),
|
||||
},
|
||||
want: &testTx2,
|
||||
want1: 1028160,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
b, _ := hex.DecodeString(tt.args.packedTx)
|
||||
got, got1, err := tt.args.parser.UnpackTx(b)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("unpackTx() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("unpackTx() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
if got1 != tt.want1 {
|
||||
t.Errorf("unpackTx() got1 = %v, want %v", got1, tt.want1)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,19 +1,22 @@
|
|||
package dash
|
||||
|
||||
import (
|
||||
"blockbook/bchain"
|
||||
"blockbook/bchain/coins/btc"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/juju/errors"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
|
||||
)
|
||||
|
||||
// DashRPC is an interface to JSON-RPC bitcoind service.
|
||||
const firstBlockWithSpecialTransactions = 1028160
|
||||
|
||||
// DashRPC is an interface to JSON-RPC bitcoind service
|
||||
type DashRPC struct {
|
||||
*btc.BitcoinRPC
|
||||
}
|
||||
|
||||
// NewDashRPC returns new DashRPC instance.
|
||||
// NewDashRPC returns new DashRPC instance
|
||||
func NewDashRPC(config json.RawMessage, pushHandler func(bchain.NotificationType)) (bchain.BlockChain, error) {
|
||||
b, err := btc.NewBitcoinRPC(config, pushHandler)
|
||||
if err != nil {
|
||||
|
@ -31,10 +34,11 @@ func NewDashRPC(config json.RawMessage, pushHandler func(bchain.NotificationType
|
|||
|
||||
// Initialize initializes DashRPC instance.
|
||||
func (b *DashRPC) Initialize() error {
|
||||
chainName, err := b.GetChainInfoAndInitializeMempool(b)
|
||||
ci, err := b.GetChainInfo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
chainName := ci.Chain
|
||||
|
||||
params := GetChainParams(chainName)
|
||||
|
||||
|
@ -54,3 +58,57 @@ func (b *DashRPC) Initialize() error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetBlock returns block with given hash
|
||||
func (b *DashRPC) GetBlock(hash string, height uint32) (*bchain.Block, error) {
|
||||
if hash == "" && height < firstBlockWithSpecialTransactions {
|
||||
return b.BitcoinRPC.GetBlock(hash, height)
|
||||
}
|
||||
|
||||
var err error
|
||||
if hash == "" && height > 0 {
|
||||
hash, err = b.GetBlockHash(height)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
glog.V(1).Info("rpc: getblock (verbosity=1) ", hash)
|
||||
|
||||
res := btc.ResGetBlockThin{}
|
||||
req := btc.CmdGetBlock{Method: "getblock"}
|
||||
req.Params.BlockHash = hash
|
||||
req.Params.Verbosity = 1
|
||||
err = b.Call(&req, &res)
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "hash %v", hash)
|
||||
}
|
||||
if res.Error != nil {
|
||||
return nil, errors.Annotatef(res.Error, "hash %v", hash)
|
||||
}
|
||||
|
||||
txs := make([]bchain.Tx, 0, len(res.Result.Txids))
|
||||
for _, txid := range res.Result.Txids {
|
||||
tx, err := b.GetTransaction(txid)
|
||||
if err != nil {
|
||||
if err == bchain.ErrTxNotFound {
|
||||
glog.Errorf("rpc: getblock: skipping transanction in block %s due error: %s", hash, err)
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
txs = append(txs, *tx)
|
||||
}
|
||||
block := &bchain.Block{
|
||||
BlockHeader: res.Result.BlockHeader,
|
||||
Txs: txs,
|
||||
}
|
||||
return block, nil
|
||||
}
|
||||
|
||||
// GetTransactionForMempool returns a transaction by the transaction ID.
|
||||
// It could be optimized for mempool, i.e. without block time and confirmations
|
||||
func (b *DashRPC) GetTransactionForMempool(txid string) (*bchain.Tx, error) {
|
||||
return b.GetTransaction(txid)
|
||||
}
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1 @@
|
|||
00000020b652661d4cc8a262f9561a313b8cb15fcb4f69e6f88e908b3e0000000000000048a11a95e5a224637dc3639bf6c20c0ee0ca16b66a5dba5a9823ad9515a772c07625765cb8f34119a74e94e10503000500010000000000000000000000000000000000000000000000000000000000000000ffffffff170340b00f1291af3c09542bc8349901000000002f4e614effffffff024181f809000000001976a9146a341485a9444b35dc9cb90d24e7483de7d37e0088ac3581f809000000001976a9140d1156f6026bf975ea3553b03fb534d0959c294c88ac0000000026010040b00f0000000000000000000000000000000000000000000000000000000000000000000100000001f85264d11a747bdba77d411e5e4a3d35e3aeb5843b34a95234a2121ac65496bd000000006b483045022100dfa158fbd9773fab4f6f329c807e040af0c3a40967cbe01667169b914ed5ad960220061c5876364caa3e3c9c990ad2b4cc8b1a53d4f954dbda8434b0e67cc8348ff6012103093865e1e132b33a2a5ed01c79d2edba3473826a66cb26b8311bfa42749c2190ffffffff02ec3f8a2a010000001976a91470dcef2a22575d7a8f0779fb1d6cdd48135bd22788ac3116491d000000001976a91471348f7780e955a2a60eba17ecc4c826ebc23a9888ac000000000200000002ce167f4b13fdd25facf0a92aba23f5220f8366ac5ed29b83a3a6f2c9f76f5726000000006b483045022100c39871831ee92830da00fe1ba69925f8476cf39bcd1629857bf59b908b53649402201d168fb69ea995ed49059b7d532475891d3b482d19778f339bb95f3a6be3df6701210387bfcd2d4f7e7e68549ecb21a14e6c56eb7c5c3c5c5fddf975106fa4891268b5feffffff01fc4e7dd7446a800cf382e1f668fb155faa6c0f31f9871cdc54d801b64edbcc000000006b4830450221008b55df7a57529004a0cdf6b4ff43be097f9451c86a8457f073dd5cbab3a60f3002205c5b68ed70901e235402c850bec7ca3fac7448faf5a6989de78c2215734a7de60121034e448205a98486cb2fcc0294fa88579b6a6ba0213cbcf7e68cb9527a63a5c826feffffff028cb45e00000000001976a914cf50f0c1cecf279dac4207cd5e77caf2e1b4193f88ac2989f809000000001976a914c646b9ce369333f0b174bf70ffe169464c81776a88ac3eb00f000200000001ff6f6384099ff092fcc1257ebd2ba723fc33a1b3c858c8e4390180a071ed15aa010000006b483045022100d6e867d1c5d815552d1efa4ec06b0111d8b180d708f3497bcd53e8d945aebdbb02205fa734c5a3bb18da893da3dcd9540a2ea7535418323e8c0c1b0a88cc932a1fe5012102e03200f8de25214eaf675554a2f28d2b60c7c4c545691729dec1c2fe0d3b630bfeffffff023d346901000000001976a914953ba93fae9ab552793533769a7de6f7630781e688ac56870603000000001976a914887933e453428b99b3b2dfdd8359f6d8016873e688ac39b00f0002000000082e51598a94eea6059479fb3b31605bbc44ce50c25f014e91bd8bdd5b14891442000000006b483045022100dd04fefab5ad17929d1ed8561335a7c493761e0cbf6a23b8445d21da7b09bd4b0220398f2df4d47bc0b924ce71754d452268485b85ab3f1a8e21529559b4302003510121036b56e0f8996c289842752bc71eb60f2f9964b28653d3857725efe1bdc3193386feffffffacd131246ada5f7ad177bdc6d236766ff78559506a5da90e81fcb26366ac377b010000006a47304402202932864432b51a9894128b74fc109951d182fc91bac3a67a61b3b7a83e3bea71022021dd28448b54bf560dca69c198cdd9a0d1184adb231c72141db19d5114559ffd012102102efafa2ca31499090aaea1bf1dfd91f16bf29c96202faf48cdf14a791f757cfeffffffecba1b7c9071c42ff79e5db479369d5adb8995ec1ea28ba75676d58c09c10089000000006b48304502210096b33a9a65cbeca58cdd0ca01d4792dd075ac832aecb2d113c522bc635f535bd0220736d726b6126c4845335ad61af1e7e193d7d8661ca69c749ca257191bd24447f01210357386ba93caf20c1fee3bccf00cd4ad96535e42bda22d22c605cf3cf7532dde4feffffff0e7e7a7cfffa2c6917dc5578eac7153b334d7f813d256ccea9e60d1eb9f62da3000000006a4730440220362935ea1a705d4c275d733e350f53798f05e8a39e6cdd03f3ba5434c6d78c370220031729d52d9f8d1a301ce59335af7a5b7fa70f0bcae42dd95254e22a725bd9d4012102f7164234b08f8a6f47d92fa01eac375d0c1ebd4300ce40412302a5ea0218c19afeffffffa0f6f00c4b99b7b25ef20e64ce4a4bf5e2ca13e3cdbe8e1d7ec7dfab1e0292ab000000006a473044022077a9137654cafd4a583ae5b04faf1d549ab5d1d0655e6b48b4e1242a596efca4022008372fc1bec3490c4376408d4c9eed32b4182563ba43943b4bb959c29c91058a0121022bd6d141f0c455c7f54cb690db6b1349223ddd27fb65d64bb9ac35735ef48c5bfeffffffab6cf7419933410d25538a971b13d099e155c0339c2257f350ab2ef24944c1b9000000006b483045022100dd7b537e334e3dac76c57eb0683ea4cd74fd9a59f55f928f8c7f94a19064d8ea022045f4452f3742bb6ee2a7080507433904f5fcb94ef9ae8aa6bac0778a9889f4ef012102d00a705fcd458bc3e6dc0eab9f449162f73fff0773a4e09e2a3164b39bc26983feffffffc817f12e090dfe54e5c0bb1546fb01a6eac23f49aafa367bbbb0dc857bc98dc9000000006b483045022100959e19c75158f235abd196643dff70762f6263a058ed4b7d19c00f20ba18fef00220402189f2c8dc56139812d65c76b2b1a90b5148d7ecda6326053afc26471f9261012103330db7800ba95f92006909aba0e270985cccb34c7787dbf4776b6762f97132abfeffffff540f639ab8f986bc0832181c9efd315a1af81d07e61b367cdb10cab58f9518ff000000006b48304502210083db93b6724fe929e984f3e5df3ece7c43277a926abc841cd4cd75d15d787de0022027a299e82e420d418da973f82afcd80cf986c022042d9c758f51af64aa08fb2e01210369eccadf5758ca5eac2f3ce63001294819c119d1411a294135280cfd423ac59dfeffffff028f650f00000000001976a914535960e621cc458918703e33a7644501299a9d2188ace5a2ea01000000001976a914dcdc4137f2e7a347fcb14c85a6d1730d3e212c4b88ac00000000
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,363 @@
|
|||
package dcr
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"math"
|
||||
"math/big"
|
||||
"strconv"
|
||||
|
||||
"github.com/decred/dcrd/chaincfg/chainhash"
|
||||
cfg "github.com/decred/dcrd/chaincfg/v3"
|
||||
"github.com/decred/dcrd/dcrec"
|
||||
"github.com/decred/dcrd/dcrutil/v3"
|
||||
"github.com/decred/dcrd/hdkeychain/v3"
|
||||
"github.com/decred/dcrd/txscript/v3"
|
||||
"github.com/juju/errors"
|
||||
"github.com/martinboehm/btcd/wire"
|
||||
"github.com/martinboehm/btcutil/base58"
|
||||
"github.com/martinboehm/btcutil/chaincfg"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
// MainnetMagic is mainnet network constant
|
||||
MainnetMagic wire.BitcoinNet = 0xd9b400f9
|
||||
// TestnetMagic is testnet network constant
|
||||
TestnetMagic wire.BitcoinNet = 0xb194aa75
|
||||
)
|
||||
|
||||
var (
|
||||
// MainNetParams are parser parameters for mainnet
|
||||
MainNetParams chaincfg.Params
|
||||
// TestNet3Params are parser parameters for testnet
|
||||
TestNet3Params chaincfg.Params
|
||||
)
|
||||
|
||||
func init() {
|
||||
MainNetParams = chaincfg.MainNetParams
|
||||
MainNetParams.Net = MainnetMagic
|
||||
MainNetParams.PubKeyHashAddrID = []byte{0x07, 0x3f}
|
||||
MainNetParams.ScriptHashAddrID = []byte{0x07, 0x1a}
|
||||
|
||||
TestNet3Params = chaincfg.TestNet3Params
|
||||
TestNet3Params.Net = TestnetMagic
|
||||
TestNet3Params.PubKeyHashAddrID = []byte{0x0f, 0x21}
|
||||
TestNet3Params.ScriptHashAddrID = []byte{0x0e, 0xfc}
|
||||
}
|
||||
|
||||
// DecredParser handle
|
||||
type DecredParser struct {
|
||||
*btc.BitcoinParser
|
||||
baseParser *bchain.BaseParser
|
||||
netConfig *cfg.Params
|
||||
}
|
||||
|
||||
// NewDecredParser returns new DecredParser instance
|
||||
func NewDecredParser(params *chaincfg.Params, c *btc.Configuration) *DecredParser {
|
||||
d := &DecredParser{
|
||||
BitcoinParser: btc.NewBitcoinParser(params, c),
|
||||
baseParser: &bchain.BaseParser{},
|
||||
}
|
||||
|
||||
switch d.BitcoinParser.Params.Name {
|
||||
case "testnet3":
|
||||
d.netConfig = cfg.TestNet3Params()
|
||||
default:
|
||||
d.netConfig = cfg.MainNetParams()
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
// GetChainParams contains network parameters for the main Decred network,
|
||||
// and the test Decred network.
|
||||
func GetChainParams(chain string) *chaincfg.Params {
|
||||
var param *chaincfg.Params
|
||||
|
||||
switch chain {
|
||||
case "testnet3":
|
||||
param = &TestNet3Params
|
||||
default:
|
||||
param = &MainNetParams
|
||||
}
|
||||
|
||||
if !chaincfg.IsRegistered(param) {
|
||||
if err := chaincfg.Register(param); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
return param
|
||||
}
|
||||
|
||||
// ParseBlock parses raw block to our Block struct.
|
||||
func (p *DecredParser) ParseBlock(b []byte) (*bchain.Block, error) {
|
||||
r := bytes.NewReader(b)
|
||||
h := wire.BlockHeader{}
|
||||
if err := h.Deserialize(r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if (h.Version & utils.VersionAuxpow) != 0 {
|
||||
if err := utils.SkipAuxpow(r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var w wire.MsgBlock
|
||||
if err := utils.DecodeTransactions(r, 0, wire.WitnessEncoding, &w); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
txs := make([]bchain.Tx, len(w.Transactions))
|
||||
for ti, t := range w.Transactions {
|
||||
txs[ti] = p.TxFromMsgTx(t, false)
|
||||
}
|
||||
|
||||
return &bchain.Block{
|
||||
BlockHeader: bchain.BlockHeader{
|
||||
Size: len(b),
|
||||
Time: h.Timestamp.Unix(),
|
||||
},
|
||||
Txs: txs,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ParseTxFromJson parses JSON message containing transaction and returns Tx struct
|
||||
func (p *DecredParser) ParseTxFromJson(jsonTx json.RawMessage) (*bchain.Tx, error) {
|
||||
var getTxResult GetTransactionResult
|
||||
if err := json.Unmarshal([]byte(jsonTx), &getTxResult.Result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
vins := make([]bchain.Vin, len(getTxResult.Result.Vin))
|
||||
for index, input := range getTxResult.Result.Vin {
|
||||
hexData := bchain.ScriptSig{}
|
||||
if input.ScriptSig != nil {
|
||||
hexData.Hex = input.ScriptSig.Hex
|
||||
}
|
||||
|
||||
vins[index] = bchain.Vin{
|
||||
Coinbase: input.Coinbase,
|
||||
Txid: input.Txid,
|
||||
Vout: input.Vout,
|
||||
ScriptSig: hexData,
|
||||
Sequence: input.Sequence,
|
||||
// Addresses: []string{},
|
||||
}
|
||||
}
|
||||
|
||||
vouts := make([]bchain.Vout, len(getTxResult.Result.Vout))
|
||||
for index, output := range getTxResult.Result.Vout {
|
||||
addr := output.ScriptPubKey.Addresses
|
||||
// If nulldata type found make asm field the address data.
|
||||
if output.ScriptPubKey.Type == "nulldata" {
|
||||
addr = []string{output.ScriptPubKey.Asm}
|
||||
}
|
||||
|
||||
vouts[index] = bchain.Vout{
|
||||
ValueSat: *big.NewInt(int64(math.Round(output.Value * 1e8))),
|
||||
N: output.N,
|
||||
ScriptPubKey: bchain.ScriptPubKey{
|
||||
Hex: output.ScriptPubKey.Hex,
|
||||
Addresses: addr,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
tx := &bchain.Tx{
|
||||
Hex: getTxResult.Result.Hex,
|
||||
Txid: getTxResult.Result.Txid,
|
||||
Version: getTxResult.Result.Version,
|
||||
LockTime: getTxResult.Result.LockTime,
|
||||
BlockHeight: getTxResult.Result.BlockHeight,
|
||||
Vin: vins,
|
||||
Vout: vouts,
|
||||
Confirmations: uint32(getTxResult.Result.Confirmations),
|
||||
Time: getTxResult.Result.Time,
|
||||
Blocktime: getTxResult.Result.Blocktime,
|
||||
}
|
||||
|
||||
tx.CoinSpecificData = getTxResult.Result.TxExtraInfo
|
||||
|
||||
return tx, nil
|
||||
}
|
||||
|
||||
// GetAddrDescForUnknownInput returns nil AddressDescriptor.
|
||||
func (p *DecredParser) GetAddrDescForUnknownInput(tx *bchain.Tx, input int) bchain.AddressDescriptor {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAddrDescFromAddress returns internal address representation of a given address.
|
||||
func (p *DecredParser) GetAddrDescFromAddress(address string) (bchain.AddressDescriptor, error) {
|
||||
addressByte := []byte(address)
|
||||
return bchain.AddressDescriptor(addressByte), nil
|
||||
}
|
||||
|
||||
// GetAddrDescFromVout returns internal address representation of a given transaction output.
|
||||
func (p *DecredParser) GetAddrDescFromVout(output *bchain.Vout) (bchain.AddressDescriptor, error) {
|
||||
script, err := hex.DecodeString(output.ScriptPubKey.Hex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
const scriptVersion = 0
|
||||
const treasuryEnabled = true
|
||||
scriptClass, addresses, _, err := txscript.ExtractPkScriptAddrs(scriptVersion, script,
|
||||
p.netConfig, treasuryEnabled)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if scriptClass.String() == "nulldata" {
|
||||
if parsedOPReturn := p.BitcoinParser.TryParseOPReturn(script); parsedOPReturn != "" {
|
||||
return []byte(parsedOPReturn), nil
|
||||
}
|
||||
}
|
||||
|
||||
var addressByte []byte
|
||||
for i := range addresses {
|
||||
addressByte = append(addressByte, addresses[i].String()...)
|
||||
}
|
||||
return bchain.AddressDescriptor(addressByte), nil
|
||||
}
|
||||
|
||||
// GetAddressesFromAddrDesc returns addresses obtained from the internal address representation
|
||||
func (p *DecredParser) GetAddressesFromAddrDesc(addrDesc bchain.AddressDescriptor) ([]string, bool, error) {
|
||||
var addrs []string
|
||||
if addrDesc != nil {
|
||||
addrs = append(addrs, string(addrDesc))
|
||||
}
|
||||
return addrs, true, nil
|
||||
}
|
||||
|
||||
// PackTx packs transaction to byte array using protobuf
|
||||
func (p *DecredParser) PackTx(tx *bchain.Tx, height uint32, blockTime int64) ([]byte, error) {
|
||||
return p.baseParser.PackTx(tx, height, blockTime)
|
||||
}
|
||||
|
||||
// UnpackTx unpacks transaction from protobuf byte array
|
||||
func (p *DecredParser) UnpackTx(buf []byte) (*bchain.Tx, uint32, error) {
|
||||
return p.baseParser.UnpackTx(buf)
|
||||
}
|
||||
|
||||
func (p *DecredParser) addrDescFromExtKey(extKey *hdkeychain.ExtendedKey) (bchain.AddressDescriptor, error) {
|
||||
pk := extKey.SerializedPubKey()
|
||||
hash := dcrutil.Hash160(pk)
|
||||
addr, err := dcrutil.NewAddressPubKeyHash(hash, p.netConfig, dcrec.STEcdsaSecp256k1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p.GetAddrDescFromAddress(addr.String())
|
||||
}
|
||||
|
||||
// DeriveAddressDescriptors derives address descriptors from given xpub for
|
||||
// listed indexes
|
||||
func (p *DecredParser) DeriveAddressDescriptors(xpub string, change uint32,
|
||||
indexes []uint32) ([]bchain.AddressDescriptor, error) {
|
||||
extKey, err := hdkeychain.NewKeyFromString(xpub, p.netConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
changeExtKey, err := extKey.Child(change)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ad := make([]bchain.AddressDescriptor, len(indexes))
|
||||
for i, index := range indexes {
|
||||
indexExtKey, err := changeExtKey.Child(index)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ad[i], err = p.addrDescFromExtKey(indexExtKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return ad, nil
|
||||
}
|
||||
|
||||
// DeriveAddressDescriptorsFromTo derives address descriptors from given xpub for
|
||||
// addresses in index range
|
||||
func (p *DecredParser) DeriveAddressDescriptorsFromTo(xpub string, change uint32,
|
||||
fromIndex uint32, toIndex uint32) ([]bchain.AddressDescriptor, error) {
|
||||
if toIndex <= fromIndex {
|
||||
return nil, errors.New("toIndex<=fromIndex")
|
||||
}
|
||||
extKey, err := hdkeychain.NewKeyFromString(xpub, p.netConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
changeExtKey, err := extKey.Child(change)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ad := make([]bchain.AddressDescriptor, toIndex-fromIndex)
|
||||
for index := fromIndex; index < toIndex; index++ {
|
||||
indexExtKey, err := changeExtKey.Child(index)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ad[index-fromIndex], err = p.addrDescFromExtKey(indexExtKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return ad, nil
|
||||
}
|
||||
|
||||
// DerivationBasePath returns base path of xpub which whose full format is
|
||||
// m/44'/<coin type>'/<account>'/<branch>/<address index>. This function only
|
||||
// returns a path up to m/44'/<coin type>'/<account>'/ whereby the rest of the
|
||||
// other details (<branch>/<address index>) are populated automatically.
|
||||
func (p *DecredParser) DerivationBasePath(xpub string) (string, error) {
|
||||
var c string
|
||||
cn, depth, err := p.decodeXpub(xpub)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if cn >= hdkeychain.HardenedKeyStart {
|
||||
cn -= hdkeychain.HardenedKeyStart
|
||||
c = "'"
|
||||
}
|
||||
|
||||
c = strconv.Itoa(int(cn)) + c
|
||||
if depth != 3 {
|
||||
return "unknown/" + c, nil
|
||||
}
|
||||
|
||||
return "m/44'/" + strconv.Itoa(int(p.Slip44)) + "'/" + c, nil
|
||||
}
|
||||
|
||||
func (p *DecredParser) decodeXpub(xpub string) (childNum uint32, depth uint16, err error) {
|
||||
decoded := base58.Decode(xpub)
|
||||
|
||||
// serializedKeyLen is the length of a serialized public or private
|
||||
// extended key. It consists of 4 bytes version, 1 byte depth, 4 bytes
|
||||
// fingerprint, 4 bytes child number, 32 bytes chain code, and 33 bytes
|
||||
// public/private key data.
|
||||
serializedKeyLen := 4 + 1 + 4 + 4 + 32 + 33 // 78 bytes
|
||||
if len(decoded) != serializedKeyLen+4 {
|
||||
err = errors.New("invalid extended key length")
|
||||
return
|
||||
}
|
||||
|
||||
payload := decoded[:len(decoded)-4]
|
||||
checkSum := decoded[len(decoded)-4:]
|
||||
expectedCheckSum := chainhash.HashB(chainhash.HashB(payload))[:4]
|
||||
if !bytes.Equal(checkSum, expectedCheckSum) {
|
||||
err = errors.New("bad checksum value")
|
||||
return
|
||||
}
|
||||
|
||||
depth = uint16(payload[4:5][0])
|
||||
childNum = binary.BigEndian.Uint32(payload[9:13])
|
||||
return
|
||||
}
|
|
@ -0,0 +1,537 @@
|
|||
// +build unittest
|
||||
|
||||
package dcr
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"math/big"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
|
||||
)
|
||||
|
||||
var (
|
||||
testnetParser, mainnetParser *DecredParser
|
||||
|
||||
testTx1 = bchain.Tx{
|
||||
Hex: "01000000012372568fe80d2f9b2ab17226158dd5732d9926dc705371eaf40ab748c9e3d9720200000001ffffffff02644b252d0000000000001976a914a862f83733cc368f386a651e03d844a5bd6116d588acacdf63090000000000001976a91491dc5d18370939b3414603a0729bcb3a38e4ef7688ac000000000000000001e48d893600000000bb3d0000020000006a4730440220378e1442cc17fa7e49184518713eedd30e13e42147e077859557da6ffbbd40c702205f85563c28b6287f9c9110e6864dd18acfd92d85509ea846913c28b6e8a7f940012102bbbd7aadef33f2d2bdd9b0c5ba278815f5d66a6a01d2c019fb73f697662038b5",
|
||||
Blocktime: 1535632670,
|
||||
Time: 1535632670,
|
||||
Txid: "132acb5b474b45b830f7961c91c87e53cce3a37a6c6f0b0933ccdf0395c81a6a",
|
||||
LockTime: 0,
|
||||
Version: 1,
|
||||
Vin: []bchain.Vin{
|
||||
{
|
||||
Txid: "72d9e3c948b70af4ea715370dc26992d73d58d152672b12a9b2f0de88f567223",
|
||||
Vout: 2,
|
||||
Sequence: 4294967295,
|
||||
},
|
||||
},
|
||||
Vout: []bchain.Vout{
|
||||
{
|
||||
ValueSat: *big.NewInt(757418852),
|
||||
N: 0,
|
||||
ScriptPubKey: bchain.ScriptPubKey{
|
||||
Hex: "76a914a862f83733cc368f386a651e03d844a5bd6116d588ac",
|
||||
Addresses: []string{
|
||||
"TsgNUZKEnUhFASLESj7fVRTkgue3QR9TAeZ",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ValueSat: *big.NewInt(157540268),
|
||||
N: 1,
|
||||
ScriptPubKey: bchain.ScriptPubKey{
|
||||
Hex: "76a91491dc5d18370939b3414603a0729bcb3a38e4ef7688ac",
|
||||
Addresses: []string{
|
||||
"TseKNSWYbAzaGogpnNn25teTz53PTk3sgPu",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
testTx2 = bchain.Tx{
|
||||
Hex: "0100000001193c189c71dff482b70ccb10ec9cf0ea3421a7fc51e4c7b0cf59c98a293a2f960200000000ffffffff027c87f00b0000000000001976a91418f10131a859912119c4a8510199f87f0a4cec2488ac9889495f0000000000001976a914631fb783b1e06c3f6e71777e16da6de13450465e88ac0000000000000000015ced3d6b0000000030740000000000006a47304402204e6afc21f6d065b9c082dad81a5f29136320e2b54c6cdf6b8722e4507e1a8d8902203933c5e592df3b0bbb0568f121f48ef6cbfae9cf479a57229742b5780dedc57a012103b89bb443b6ab17724458285b302291b082c59e5a022f273af0f61d47a414a537",
|
||||
Txid: "7058766ffef2e9cee61ee4b7604a39bc91c3000cb951c4f93f3307f6e0bf4def",
|
||||
Blocktime: 1463843967,
|
||||
Time: 1463843967,
|
||||
LockTime: 0,
|
||||
Version: 1,
|
||||
Vin: []bchain.Vin{
|
||||
{
|
||||
Txid: "962f3a298ac959cfb0c7e451fca72134eaf09cec10cb0cb782f4df719c183c19",
|
||||
Vout: 2,
|
||||
Sequence: 4294967295,
|
||||
ScriptSig: bchain.ScriptSig{
|
||||
Hex: "47304402204e6afc21f6d065b9c082dad81a5f29136320e2b54c6cdf6b8722e4507e1a8d8902203933c5e592df3b0bbb0568f121f48ef6cbfae9cf479a57229742b5780dedc57a012103b89bb443b6ab17724458285b302291b082c59e5a022f273af0f61d47a414a537",
|
||||
},
|
||||
},
|
||||
},
|
||||
Vout: []bchain.Vout{
|
||||
{
|
||||
ValueSat: *big.NewInt(200312700),
|
||||
N: 0,
|
||||
ScriptPubKey: bchain.ScriptPubKey{
|
||||
Hex: "76a91418f10131a859912119c4a8510199f87f0a4cec2488ac",
|
||||
Addresses: []string{
|
||||
"DsTEnRLDEjQNeQ4A47fdS2pqtaFrGNzkqNa",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ValueSat: *big.NewInt(1598654872),
|
||||
N: 1,
|
||||
ScriptPubKey: bchain.ScriptPubKey{
|
||||
Hex: "76a914631fb783b1e06c3f6e71777e16da6de13450465e88ac",
|
||||
Addresses: []string{
|
||||
"Dsa12P9VnCd55hTnUXpvGgFKSeGkFkzRvYb",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
testTx3 = bchain.Tx{
|
||||
Hex: "0100000001c56d80756eaa7fc6e3542b29f596c60a9bcc959cf04d5f6e6b12749e241ece290200000001ffffffff02cf20b42d0000000000001976a9140799daa3cd36b44def220886802eb99e10c4a7c488ac0c25c7070000000000001976a9140b102deb3314213164cb6322211225365658407e88ac000000000000000001afa87b3500000000e33d0000000000006a47304402201ff342e5aa55b6030171f85729221ca0b81938826cc09449b77752e6e3b615be0220281e160b618e57326b95a0e0c3ac7a513bd041aba63cbace2f71919e111cfdba01210290a8de6665c8caac2bb8ca1aabd3dc09a334f997f97bd894772b1e51cab003d9",
|
||||
Blocktime: 1535638326,
|
||||
Time: 1535638326,
|
||||
Txid: "caf34c934d4c36b410c0265222b069f52e2df459ebb09d6797a635ceee0edd60",
|
||||
LockTime: 0,
|
||||
Version: 1,
|
||||
Vin: []bchain.Vin{
|
||||
{
|
||||
Txid: "29ce1e249e74126b6e5f4df09c95cc9b0ac696f5292b54e3c67faa6e75806dc5",
|
||||
Vout: 2,
|
||||
Sequence: 4294967295,
|
||||
},
|
||||
},
|
||||
Vout: []bchain.Vout{
|
||||
{
|
||||
ValueSat: *big.NewInt(766779599),
|
||||
N: 0,
|
||||
ScriptPubKey: bchain.ScriptPubKey{
|
||||
Hex: "76a9140799daa3cd36b44def220886802eb99e10c4a7c488ac",
|
||||
Addresses: []string{
|
||||
"TsRiKWsS9ucaqYDw9qhg6NukTthS5LwTRnv",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ValueSat: *big.NewInt(13049166),
|
||||
N: 1,
|
||||
ScriptPubKey: bchain.ScriptPubKey{
|
||||
Hex: "76a9140b102deb3314213164cb6322211225365658407e88ac",
|
||||
Addresses: []string{
|
||||
"TsS2dHqESY1vffjddpo1VMTbwLnDspfEj5W",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
testnetParser = NewDecredParser(GetChainParams("testnet3"), &btc.Configuration{Slip44: 1})
|
||||
mainnetParser = NewDecredParser(GetChainParams("mainnet"), &btc.Configuration{Slip44: 42})
|
||||
exitCode := m.Run()
|
||||
os.Exit(exitCode)
|
||||
}
|
||||
|
||||
func TestGetAddrDescFromAddress(t *testing.T) {
|
||||
type args struct {
|
||||
address string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "P2PKH",
|
||||
args: args{address: "TcrypGAcGCRVXrES7hWqVZb5oLJKCZEtoL1"},
|
||||
want: "5463727970474163474352565872455337685771565a62356f4c4a4b435a45746f4c31",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "P2PKH",
|
||||
args: args{address: "TsfDLrRkk9ciUuwfp2b8PawwnukYD7yAjGd"},
|
||||
want: "547366444c72526b6b3963695575776670326238506177776e756b59443779416a4764",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "P2PKH",
|
||||
args: args{address: "TsTevp3WYTiV3X1qjvZqa7nutuTqt5VNeoU"},
|
||||
want: "547354657670335759546956335831716a765a7161376e75747554717435564e656f55",
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := testnetParser.GetAddrDescFromAddress(tt.args.address)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Fatalf("GetAddrDescFromAddress() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
h := hex.EncodeToString(got)
|
||||
if !reflect.DeepEqual(h, tt.want) {
|
||||
t.Errorf("GetAddrDescFromAddress() = %v, want %v", h, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetAddrDescFromVout(t *testing.T) {
|
||||
type args struct {
|
||||
vout bchain.Vout
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "P2PK",
|
||||
args: args{vout: bchain.Vout{ScriptPubKey: bchain.ScriptPubKey{Hex: "76a914936f3a56a2dd0fb3bfde6bc820d4643e1701542a88ac"}}},
|
||||
want: "54736554683431516f356b594c3337614c474d535167346e67636f71396a7a44583659",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "P2PK",
|
||||
args: args{vout: bchain.Vout{ScriptPubKey: bchain.ScriptPubKey{Hex: "76a9144b31f712b03837b1303cddcb1ae9abd98da44f1088ac"}}},
|
||||
want: "547358736a3161747744736455746e354455576b666f6d5a586e4a6151467862395139",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "P2PK",
|
||||
args: args{vout: bchain.Vout{ScriptPubKey: bchain.ScriptPubKey{Hex: "76a9140d85a1d3f77383eb3dacfd83c46e2c7915aba91d88ac"}}},
|
||||
want: "54735346644c79657942776e68486978737367784b34546f4664763876525931793871",
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := testnetParser.GetAddrDescFromVout(&tt.args.vout)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Fatalf("GetAddrDescFromVout() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
h := hex.EncodeToString(got)
|
||||
if !reflect.DeepEqual(h, tt.want) {
|
||||
t.Errorf("GetAddrDescFromVout() = %v, want %v", h, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetAddressesFromAddrDesc(t *testing.T) {
|
||||
type args struct {
|
||||
script string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []string
|
||||
want2 bool
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "P2PKH",
|
||||
args: args{script: "5463727970474163474352565872455337685771565a62356f4c4a4b435a45746f4c31"},
|
||||
want: []string{"TcrypGAcGCRVXrES7hWqVZb5oLJKCZEtoL1"},
|
||||
want2: true,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "P2PKH",
|
||||
args: args{script: "547366444c72526b6b3963695575776670326238506177776e756b59443779416a4764"},
|
||||
want: []string{"TsfDLrRkk9ciUuwfp2b8PawwnukYD7yAjGd"},
|
||||
want2: true,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "P2PKH",
|
||||
args: args{script: "547354657670335759546956335831716a765a7161376e75747554717435564e656f55"},
|
||||
want: []string{"TsTevp3WYTiV3X1qjvZqa7nutuTqt5VNeoU"},
|
||||
want2: true,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
b, _ := hex.DecodeString(tt.args.script)
|
||||
got, got2, err := testnetParser.GetAddressesFromAddrDesc(b)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Fatalf("GetAddressesFromAddrDesc() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("GetAddressesFromAddrDesc() = %v, want %v", got, tt.want)
|
||||
}
|
||||
if !reflect.DeepEqual(got2, tt.want2) {
|
||||
t.Errorf("GetAddressesFromAddrDesc() = %v, want %v", got2, tt.want2)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeriveAddressDescriptors(t *testing.T) {
|
||||
type args struct {
|
||||
xpub string
|
||||
change uint32
|
||||
indexes []uint32
|
||||
parser *DecredParser
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "m/44'/42'/0'",
|
||||
args: args{
|
||||
xpub: "dpubZFYFpu8cZxwrApmtot59LZLChk5JcdB8xCxVQ4pcsTig4fscH3EfAkhxcKKhXBQH6SGyYs2VDidoomA5qukTWMaHDkBsAtnpodAHm61ozbD",
|
||||
change: 0,
|
||||
indexes: []uint32{0, 5},
|
||||
parser: mainnetParser,
|
||||
},
|
||||
want: []string{"DsUPx4NgAJzUQFRXnn2XZnWwEeQkQpwhqFD", "DsaT4kaGCeJU1Fef721J2DNt8UgcrmE2UsD"},
|
||||
},
|
||||
{
|
||||
name: "m/44'/42'/1'",
|
||||
args: args{
|
||||
xpub: "dpubZFYFpu8cZxwrESo75eazNjVHtC4nWJqL5aXxExZHKnyvZxKirkpypbgeJhVzhTdfnK2986DLjich4JQqcSaSyxu5KSoZ25KJ67j4mQJ9iqx",
|
||||
change: 0,
|
||||
indexes: []uint32{0, 5},
|
||||
parser: mainnetParser,
|
||||
},
|
||||
want: []string{"DsX5px9k9XZKFNP2Z9kyZBbfHgecm1ftNz6", "Dshjbo35CSWwNo7xMgG7UM8AWykwEjJ5DCP"},
|
||||
},
|
||||
{
|
||||
name: "m/44'/1'/0'",
|
||||
args: args{
|
||||
xpub: "tpubVossdTiJthe9xZZ5rz47szxN6ncpLJ4XmtJS26hKciDUPtboikdwHKZPWfo4FWYuLRZ6MNkLjyPRKhxqjStBTV2BE1LCULznpqsFakkPfPr",
|
||||
change: 0,
|
||||
indexes: []uint32{0, 2},
|
||||
parser: testnetParser,
|
||||
},
|
||||
want: []string{"TsboBwzpaH831s9J63XDcDx5GbKLcwv9ujo", "TsXrNt9nP3kBUM2Wr3rQGoPrpL7RMMSJyJH"},
|
||||
},
|
||||
{
|
||||
name: "m/44'/1'/1'",
|
||||
args: args{
|
||||
xpub: "tpubVossdTiJtheA1fQniKn9EN1JE1Eq1kBofaq2KwywrvuNhAk1KsEM7J2r8anhMJUmmcn9Wmoh73EctpW7Vxs3gS8cbF7N3m4zVjzuyvBj3qC",
|
||||
change: 0,
|
||||
indexes: []uint32{0, 3},
|
||||
parser: testnetParser,
|
||||
},
|
||||
want: []string{"TsndBjzcwZVjoZEuqYKwiMbCJH9QpkEekg4", "TsbrkVdFciW3Lfh1W8qjwRY9uSbdiBmY4VP"},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := tt.args.parser.DeriveAddressDescriptors(tt.args.xpub, tt.args.change, tt.args.indexes)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("DeriveAddressDescriptorsFromTo() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
gotAddresses := make([]string, len(got))
|
||||
for i, ad := range got {
|
||||
aa, _, err := tt.args.parser.GetAddressesFromAddrDesc(ad)
|
||||
if err != nil || len(aa) != 1 {
|
||||
t.Errorf("DeriveAddressDescriptorsFromTo() got incorrect address descriptor %v, error %v", ad, err)
|
||||
return
|
||||
}
|
||||
gotAddresses[i] = aa[0]
|
||||
}
|
||||
if !reflect.DeepEqual(gotAddresses, tt.want) {
|
||||
t.Errorf("DeriveAddressDescriptorsFromTo() = %v, want %v", gotAddresses, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeriveAddressDescriptorsFromTo(t *testing.T) {
|
||||
type args struct {
|
||||
xpub string
|
||||
change uint32
|
||||
fromIndex uint32
|
||||
toIndex uint32
|
||||
parser *DecredParser
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "m/44'/42'/2'",
|
||||
args: args{
|
||||
xpub: "dpubZFYFpu8cZxwrGnWbdHmvsAcTaMve4W9EAUiSHzXp1c5hQvfeWgk7LxsE5LqopwfxV62CoB51fxw97YaNpdA3tdo4GHbLxtUzRmYcUtVPYUi",
|
||||
change: 0,
|
||||
fromIndex: 0,
|
||||
toIndex: 1,
|
||||
parser: mainnetParser,
|
||||
},
|
||||
want: []string{"Dshtd1N7pKw814wgWXUq5qFVC5ENQ9oSGK7"},
|
||||
},
|
||||
{
|
||||
name: "m/44'/42'/1'",
|
||||
args: args{
|
||||
xpub: "dpubZFYFpu8cZxwrESo75eazNjVHtC4nWJqL5aXxExZHKnyvZxKirkpypbgeJhVzhTdfnK2986DLjich4JQqcSaSyxu5KSoZ25KJ67j4mQJ9iqx",
|
||||
change: 0,
|
||||
fromIndex: 0,
|
||||
toIndex: 1,
|
||||
parser: mainnetParser,
|
||||
},
|
||||
want: []string{"DsX5px9k9XZKFNP2Z9kyZBbfHgecm1ftNz6"},
|
||||
},
|
||||
{
|
||||
name: "m/44'/1'/2'",
|
||||
args: args{
|
||||
xpub: "tpubVossdTiJtheA51AuNQZtqvKUbhM867Von8XBadxX3tRkDm71kyyi6U966jDPEw9RnQjNcQLwxYSnQ9kBjZxrxfmSbByRbz7D1PLjgAPmL42",
|
||||
change: 0,
|
||||
fromIndex: 0,
|
||||
toIndex: 1,
|
||||
parser: testnetParser,
|
||||
},
|
||||
want: []string{"TsSpo87rBG21PLvvbzFk2Ust2Dbyvjfn8pQ"},
|
||||
},
|
||||
{
|
||||
name: "m/44'/1'/1'",
|
||||
args: args{
|
||||
xpub: "tpubVossdTiJtheA1fQniKn9EN1JE1Eq1kBofaq2KwywrvuNhAk1KsEM7J2r8anhMJUmmcn9Wmoh73EctpW7Vxs3gS8cbF7N3m4zVjzuyvBj3qC",
|
||||
change: 0,
|
||||
fromIndex: 0,
|
||||
toIndex: 5,
|
||||
parser: testnetParser,
|
||||
},
|
||||
want: []string{"TsndBjzcwZVjoZEuqYKwiMbCJH9QpkEekg4", "TshWHbnPAVCDARTcCfTEQyL9SzeHxxexX4J", "TspE6pMdC937UHHyfYJpTiKi6vPj5rVnWiG",
|
||||
"TsbrkVdFciW3Lfh1W8qjwRY9uSbdiBmY4VP", "TsagMXjC4Xj6ckPEJh8f1RKHU4cEzTtdVW6"},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := tt.args.parser.DeriveAddressDescriptorsFromTo(tt.args.xpub, tt.args.change, tt.args.fromIndex, tt.args.toIndex)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("DeriveAddressDescriptorsFromTo() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
gotAddresses := make([]string, len(got))
|
||||
for i, ad := range got {
|
||||
aa, _, err := tt.args.parser.GetAddressesFromAddrDesc(ad)
|
||||
if err != nil || len(aa) != 1 {
|
||||
t.Errorf("DeriveAddressDescriptorsFromTo() got incorrect address descriptor %v, error %v", ad, err)
|
||||
return
|
||||
}
|
||||
gotAddresses[i] = aa[0]
|
||||
}
|
||||
if !reflect.DeepEqual(gotAddresses, tt.want) {
|
||||
t.Errorf("DeriveAddressDescriptorsFromTo() = %v, want %v", gotAddresses, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDerivationBasePath(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
xpub string
|
||||
parser *DecredParser
|
||||
}{
|
||||
{
|
||||
name: "m/44'/42'/2'",
|
||||
xpub: "dpubZFYFpu8cZxwrGnWbdHmvsAcTaMve4W9EAUiSHzXp1c5hQvfeWgk7LxsE5LqopwfxV62CoB51fxw97YaNpdA3tdo4GHbLxtUzRmYcUtVPYUi",
|
||||
parser: mainnetParser,
|
||||
},
|
||||
{
|
||||
name: "m/44'/42'/1'",
|
||||
xpub: "dpubZFYFpu8cZxwrESo75eazNjVHtC4nWJqL5aXxExZHKnyvZxKirkpypbgeJhVzhTdfnK2986DLjich4JQqcSaSyxu5KSoZ25KJ67j4mQJ9iqx",
|
||||
parser: mainnetParser,
|
||||
},
|
||||
{
|
||||
name: "m/44'/1'/2'",
|
||||
xpub: "tpubVossdTiJtheA51AuNQZtqvKUbhM867Von8XBadxX3tRkDm71kyyi6U966jDPEw9RnQjNcQLwxYSnQ9kBjZxrxfmSbByRbz7D1PLjgAPmL42",
|
||||
parser: testnetParser,
|
||||
},
|
||||
{
|
||||
name: "m/44'/1'/1'",
|
||||
xpub: "tpubVossdTiJtheA1fQniKn9EN1JE1Eq1kBofaq2KwywrvuNhAk1KsEM7J2r8anhMJUmmcn9Wmoh73EctpW7Vxs3gS8cbF7N3m4zVjzuyvBj3qC",
|
||||
parser: testnetParser,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := tt.parser.DerivationBasePath(tt.xpub)
|
||||
if err != nil {
|
||||
t.Errorf("DerivationBasePath() expected no error but got %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if got != tt.name {
|
||||
t.Errorf("DerivationBasePath() = %v, want %v", got, tt.name)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPackAndUnpack(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
txInfo *bchain.Tx
|
||||
height uint32
|
||||
parser *DecredParser
|
||||
}{
|
||||
{
|
||||
name: "Test_1",
|
||||
txInfo: &testTx1,
|
||||
height: 15819,
|
||||
parser: testnetParser,
|
||||
},
|
||||
{
|
||||
name: "Test_2",
|
||||
txInfo: &testTx2,
|
||||
height: 300000,
|
||||
parser: mainnetParser,
|
||||
},
|
||||
{
|
||||
name: "Test_3",
|
||||
txInfo: &testTx3,
|
||||
height: 15859,
|
||||
parser: testnetParser,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
packedTx, err := tt.parser.PackTx(tt.txInfo, tt.height, tt.txInfo.Blocktime)
|
||||
if err != nil {
|
||||
t.Errorf("PackTx() expected no error but got %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
unpackedtx, gotHeight, err := tt.parser.UnpackTx(packedTx)
|
||||
if err != nil {
|
||||
t.Errorf("PackTx() expected no error but got %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(tt.txInfo, unpackedtx) {
|
||||
t.Errorf("TestPackAndUnpack() expected the raw tx and the unpacked tx to match but they didn't")
|
||||
}
|
||||
|
||||
if gotHeight != tt.height {
|
||||
t.Errorf("TestPackAndUnpack() = got height %v, but want %v", gotHeight, tt.height)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,876 @@
|
|||
package dcr
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"net"
|
||||
"net/http"
|
||||
"runtime/debug"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/decred/dcrd/dcrjson/v3"
|
||||
"github.com/golang/glog"
|
||||
"github.com/juju/errors"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
|
||||
"spacecruft.org/spacecruft/blockbook/common"
|
||||
)
|
||||
|
||||
// voteBitYes defines the vote bit set when a given block validates the previous
|
||||
// block
|
||||
const voteBitYes = 0x0001
|
||||
|
||||
type DecredRPC struct {
|
||||
*btc.BitcoinRPC
|
||||
mtx sync.Mutex
|
||||
client http.Client
|
||||
rpcURL string
|
||||
rpcUser string
|
||||
bestBlock uint32
|
||||
rpcPassword string
|
||||
}
|
||||
|
||||
// NewDecredRPC returns new DecredRPC instance.
|
||||
func NewDecredRPC(config json.RawMessage, pushHandler func(bchain.NotificationType)) (bchain.BlockChain, error) {
|
||||
b, err := btc.NewBitcoinRPC(config, pushHandler)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var c btc.Configuration
|
||||
if err = json.Unmarshal(config, &c); err != nil {
|
||||
return nil, errors.Annotate(err, "Invalid configuration file")
|
||||
}
|
||||
|
||||
transport := &http.Transport{
|
||||
Dial: (&net.Dialer{KeepAlive: 600 * time.Second}).Dial,
|
||||
MaxIdleConns: 100,
|
||||
MaxIdleConnsPerHost: 100, // necessary to not to deplete ports
|
||||
}
|
||||
|
||||
d := &DecredRPC{
|
||||
BitcoinRPC: b.(*btc.BitcoinRPC),
|
||||
client: http.Client{Timeout: time.Duration(c.RPCTimeout) * time.Second, Transport: transport},
|
||||
rpcURL: c.RPCURL,
|
||||
rpcUser: c.RPCUser,
|
||||
rpcPassword: c.RPCPass,
|
||||
}
|
||||
|
||||
d.BitcoinRPC.RPCMarshaler = btc.JSONMarshalerV1{}
|
||||
d.BitcoinRPC.ChainConfig.SupportsEstimateSmartFee = false
|
||||
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// Initialize initializes DecredRPC instance.
|
||||
func (d *DecredRPC) Initialize() error {
|
||||
chainInfo, err := d.GetChainInfo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
chainName := chainInfo.Chain
|
||||
glog.Info("Chain name ", chainName)
|
||||
|
||||
params := GetChainParams(chainName)
|
||||
|
||||
// always create parser
|
||||
d.BitcoinRPC.Parser = NewDecredParser(params, d.BitcoinRPC.ChainConfig)
|
||||
|
||||
// parameters for getInfo request
|
||||
if params.Net == MainnetMagic {
|
||||
d.BitcoinRPC.Testnet = false
|
||||
d.BitcoinRPC.Network = "livenet"
|
||||
} else {
|
||||
d.BitcoinRPC.Testnet = true
|
||||
d.BitcoinRPC.Network = "testnet"
|
||||
}
|
||||
|
||||
glog.Info("rpc: block chain ", params.Name)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type Error struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type GenericCmd struct {
|
||||
ID int `json:"id"`
|
||||
Method string `json:"method"`
|
||||
Params []interface{} `json:"params,omitempty"`
|
||||
}
|
||||
|
||||
type GetBlockChainInfoResult struct {
|
||||
Error Error `json:"error"`
|
||||
Result struct {
|
||||
Chain string `json:"chain"`
|
||||
Blocks int64 `json:"blocks"`
|
||||
Headers int64 `json:"headers"`
|
||||
SyncHeight int64 `json:"syncheight"`
|
||||
BestBlockHash string `json:"bestblockhash"`
|
||||
Difficulty uint32 `json:"difficulty"`
|
||||
VerificationProgress float64 `json:"verificationprogress"`
|
||||
ChainWork string `json:"chainwork"`
|
||||
InitialBlockDownload bool `json:"initialblockdownload"`
|
||||
MaxBlockSize int64 `json:"maxblocksize"`
|
||||
} `json:"result"`
|
||||
}
|
||||
|
||||
type GetNetworkInfoResult struct {
|
||||
Error Error `json:"error"`
|
||||
Result struct {
|
||||
Version int32 `json:"version"`
|
||||
ProtocolVersion int32 `json:"protocolversion"`
|
||||
TimeOffset int64 `json:"timeoffset"`
|
||||
Connections int32 `json:"connections"`
|
||||
RelayFee float64 `json:"relayfee"`
|
||||
} `json:"result"`
|
||||
}
|
||||
|
||||
type GetInfoChainResult struct {
|
||||
Error Error `json:"error"`
|
||||
Result struct {
|
||||
Version int32 `json:"version"`
|
||||
ProtocolVersion int32 `json:"protocolversion"`
|
||||
Blocks int64 `json:"blocks"`
|
||||
TimeOffset int64 `json:"timeoffset"`
|
||||
Connections int32 `json:"connections"`
|
||||
Proxy string `json:"proxy"`
|
||||
Difficulty float64 `json:"difficulty"`
|
||||
TestNet bool `json:"testnet"`
|
||||
RelayFee float64 `json:"relayfee"`
|
||||
Errors string `json:"errors"`
|
||||
}
|
||||
}
|
||||
|
||||
type GetBestBlockResult struct {
|
||||
Error Error `json:"error"`
|
||||
Result struct {
|
||||
Hash string `json:"hash"`
|
||||
Height uint32 `json:"height"`
|
||||
} `json:"result"`
|
||||
}
|
||||
|
||||
type GetBlockHashResult struct {
|
||||
Error Error `json:"error"`
|
||||
Result string `json:"result"`
|
||||
}
|
||||
|
||||
type GetBlockResult struct {
|
||||
Error Error `json:"error"`
|
||||
Result struct {
|
||||
Hash string `json:"hash"`
|
||||
Confirmations int64 `json:"confirmations"`
|
||||
Size int32 `json:"size"`
|
||||
Height uint32 `json:"height"`
|
||||
Version common.JSONNumber `json:"version"`
|
||||
MerkleRoot string `json:"merkleroot"`
|
||||
StakeRoot string `json:"stakeroot"`
|
||||
RawTx []RawTx `json:"rawtx"`
|
||||
Tx []string `json:"tx,omitempty"`
|
||||
STx []string `json:"stx,omitempty"`
|
||||
Time int64 `json:"time"`
|
||||
Nonce common.JSONNumber `json:"nonce"`
|
||||
VoteBits uint16 `json:"votebits"`
|
||||
FinalState string `json:"finalstate"`
|
||||
Voters uint16 `json:"voters"`
|
||||
FreshStake uint8 `json:"freshstake"`
|
||||
Revocations uint8 `json:"revocations"`
|
||||
PoolSize uint32 `json:"poolsize"`
|
||||
Bits string `json:"bits"`
|
||||
SBits float64 `json:"sbits"`
|
||||
ExtraData string `json:"extradata"`
|
||||
StakeVersion uint32 `json:"stakeversion"`
|
||||
Difficulty float64 `json:"difficulty"`
|
||||
ChainWork string `json:"chainwork"`
|
||||
PreviousHash string `json:"previousblockhash"`
|
||||
NextHash string `json:"nextblockhash,omitempty"`
|
||||
} `json:"result"`
|
||||
}
|
||||
|
||||
type GetBlockHeaderResult struct {
|
||||
Error Error `json:"error"`
|
||||
Result struct {
|
||||
Hash string `json:"hash"`
|
||||
Confirmations int64 `json:"confirmations"`
|
||||
Version common.JSONNumber `json:"version"`
|
||||
MerkleRoot string `json:"merkleroot"`
|
||||
StakeRoot string `json:"stakeroot"`
|
||||
VoteBits uint16 `json:"votebits"`
|
||||
FinalState string `json:"finalstate"`
|
||||
Voters uint16 `json:"voters"`
|
||||
FreshStake uint8 `json:"freshstake"`
|
||||
Revocations uint8 `json:"revocations"`
|
||||
PoolSize uint32 `json:"poolsize"`
|
||||
Bits string `json:"bits"`
|
||||
SBits float64 `json:"sbits"`
|
||||
Height uint32 `json:"height"`
|
||||
Size uint32 `json:"size"`
|
||||
Time int64 `json:"time"`
|
||||
Nonce uint32 `json:"nonce"`
|
||||
ExtraData string `json:"extradata"`
|
||||
StakeVersion uint32 `json:"stakeversion"`
|
||||
Difficulty float64 `json:"difficulty"`
|
||||
ChainWork string `json:"chainwork"`
|
||||
PreviousHash string `json:"previousblockhash,omitempty"`
|
||||
NextHash string `json:"nextblockhash,omitempty"`
|
||||
} `json:"result"`
|
||||
}
|
||||
|
||||
type ScriptSig struct {
|
||||
Asm string `json:"asm"`
|
||||
Hex string `json:"hex"`
|
||||
}
|
||||
|
||||
type Vin struct {
|
||||
Coinbase string `json:"coinbase"`
|
||||
Stakebase string `json:"stakebase"`
|
||||
Txid string `json:"txid"`
|
||||
Vout uint32 `json:"vout"`
|
||||
Tree int8 `json:"tree"`
|
||||
Sequence uint32 `json:"sequence"`
|
||||
AmountIn float64 `json:"amountin"`
|
||||
BlockHeight uint32 `json:"blockheight"`
|
||||
BlockIndex uint32 `json:"blockindex"`
|
||||
ScriptSig *ScriptSig `json:"scriptsig"`
|
||||
}
|
||||
|
||||
type ScriptPubKeyResult struct {
|
||||
Asm string `json:"asm"`
|
||||
Hex string `json:"hex,omitempty"`
|
||||
ReqSigs int32 `json:"reqSigs,omitempty"`
|
||||
Type string `json:"type"`
|
||||
Addresses []string `json:"addresses,omitempty"`
|
||||
CommitAmt *float64 `json:"commitamt,omitempty"`
|
||||
}
|
||||
|
||||
type Vout struct {
|
||||
Value float64 `json:"value"`
|
||||
N uint32 `json:"n"`
|
||||
Version uint16 `json:"version"`
|
||||
ScriptPubKey ScriptPubKeyResult `json:"scriptPubKey"`
|
||||
}
|
||||
|
||||
type RawTx struct {
|
||||
Hex string `json:"hex"`
|
||||
Txid string `json:"txid"`
|
||||
Version int32 `json:"version"`
|
||||
LockTime uint32 `json:"locktime"`
|
||||
Vin []Vin `json:"vin"`
|
||||
Vout []Vout `json:"vout"`
|
||||
Expiry uint32 `json:"expiry"`
|
||||
BlockIndex uint32 `json:"blockindex,omitempty"`
|
||||
Confirmations int64 `json:"confirmations,omitempty"`
|
||||
Time int64 `json:"time,omitempty"`
|
||||
Blocktime int64 `json:"blocktime,omitempty"`
|
||||
TxExtraInfo
|
||||
}
|
||||
|
||||
type GetTransactionResult struct {
|
||||
Error Error `json:"error"`
|
||||
Result struct {
|
||||
RawTx
|
||||
} `json:"result"`
|
||||
}
|
||||
|
||||
type MempoolTxsResult struct {
|
||||
Error Error `json:"error"`
|
||||
Result []string `json:"result"`
|
||||
}
|
||||
|
||||
type EstimateSmartFeeResult struct {
|
||||
Error Error `json:"error"`
|
||||
Result struct {
|
||||
FeeRate float64 `json:"feerate"`
|
||||
Errors []string `json:"errors"`
|
||||
Blocks int64 `json:"blocks"`
|
||||
} `json:"result"`
|
||||
}
|
||||
|
||||
type EstimateFeeResult struct {
|
||||
Error Error `json:"error"`
|
||||
Result common.JSONNumber `json:"result"`
|
||||
}
|
||||
|
||||
type SendRawTransactionResult struct {
|
||||
Error Error `json:"error"`
|
||||
Result string `json:"result"`
|
||||
}
|
||||
|
||||
type DecodeRawTransactionResult struct {
|
||||
Error Error `json:"error"`
|
||||
Result struct {
|
||||
Txid string `json:"txid"`
|
||||
Version int32 `json:"version"`
|
||||
Locktime uint32 `json:"locktime"`
|
||||
Expiry uint32 `json:"expiry"`
|
||||
Vin []Vin `json:"vin"`
|
||||
Vout []Vout `json:"vout"`
|
||||
TxExtraInfo
|
||||
} `json:"result"`
|
||||
}
|
||||
|
||||
type TxExtraInfo struct {
|
||||
BlockHeight uint32 `json:"blockheight,omitempty"`
|
||||
BlockHash string `json:"blockhash,omitempty"`
|
||||
}
|
||||
|
||||
func (d *DecredRPC) GetChainInfo() (*bchain.ChainInfo, error) {
|
||||
blockchainInfoRequest := GenericCmd{
|
||||
ID: 1,
|
||||
Method: "getblockchaininfo",
|
||||
}
|
||||
|
||||
var blockchainInfoResult GetBlockChainInfoResult
|
||||
if err := d.Call(blockchainInfoRequest, &blockchainInfoResult); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if blockchainInfoResult.Error.Message != "" {
|
||||
return nil, mapToStandardErr("Error fetching blockchain info: %s", blockchainInfoResult.Error)
|
||||
}
|
||||
|
||||
infoChainRequest := GenericCmd{
|
||||
ID: 2,
|
||||
Method: "getinfo",
|
||||
}
|
||||
|
||||
var infoChainResult GetInfoChainResult
|
||||
if err := d.Call(infoChainRequest, &infoChainResult); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if infoChainResult.Error.Message != "" {
|
||||
return nil, mapToStandardErr("Error fetching network info: %s", infoChainResult.Error)
|
||||
}
|
||||
|
||||
chainInfo := &bchain.ChainInfo{
|
||||
Chain: blockchainInfoResult.Result.Chain,
|
||||
Blocks: int(blockchainInfoResult.Result.Blocks),
|
||||
Headers: int(blockchainInfoResult.Result.Headers),
|
||||
Bestblockhash: blockchainInfoResult.Result.BestBlockHash,
|
||||
Difficulty: strconv.Itoa(int(blockchainInfoResult.Result.Difficulty)),
|
||||
SizeOnDisk: blockchainInfoResult.Result.SyncHeight,
|
||||
Version: strconv.Itoa(int(infoChainResult.Result.Version)),
|
||||
Subversion: "",
|
||||
ProtocolVersion: strconv.Itoa(int(infoChainResult.Result.ProtocolVersion)),
|
||||
Timeoffset: float64(infoChainResult.Result.TimeOffset),
|
||||
Warnings: "",
|
||||
}
|
||||
return chainInfo, nil
|
||||
}
|
||||
|
||||
// getChainBestBlock returns the best block according to dcrd chain. This block
|
||||
// has no atleast one confirming block.
|
||||
func (d *DecredRPC) getChainBestBlock() (*GetBestBlockResult, error) {
|
||||
bestBlockRequest := GenericCmd{
|
||||
ID: 1,
|
||||
Method: "getbestblock",
|
||||
}
|
||||
|
||||
var bestBlockResult GetBestBlockResult
|
||||
if err := d.Call(bestBlockRequest, &bestBlockResult); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if bestBlockResult.Error.Message != "" {
|
||||
return nil, mapToStandardErr("Error fetching best block: %s", bestBlockResult.Error)
|
||||
}
|
||||
|
||||
return &bestBlockResult, nil
|
||||
}
|
||||
|
||||
// getBestBlock returns details for the block mined immediately before the
|
||||
// official dcrd chain's bestblock i.e. it has a minimum of 1 confirmation.
|
||||
// The chain's best block is not returned as its block validity is not guarranteed.
|
||||
func (d *DecredRPC) getBestBlock() (*GetBestBlockResult, error) {
|
||||
bestBlockResult, err := d.getChainBestBlock()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// remove the block with less than 1 confirming block
|
||||
bestBlockResult.Result.Height--
|
||||
validBlockHash, err := d.getBlockHashByHeight(bestBlockResult.Result.Height)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bestBlockResult.Result.Hash = validBlockHash.Result
|
||||
|
||||
return bestBlockResult, nil
|
||||
}
|
||||
|
||||
// GetBestBlockHash returns the block hash of the most recent block to be mined
|
||||
// and has a minimum of 1 confirming block.
|
||||
func (d *DecredRPC) GetBestBlockHash() (string, error) {
|
||||
bestBlock, err := d.getBestBlock()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return bestBlock.Result.Hash, nil
|
||||
}
|
||||
|
||||
// GetBestBlockHeight returns the block height of the most recent block to be mined
|
||||
// and has a minimum of 1 confirming block.
|
||||
func (d *DecredRPC) GetBestBlockHeight() (uint32, error) {
|
||||
bestBlock, err := d.getBestBlock()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return uint32(bestBlock.Result.Height), err
|
||||
}
|
||||
|
||||
// GetBlockHash returns the block hash of the block at the provided height.
|
||||
func (d *DecredRPC) GetBlockHash(height uint32) (string, error) {
|
||||
blockHashResult, err := d.getBlockHashByHeight(height)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return blockHashResult.Result, nil
|
||||
}
|
||||
|
||||
func (d *DecredRPC) getBlockHashByHeight(height uint32) (*GetBlockHashResult, error) {
|
||||
blockHashRequest := GenericCmd{
|
||||
ID: 1,
|
||||
Method: "getblockhash",
|
||||
Params: []interface{}{height},
|
||||
}
|
||||
|
||||
var blockHashResult GetBlockHashResult
|
||||
if err := d.Call(blockHashRequest, &blockHashResult); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if blockHashResult.Error.Message != "" {
|
||||
return nil, mapToStandardErr("Error fetching block hash: %s", blockHashResult.Error)
|
||||
}
|
||||
|
||||
return &blockHashResult, nil
|
||||
}
|
||||
|
||||
// GetBlockHeader returns the block header of the block the provided block hash.
|
||||
func (d *DecredRPC) GetBlockHeader(hash string) (*bchain.BlockHeader, error) {
|
||||
blockHeaderRequest := GenericCmd{
|
||||
ID: 1,
|
||||
Method: "getblockheader",
|
||||
Params: []interface{}{hash},
|
||||
}
|
||||
|
||||
var blockHeader GetBlockHeaderResult
|
||||
if err := d.Call(blockHeaderRequest, &blockHeader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if blockHeader.Error.Message != "" {
|
||||
return nil, mapToStandardErr("Error fetching block info: %s", blockHeader.Error)
|
||||
}
|
||||
|
||||
header := &bchain.BlockHeader{
|
||||
Hash: blockHeader.Result.Hash,
|
||||
Prev: blockHeader.Result.PreviousHash,
|
||||
Next: blockHeader.Result.NextHash,
|
||||
Height: blockHeader.Result.Height,
|
||||
Confirmations: int(blockHeader.Result.Confirmations),
|
||||
Size: int(blockHeader.Result.Size),
|
||||
Time: blockHeader.Result.Time,
|
||||
}
|
||||
|
||||
return header, nil
|
||||
}
|
||||
|
||||
// GetBlock returns the block retrieved using the provided block hash by default
|
||||
// or using the block height if an empty hash string was provided. If the
|
||||
// requested block has less than 2 confirmation bchain.ErrBlockNotFound error
|
||||
// is returned. This rule is in places to guarrantee that only validated block
|
||||
// details (txs) are saved to the db. Access to the bestBlock height is threadsafe.
|
||||
func (d *DecredRPC) GetBlock(hash string, height uint32) (*bchain.Block, error) {
|
||||
// Confirm if the block at provided height has at least 2 confirming blocks.
|
||||
d.mtx.Lock()
|
||||
if height > d.bestBlock {
|
||||
bestBlock, err := d.getBestBlock()
|
||||
if err != nil || height > bestBlock.Result.Height {
|
||||
// If an error occurred or the current height doesn't have a minimum
|
||||
// of two confirming blocks (greater than best block), quit.
|
||||
d.mtx.Unlock()
|
||||
return nil, bchain.ErrBlockNotFound
|
||||
}
|
||||
|
||||
d.bestBlock = bestBlock.Result.Height
|
||||
}
|
||||
d.mtx.Unlock() // Releases the lock soonest possible
|
||||
|
||||
if hash == "" {
|
||||
getHashResult, err := d.getBlockHashByHeight(height)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hash = getHashResult.Result
|
||||
}
|
||||
|
||||
block, err := d.getBlock(hash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
header := bchain.BlockHeader{
|
||||
Hash: block.Result.Hash,
|
||||
Prev: block.Result.PreviousHash,
|
||||
Next: block.Result.NextHash,
|
||||
Height: block.Result.Height,
|
||||
Confirmations: int(block.Result.Confirmations),
|
||||
Size: int(block.Result.Size),
|
||||
Time: block.Result.Time,
|
||||
}
|
||||
|
||||
bchainBlock := &bchain.Block{BlockHeader: header}
|
||||
|
||||
// Check the current block validity by fetch the next block
|
||||
nextBlockHashResult, err := d.getBlockHashByHeight(height + 1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nextBlock, err := d.getBlock(nextBlockHashResult.Result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// If the Votesbits set equals to voteBitYes append the regular transactions.
|
||||
if nextBlock.Result.VoteBits == voteBitYes {
|
||||
for _, txID := range block.Result.Tx {
|
||||
if block.Result.Height == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
tx, err := d.GetTransaction(txID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bchainBlock.Txs = append(bchainBlock.Txs, *tx)
|
||||
}
|
||||
}
|
||||
|
||||
return bchainBlock, nil
|
||||
}
|
||||
|
||||
func (d *DecredRPC) getBlock(hash string) (*GetBlockResult, error) {
|
||||
blockRequest := GenericCmd{
|
||||
ID: 1,
|
||||
Method: "getblock",
|
||||
Params: []interface{}{hash},
|
||||
}
|
||||
|
||||
var block GetBlockResult
|
||||
if err := d.Call(blockRequest, &block); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if block.Error.Message != "" {
|
||||
return nil, mapToStandardErr("Error fetching block info: %s", block.Error)
|
||||
}
|
||||
|
||||
return &block, nil
|
||||
}
|
||||
|
||||
func (d *DecredRPC) decodeRawTransaction(txHex string) (*bchain.Tx, error) {
|
||||
decodeRawTxRequest := GenericCmd{
|
||||
ID: 1,
|
||||
Method: "decoderawtransaction",
|
||||
Params: []interface{}{txHex},
|
||||
}
|
||||
|
||||
var decodeRawTxResult DecodeRawTransactionResult
|
||||
if err := d.Call(decodeRawTxRequest, &decodeRawTxResult); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if decodeRawTxResult.Error.Message != "" {
|
||||
return nil, mapToStandardErr("Error decoding raw tx: %s", decodeRawTxResult.Error)
|
||||
}
|
||||
|
||||
tx := &bchain.Tx{
|
||||
Hex: txHex,
|
||||
Txid: decodeRawTxResult.Result.Txid,
|
||||
Version: decodeRawTxResult.Result.Version,
|
||||
LockTime: decodeRawTxResult.Result.Locktime,
|
||||
}
|
||||
|
||||
// Add block height and block hash info
|
||||
tx.CoinSpecificData = decodeRawTxResult.Result.TxExtraInfo
|
||||
|
||||
return tx, nil
|
||||
}
|
||||
|
||||
func (d *DecredRPC) GetBlockInfo(hash string) (*bchain.BlockInfo, error) {
|
||||
block, err := d.getBlock(hash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
header := bchain.BlockHeader{
|
||||
Hash: block.Result.Hash,
|
||||
Prev: block.Result.PreviousHash,
|
||||
Next: block.Result.NextHash,
|
||||
Height: block.Result.Height,
|
||||
Confirmations: int(block.Result.Confirmations),
|
||||
Size: int(block.Result.Size),
|
||||
Time: int64(block.Result.Time),
|
||||
}
|
||||
|
||||
bInfo := &bchain.BlockInfo{
|
||||
BlockHeader: header,
|
||||
MerkleRoot: block.Result.MerkleRoot,
|
||||
Version: block.Result.Version,
|
||||
Nonce: block.Result.Nonce,
|
||||
Bits: block.Result.Bits,
|
||||
Difficulty: common.JSONNumber(strconv.FormatFloat(block.Result.Difficulty, 'e', -1, 64)),
|
||||
Txids: block.Result.Tx,
|
||||
}
|
||||
|
||||
return bInfo, nil
|
||||
}
|
||||
|
||||
// GetTransaction returns a transaction by the transaction ID
|
||||
func (d *DecredRPC) GetTransaction(txid string) (*bchain.Tx, error) {
|
||||
r, err := d.getRawTransaction(txid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tx, err := d.Parser.ParseTxFromJson(r)
|
||||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "txid %v", txid)
|
||||
}
|
||||
|
||||
return tx, nil
|
||||
}
|
||||
|
||||
// getRawTransaction returns json as returned by backend, with all coin specific data
|
||||
func (d *DecredRPC) getRawTransaction(txid string) (json.RawMessage, error) {
|
||||
if txid == "" {
|
||||
return nil, bchain.ErrTxidMissing
|
||||
}
|
||||
|
||||
verbose := 1
|
||||
getTxRequest := GenericCmd{
|
||||
ID: 1,
|
||||
Method: "getrawtransaction",
|
||||
Params: []interface{}{txid, &verbose},
|
||||
}
|
||||
|
||||
var getTxResult GetTransactionResult
|
||||
if err := d.Call(getTxRequest, &getTxResult); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if getTxResult.Error.Message != "" {
|
||||
return nil, mapToStandardErr("Error fetching transaction: %s", getTxResult.Error)
|
||||
}
|
||||
|
||||
bytes, err := json.Marshal(getTxResult.Result)
|
||||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "txid %v", txid)
|
||||
}
|
||||
|
||||
return json.RawMessage(bytes), nil
|
||||
}
|
||||
|
||||
// GetTransactionForMempool returns the full tx information identified by the
|
||||
// provided txid.
|
||||
func (d *DecredRPC) GetTransactionForMempool(txid string) (*bchain.Tx, error) {
|
||||
return d.GetTransaction(txid)
|
||||
}
|
||||
|
||||
// GetMempoolTransactions returns a slice of regular transactions currently in
|
||||
// the mempool. The block whose validation is still undecided will have its txs,
|
||||
// listed like they are still in the mempool till the block is confirmed.
|
||||
func (d *DecredRPC) GetMempoolTransactions() ([]string, error) {
|
||||
verbose := false
|
||||
txType := "regular"
|
||||
mempoolRequest := GenericCmd{
|
||||
ID: 1,
|
||||
Method: "getrawmempool",
|
||||
Params: []interface{}{&verbose, &txType},
|
||||
}
|
||||
|
||||
var mempool MempoolTxsResult
|
||||
if err := d.Call(mempoolRequest, &mempool); err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
|
||||
if mempool.Error.Message != "" {
|
||||
return nil, mapToStandardErr("Error fetching mempool data: %s", mempool.Error)
|
||||
}
|
||||
|
||||
unvalidatedBlockResult, err := d.getChainBestBlock()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
unvalidatedBlock, err := d.getBlock(unvalidatedBlockResult.Result.Hash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mempool.Result = append(mempool.Result, unvalidatedBlock.Result.Tx...)
|
||||
|
||||
return mempool.Result, nil
|
||||
}
|
||||
|
||||
// GetTransactionSpecific returns the json raw message for the tx identified by
|
||||
// the provided txid.
|
||||
func (d *DecredRPC) GetTransactionSpecific(tx *bchain.Tx) (json.RawMessage, error) {
|
||||
return d.getRawTransaction(tx.Txid)
|
||||
}
|
||||
|
||||
// EstimateSmartFee returns fee estimation
|
||||
func (d *DecredRPC) EstimateSmartFee(blocks int, conservative bool) (big.Int, error) {
|
||||
estimateSmartFeeRequest := GenericCmd{
|
||||
ID: 1,
|
||||
Method: "estimatesmartfee",
|
||||
Params: []interface{}{blocks},
|
||||
}
|
||||
|
||||
var smartFeeEstimate EstimateSmartFeeResult
|
||||
if err := d.Call(estimateSmartFeeRequest, &smartFeeEstimate); err != nil {
|
||||
return *big.NewInt(0), err
|
||||
}
|
||||
|
||||
if smartFeeEstimate.Error.Message != "" {
|
||||
return *big.NewInt(0), mapToStandardErr("Error fetching smart fee estimate: %s", smartFeeEstimate.Error)
|
||||
}
|
||||
|
||||
return *big.NewInt(int64(smartFeeEstimate.Result.FeeRate)), nil
|
||||
}
|
||||
|
||||
// EstimateFee returns fee estimation.
|
||||
func (d *DecredRPC) EstimateFee(blocks int) (big.Int, error) {
|
||||
estimateFeeRequest := GenericCmd{
|
||||
ID: 1,
|
||||
Method: "estimatefee",
|
||||
Params: []interface{}{blocks},
|
||||
}
|
||||
|
||||
var feeEstimate EstimateFeeResult
|
||||
if err := d.Call(estimateFeeRequest, &feeEstimate); err != nil {
|
||||
return *big.NewInt(0), err
|
||||
}
|
||||
|
||||
if feeEstimate.Error.Message != "" {
|
||||
return *big.NewInt(0), mapToStandardErr("Error fetching fee estimate: %s", feeEstimate.Error)
|
||||
}
|
||||
|
||||
r, err := d.Parser.AmountToBigInt(feeEstimate.Result)
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (d *DecredRPC) SendRawTransaction(tx string) (string, error) {
|
||||
sendRawTxRequest := &GenericCmd{
|
||||
ID: 1,
|
||||
Method: "sendrawtransaction",
|
||||
Params: []interface{}{tx},
|
||||
}
|
||||
|
||||
var sendRawTxResult SendRawTransactionResult
|
||||
err := d.Call(sendRawTxRequest, &sendRawTxResult)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if sendRawTxResult.Error.Message != "" {
|
||||
return "", mapToStandardErr("error sending raw transaction: %s", sendRawTxResult.Error)
|
||||
}
|
||||
|
||||
return sendRawTxResult.Result, nil
|
||||
}
|
||||
|
||||
// Call calls Backend RPC interface, using RPCMarshaler interface to marshall the request
|
||||
func (d *DecredRPC) Call(req interface{}, res interface{}) error {
|
||||
httpData, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
httpReq, err := http.NewRequest("POST", d.rpcURL, bytes.NewBuffer(httpData))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
httpReq.SetBasicAuth(d.rpcUser, d.rpcPassword)
|
||||
httpRes, err := d.client.Do(httpReq)
|
||||
// in some cases the httpRes can contain data even if it returns error
|
||||
// see http://devs.cloudimmunity.com/gotchas-and-common-mistakes-in-go-golang/
|
||||
if httpRes != nil {
|
||||
defer httpRes.Body.Close()
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// if server returns HTTP error code it might not return json with response
|
||||
// handle both cases
|
||||
if httpRes.StatusCode != 200 {
|
||||
if err = safeDecodeResponse(httpRes.Body, &res); err != nil {
|
||||
return errors.Errorf("%v %v", httpRes.Status, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return safeDecodeResponse(httpRes.Body, &res)
|
||||
}
|
||||
|
||||
func safeDecodeResponse(body io.ReadCloser, res *interface{}) (err error) {
|
||||
var data []byte
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
glog.Error("unmarshal json recovered from panic: ", r, "; data: ", string(data))
|
||||
debug.PrintStack()
|
||||
if len(data) > 0 && len(data) < 2048 {
|
||||
err = errors.Errorf("Error: %v", string(data))
|
||||
} else {
|
||||
err = errors.New("Internal error")
|
||||
}
|
||||
}
|
||||
}()
|
||||
data, err = ioutil.ReadAll(body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
error := json.Unmarshal(data, res)
|
||||
return error
|
||||
}
|
||||
|
||||
// mapToStandardErr map the dcrd API Message errors to the standard error messages
|
||||
// supported by trezor. Dcrd errors to be mapped are listed here:
|
||||
// https://github.com/decred/dcrd/blob/2f5e47371263b996bb99e8dc3484f659309bd83a/dcrjson/jsonerr.go
|
||||
func mapToStandardErr(customPrefix string, err Error) error {
|
||||
switch {
|
||||
case strings.Contains(err.Message, dcrjson.ErrBlockNotFound.Message) || // Block not found
|
||||
strings.Contains(err.Message, dcrjson.ErrOutOfRange.Message) || // Block number out of range
|
||||
strings.Contains(err.Message, dcrjson.ErrBestBlockHash.Message): // Error getting best block hash
|
||||
return bchain.ErrBlockNotFound
|
||||
case strings.Contains(err.Message, dcrjson.ErrNoTxInfo.Message): // No information available about transaction
|
||||
return bchain.ErrTxNotFound
|
||||
case strings.Contains(err.Message, dcrjson.ErrInvalidTxVout.Message): // Output index number (vout) does not exist for transaction
|
||||
return bchain.ErrTxidMissing
|
||||
default:
|
||||
return fmt.Errorf(customPrefix, err.Message)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
package deeponion
|
||||
|
||||
import (
|
||||
"github.com/martinboehm/btcd/wire"
|
||||
"github.com/martinboehm/btcutil/chaincfg"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
|
||||
)
|
||||
|
||||
// magic numbers
|
||||
const (
|
||||
MainnetMagic wire.BitcoinNet = 0xf2dbf1d1
|
||||
)
|
||||
|
||||
// chain parameters
|
||||
var (
|
||||
MainNetParams chaincfg.Params
|
||||
)
|
||||
|
||||
func init() {
|
||||
MainNetParams = chaincfg.MainNetParams
|
||||
MainNetParams.Net = MainnetMagic
|
||||
MainNetParams.PubKeyHashAddrID = []byte{31}
|
||||
MainNetParams.ScriptHashAddrID = []byte{78}
|
||||
MainNetParams.Bech32HRPSegwit = "dpn"
|
||||
}
|
||||
|
||||
// DeepOnionParser handle
|
||||
type DeepOnionParser struct {
|
||||
*btc.BitcoinParser
|
||||
baseparser *bchain.BaseParser
|
||||
}
|
||||
|
||||
// NewDeepOnionParser returns new DeepOnionParser instance
|
||||
func NewDeepOnionParser(params *chaincfg.Params, c *btc.Configuration) *DeepOnionParser {
|
||||
return &DeepOnionParser{
|
||||
BitcoinParser: btc.NewBitcoinParser(params, c),
|
||||
baseparser: &bchain.BaseParser{},
|
||||
}
|
||||
}
|
||||
|
||||
// GetChainParams contains network parameters for the main DeepOnion network,
|
||||
func GetChainParams(chain string) *chaincfg.Params {
|
||||
// register bitcoin parameters in addition to deeponion parameters
|
||||
// deeponion has dual standard of addresses and we want to be able to
|
||||
// parse both standards
|
||||
if !chaincfg.IsRegistered(&chaincfg.MainNetParams) {
|
||||
chaincfg.RegisterBitcoinParams()
|
||||
}
|
||||
if !chaincfg.IsRegistered(&MainNetParams) {
|
||||
err := chaincfg.Register(&MainNetParams)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
return &MainNetParams
|
||||
}
|
||||
|
||||
// PackTx packs transaction to byte array using protobuf
|
||||
func (p *DeepOnionParser) PackTx(tx *bchain.Tx, height uint32, blockTime int64) ([]byte, error) {
|
||||
return p.baseparser.PackTx(tx, height, blockTime)
|
||||
}
|
||||
|
||||
// UnpackTx unpacks transaction from protobuf byte array
|
||||
func (p *DeepOnionParser) UnpackTx(buf []byte) (*bchain.Tx, uint32, error) {
|
||||
return p.baseparser.UnpackTx(buf)
|
||||
}
|
|
@ -0,0 +1,200 @@
|
|||
// +build unittest
|
||||
|
||||
package deeponion
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"math/big"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/martinboehm/btcutil/chaincfg"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
c := m.Run()
|
||||
chaincfg.ResetParams()
|
||||
os.Exit(c)
|
||||
}
|
||||
|
||||
func Test_GetAddrDescFromAddress_Mainnet(t *testing.T) {
|
||||
type args struct {
|
||||
address string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "P2PKH1",
|
||||
args: args{address: "DYPyxvq57iSRA5xUXzSVfsTENPz4DKFr5S"},
|
||||
want: "76a9142afc25b8b5d4ed490026d38b3b464c140a32dc7588ac",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "P2PKH2",
|
||||
args: args{address: "DshhBSub7vexDFNm45UtG2wBJFt8cm5Uwr"},
|
||||
want: "76a914fec0038b0db67c1b304f6c25b3e860277a96226188ac",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "P2SH1",
|
||||
args: args{address: "YYDTMNJmKqajnWjFPjenzs2awwE4cwYHtC"},
|
||||
want: "a91461190c0272b059b2c09b352da81b1712dd83305e87",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "P2SH2",
|
||||
args: args{address: "Yh1qpMEA4EFMTB4BmhkeyivJ92WiGr3ETX"},
|
||||
want: "a914c19ff0bfc8f4387bee48e2cd3628bf72f7053cd787",
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
parser := NewDeepOnionParser(GetChainParams("main"), &btc.Configuration{})
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := parser.GetAddrDescFromAddress(tt.args.address)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("GetAddrDescFromAddress() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
h := hex.EncodeToString(got)
|
||||
if !reflect.DeepEqual(h, tt.want) {
|
||||
t.Errorf("GetAddrDescFromAddress() = %v, want %v", h, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
testTx1 bchain.Tx
|
||||
|
||||
testTxPacked1 = "0a206ba18524d81af732d0226ffdb63d2bcdc0d58a35ac97b5ad731057932d324e1412b401010000001134415d0114caae2bf9a7808aee0798e6245a347405d46c8131dbf55cbbbc689bbee367e902000000484730440220280f3fa80b4e93834fe0a8d9884105310eaa8d36d77b9aff113b6c498138e5bb02204578409f0a14fa1950ea4951314fd495fd503b42a6325efb5c139a6c8253912401ffffffff0200000000000000000005f22f5904000000232102bdb95d89f07e3a29305f3c8de86ec211ed77b7e15cf314c85c532a6b71c2ce07ac000000001891e884ea05200028b88a5432760a001220e967e3be9b68bcbb5cf5db31816cd40574345a24e69807ee8a80a7f92baeca14180222484730440220280f3fa80b4e93834fe0a8d9884105310eaa8d36d77b9aff113b6c498138e5bb02204578409f0a14fa1950ea4951314fd495fd503b42a6325efb5c139a6c825391240128ffffffff0f3a0210003a520a0504583af7fb10011a232102bdb95d89f07e3a29305f3c8de86ec211ed77b7e15cf314c85c532a6b71c2ce07ac2222446d343835624e4a6169474a6d4556746832426e5a345931796763756644736934454001"
|
||||
)
|
||||
|
||||
func init() {
|
||||
testTx1 = bchain.Tx{
|
||||
Hex: "010000001134415d0114caae2bf9a7808aee0798e6245a347405d46c8131dbf55cbbbc689bbee367e902000000484730440220280f3fa80b4e93834fe0a8d9884105310eaa8d36d77b9aff113b6c498138e5bb02204578409f0a14fa1950ea4951314fd495fd503b42a6325efb5c139a6c8253912401ffffffff0200000000000000000005f22f5904000000232102bdb95d89f07e3a29305f3c8de86ec211ed77b7e15cf314c85c532a6b71c2ce07ac00000000",
|
||||
Blocktime: 1564554257,
|
||||
Txid: "6ba18524d81af732d0226ffdb63d2bcdc0d58a35ac97b5ad731057932d324e14",
|
||||
LockTime: 0,
|
||||
Time: 1564554257,
|
||||
Version: 1,
|
||||
Vin: []bchain.Vin{
|
||||
{
|
||||
ScriptSig: bchain.ScriptSig{
|
||||
Hex: "4730440220280f3fa80b4e93834fe0a8d9884105310eaa8d36d77b9aff113b6c498138e5bb02204578409f0a14fa1950ea4951314fd495fd503b42a6325efb5c139a6c8253912401",
|
||||
},
|
||||
Txid: "e967e3be9b68bcbb5cf5db31816cd40574345a24e69807ee8a80a7f92baeca14",
|
||||
Vout: 2,
|
||||
Sequence: 4294967295,
|
||||
},
|
||||
},
|
||||
Vout: []bchain.Vout{
|
||||
{
|
||||
ValueSat: *big.NewInt(0),
|
||||
N: 0,
|
||||
ScriptPubKey: bchain.ScriptPubKey{
|
||||
Hex: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
ValueSat: *big.NewInt(18660128763),
|
||||
N: 1,
|
||||
ScriptPubKey: bchain.ScriptPubKey{
|
||||
Hex: "2102bdb95d89f07e3a29305f3c8de86ec211ed77b7e15cf314c85c532a6b71c2ce07ac",
|
||||
Addresses: []string{
|
||||
"Dm485bNJaiGJmEVth2BnZ4Y1ygcufDsi4E",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func Test_PackTx(t *testing.T) {
|
||||
type args struct {
|
||||
tx bchain.Tx
|
||||
height uint32
|
||||
blockTime int64
|
||||
parser *DeepOnionParser
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "deeponion-1",
|
||||
args: args{
|
||||
tx: testTx1,
|
||||
height: 1377592,
|
||||
blockTime: 1564554257,
|
||||
parser: NewDeepOnionParser(GetChainParams("main"), &btc.Configuration{}),
|
||||
},
|
||||
want: testTxPacked1,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := tt.args.parser.PackTx(&tt.args.tx, tt.args.height, tt.args.blockTime)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("packTx() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
h := hex.EncodeToString(got)
|
||||
if !reflect.DeepEqual(h, tt.want) {
|
||||
t.Errorf("packTx() = %v, want %v", h, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_UnpackTx(t *testing.T) {
|
||||
type args struct {
|
||||
packedTx string
|
||||
parser *DeepOnionParser
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *bchain.Tx
|
||||
want1 uint32
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "deeponion-1",
|
||||
args: args{
|
||||
packedTx: testTxPacked1,
|
||||
parser: NewDeepOnionParser(GetChainParams("main"), &btc.Configuration{}),
|
||||
},
|
||||
want: &testTx1,
|
||||
want1: 1377592,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
b, _ := hex.DecodeString(tt.args.packedTx)
|
||||
got, got1, err := tt.args.parser.UnpackTx(b)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("unpackTx(1) error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("unpackTx(2) got = %v, want %v", got, tt.want)
|
||||
}
|
||||
if got1 != tt.want1 {
|
||||
t.Errorf("unpackTx(3) got1 = %v, want %v", got1, tt.want1)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,109 @@
|
|||
package deeponion
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/juju/errors"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
|
||||
)
|
||||
|
||||
// DeepOnionRPC is an interface to JSON-RPC bitcoind service.
|
||||
type DeepOnionRPC struct {
|
||||
*btc.BitcoinRPC
|
||||
}
|
||||
|
||||
// NewDeepOnionRPC returns new DeepOnionRPC instance.
|
||||
func NewDeepOnionRPC(config json.RawMessage, pushHandler func(bchain.NotificationType)) (bchain.BlockChain, error) {
|
||||
b, err := btc.NewBitcoinRPC(config, pushHandler)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s := &DeepOnionRPC{
|
||||
b.(*btc.BitcoinRPC),
|
||||
}
|
||||
s.RPCMarshaler = btc.JSONMarshalerV2{}
|
||||
s.ChainConfig.SupportsEstimateFee = false
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// Initialize initializes DeepOnionRPC instance.
|
||||
func (b *DeepOnionRPC) Initialize() error {
|
||||
ci, err := b.GetChainInfo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
chainName := ci.Chain
|
||||
|
||||
glog.Info("Chain name ", chainName)
|
||||
params := GetChainParams(chainName)
|
||||
|
||||
// always create parser
|
||||
b.Parser = NewDeepOnionParser(params, b.ChainConfig)
|
||||
|
||||
// parameters for getInfo request
|
||||
if params.Net == MainnetMagic {
|
||||
b.Testnet = false
|
||||
b.Network = "livenet"
|
||||
} else {
|
||||
b.Testnet = true
|
||||
b.Network = "testnet"
|
||||
}
|
||||
|
||||
glog.Info("rpc: block chain ", params.Name)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetBlock returns block with given hash.
|
||||
func (s *DeepOnionRPC) GetBlock(hash string, height uint32) (*bchain.Block, error) {
|
||||
var err error
|
||||
if hash == "" && height > 0 {
|
||||
hash, err = s.GetBlockHash(height)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
glog.V(1).Info("rpc: getblock (verbosity=1) ", hash)
|
||||
|
||||
res := btc.ResGetBlockThin{}
|
||||
req := btc.CmdGetBlock{Method: "getblock"}
|
||||
req.Params.BlockHash = hash
|
||||
req.Params.Verbosity = 1
|
||||
err = s.Call(&req, &res)
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "hash %v", hash)
|
||||
}
|
||||
if res.Error != nil {
|
||||
return nil, errors.Annotatef(res.Error, "hash %v", hash)
|
||||
}
|
||||
|
||||
txs := make([]bchain.Tx, 0, len(res.Result.Txids))
|
||||
for _, txid := range res.Result.Txids {
|
||||
tx, err := s.GetTransaction(txid)
|
||||
if err != nil {
|
||||
if err == bchain.ErrTxNotFound {
|
||||
glog.Errorf("rpc: getblock: skipping transanction in block %s due error: %s", hash, err)
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
txs = append(txs, *tx)
|
||||
}
|
||||
block := &bchain.Block{
|
||||
BlockHeader: res.Result.BlockHeader,
|
||||
Txs: txs,
|
||||
}
|
||||
return block, nil
|
||||
}
|
||||
|
||||
// GetTransactionForMempool returns a transaction by the transaction ID.
|
||||
// It could be optimized for mempool, i.e. without block time and confirmations
|
||||
func (s *DeepOnionRPC) GetTransactionForMempool(txid string) (*bchain.Tx, error) {
|
||||
return s.GetTransaction(txid)
|
||||
}
|
|
@ -1,18 +1,21 @@
|
|||
package digibyte
|
||||
|
||||
import (
|
||||
"blockbook/bchain/coins/btc"
|
||||
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/jakm/btcutil/chaincfg"
|
||||
"github.com/martinboehm/btcd/wire"
|
||||
"github.com/martinboehm/btcutil/chaincfg"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
|
||||
)
|
||||
|
||||
const (
|
||||
// MainnetMagic is mainnet network constant
|
||||
MainnetMagic wire.BitcoinNet = 0xdab6c3fa
|
||||
TestnetMagic wire.BitcoinNet = 0xddbdc8fd
|
||||
)
|
||||
|
||||
var (
|
||||
// MainNetParams are parser parameters for mainnet
|
||||
MainNetParams chaincfg.Params
|
||||
TestNetParams chaincfg.Params
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
@ -21,6 +24,12 @@ func init() {
|
|||
MainNetParams.PubKeyHashAddrID = []byte{30}
|
||||
MainNetParams.ScriptHashAddrID = []byte{63}
|
||||
MainNetParams.Bech32HRPSegwit = "dgb"
|
||||
|
||||
TestNetParams = chaincfg.TestNet3Params
|
||||
TestNetParams.Net = TestnetMagic
|
||||
TestNetParams.PubKeyHashAddrID = []byte{126}
|
||||
TestNetParams.ScriptHashAddrID = []byte{140}
|
||||
TestNetParams.Bech32HRPSegwit = "dgbt"
|
||||
}
|
||||
|
||||
// DigiByteParser handle
|
||||
|
@ -28,18 +37,27 @@ type DigiByteParser struct {
|
|||
*btc.BitcoinParser
|
||||
}
|
||||
|
||||
// NewDigiByteParser returns new VertcoinParser instance
|
||||
// NewDigiByteParser returns new DigiByteParser instance
|
||||
func NewDigiByteParser(params *chaincfg.Params, c *btc.Configuration) *DigiByteParser {
|
||||
return &DigiByteParser{BitcoinParser: btc.NewBitcoinParser(params, c)}
|
||||
}
|
||||
|
||||
// GetChainParams contains network parameters for the main DigiByte network
|
||||
// and the DigiByte Testnet network
|
||||
func GetChainParams(chain string) *chaincfg.Params {
|
||||
if !chaincfg.IsRegistered(&MainNetParams) {
|
||||
err := chaincfg.Register(&MainNetParams)
|
||||
if err == nil {
|
||||
err = chaincfg.Register(&TestNetParams)
|
||||
}
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
return &MainNetParams
|
||||
switch chain {
|
||||
case "test":
|
||||
return &TestNetParams
|
||||
default:
|
||||
return &MainNetParams
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,15 +3,15 @@
|
|||
package digibyte
|
||||
|
||||
import (
|
||||
"blockbook/bchain"
|
||||
"blockbook/bchain/coins/btc"
|
||||
"encoding/hex"
|
||||
"math/big"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/jakm/btcutil/chaincfg"
|
||||
"github.com/martinboehm/btcutil/chaincfg"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
package digibyte
|
||||
|
||||
import (
|
||||
"blockbook/bchain"
|
||||
"blockbook/bchain/coins/btc"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
|
||||
)
|
||||
|
||||
// DigiByteRPC is an interface to JSON-RPC bitcoind service.
|
||||
|
@ -31,10 +31,11 @@ func NewDigiByteRPC(config json.RawMessage, pushHandler func(bchain.Notification
|
|||
|
||||
// Initialize initializes DigiByteRPC instance.
|
||||
func (b *DigiByteRPC) Initialize() error {
|
||||
chainName, err := b.GetChainInfoAndInitializeMempool(b)
|
||||
ci, err := b.GetChainInfo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
chainName := ci.Chain
|
||||
|
||||
glog.Info("Chain name ", chainName)
|
||||
params := GetChainParams(chainName)
|
||||
|
|
|
@ -0,0 +1,227 @@
|
|||
package divi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"math/big"
|
||||
|
||||
"github.com/juju/errors"
|
||||
"github.com/martinboehm/btcd/wire"
|
||||
"github.com/martinboehm/btcutil/chaincfg"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
// MainnetMagic = "network messages so the messages can be identified to belong to a specific coin"
|
||||
// Source https://github.com/DiviProject/Divi/blob/master0/divi/src/chainparams.cpp#L128-L136
|
||||
MainnetMagic wire.BitcoinNet = 0x8f8da0df
|
||||
)
|
||||
|
||||
var (
|
||||
// MainNetParams = ???
|
||||
MainNetParams chaincfg.Params
|
||||
)
|
||||
|
||||
func init() {
|
||||
// DIVI mainnet Address encoding magics
|
||||
MainNetParams = chaincfg.MainNetParams
|
||||
MainNetParams.Net = MainnetMagic
|
||||
MainNetParams.PubKeyHashAddrID = []byte{30} // starting with 'D'
|
||||
MainNetParams.ScriptHashAddrID = []byte{13}
|
||||
MainNetParams.PrivateKeyID = []byte{212}
|
||||
}
|
||||
|
||||
// DivicoinParser handle
|
||||
type DivicoinParser struct {
|
||||
*btc.BitcoinParser
|
||||
baseparser *bchain.BaseParser
|
||||
BitcoinOutputScriptToAddressesFunc btc.OutputScriptToAddressesFunc
|
||||
}
|
||||
|
||||
// NewDiviParser returns new DivicoinParser instance
|
||||
func NewDiviParser(params *chaincfg.Params, c *btc.Configuration) *DivicoinParser {
|
||||
p := &DivicoinParser{
|
||||
BitcoinParser: btc.NewBitcoinParser(params, c),
|
||||
baseparser: &bchain.BaseParser{},
|
||||
}
|
||||
p.BitcoinOutputScriptToAddressesFunc = p.OutputScriptToAddressesFunc
|
||||
p.OutputScriptToAddressesFunc = p.outputScriptToAddresses
|
||||
return p
|
||||
}
|
||||
|
||||
// GetChainParams contains network parameters for the main Divi network
|
||||
func GetChainParams(chain string) *chaincfg.Params {
|
||||
if !chaincfg.IsRegistered(&MainNetParams) {
|
||||
err := chaincfg.Register(&MainNetParams)
|
||||
/*if err == nil {
|
||||
err = chaincfg.Register(&TestNetParams)
|
||||
}*/
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
} /*
|
||||
switch chain {
|
||||
case "test":
|
||||
return &TestNetParams
|
||||
default:
|
||||
*/return &MainNetParams
|
||||
//}
|
||||
}
|
||||
|
||||
// ParseBlock parses raw block to our Block struct
|
||||
func (p *DivicoinParser) ParseBlock(b []byte) (*bchain.Block, error) {
|
||||
r := bytes.NewReader(b)
|
||||
w := wire.MsgBlock{}
|
||||
h := wire.BlockHeader{}
|
||||
err := h.Deserialize(r)
|
||||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "Deserialize")
|
||||
}
|
||||
|
||||
if h.Version > 3 {
|
||||
// Skip past AccumulatorCheckpoint which was added in pivx block version 4
|
||||
r.Seek(32, io.SeekCurrent)
|
||||
}
|
||||
|
||||
err = utils.DecodeTransactions(r, 0, wire.WitnessEncoding, &w)
|
||||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "DecodeTransactions")
|
||||
}
|
||||
|
||||
txs := make([]bchain.Tx, len(w.Transactions))
|
||||
for ti, t := range w.Transactions {
|
||||
txs[ti] = p.TxFromMsgTx(t, false)
|
||||
}
|
||||
|
||||
return &bchain.Block{
|
||||
BlockHeader: bchain.BlockHeader{
|
||||
Size: len(b),
|
||||
Time: h.Timestamp.Unix(),
|
||||
},
|
||||
Txs: txs,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// PackTx packs transaction to byte array using protobuf
|
||||
func (p *DivicoinParser) PackTx(tx *bchain.Tx, height uint32, blockTime int64) ([]byte, error) {
|
||||
return p.baseparser.PackTx(tx, height, blockTime)
|
||||
}
|
||||
|
||||
// UnpackTx unpacks transaction from protobuf byte array
|
||||
func (p *DivicoinParser) UnpackTx(buf []byte) (*bchain.Tx, uint32, error) {
|
||||
return p.baseparser.UnpackTx(buf)
|
||||
}
|
||||
|
||||
// ParseTx parses byte array containing transaction and returns Tx struct
|
||||
func (p *DivicoinParser) ParseTx(b []byte) (*bchain.Tx, error) {
|
||||
t := wire.MsgTx{}
|
||||
r := bytes.NewReader(b)
|
||||
if err := t.Deserialize(r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tx := p.TxFromMsgTx(&t, true)
|
||||
tx.Hex = hex.EncodeToString(b)
|
||||
return &tx, nil
|
||||
}
|
||||
|
||||
// TxFromMsgTx parses tx and adds handling for OP_ZEROCOINSPEND inputs
|
||||
func (p *DivicoinParser) TxFromMsgTx(t *wire.MsgTx, parseAddresses bool) bchain.Tx {
|
||||
vin := make([]bchain.Vin, len(t.TxIn))
|
||||
for i, in := range t.TxIn {
|
||||
s := bchain.ScriptSig{
|
||||
Hex: hex.EncodeToString(in.SignatureScript),
|
||||
// missing: Asm,
|
||||
}
|
||||
|
||||
txid := in.PreviousOutPoint.Hash.String()
|
||||
|
||||
vin[i] = bchain.Vin{
|
||||
Txid: txid,
|
||||
Vout: in.PreviousOutPoint.Index,
|
||||
Sequence: in.Sequence,
|
||||
ScriptSig: s,
|
||||
}
|
||||
}
|
||||
vout := make([]bchain.Vout, len(t.TxOut))
|
||||
for i, out := range t.TxOut {
|
||||
addrs := []string{}
|
||||
if parseAddresses {
|
||||
addrs, _, _ = p.OutputScriptToAddressesFunc(out.PkScript)
|
||||
}
|
||||
s := bchain.ScriptPubKey{
|
||||
Hex: hex.EncodeToString(out.PkScript),
|
||||
Addresses: addrs,
|
||||
// missing: Asm,
|
||||
// missing: Type,
|
||||
}
|
||||
var vs big.Int
|
||||
vs.SetInt64(out.Value)
|
||||
vout[i] = bchain.Vout{
|
||||
ValueSat: vs,
|
||||
N: uint32(i),
|
||||
ScriptPubKey: s,
|
||||
}
|
||||
}
|
||||
tx := bchain.Tx{
|
||||
Txid: t.TxHash().String(),
|
||||
Version: t.Version,
|
||||
LockTime: t.LockTime,
|
||||
Vin: vin,
|
||||
Vout: vout,
|
||||
// skip: BlockHash,
|
||||
// skip: Confirmations,
|
||||
// skip: Time,
|
||||
// skip: Blocktime,
|
||||
}
|
||||
return tx
|
||||
}
|
||||
|
||||
// ParseTxFromJSON parses JSON message containing transaction and returns Tx struct
|
||||
func (p *DivicoinParser) ParseTxFromJSON(msg json.RawMessage) (*bchain.Tx, error) {
|
||||
var tx bchain.Tx
|
||||
err := json.Unmarshal(msg, &tx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i := range tx.Vout {
|
||||
vout := &tx.Vout[i]
|
||||
// convert vout.JsonValue to big.Int and clear it, it is only temporary value used for unmarshal
|
||||
vout.ValueSat, err = p.AmountToBigInt(vout.JsonValue)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vout.JsonValue = ""
|
||||
|
||||
if vout.ScriptPubKey.Addresses == nil {
|
||||
vout.ScriptPubKey.Addresses = []string{}
|
||||
}
|
||||
}
|
||||
|
||||
return &tx, nil
|
||||
}
|
||||
|
||||
// outputScriptToAddresses converts ScriptPubKey to bitcoin addresses
|
||||
func (p *DivicoinParser) outputScriptToAddresses(script []byte) ([]string, bool, error) {
|
||||
rv, s, _ := p.BitcoinOutputScriptToAddressesFunc(script)
|
||||
return rv, s, nil
|
||||
}
|
||||
|
||||
// GetAddrDescForUnknownInput = ???
|
||||
func (p *DivicoinParser) GetAddrDescForUnknownInput(tx *bchain.Tx, input int) bchain.AddressDescriptor {
|
||||
if len(tx.Vin) > input {
|
||||
scriptHex := tx.Vin[input].ScriptSig.Hex
|
||||
|
||||
if scriptHex != "" {
|
||||
script, _ := hex.DecodeString(scriptHex)
|
||||
return script
|
||||
}
|
||||
}
|
||||
|
||||
s := make([]byte, 10)
|
||||
return s
|
||||
}
|
|
@ -0,0 +1,376 @@
|
|||
// +build unittest
|
||||
|
||||
package divi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/martinboehm/btcutil/chaincfg"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
c := m.Run()
|
||||
chaincfg.ResetParams()
|
||||
os.Exit(c)
|
||||
}
|
||||
|
||||
// Test getting the address details from the address hash
|
||||
|
||||
func Test_GetAddrDescFromAddress_Mainnet(t *testing.T) {
|
||||
type args struct {
|
||||
address string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "P2PKH1",
|
||||
args: args{address: "DDSsBchWiVfvPVn6Ldp1nL7k4L77cSDqM7"},
|
||||
want: "76a9145b1d583a4c270f2f14be77b298f0a9c6df97471388ac",
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
parser := NewDiviParser(GetChainParams("main"), &btc.Configuration{})
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := parser.GetAddrDescFromAddress(tt.args.address)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("GetAddrDescFromAddress() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
h := hex.EncodeToString(got)
|
||||
if !reflect.DeepEqual(h, tt.want) {
|
||||
t.Errorf("GetAddrDescFromAddress() = %v, want %v", h, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_GetAddressesFromAddrDesc(t *testing.T) {
|
||||
type args struct {
|
||||
script string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []string
|
||||
want2 bool
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "Normal",
|
||||
args: args{script: "76a914cb1196fb1b98d04b0cb8d2ffde3c2de3eb83d9fe88ac"},
|
||||
want: []string{"DPepnMkaNHKCa6cQi7oBThrdiFEwSSYFzv"},
|
||||
want2: true,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
parser := NewDiviParser(GetChainParams("main"), &btc.Configuration{})
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
b, _ := hex.DecodeString(tt.args.script)
|
||||
got, got2, err := parser.GetAddressesFromAddrDesc(b)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("GetAddressesFromAddrDesc() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("GetAddressesFromAddrDesc() = %v, want %v", got, tt.want)
|
||||
}
|
||||
if !reflect.DeepEqual(got2, tt.want2) {
|
||||
t.Errorf("GetAddressesFromAddrDesc() = %v, want %v", got2, tt.want2)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Test the packing and unpacking of raw transaction data
|
||||
|
||||
var (
|
||||
// Mint transaction
|
||||
testTx1 bchain.Tx
|
||||
testTxPacked1 = "0a20f7a5324866ba18058ab032196f34458d19f7ec5a4ac284670c3ef07bfa724644124201000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0603de3d060101ffffffff010000000000000000000000000018aefd9ce905200028defb1832160a0c303364653364303630313031180028ffffffff0f3a0210004000"
|
||||
|
||||
// Normal transaction
|
||||
testTx2 bchain.Tx
|
||||
testTxPacked2 = "0a20eace41778a2940ff423b72a42033990eb5d6092810734a5806da6f3e5b34086412ea010100000001084b029489e1cddf726080c447c8a2b1d4bbe43024db31b8b19bc07585db9555010000006a473044022017422b9e3414d6233fa75f9eb7778469bebbb40686b0f7eb77d90a04c80149610220411f1063086fe205ea821ceb0de89e8158e202aba00f5ebb92b51f97381311fd012102ccb10a2f0603a0624b8708abefb5f4700631fc131c5de38b51e0359e2ffa7d1cffffffff03000000000000000000f260de1a580100001976a9145b1d583a4c270f2f14be77b298f0a9c6df97471388ac009ca6920c0000001976a914cb1196fb1b98d04b0cb8d2ffde3c2de3eb83d9fe88ac0000000018aefd9ce905200028defb183298010a0012205595db8575c09bb1b831db2430e4bbd4b1a2c847c4806072dfcde18994024b081801226a473044022017422b9e3414d6233fa75f9eb7778469bebbb40686b0f7eb77d90a04c80149610220411f1063086fe205ea821ceb0de89e8158e202aba00f5ebb92b51f97381311fd012102ccb10a2f0603a0624b8708abefb5f4700631fc131c5de38b51e0359e2ffa7d1c28ffffffff0f3a0210003a490a0601581ade60f210011a1976a9145b1d583a4c270f2f14be77b298f0a9c6df97471388ac222244445373426368576956667650566e364c6470316e4c376b344c3737635344714d373a480a050c92a69c0010021a1976a914cb1196fb1b98d04b0cb8d2ffde3c2de3eb83d9fe88ac2222445065706e4d6b614e484b436136635169376f425468726469464577535359467a764000"
|
||||
)
|
||||
|
||||
func init() {
|
||||
testTx1 = bchain.Tx{
|
||||
Hex: "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0603de3d060101ffffffff0100000000000000000000000000",
|
||||
Txid: "f7a5324866ba18058ab032196f34458d19f7ec5a4ac284670c3ef07bfa724644",
|
||||
LockTime: 0,
|
||||
Vin: []bchain.Vin{
|
||||
{
|
||||
Coinbase: "03de3d060101",
|
||||
Sequence: 4294967295,
|
||||
},
|
||||
},
|
||||
Vout: []bchain.Vout{
|
||||
{
|
||||
ValueSat: *big.NewInt(0),
|
||||
N: 0,
|
||||
ScriptPubKey: bchain.ScriptPubKey{
|
||||
Hex: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
Blocktime: 1562853038,
|
||||
Time: 1562853038,
|
||||
}
|
||||
|
||||
testTx2 = bchain.Tx{
|
||||
Hex: "0100000001084b029489e1cddf726080c447c8a2b1d4bbe43024db31b8b19bc07585db9555010000006a473044022017422b9e3414d6233fa75f9eb7778469bebbb40686b0f7eb77d90a04c80149610220411f1063086fe205ea821ceb0de89e8158e202aba00f5ebb92b51f97381311fd012102ccb10a2f0603a0624b8708abefb5f4700631fc131c5de38b51e0359e2ffa7d1cffffffff03000000000000000000f260de1a580100001976a9145b1d583a4c270f2f14be77b298f0a9c6df97471388ac009ca6920c0000001976a914cb1196fb1b98d04b0cb8d2ffde3c2de3eb83d9fe88ac00000000",
|
||||
Txid: "eace41778a2940ff423b72a42033990eb5d6092810734a5806da6f3e5b340864",
|
||||
LockTime: 0,
|
||||
Vin: []bchain.Vin{
|
||||
{
|
||||
ScriptSig: bchain.ScriptSig{
|
||||
Hex: "473044022017422b9e3414d6233fa75f9eb7778469bebbb40686b0f7eb77d90a04c80149610220411f1063086fe205ea821ceb0de89e8158e202aba00f5ebb92b51f97381311fd012102ccb10a2f0603a0624b8708abefb5f4700631fc131c5de38b51e0359e2ffa7d1c",
|
||||
},
|
||||
Txid: "5595db8575c09bb1b831db2430e4bbd4b1a2c847c4806072dfcde18994024b08",
|
||||
Vout: 1,
|
||||
Sequence: 4294967295,
|
||||
},
|
||||
},
|
||||
Vout: []bchain.Vout{
|
||||
{
|
||||
ValueSat: *big.NewInt(0),
|
||||
N: 0,
|
||||
ScriptPubKey: bchain.ScriptPubKey{
|
||||
Hex: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
ValueSat: *big.NewInt(1477919531250),
|
||||
N: 1,
|
||||
ScriptPubKey: bchain.ScriptPubKey{
|
||||
Hex: "76a9145b1d583a4c270f2f14be77b298f0a9c6df97471388ac",
|
||||
Addresses: []string{
|
||||
"DDSsBchWiVfvPVn6Ldp1nL7k4L77cSDqM7",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ValueSat: *big.NewInt(54000000000),
|
||||
N: 2,
|
||||
ScriptPubKey: bchain.ScriptPubKey{
|
||||
Hex: "76a914cb1196fb1b98d04b0cb8d2ffde3c2de3eb83d9fe88ac",
|
||||
Addresses: []string{
|
||||
"DPepnMkaNHKCa6cQi7oBThrdiFEwSSYFzv",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Blocktime: 1562853038,
|
||||
Time: 1562853038,
|
||||
}
|
||||
}
|
||||
|
||||
func Test_PackTx(t *testing.T) {
|
||||
type args struct {
|
||||
tx bchain.Tx
|
||||
height uint32
|
||||
blockTime int64
|
||||
parser *DivicoinParser
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "divi-1",
|
||||
args: args{
|
||||
tx: testTx1,
|
||||
height: 409054,
|
||||
blockTime: 1562853038,
|
||||
parser: NewDiviParser(GetChainParams("main"), &btc.Configuration{}),
|
||||
},
|
||||
want: testTxPacked1,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "divi-2",
|
||||
args: args{
|
||||
tx: testTx2,
|
||||
height: 409054,
|
||||
blockTime: 1562853038,
|
||||
parser: NewDiviParser(GetChainParams("main"), &btc.Configuration{}),
|
||||
},
|
||||
want: testTxPacked2,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := tt.args.parser.PackTx(&tt.args.tx, tt.args.height, tt.args.blockTime)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("packTx() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
h := hex.EncodeToString(got)
|
||||
if !reflect.DeepEqual(h, tt.want) {
|
||||
t.Errorf("packTx() = %v, want %v", h, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_UnpackTx(t *testing.T) {
|
||||
type args struct {
|
||||
packedTx string
|
||||
parser *DivicoinParser
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *bchain.Tx
|
||||
want1 uint32
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "divi-1",
|
||||
args: args{
|
||||
packedTx: testTxPacked1,
|
||||
parser: NewDiviParser(GetChainParams("main"), &btc.Configuration{}),
|
||||
},
|
||||
want: &testTx1,
|
||||
want1: 409054,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "divi-2",
|
||||
args: args{
|
||||
packedTx: testTxPacked2,
|
||||
parser: NewDiviParser(GetChainParams("main"), &btc.Configuration{}),
|
||||
},
|
||||
want: &testTx2,
|
||||
want1: 409054,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
b, _ := hex.DecodeString(tt.args.packedTx)
|
||||
got, got1, err := tt.args.parser.UnpackTx(b)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("unpackTx() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("unpackTx() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
if got1 != tt.want1 {
|
||||
t.Errorf("unpackTx() got1 = %v, want %v", got1, tt.want1)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Block test - looks for size, time, and transaction hashes
|
||||
|
||||
type testBlock struct {
|
||||
size int
|
||||
time int64
|
||||
tx []string
|
||||
}
|
||||
|
||||
var testParseBlockTxs = map[int]testBlock{
|
||||
407407: {
|
||||
size: 479,
|
||||
time: 1562753629,
|
||||
tx: []string{
|
||||
"3f8f01aec6717ede0e167f267fe486f18ddd25a13afd910dc1d41537aa1c6658",
|
||||
"b25224449d0f5266073876e924c4d6a4f127175aae151a66db6619e4ca41fe1d",
|
||||
},
|
||||
},
|
||||
409054: {
|
||||
size: 479,
|
||||
time: 1562853038,
|
||||
tx: []string{
|
||||
"f7a5324866ba18058ab032196f34458d19f7ec5a4ac284670c3ef07bfa724644",
|
||||
"eace41778a2940ff423b72a42033990eb5d6092810734a5806da6f3e5b340864",
|
||||
},
|
||||
},
|
||||
408074: {
|
||||
size: 1303,
|
||||
time: 1562794078,
|
||||
tx: []string{
|
||||
"bf0004680570d49eefab2ab806bd41f99587b6f3e65d1e0fb1d8e8f766f211f3",
|
||||
"8a334d86443d5e54d3d112b7ab4eff79ed0b879cbc62c580beee080b3c9e1142",
|
||||
"1ba350ba68b8db6af589136a85246c961694434ec2ffd1ad9c86831965b96932",
|
||||
"e05dcfece505455e8b4bcaeeb9ae1060fcf9c95ad1402c4fbd3b2c2bf1778683",
|
||||
"d3980118dedde2666d5bcd03ebf2c2d91ad6056404503afe0c37ed6cdd549f62",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func helperLoadBlock(t *testing.T, height int) []byte {
|
||||
name := fmt.Sprintf("block_dump.%d", height)
|
||||
path := filepath.Join("testdata", name)
|
||||
|
||||
d, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
d = bytes.TrimSpace(d)
|
||||
|
||||
b := make([]byte, hex.DecodedLen(len(d)))
|
||||
_, err = hex.Decode(b, d)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
func TestParseBlock(t *testing.T) {
|
||||
p := NewDiviParser(GetChainParams("main"), &btc.Configuration{})
|
||||
|
||||
for height, tb := range testParseBlockTxs {
|
||||
b := helperLoadBlock(t, height)
|
||||
|
||||
blk, err := p.ParseBlock(b)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if blk.Size != tb.size {
|
||||
t.Errorf("ParseBlock() block size: got %d, want %d", blk.Size, tb.size)
|
||||
}
|
||||
|
||||
if blk.Time != tb.time {
|
||||
t.Errorf("ParseBlock() block time: got %d, want %d", blk.Time, tb.time)
|
||||
}
|
||||
|
||||
if len(blk.Txs) != len(tb.tx) {
|
||||
t.Errorf("ParseBlock() number of transactions: got %d, want %d", len(blk.Txs), len(tb.tx))
|
||||
}
|
||||
|
||||
for ti, tx := range tb.tx {
|
||||
if blk.Txs[ti].Txid != tx {
|
||||
t.Errorf("ParseBlock() transaction %d: got %s, want %s", ti, blk.Txs[ti].Txid, tx)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
package divi
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
|
||||
)
|
||||
|
||||
// DivicoinRPC is an interface to JSON-RPC bitcoind service.
|
||||
type DivicoinRPC struct {
|
||||
*btc.BitcoinRPC
|
||||
}
|
||||
|
||||
// NewDiviRPC returns new DivicoinRPC instance.
|
||||
func NewDiviRPC(config json.RawMessage, pushHandler func(bchain.NotificationType)) (bchain.BlockChain, error) {
|
||||
b, err := btc.NewBitcoinRPC(config, pushHandler)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s := &DivicoinRPC{
|
||||
b.(*btc.BitcoinRPC),
|
||||
}
|
||||
s.RPCMarshaler = btc.JSONMarshalerV1{}
|
||||
s.ChainConfig.SupportsEstimateFee = true
|
||||
s.ChainConfig.SupportsEstimateSmartFee = false
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// Initialize initializes DivicoinRPC instance.
|
||||
func (b *DivicoinRPC) Initialize() error {
|
||||
ci, err := b.GetChainInfo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
chainName := ci.Chain
|
||||
|
||||
glog.Info("Chain name ", chainName)
|
||||
params := GetChainParams(chainName)
|
||||
|
||||
// always create parser
|
||||
b.Parser = NewDiviParser(params, b.ChainConfig)
|
||||
|
||||
/* parameters for getInfo request
|
||||
-- can be added later
|
||||
if params.Net == MainnetMagic {*/
|
||||
b.Testnet = false
|
||||
b.Network = "livenet"
|
||||
/*} else {
|
||||
b.Testnet = true
|
||||
b.Network = "testnet"
|
||||
}*/
|
||||
|
||||
glog.Info("rpc: block chain ", params.Name)
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
04000000881f4e2a409a1b75c62ed6fdc6fa278b00f304bbe0729853bc336cbc30c4931da66efc4bd6260f95886061747185eace5294209dbe390fa3408534e42a0f5d525dba255dfe78101b0000000000000000000000000000000000000000000000000000000000000000000000000201000000010000000000000000000000000000000000000000000000000000000000000000ffffffff06036f37060101ffffffff01000000000000000000000000000100000001652779264ccc941129adb833b8a42a6d278a98f295d25a0a53868ab1dc13bed4020000006a473044022046c6ab760d25c7d94f20aa2c81cc281a57bb3fa174023dcaac385727ab05f214022056841e947398a1d86bf7d0b99191f7d3421d0abf6ad1357955d38f5fe6c3a8c40121023c85ccefc390984ba8f71a0f0b999b7b6c6e9a30ccd63087d67d34d5c1fefc7dffffffff0300000000000000000080170a4e830000001976a914771f45c78657c739f0cc26dfc0b5a87ddd6e431c88ac009ca6920c0000001976a914e7c53cbb09860c3713b44958dd644e4d9154345688ac00000000412066000b291e3e3fd8f1fce0812ba4aaad2502a702fd3466a3d8a7dc1a78c816be01209f1179d41f7d38da229f1843a9c19cf67a9d610c2a767979ea31b264f3eb
|
|
@ -0,0 +1 @@
|
|||
040000000ff149a32a715106e7023cd41193d7ea64ca0092edb4659f035a881fc452b845e9249f2c4463db2074e307ba62e93619fd3d17a3b6b7437a326a79869f35c00c5e58265db760101b0000000000000000000000000000000000000000000000000000000000000000000000000501000000010000000000000000000000000000000000000000000000000000000000000000ffffffff06030a3a060101ffffffff01000000000000000000000000000100000001fdb6b4d9d9aa8afbdaac2d9bc4c29371147a6a3672529ad7ae45a7944558ec93020000006a473044022061bc3ffe2b069a39acdba6a169ae4fbaa34b32360ff9a45fce9c92eb0434ca6602206fa6c22110f4a14c8cee58e458e3522f41993e461278ef8ccb94322595981ec1012103b2e3a485f7a7da6eb16ea025ef45efa613d18350b2538899fab0b5a2e86ffda3ffffffff0300000000000000000000a882a22d0000001976a914899ef2e7669830485476f9c910a8330959d245e188ac009ca6920c0000001976a914a9147e5ce1507c2f49d9ceb8bbd2d280fb410b8188ac000000000100000002a0a25308b7afb81327a4a8cb1d13aa54fa53edf292bdf7fe2739f297f4027bb4000000006b4830450221009396b072158c158e7e90cdd797a0a58caf15c7e1589030d542a32e3568324a22022063d90727f3a71a9c01f75ece872ee0240f46eeac14531a7f2bfdb3fe5d0e793f01210263e67be5854349c3c8e23fea5020aa2b7bfd46916ce4e805676e4cd3b375cda5ffffffff8c7745969fbcd3bfb698d51cc8b110fe540d6a0ed9816bd2c1e375f0e7c2ec94000000006a4730440220748c808b1107ecf01559159c76163a2206af771604643aefea438f3c99c699a0022006c067c603fb0b4bc7a5cd129750fc010613957ec0666c41185d035b6f5fcbb1012102a535454382f62235104836bbbe1c894f1d6705c828b8be11f623068159882eb0ffffffff022018e505000000001976a914bba57e3d30f892d804d588ee633dcbb6a96b0cbb88ac0010a5d4e80000001976a914dd40b08afb0dc8496892bfe5e3ae1aceda57135b88ac000000000100000001583f174f32c563eb47555fbacdb0937881b4a25f35fe5f13d8c49cbf92654fdd000000006b483045022100cbacf5f775ab4fabdfb147c095c0865dd56bc461115b03a7a29ab96037fc4b3f02206b824c334b950d856550fff524717e07095fa2d9bbe8d5d8096813721d98715c012103f4e09f09921ad224e9e590df477cea01fc8114e816a2112ed59518bf99cdaa3effffffff0200f2052a010000001976a9148226210ccf912fb3969c0996a5582dcfb99a2e1788ac60b66daee00000001976a914d649408521dfef8449203417d77e44eadfc329f588ac00000000010000000199bd849243dea120d7d7df9037caa11327ed9df671e90c06442d12859ef4c052000000006a4730440220101b4e2022b3ffa023b83197d8ceb45b3ef31ff2da5eba7a6f7f914448397c31022023cb7908eafab080da4ff37460feb3c72e1ac6ecdb052d2df0c69a3954ec44550121026fd3594eb77ded5de88f54a528950659822859472fcee072a59657032aaf6fe2ffffffff02f071dee22c0000001976a914a316f474589a2f2013bd60b7d0ffb735e33915d188ac00e1823e0c0100001976a914ee628c1d309de0e0d3033d047b19921563f97ee288ac00000000411f3b6a2aabf7c3ccdd2e4d30a581a53670b6bc623e324eb3416100dfe64b3c54a84567bace9246f935b310e026c137eb7ec9836dfb46854aa6695af50a1ac5dd64
|
|
@ -0,0 +1 @@
|
|||
04000000c7fed5984ba7d11c7e4865c1d41ad1755a1082782d81a27fc08888bc749881e9e709a5bb1daf5a76582def89c13eba5e7c4cf8104407ecf81f74be6ba18e89c6ae3e275da61b0d1b0000000000000000000000000000000000000000000000000000000000000000000000000201000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0603de3d060101ffffffff01000000000000000000000000000100000001084b029489e1cddf726080c447c8a2b1d4bbe43024db31b8b19bc07585db9555010000006a473044022017422b9e3414d6233fa75f9eb7778469bebbb40686b0f7eb77d90a04c80149610220411f1063086fe205ea821ceb0de89e8158e202aba00f5ebb92b51f97381311fd012102ccb10a2f0603a0624b8708abefb5f4700631fc131c5de38b51e0359e2ffa7d1cffffffff03000000000000000000f260de1a580100001976a9145b1d583a4c270f2f14be77b298f0a9c6df97471388ac009ca6920c0000001976a914cb1196fb1b98d04b0cb8d2ffde3c2de3eb83d9fe88ac00000000411f90bba5b16a087a5b7e0b8fc2aba041d81d74d270b502b24ea5801b5f4f975bba382260a6fd247facbb49acb8f304324deb3c1e00060d10a4958c047e4530c9fa
|
|
@ -1,19 +1,21 @@
|
|||
package dogecoin
|
||||
|
||||
import (
|
||||
"blockbook/bchain"
|
||||
"blockbook/bchain/coins/btc"
|
||||
"blockbook/bchain/coins/utils"
|
||||
"bytes"
|
||||
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/jakm/btcutil/chaincfg"
|
||||
"github.com/martinboehm/btcd/wire"
|
||||
"github.com/martinboehm/btcutil/chaincfg"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/utils"
|
||||
)
|
||||
|
||||
// magic numbers
|
||||
const (
|
||||
MainnetMagic wire.BitcoinNet = 0xc0c0c0c0
|
||||
)
|
||||
|
||||
// chain parameters
|
||||
var (
|
||||
MainNetParams chaincfg.Params
|
||||
)
|
||||
|
|
|
@ -3,8 +3,6 @@
|
|||
package dogecoin
|
||||
|
||||
import (
|
||||
"blockbook/bchain"
|
||||
"blockbook/bchain/coins/btc"
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
|
@ -15,7 +13,9 @@ import (
|
|||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/jakm/btcutil/chaincfg"
|
||||
"github.com/martinboehm/btcutil/chaincfg"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
|
@ -46,6 +46,12 @@ func Test_GetAddrDescFromAddress_Mainnet(t *testing.T) {
|
|||
want: "76a914efb6158f75743c611858fdfd0f4aaec6cc6196bc88ac",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "P2PKH3",
|
||||
args: args{address: "DHobAps6DjZ5n4xMV75n7kJv299Zi85FCG"},
|
||||
want: "76a9148ae937291e72f7368421dbaa966c44950eb14db788ac",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "P2SH1",
|
||||
args: args{address: "9tg1kVUk339Tk58ewu5T8QT82Z6cE4UvSU"},
|
||||
|
@ -76,6 +82,81 @@ func Test_GetAddrDescFromAddress_Mainnet(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func Test_GetAddressesFromAddrDesc_Mainnet(t *testing.T) {
|
||||
type args struct {
|
||||
script string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []string
|
||||
want2 bool
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "P2PKH1",
|
||||
args: args{script: "76a9148841590909747c0f97af158f22fadacb1652522088ac"},
|
||||
want: []string{"DHZYinsaM9nW5piCMN639ELRKbZomThPnZ"},
|
||||
want2: true,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "P2PKH2",
|
||||
args: args{script: "76a914efb6158f75743c611858fdfd0f4aaec6cc6196bc88ac"},
|
||||
want: []string{"DSzaAYEYyy9ngjoJ294r7jzFM3xhD6bKHK"},
|
||||
want2: true,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "P2PKH3",
|
||||
args: args{script: "76a91450e86eeac599ad023b8981296d01b50bdabcdd9788ac"},
|
||||
want: []string{"DCWu3MLz9xBGFuuLyNDf6QjuGp49f5tfc9"},
|
||||
want2: true,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "P2SH1",
|
||||
args: args{script: "a9141889a089400ea25d28694fd98aa7702b21eeeab187"},
|
||||
want: []string{"9tg1kVUk339Tk58ewu5T8QT82Z6cE4UvSU"},
|
||||
want2: true,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "OP_RETURN ascii",
|
||||
args: args{script: "6a0461686f6a"},
|
||||
want: []string{"OP_RETURN (ahoj)"},
|
||||
want2: false,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "OP_RETURN hex",
|
||||
args: args{script: "6a072020f1686f6a20"},
|
||||
want: []string{"OP_RETURN 2020f1686f6a20"},
|
||||
want2: false,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
parser := NewDogecoinParser(GetChainParams("main"), &btc.Configuration{})
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
b, _ := hex.DecodeString(tt.args.script)
|
||||
got, got2, err := parser.GetAddressesFromAddrDesc(b)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("GetAddressesFromAddrDesc() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("GetAddressesFromAddrDesc() = %v, want %v", got, tt.want)
|
||||
}
|
||||
if !reflect.DeepEqual(got2, tt.want2) {
|
||||
t.Errorf("GetAddressesFromAddrDesc() = %v, want %v", got2, tt.want2)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
testTx1 bchain.Tx
|
||||
testTxPacked1 = "00030e6d8ba8d7aa2001000000016b3c0c53267964120acf7f7e72217e3f463e52ce622f89659f6a6bb8e69a4d91000000006c493046022100a96454237e3a020994534583e28c04757881374bceac89f933ea9ff00b4db259022100fbb757ff7ea4f02c4e42556b2834c61eba1f1af605db089d836a0614d90a3b46012103cebdde6d1046e285df4f48497bc50dc20a4a258ca5b7308cb0a929c9fdadcd9dffffffff0217e823ca7f0200001976a914eef21768a546590993e313c7f3dfadf6a6efa1e888acaddf4cba010000001976a914e0fee2ea29dd9c6c759d8341bd0da4c4f738cced88ac00000000"
|
||||
|
@ -276,7 +357,7 @@ type testBlock struct {
|
|||
|
||||
var testParseBlockTxs = map[int]testBlock{
|
||||
// block without auxpow
|
||||
12345: testBlock{
|
||||
12345: {
|
||||
size: 8582,
|
||||
time: 1387104223,
|
||||
txs: []string{
|
||||
|
@ -314,7 +395,7 @@ var testParseBlockTxs = map[int]testBlock{
|
|||
},
|
||||
},
|
||||
// 1st block with auxpow
|
||||
371337: testBlock{
|
||||
371337: {
|
||||
size: 1704,
|
||||
time: 1410464577,
|
||||
txs: []string{
|
||||
|
@ -327,7 +408,7 @@ var testParseBlockTxs = map[int]testBlock{
|
|||
},
|
||||
},
|
||||
// block with auxpow
|
||||
567890: testBlock{
|
||||
567890: {
|
||||
size: 3833,
|
||||
time: 1422855443,
|
||||
txs: []string{
|
||||
|
@ -343,7 +424,7 @@ var testParseBlockTxs = map[int]testBlock{
|
|||
},
|
||||
},
|
||||
// recent block
|
||||
2264125: testBlock{
|
||||
2264125: {
|
||||
size: 8531,
|
||||
time: 1529099968,
|
||||
txs: []string{
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
package dogecoin
|
||||
|
||||
import (
|
||||
"blockbook/bchain"
|
||||
"blockbook/bchain/coins/btc"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
|
||||
)
|
||||
|
||||
// DogecoinRPC is an interface to JSON-RPC dogecoind service.
|
||||
|
@ -31,10 +31,11 @@ func NewDogecoinRPC(config json.RawMessage, pushHandler func(bchain.Notification
|
|||
|
||||
// Initialize initializes DogecoinRPC instance.
|
||||
func (b *DogecoinRPC) Initialize() error {
|
||||
chainName, err := b.GetChainInfoAndInitializeMempool(b)
|
||||
ci, err := b.GetChainInfo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
chainName := ci.Chain
|
||||
|
||||
glog.Info("Chain name ", chainName)
|
||||
params := GetChainParams(chainName)
|
||||
|
|
|
@ -0,0 +1,242 @@
|
|||
package eth
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"math/big"
|
||||
"strings"
|
||||
"sync"
|
||||
"unicode/utf8"
|
||||
|
||||
ethcommon "github.com/ethereum/go-ethereum/common"
|
||||
"github.com/golang/glog"
|
||||
"github.com/juju/errors"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
)
|
||||
|
||||
var erc20abi = `[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function","signature":"0x06fdde03"},
|
||||
{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function","signature":"0x95d89b41"},
|
||||
{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"type":"function","signature":"0x313ce567"},
|
||||
{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function","signature":"0x18160ddd"},
|
||||
{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"type":"function","signature":"0x70a08231"},
|
||||
{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function","signature":"0xa9059cbb"},
|
||||
{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function","signature":"0x23b872dd"},
|
||||
{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"}],"name":"approve","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function","signature":"0x095ea7b3"},
|
||||
{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"remaining","type":"uint256"}],"payable":false,"type":"function","signature":"0xdd62ed3e"},
|
||||
{"anonymous":false,"inputs":[{"indexed":true,"name":"_from","type":"address"},{"indexed":true,"name":"_to","type":"address"},{"indexed":false,"name":"_value","type":"uint256"}],"name":"Transfer","type":"event","signature":"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"},
|
||||
{"anonymous":false,"inputs":[{"indexed":true,"name":"_owner","type":"address"},{"indexed":true,"name":"_spender","type":"address"},{"indexed":false,"name":"_value","type":"uint256"}],"name":"Approval","type":"event","signature":"0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925"},
|
||||
{"inputs":[{"name":"_initialAmount","type":"uint256"},{"name":"_tokenName","type":"string"},{"name":"_decimalUnits","type":"uint8"},{"name":"_tokenSymbol","type":"string"}],"payable":false,"type":"constructor"},
|
||||
{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"},{"name":"_extraData","type":"bytes"}],"name":"approveAndCall","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function","signature":"0xcae9ca51"},
|
||||
{"constant":true,"inputs":[],"name":"version","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function","signature":"0x54fd4d50"}]`
|
||||
|
||||
// doing the parsing/processing without using go-ethereum/accounts/abi library, it is simple to get data from Transfer event
|
||||
const erc20TransferMethodSignature = "0xa9059cbb"
|
||||
const erc20TransferEventSignature = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
|
||||
const erc20NameSignature = "0x06fdde03"
|
||||
const erc20SymbolSignature = "0x95d89b41"
|
||||
const erc20DecimalsSignature = "0x313ce567"
|
||||
const erc20BalanceOf = "0x70a08231"
|
||||
|
||||
var cachedContracts = make(map[string]*bchain.Erc20Contract)
|
||||
var cachedContractsMux sync.Mutex
|
||||
|
||||
func addressFromPaddedHex(s string) (string, error) {
|
||||
var t big.Int
|
||||
var ok bool
|
||||
if has0xPrefix(s) {
|
||||
_, ok = t.SetString(s[2:], 16)
|
||||
} else {
|
||||
_, ok = t.SetString(s, 16)
|
||||
}
|
||||
if !ok {
|
||||
return "", errors.New("Data is not a number")
|
||||
}
|
||||
a := ethcommon.BigToAddress(&t)
|
||||
return a.String(), nil
|
||||
}
|
||||
|
||||
func erc20GetTransfersFromLog(logs []*rpcLog) ([]bchain.Erc20Transfer, error) {
|
||||
var r []bchain.Erc20Transfer
|
||||
for _, l := range logs {
|
||||
if len(l.Topics) == 3 && l.Topics[0] == erc20TransferEventSignature {
|
||||
var t big.Int
|
||||
_, ok := t.SetString(l.Data, 0)
|
||||
if !ok {
|
||||
return nil, errors.New("Data is not a number")
|
||||
}
|
||||
from, err := addressFromPaddedHex(l.Topics[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
to, err := addressFromPaddedHex(l.Topics[2])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r = append(r, bchain.Erc20Transfer{
|
||||
Contract: EIP55AddressFromAddress(l.Address),
|
||||
From: EIP55AddressFromAddress(from),
|
||||
To: EIP55AddressFromAddress(to),
|
||||
Tokens: t,
|
||||
})
|
||||
}
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func erc20GetTransfersFromTx(tx *rpcTransaction) ([]bchain.Erc20Transfer, error) {
|
||||
var r []bchain.Erc20Transfer
|
||||
if len(tx.Payload) == 128+len(erc20TransferMethodSignature) && strings.HasPrefix(tx.Payload, erc20TransferMethodSignature) {
|
||||
to, err := addressFromPaddedHex(tx.Payload[len(erc20TransferMethodSignature) : 64+len(erc20TransferMethodSignature)])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var t big.Int
|
||||
_, ok := t.SetString(tx.Payload[len(erc20TransferMethodSignature)+64:], 16)
|
||||
if !ok {
|
||||
return nil, errors.New("Data is not a number")
|
||||
}
|
||||
r = append(r, bchain.Erc20Transfer{
|
||||
Contract: EIP55AddressFromAddress(tx.To),
|
||||
From: EIP55AddressFromAddress(tx.From),
|
||||
To: EIP55AddressFromAddress(to),
|
||||
Tokens: t,
|
||||
})
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (b *EthereumRPC) ethCall(data, to string) (string, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
|
||||
defer cancel()
|
||||
var r string
|
||||
err := b.rpc.CallContext(ctx, &r, "eth_call", map[string]interface{}{
|
||||
"data": data,
|
||||
"to": to,
|
||||
}, "latest")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func parseErc20NumericProperty(contractDesc bchain.AddressDescriptor, data string) *big.Int {
|
||||
if has0xPrefix(data) {
|
||||
data = data[2:]
|
||||
}
|
||||
if len(data) == 64 {
|
||||
var n big.Int
|
||||
_, ok := n.SetString(data, 16)
|
||||
if ok {
|
||||
return &n
|
||||
}
|
||||
}
|
||||
if glog.V(1) {
|
||||
glog.Warning("Cannot parse '", data, "' for contract ", contractDesc)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseErc20StringProperty(contractDesc bchain.AddressDescriptor, data string) string {
|
||||
if has0xPrefix(data) {
|
||||
data = data[2:]
|
||||
}
|
||||
if len(data) > 128 {
|
||||
n := parseErc20NumericProperty(contractDesc, data[64:128])
|
||||
if n != nil {
|
||||
l := n.Uint64()
|
||||
if l > 0 && 2*int(l) <= len(data)-128 {
|
||||
b, err := hex.DecodeString(data[128 : 128+2*l])
|
||||
if err == nil {
|
||||
return string(b)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// allow string properties as UTF-8 data
|
||||
b, err := hex.DecodeString(data)
|
||||
if err == nil {
|
||||
i := bytes.Index(b, []byte{0})
|
||||
if i > 32 {
|
||||
i = 32
|
||||
}
|
||||
if i > 0 {
|
||||
b = b[:i]
|
||||
}
|
||||
if utf8.Valid(b) {
|
||||
return string(b)
|
||||
}
|
||||
}
|
||||
if glog.V(1) {
|
||||
glog.Warning("Cannot parse '", data, "' for contract ", contractDesc)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// EthereumTypeGetErc20ContractInfo returns information about ERC20 contract
|
||||
func (b *EthereumRPC) EthereumTypeGetErc20ContractInfo(contractDesc bchain.AddressDescriptor) (*bchain.Erc20Contract, error) {
|
||||
cds := string(contractDesc)
|
||||
cachedContractsMux.Lock()
|
||||
contract, found := cachedContracts[cds]
|
||||
cachedContractsMux.Unlock()
|
||||
if !found {
|
||||
address := EIP55Address(contractDesc)
|
||||
data, err := b.ethCall(erc20NameSignature, address)
|
||||
if err != nil {
|
||||
// ignore the error from the eth_call - since geth v1.9.15 they changed the behavior
|
||||
// and returning error "execution reverted" for some non contract addresses
|
||||
// https://github.com/ethereum/go-ethereum/issues/21249#issuecomment-648647672
|
||||
glog.Warning(errors.Annotatef(err, "erc20NameSignature %v", address))
|
||||
return nil, nil
|
||||
// return nil, errors.Annotatef(err, "erc20NameSignature %v", address)
|
||||
}
|
||||
name := parseErc20StringProperty(contractDesc, data)
|
||||
if name != "" {
|
||||
data, err = b.ethCall(erc20SymbolSignature, address)
|
||||
if err != nil {
|
||||
glog.Warning(errors.Annotatef(err, "erc20SymbolSignature %v", address))
|
||||
return nil, nil
|
||||
// return nil, errors.Annotatef(err, "erc20SymbolSignature %v", address)
|
||||
}
|
||||
symbol := parseErc20StringProperty(contractDesc, data)
|
||||
data, err = b.ethCall(erc20DecimalsSignature, address)
|
||||
if err != nil {
|
||||
glog.Warning(errors.Annotatef(err, "erc20DecimalsSignature %v", address))
|
||||
// return nil, errors.Annotatef(err, "erc20DecimalsSignature %v", address)
|
||||
}
|
||||
contract = &bchain.Erc20Contract{
|
||||
Contract: address,
|
||||
Name: name,
|
||||
Symbol: symbol,
|
||||
}
|
||||
d := parseErc20NumericProperty(contractDesc, data)
|
||||
if d != nil {
|
||||
contract.Decimals = int(uint8(d.Uint64()))
|
||||
} else {
|
||||
contract.Decimals = EtherAmountDecimalPoint
|
||||
}
|
||||
} else {
|
||||
contract = nil
|
||||
}
|
||||
cachedContractsMux.Lock()
|
||||
cachedContracts[cds] = contract
|
||||
cachedContractsMux.Unlock()
|
||||
}
|
||||
return contract, nil
|
||||
}
|
||||
|
||||
// EthereumTypeGetErc20ContractBalance returns balance of ERC20 contract for given address
|
||||
func (b *EthereumRPC) EthereumTypeGetErc20ContractBalance(addrDesc, contractDesc bchain.AddressDescriptor) (*big.Int, error) {
|
||||
addr := EIP55Address(addrDesc)
|
||||
contract := EIP55Address(contractDesc)
|
||||
req := erc20BalanceOf + "0000000000000000000000000000000000000000000000000000000000000000"[len(addr)-2:] + addr[2:]
|
||||
data, err := b.ethCall(req, contract)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r := parseErc20NumericProperty(contractDesc, data)
|
||||
if r == nil {
|
||||
return nil, errors.New("Invalid balance")
|
||||
}
|
||||
return r, nil
|
||||
}
|
|
@ -0,0 +1,204 @@
|
|||
// +build unittest
|
||||
|
||||
package eth
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
"spacecruft.org/spacecruft/blockbook/tests/dbtestdata"
|
||||
)
|
||||
|
||||
func TestErc20_erc20GetTransfersFromLog(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args []*rpcLog
|
||||
want []bchain.Erc20Transfer
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "1",
|
||||
args: []*rpcLog{
|
||||
{
|
||||
Address: "0x76a45e8976499ab9ae223cc584019341d5a84e96",
|
||||
Topics: []string{
|
||||
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
|
||||
"0x0000000000000000000000002aacf811ac1a60081ea39f7783c0d26c500871a8",
|
||||
"0x000000000000000000000000e9a5216ff992cfa01594d43501a56e12769eb9d2",
|
||||
},
|
||||
Data: "0x0000000000000000000000000000000000000000000000000000000000000123",
|
||||
},
|
||||
},
|
||||
want: []bchain.Erc20Transfer{
|
||||
{
|
||||
Contract: "0x76a45e8976499ab9ae223cc584019341d5a84e96",
|
||||
From: "0x2aacf811ac1a60081ea39f7783c0d26c500871a8",
|
||||
To: "0xe9a5216ff992cfa01594d43501a56e12769eb9d2",
|
||||
Tokens: *big.NewInt(0x123),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "2",
|
||||
args: []*rpcLog{
|
||||
{ // Transfer
|
||||
Address: "0x0d0f936ee4c93e25944694d6c121de94d9760f11",
|
||||
Topics: []string{
|
||||
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
|
||||
"0x0000000000000000000000006f44cceb49b4a5812d54b6f494fc2febf25511ed",
|
||||
"0x0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d",
|
||||
},
|
||||
Data: "0x0000000000000000000000000000000000000000000000006a8313d60b1f606b",
|
||||
},
|
||||
{ // Transfer
|
||||
Address: "0xc778417e063141139fce010982780140aa0cd5ab",
|
||||
Topics: []string{
|
||||
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
|
||||
"0x0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d",
|
||||
"0x0000000000000000000000006f44cceb49b4a5812d54b6f494fc2febf25511ed",
|
||||
},
|
||||
Data: "0x000000000000000000000000000000000000000000000000000308fd0e798ac0",
|
||||
},
|
||||
{ // not Transfer
|
||||
Address: "0x479cc461fecd078f766ecc58533d6f69580cf3ac",
|
||||
Topics: []string{
|
||||
"0x0d0b9391970d9a25552f37d436d2aae2925e2bfe1b2a923754bada030c498cb3",
|
||||
"0x0000000000000000000000006f44cceb49b4a5812d54b6f494fc2febf25511ed",
|
||||
"0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"0x5af266c0a89a07c1917deaa024414577e6c3c31c8907d079e13eb448c082594f",
|
||||
},
|
||||
Data: "0x0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d0000000000000",
|
||||
},
|
||||
{ // not Transfer
|
||||
Address: "0x0d0f936ee4c93e25944694d6c121de94d9760f11",
|
||||
Topics: []string{
|
||||
"0x0d0b9391970d9a25552f37d436d2aae2925e2bfe1b2a923754bada030c498cb3",
|
||||
"0x0000000000000000000000007b62eb7fe80350dc7ec945c0b73242cb9877fb1b",
|
||||
"0xb0b69dad58df6032c3b266e19b1045b19c87acd2c06fb0c598090f44b8e263aa",
|
||||
},
|
||||
Data: "0x0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d000000000000000000000000c778417e063141139fce010982780140aa0cd5ab0000000000000000000000000d0f936ee4c93e25944694d6c121de94d9760f1100000000000000000000000000000000000000000000000000031855667df7a80000000000000000000000000000000000000000000000006a8313d60b1f800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
||||
},
|
||||
},
|
||||
want: []bchain.Erc20Transfer{
|
||||
{
|
||||
Contract: "0x0d0f936ee4c93e25944694d6c121de94d9760f11",
|
||||
From: "0x6f44cceb49b4a5812d54b6f494fc2febf25511ed",
|
||||
To: "0x4bda106325c335df99eab7fe363cac8a0ba2a24d",
|
||||
Tokens: *big.NewInt(0x6a8313d60b1f606b),
|
||||
},
|
||||
{
|
||||
Contract: "0xc778417e063141139fce010982780140aa0cd5ab",
|
||||
From: "0x4bda106325c335df99eab7fe363cac8a0ba2a24d",
|
||||
To: "0x6f44cceb49b4a5812d54b6f494fc2febf25511ed",
|
||||
Tokens: *big.NewInt(0x308fd0e798ac0),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := erc20GetTransfersFromLog(tt.args)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("erc20GetTransfersFromLog error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
// the addresses could have different case
|
||||
if strings.ToLower(fmt.Sprint(got)) != strings.ToLower(fmt.Sprint(tt.want)) {
|
||||
t.Errorf("erc20GetTransfersFromLog = %+v, want %+v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestErc20_parseErc20StringProperty(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "1",
|
||||
args: "0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000758504c4f44444500000000000000000000000000000000000000000000000000",
|
||||
want: "XPLODDE",
|
||||
},
|
||||
{
|
||||
name: "2",
|
||||
args: "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000022426974436c617665202d20436f6e73756d657220416374697669747920546f6b656e00000000000000",
|
||||
want: "BitClave - Consumer Activity Token",
|
||||
},
|
||||
{
|
||||
name: "short",
|
||||
args: "0x44616920537461626c65636f696e2076312e3000000000000000000000000000",
|
||||
want: "Dai Stablecoin v1.0",
|
||||
},
|
||||
{
|
||||
name: "short2",
|
||||
args: "0x44616920537461626c65636f696e2076312e3020444444444444444444444444",
|
||||
want: "Dai Stablecoin v1.0 DDDDDDDDDDDD",
|
||||
},
|
||||
{
|
||||
name: "long",
|
||||
args: "0x556e6973776170205631000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
||||
want: "Uniswap V1",
|
||||
},
|
||||
{
|
||||
name: "garbage",
|
||||
args: "0x2234880850896048596206002535425366538144616734015984380565810000",
|
||||
want: "",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := parseErc20StringProperty(nil, tt.args)
|
||||
// the addresses could have different case
|
||||
if got != tt.want {
|
||||
t.Errorf("parseErc20StringProperty = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestErc20_erc20GetTransfersFromTx(t *testing.T) {
|
||||
p := NewEthereumParser(1)
|
||||
b := dbtestdata.GetTestEthereumTypeBlock1(p)
|
||||
bn, _ := new(big.Int).SetString("21e19e0c9bab2400000", 16)
|
||||
tests := []struct {
|
||||
name string
|
||||
args *rpcTransaction
|
||||
want []bchain.Erc20Transfer
|
||||
}{
|
||||
{
|
||||
name: "0",
|
||||
args: (b.Txs[0].CoinSpecificData.(completeTransaction)).Tx,
|
||||
want: []bchain.Erc20Transfer{},
|
||||
},
|
||||
{
|
||||
name: "1",
|
||||
args: (b.Txs[1].CoinSpecificData.(completeTransaction)).Tx,
|
||||
want: []bchain.Erc20Transfer{
|
||||
{
|
||||
Contract: "0x4af4114f73d1c1c903ac9e0361b379d1291808a2",
|
||||
From: "0x20cd153de35d469ba46127a0c8f18626b59a256a",
|
||||
To: "0x555ee11fbddc0e49a9bab358a8941ad95ffdb48f",
|
||||
Tokens: *bn,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := erc20GetTransfersFromTx(tt.args)
|
||||
if err != nil {
|
||||
t.Errorf("erc20GetTransfersFromTx error = %v", err)
|
||||
return
|
||||
}
|
||||
// the addresses could have different case
|
||||
if strings.ToLower(fmt.Sprint(got)) != strings.ToLower(fmt.Sprint(tt.want)) {
|
||||
t.Errorf("erc20GetTransfersFromTx = %+v, want %+v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,59 +1,92 @@
|
|||
package eth
|
||||
|
||||
import (
|
||||
"blockbook/bchain"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"math/big"
|
||||
"strconv"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/juju/errors"
|
||||
|
||||
ethcommon "github.com/ethereum/go-ethereum/common"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
"golang.org/x/crypto/sha3"
|
||||
)
|
||||
|
||||
// EthereumTypeAddressDescriptorLen - in case of EthereumType, the AddressDescriptor has fixed length
|
||||
const EthereumTypeAddressDescriptorLen = 20
|
||||
|
||||
// EtherAmountDecimalPoint defines number of decimal points in Ether amounts
|
||||
const EtherAmountDecimalPoint = 18
|
||||
|
||||
// EthereumParser handle
|
||||
type EthereumParser struct {
|
||||
*bchain.BaseParser
|
||||
}
|
||||
|
||||
// NewEthereumParser returns new EthereumParser instance
|
||||
func NewEthereumParser() *EthereumParser {
|
||||
func NewEthereumParser(b int) *EthereumParser {
|
||||
return &EthereumParser{&bchain.BaseParser{
|
||||
BlockAddressesToKeep: 0,
|
||||
AmountDecimalPoint: 18,
|
||||
BlockAddressesToKeep: b,
|
||||
AmountDecimalPoint: EtherAmountDecimalPoint,
|
||||
}}
|
||||
}
|
||||
|
||||
type rpcHeader struct {
|
||||
Hash string `json:"hash"`
|
||||
ParentHash string `json:"parentHash"`
|
||||
Difficulty string `json:"difficulty"`
|
||||
Number string `json:"number"`
|
||||
Time string `json:"timestamp"`
|
||||
Size string `json:"size"`
|
||||
Nonce string `json:"nonce"`
|
||||
}
|
||||
|
||||
type rpcTransaction struct {
|
||||
AccountNonce string `json:"nonce" gencodec:"required"`
|
||||
Price string `json:"gasPrice" gencodec:"required"`
|
||||
GasLimit string `json:"gas" gencodec:"required"`
|
||||
To string `json:"to" rlp:"nil"` // nil means contract creation
|
||||
Value string `json:"value" gencodec:"required"`
|
||||
Payload string `json:"input" gencodec:"required"`
|
||||
Hash ethcommon.Hash `json:"hash" rlp:"-"`
|
||||
BlockNumber string `json:"blockNumber"`
|
||||
BlockHash *ethcommon.Hash `json:"blockHash,omitempty"`
|
||||
From string `json:"from"`
|
||||
TransactionIndex string `json:"transactionIndex"`
|
||||
// Signature values
|
||||
V string `json:"v" gencodec:"required"`
|
||||
R string `json:"r" gencodec:"required"`
|
||||
S string `json:"s" gencodec:"required"`
|
||||
AccountNonce string `json:"nonce"`
|
||||
GasPrice string `json:"gasPrice"`
|
||||
GasLimit string `json:"gas"`
|
||||
To string `json:"to"` // nil means contract creation
|
||||
Value string `json:"value"`
|
||||
Payload string `json:"input"`
|
||||
Hash string `json:"hash"`
|
||||
BlockNumber string `json:"blockNumber"`
|
||||
BlockHash string `json:"blockHash,omitempty"`
|
||||
From string `json:"from"`
|
||||
TransactionIndex string `json:"transactionIndex"`
|
||||
// Signature values - ignored
|
||||
// V string `json:"v"`
|
||||
// R string `json:"r"`
|
||||
// S string `json:"s"`
|
||||
}
|
||||
|
||||
type rpcBlock struct {
|
||||
Hash ethcommon.Hash `json:"hash"`
|
||||
type rpcLog struct {
|
||||
Address string `json:"address"`
|
||||
Topics []string `json:"topics"`
|
||||
Data string `json:"data"`
|
||||
}
|
||||
|
||||
type rpcLogWithTxHash struct {
|
||||
rpcLog
|
||||
Hash string `json:"transactionHash"`
|
||||
}
|
||||
|
||||
type rpcReceipt struct {
|
||||
GasUsed string `json:"gasUsed"`
|
||||
Status string `json:"status"`
|
||||
Logs []*rpcLog `json:"logs"`
|
||||
}
|
||||
|
||||
type completeTransaction struct {
|
||||
Tx *rpcTransaction `json:"tx"`
|
||||
Receipt *rpcReceipt `json:"receipt,omitempty"`
|
||||
}
|
||||
|
||||
type rpcBlockTransactions struct {
|
||||
Transactions []rpcTransaction `json:"transactions"`
|
||||
UncleHashes []ethcommon.Hash `json:"uncles"`
|
||||
}
|
||||
|
||||
func ethHashToHash(h ethcommon.Hash) string {
|
||||
return h.Hex()
|
||||
type rpcBlockTxids struct {
|
||||
Transactions []string `json:"transactions"`
|
||||
}
|
||||
|
||||
func ethNumber(n string) (int64, error) {
|
||||
|
@ -63,27 +96,35 @@ func ethNumber(n string) (int64, error) {
|
|||
return 0, errors.Errorf("Not a number: '%v'", n)
|
||||
}
|
||||
|
||||
func (p *EthereumParser) ethTxToTx(tx *rpcTransaction, blocktime int64, confirmations uint32) (*bchain.Tx, error) {
|
||||
txid := ethHashToHash(tx.Hash)
|
||||
func (p *EthereumParser) ethTxToTx(tx *rpcTransaction, receipt *rpcReceipt, blocktime int64, confirmations uint32, fixEIP55 bool) (*bchain.Tx, error) {
|
||||
txid := tx.Hash
|
||||
var (
|
||||
fa, ta []string
|
||||
err error
|
||||
)
|
||||
if len(tx.From) > 2 {
|
||||
if fixEIP55 {
|
||||
tx.From = EIP55AddressFromAddress(tx.From)
|
||||
}
|
||||
fa = []string{tx.From}
|
||||
}
|
||||
if len(tx.To) > 2 {
|
||||
if fixEIP55 {
|
||||
tx.To = EIP55AddressFromAddress(tx.To)
|
||||
}
|
||||
ta = []string{tx.To}
|
||||
}
|
||||
// temporarily, the complete rpcTransaction without BlockHash is marshalled and hex encoded to bchain.Tx.Hex
|
||||
bh := tx.BlockHash
|
||||
tx.BlockHash = nil
|
||||
b, err := json.Marshal(tx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if fixEIP55 && receipt != nil && receipt.Logs != nil {
|
||||
for _, l := range receipt.Logs {
|
||||
if len(l.Address) > 2 {
|
||||
l.Address = EIP55AddressFromAddress(l.Address)
|
||||
}
|
||||
}
|
||||
}
|
||||
ct := completeTransaction{
|
||||
Tx: tx,
|
||||
Receipt: receipt,
|
||||
}
|
||||
tx.BlockHash = bh
|
||||
h := hex.EncodeToString(b)
|
||||
vs, err := hexutil.DecodeBig(tx.Value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -91,7 +132,7 @@ func (p *EthereumParser) ethTxToTx(tx *rpcTransaction, blocktime int64, confirma
|
|||
return &bchain.Tx{
|
||||
Blocktime: blocktime,
|
||||
Confirmations: confirmations,
|
||||
Hex: h,
|
||||
// Hex
|
||||
// LockTime
|
||||
Time: blocktime,
|
||||
Txid: txid,
|
||||
|
@ -115,6 +156,7 @@ func (p *EthereumParser) ethTxToTx(tx *rpcTransaction, blocktime int64, confirma
|
|||
},
|
||||
},
|
||||
},
|
||||
CoinSpecificData: ct,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -136,18 +178,52 @@ func (p *EthereumParser) GetAddrDescFromAddress(address string) (bchain.AddressD
|
|||
if has0xPrefix(address) {
|
||||
address = address[2:]
|
||||
}
|
||||
if len(address) == 0 {
|
||||
if len(address) != EthereumTypeAddressDescriptorLen*2 {
|
||||
return nil, bchain.ErrAddressMissing
|
||||
}
|
||||
if len(address)&1 == 1 {
|
||||
address = "0" + address
|
||||
}
|
||||
return hex.DecodeString(address)
|
||||
}
|
||||
|
||||
// EIP55Address returns an EIP55-compliant hex string representation of the address
|
||||
func EIP55Address(addrDesc bchain.AddressDescriptor) string {
|
||||
raw := hexutil.Encode(addrDesc)
|
||||
if len(raw) != 42 {
|
||||
return raw
|
||||
}
|
||||
sha := sha3.NewLegacyKeccak256()
|
||||
result := []byte(raw)
|
||||
sha.Write(result[2:])
|
||||
hash := sha.Sum(nil)
|
||||
|
||||
for i := 2; i < len(result); i++ {
|
||||
hashByte := hash[(i-2)>>1]
|
||||
if i%2 == 0 {
|
||||
hashByte = hashByte >> 4
|
||||
} else {
|
||||
hashByte &= 0xf
|
||||
}
|
||||
if result[i] > '9' && hashByte > 7 {
|
||||
result[i] -= 32
|
||||
}
|
||||
}
|
||||
return string(result)
|
||||
}
|
||||
|
||||
// EIP55AddressFromAddress returns an EIP55-compliant hex string representation of the address
|
||||
func EIP55AddressFromAddress(address string) string {
|
||||
if has0xPrefix(address) {
|
||||
address = address[2:]
|
||||
}
|
||||
b, err := hex.DecodeString(address)
|
||||
if err != nil {
|
||||
return address
|
||||
}
|
||||
return EIP55Address(b)
|
||||
}
|
||||
|
||||
// GetAddressesFromAddrDesc returns addresses for given address descriptor with flag if the addresses are searchable
|
||||
func (p *EthereumParser) GetAddressesFromAddrDesc(addrDesc bchain.AddressDescriptor) ([]string, bool, error) {
|
||||
return []string{hexutil.Encode(addrDesc)}, true, nil
|
||||
return []string{EIP55Address(addrDesc)}, true, nil
|
||||
}
|
||||
|
||||
// GetScriptFromAddrDesc returns output script for given address descriptor
|
||||
|
@ -179,83 +255,148 @@ func hexEncodeBig(b []byte) string {
|
|||
|
||||
// PackTx packs transaction to byte array
|
||||
func (p *EthereumParser) PackTx(tx *bchain.Tx, height uint32, blockTime int64) ([]byte, error) {
|
||||
b, err := hex.DecodeString(tx.Hex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var r rpcTransaction
|
||||
var err error
|
||||
var n uint64
|
||||
err = json.Unmarshal(b, &r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
r, ok := tx.CoinSpecificData.(completeTransaction)
|
||||
if !ok {
|
||||
return nil, errors.New("Missing CoinSpecificData")
|
||||
}
|
||||
pt := &ProtoTransaction{}
|
||||
if pt.AccountNonce, err = hexutil.DecodeUint64(r.AccountNonce); err != nil {
|
||||
return nil, errors.Annotatef(err, "AccountNonce %v", r.AccountNonce)
|
||||
pt := &ProtoCompleteTransaction{}
|
||||
pt.Tx = &ProtoCompleteTransaction_TxType{}
|
||||
if pt.Tx.AccountNonce, err = hexutil.DecodeUint64(r.Tx.AccountNonce); err != nil {
|
||||
return nil, errors.Annotatef(err, "AccountNonce %v", r.Tx.AccountNonce)
|
||||
}
|
||||
if n, err = hexutil.DecodeUint64(r.BlockNumber); err != nil {
|
||||
return nil, errors.Annotatef(err, "BlockNumber %v", r.BlockNumber)
|
||||
// pt.BlockNumber = height
|
||||
if n, err = hexutil.DecodeUint64(r.Tx.BlockNumber); err != nil {
|
||||
return nil, errors.Annotatef(err, "BlockNumber %v", r.Tx.BlockNumber)
|
||||
}
|
||||
pt.BlockNumber = uint32(n)
|
||||
pt.BlockTime = uint64(blockTime)
|
||||
if pt.From, err = hexDecode(r.From); err != nil {
|
||||
return nil, errors.Annotatef(err, "From %v", r.From)
|
||||
if pt.Tx.From, err = hexDecode(r.Tx.From); err != nil {
|
||||
return nil, errors.Annotatef(err, "From %v", r.Tx.From)
|
||||
}
|
||||
if pt.GasLimit, err = hexutil.DecodeUint64(r.GasLimit); err != nil {
|
||||
return nil, errors.Annotatef(err, "GasLimit %v", r.GasLimit)
|
||||
if pt.Tx.GasLimit, err = hexutil.DecodeUint64(r.Tx.GasLimit); err != nil {
|
||||
return nil, errors.Annotatef(err, "GasLimit %v", r.Tx.GasLimit)
|
||||
}
|
||||
pt.Hash = r.Hash.Bytes()
|
||||
if pt.Payload, err = hexDecode(r.Payload); err != nil {
|
||||
return nil, errors.Annotatef(err, "Payload %v", r.Payload)
|
||||
if pt.Tx.Hash, err = hexDecode(r.Tx.Hash); err != nil {
|
||||
return nil, errors.Annotatef(err, "Hash %v", r.Tx.Hash)
|
||||
}
|
||||
if pt.Price, err = hexDecodeBig(r.Price); err != nil {
|
||||
return nil, errors.Annotatef(err, "Price %v", r.Price)
|
||||
if pt.Tx.Payload, err = hexDecode(r.Tx.Payload); err != nil {
|
||||
return nil, errors.Annotatef(err, "Payload %v", r.Tx.Payload)
|
||||
}
|
||||
if pt.R, err = hexDecodeBig(r.R); err != nil {
|
||||
return nil, errors.Annotatef(err, "R %v", r.R)
|
||||
if pt.Tx.GasPrice, err = hexDecodeBig(r.Tx.GasPrice); err != nil {
|
||||
return nil, errors.Annotatef(err, "Price %v", r.Tx.GasPrice)
|
||||
}
|
||||
if pt.S, err = hexDecodeBig(r.S); err != nil {
|
||||
return nil, errors.Annotatef(err, "S %v", r.S)
|
||||
// if pt.R, err = hexDecodeBig(r.R); err != nil {
|
||||
// return nil, errors.Annotatef(err, "R %v", r.R)
|
||||
// }
|
||||
// if pt.S, err = hexDecodeBig(r.S); err != nil {
|
||||
// return nil, errors.Annotatef(err, "S %v", r.S)
|
||||
// }
|
||||
// if pt.V, err = hexDecodeBig(r.V); err != nil {
|
||||
// return nil, errors.Annotatef(err, "V %v", r.V)
|
||||
// }
|
||||
if pt.Tx.To, err = hexDecode(r.Tx.To); err != nil {
|
||||
return nil, errors.Annotatef(err, "To %v", r.Tx.To)
|
||||
}
|
||||
if pt.V, err = hexDecodeBig(r.V); err != nil {
|
||||
return nil, errors.Annotatef(err, "V %v", r.V)
|
||||
if n, err = hexutil.DecodeUint64(r.Tx.TransactionIndex); err != nil {
|
||||
return nil, errors.Annotatef(err, "TransactionIndex %v", r.Tx.TransactionIndex)
|
||||
}
|
||||
if pt.To, err = hexDecode(r.To); err != nil {
|
||||
return nil, errors.Annotatef(err, "To %v", r.To)
|
||||
pt.Tx.TransactionIndex = uint32(n)
|
||||
if pt.Tx.Value, err = hexDecodeBig(r.Tx.Value); err != nil {
|
||||
return nil, errors.Annotatef(err, "Value %v", r.Tx.Value)
|
||||
}
|
||||
if n, err = hexutil.DecodeUint64(r.TransactionIndex); err != nil {
|
||||
return nil, errors.Annotatef(err, "TransactionIndex %v", r.TransactionIndex)
|
||||
}
|
||||
pt.TransactionIndex = uint32(n)
|
||||
if pt.Value, err = hexDecodeBig(r.Value); err != nil {
|
||||
return nil, errors.Annotatef(err, "Value %v", r.Value)
|
||||
if r.Receipt != nil {
|
||||
pt.Receipt = &ProtoCompleteTransaction_ReceiptType{}
|
||||
if pt.Receipt.GasUsed, err = hexDecodeBig(r.Receipt.GasUsed); err != nil {
|
||||
return nil, errors.Annotatef(err, "GasUsed %v", r.Receipt.GasUsed)
|
||||
}
|
||||
if r.Receipt.Status != "" {
|
||||
if pt.Receipt.Status, err = hexDecodeBig(r.Receipt.Status); err != nil {
|
||||
return nil, errors.Annotatef(err, "Status %v", r.Receipt.Status)
|
||||
}
|
||||
} else {
|
||||
// unknown status, use 'U' as status bytes
|
||||
// there is a potential for conflict with value 0x55 but this is not used by any chain at this moment
|
||||
pt.Receipt.Status = []byte{'U'}
|
||||
}
|
||||
ptLogs := make([]*ProtoCompleteTransaction_ReceiptType_LogType, len(r.Receipt.Logs))
|
||||
for i, l := range r.Receipt.Logs {
|
||||
a, err := hexutil.Decode(l.Address)
|
||||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "Address cannot be decoded %v", l)
|
||||
}
|
||||
d, err := hexutil.Decode(l.Data)
|
||||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "Data cannot be decoded %v", l)
|
||||
}
|
||||
t := make([][]byte, len(l.Topics))
|
||||
for j, s := range l.Topics {
|
||||
t[j], err = hexutil.Decode(s)
|
||||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "Topic cannot be decoded %v", l)
|
||||
}
|
||||
}
|
||||
ptLogs[i] = &ProtoCompleteTransaction_ReceiptType_LogType{
|
||||
Address: a,
|
||||
Data: d,
|
||||
Topics: t,
|
||||
}
|
||||
|
||||
}
|
||||
pt.Receipt.Log = ptLogs
|
||||
}
|
||||
return proto.Marshal(pt)
|
||||
}
|
||||
|
||||
// UnpackTx unpacks transaction from byte array
|
||||
func (p *EthereumParser) UnpackTx(buf []byte) (*bchain.Tx, uint32, error) {
|
||||
var pt ProtoTransaction
|
||||
var pt ProtoCompleteTransaction
|
||||
err := proto.Unmarshal(buf, &pt)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
r := rpcTransaction{
|
||||
AccountNonce: hexutil.EncodeUint64(pt.AccountNonce),
|
||||
BlockNumber: hexutil.EncodeUint64(uint64(pt.BlockNumber)),
|
||||
From: hexutil.Encode(pt.From),
|
||||
GasLimit: hexutil.EncodeUint64(pt.GasLimit),
|
||||
Hash: ethcommon.BytesToHash(pt.Hash),
|
||||
Payload: hexutil.Encode(pt.Payload),
|
||||
Price: hexEncodeBig(pt.Price),
|
||||
R: hexEncodeBig(pt.R),
|
||||
S: hexEncodeBig(pt.S),
|
||||
V: hexEncodeBig(pt.V),
|
||||
To: hexutil.Encode(pt.To),
|
||||
TransactionIndex: hexutil.EncodeUint64(uint64(pt.TransactionIndex)),
|
||||
Value: hexEncodeBig(pt.Value),
|
||||
rt := rpcTransaction{
|
||||
AccountNonce: hexutil.EncodeUint64(pt.Tx.AccountNonce),
|
||||
BlockNumber: hexutil.EncodeUint64(uint64(pt.BlockNumber)),
|
||||
From: EIP55Address(pt.Tx.From),
|
||||
GasLimit: hexutil.EncodeUint64(pt.Tx.GasLimit),
|
||||
Hash: hexutil.Encode(pt.Tx.Hash),
|
||||
Payload: hexutil.Encode(pt.Tx.Payload),
|
||||
GasPrice: hexEncodeBig(pt.Tx.GasPrice),
|
||||
// R: hexEncodeBig(pt.R),
|
||||
// S: hexEncodeBig(pt.S),
|
||||
// V: hexEncodeBig(pt.V),
|
||||
To: EIP55Address(pt.Tx.To),
|
||||
TransactionIndex: hexutil.EncodeUint64(uint64(pt.Tx.TransactionIndex)),
|
||||
Value: hexEncodeBig(pt.Tx.Value),
|
||||
}
|
||||
tx, err := p.ethTxToTx(&r, int64(pt.BlockTime), 0)
|
||||
var rr *rpcReceipt
|
||||
if pt.Receipt != nil {
|
||||
logs := make([]*rpcLog, len(pt.Receipt.Log))
|
||||
for i, l := range pt.Receipt.Log {
|
||||
topics := make([]string, len(l.Topics))
|
||||
for j, t := range l.Topics {
|
||||
topics[j] = hexutil.Encode(t)
|
||||
}
|
||||
logs[i] = &rpcLog{
|
||||
Address: EIP55Address(l.Address),
|
||||
Data: hexutil.Encode(l.Data),
|
||||
Topics: topics,
|
||||
}
|
||||
}
|
||||
status := ""
|
||||
// handle a special value []byte{'U'} as unknown state
|
||||
if len(pt.Receipt.Status) != 1 || pt.Receipt.Status[0] != 'U' {
|
||||
status = hexEncodeBig(pt.Receipt.Status)
|
||||
}
|
||||
rr = &rpcReceipt{
|
||||
GasUsed: hexEncodeBig(pt.Receipt.GasUsed),
|
||||
Status: status,
|
||||
Logs: logs,
|
||||
}
|
||||
}
|
||||
tx, err := p.ethTxToTx(&rt, rr, int64(pt.BlockTime), 0, false)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
@ -293,7 +434,92 @@ func (p *EthereumParser) UnpackBlockHash(buf []byte) (string, error) {
|
|||
return hexutil.Encode(buf), nil
|
||||
}
|
||||
|
||||
// IsUTXOChain returns true if the block chain is UTXO type, otherwise false
|
||||
func (p *EthereumParser) IsUTXOChain() bool {
|
||||
return false
|
||||
// GetChainType returns EthereumType
|
||||
func (p *EthereumParser) GetChainType() bchain.ChainType {
|
||||
return bchain.ChainEthereumType
|
||||
}
|
||||
|
||||
// GetHeightFromTx returns ethereum specific data from bchain.Tx
|
||||
func GetHeightFromTx(tx *bchain.Tx) (uint32, error) {
|
||||
var bn string
|
||||
csd, ok := tx.CoinSpecificData.(completeTransaction)
|
||||
if !ok {
|
||||
return 0, errors.New("Missing CoinSpecificData")
|
||||
}
|
||||
bn = csd.Tx.BlockNumber
|
||||
n, err := hexutil.DecodeUint64(bn)
|
||||
if err != nil {
|
||||
return 0, errors.Annotatef(err, "BlockNumber %v", bn)
|
||||
}
|
||||
return uint32(n), nil
|
||||
}
|
||||
|
||||
// EthereumTypeGetErc20FromTx returns Erc20 data from bchain.Tx
|
||||
func (p *EthereumParser) EthereumTypeGetErc20FromTx(tx *bchain.Tx) ([]bchain.Erc20Transfer, error) {
|
||||
var r []bchain.Erc20Transfer
|
||||
var err error
|
||||
csd, ok := tx.CoinSpecificData.(completeTransaction)
|
||||
if ok {
|
||||
if csd.Receipt != nil {
|
||||
r, err = erc20GetTransfersFromLog(csd.Receipt.Logs)
|
||||
} else {
|
||||
r, err = erc20GetTransfersFromTx(csd.Tx)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// TxStatus is status of transaction
|
||||
type TxStatus int
|
||||
|
||||
// statuses of transaction
|
||||
const (
|
||||
TxStatusUnknown = TxStatus(iota - 2)
|
||||
TxStatusPending
|
||||
TxStatusFailure
|
||||
TxStatusOK
|
||||
)
|
||||
|
||||
// EthereumTxData contains ethereum specific transaction data
|
||||
type EthereumTxData struct {
|
||||
Status TxStatus `json:"status"` // 1 OK, 0 Fail, -1 pending, -2 unknown
|
||||
Nonce uint64 `json:"nonce"`
|
||||
GasLimit *big.Int `json:"gaslimit"`
|
||||
GasUsed *big.Int `json:"gasused"`
|
||||
GasPrice *big.Int `json:"gasprice"`
|
||||
Data string `json:"data"`
|
||||
}
|
||||
|
||||
// GetEthereumTxData returns EthereumTxData from bchain.Tx
|
||||
func GetEthereumTxData(tx *bchain.Tx) *EthereumTxData {
|
||||
return GetEthereumTxDataFromSpecificData(tx.CoinSpecificData)
|
||||
}
|
||||
|
||||
// GetEthereumTxDataFromSpecificData returns EthereumTxData from coinSpecificData
|
||||
func GetEthereumTxDataFromSpecificData(coinSpecificData interface{}) *EthereumTxData {
|
||||
etd := EthereumTxData{Status: TxStatusPending}
|
||||
csd, ok := coinSpecificData.(completeTransaction)
|
||||
if ok {
|
||||
if csd.Tx != nil {
|
||||
etd.Nonce, _ = hexutil.DecodeUint64(csd.Tx.AccountNonce)
|
||||
etd.GasLimit, _ = hexutil.DecodeBig(csd.Tx.GasLimit)
|
||||
etd.GasPrice, _ = hexutil.DecodeBig(csd.Tx.GasPrice)
|
||||
etd.Data = csd.Tx.Payload
|
||||
}
|
||||
if csd.Receipt != nil {
|
||||
switch csd.Receipt.Status {
|
||||
case "0x1":
|
||||
etd.Status = TxStatusOK
|
||||
case "": // old transactions did not set status
|
||||
etd.Status = TxStatusUnknown
|
||||
default:
|
||||
etd.Status = TxStatusFailure
|
||||
}
|
||||
etd.GasUsed, _ = hexutil.DecodeBig(csd.Receipt.GasUsed)
|
||||
}
|
||||
}
|
||||
return &etd
|
||||
}
|
||||
|
|
|
@ -3,11 +3,14 @@
|
|||
package eth
|
||||
|
||||
import (
|
||||
"blockbook/bchain"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
"spacecruft.org/spacecruft/blockbook/tests/dbtestdata"
|
||||
)
|
||||
|
||||
func TestEthParser_GetAddrDescFromAddress(t *testing.T) {
|
||||
|
@ -31,9 +34,10 @@ func TestEthParser_GetAddrDescFromAddress(t *testing.T) {
|
|||
want: "47526228d673e9f079630d6cdaff5a2ed13e0e60",
|
||||
},
|
||||
{
|
||||
name: "odd address",
|
||||
args: args{address: "7526228d673e9f079630d6cdaff5a2ed13e0e60"},
|
||||
want: "07526228d673e9f079630d6cdaff5a2ed13e0e60",
|
||||
name: "address of wrong length",
|
||||
args: args{address: "7526228d673e9f079630d6cdaff5a2ed13e0e60"},
|
||||
want: "",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "ErrAddressMissing",
|
||||
|
@ -50,7 +54,7 @@ func TestEthParser_GetAddrDescFromAddress(t *testing.T) {
|
|||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
p := NewEthereumParser()
|
||||
p := NewEthereumParser(1)
|
||||
got, err := p.GetAddrDescFromAddress(tt.args.address)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("EthParser.GetAddrDescFromAddress() error = %v, wantErr %v", err, tt.wantErr)
|
||||
|
@ -64,53 +68,171 @@ func TestEthParser_GetAddrDescFromAddress(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
var (
|
||||
testTx1, testTx2 bchain.Tx
|
||||
testTxPacked1 = "08aebf0a1205012a05f20018a0f73622081234567890abcdef2a24f025caaf00000000000000000000000000000000000000000000000000000000000002253220e6b168d6bb3d8ed78e03dbf828b6bfd1fb613f6e129cba624964984553724c5d38f095af014092f4c1d5054a14682b7903a11098cf770c7aef4aa02a85b3f3601a5214dacc9c61754a0c4616fc5323dc946e89eb272302580162011b6a201bd40a31122c03918df6d166d740a6a3a22f08a25934ceb1688c62977661c80c7220607fbc15c1f7995a4258f5a9bccc63b040362d1991d5efe1361c56222e4ca89f"
|
||||
testTxPacked2 = "08ece40212050430e234001888a4012201213220cd647151552b5132b2aef7c9be00dc6f73afc5901dde157aab131335baaa853b38889eaf0140fa83c3d5054a14555ee11fbddc0e49a9bab358a8941ad95ffdb48f52143e3a3d69dc66ba10737f531ed088954a9ec89d97580a6201296a20f7161c170d43573ad9c8d701cdaf714ff2a548a562b0dc639230d17889fcd40572203c4977fc90385a27efa0032e17b49fd575b2826cb56e3d1ecf21524f2a94f915"
|
||||
)
|
||||
var testTx1, testTx2, testTx1Failed, testTx1NoStatus bchain.Tx
|
||||
|
||||
func init() {
|
||||
|
||||
testTx1 = bchain.Tx{
|
||||
Blocktime: 1521515026,
|
||||
Hex: "7b226e6f6e6365223a2230783239666165222c226761735072696365223a223078313261303566323030222c22676173223a2230786462626130222c22746f223a22307836383262373930336131313039386366373730633761656634616130326138356233663336303161222c2276616c7565223a22307831323334353637383930616263646566222c22696e707574223a223078663032356361616630303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030323235222c2268617368223a22307865366231363864366262336438656437386530336462663832386236626664316662363133663665313239636261363234393634393834353533373234633564222c22626c6f636b4e756d626572223a223078326263616630222c2266726f6d223a22307864616363396336313735346130633436313666633533323364633934366538396562323732333032222c227472616e73616374696f6e496e646578223a22307831222c2276223a2230783162222c2272223a22307831626434306133313132326330333931386466366431363664373430613661336132326630386132353933346365623136383863363239373736363163383063222c2273223a22307836303766626331356331663739393561343235386635613962636363363362303430333632643139393164356566653133363163353632323265346361383966227d",
|
||||
Time: 1521515026,
|
||||
Txid: "0xe6b168d6bb3d8ed78e03dbf828b6bfd1fb613f6e129cba624964984553724c5d",
|
||||
Blocktime: 1534858022,
|
||||
Time: 1534858022,
|
||||
Txid: "0xcd647151552b5132b2aef7c9be00dc6f73afc5901dde157aab131335baaa853b",
|
||||
Vin: []bchain.Vin{
|
||||
{
|
||||
Addresses: []string{"0xdacc9c61754a0c4616fc5323dc946e89eb272302"},
|
||||
Addresses: []string{"0x3E3a3D69dc66bA10737F531ed088954a9EC89d97"},
|
||||
},
|
||||
},
|
||||
Vout: []bchain.Vout{
|
||||
{
|
||||
ValueSat: *big.NewInt(1311768467294899695),
|
||||
ValueSat: *big.NewInt(1999622000000000000),
|
||||
ScriptPubKey: bchain.ScriptPubKey{
|
||||
Addresses: []string{"0x682b7903a11098cf770c7aef4aa02a85b3f3601a"},
|
||||
Addresses: []string{"0x555Ee11FBDDc0E49A9bAB358A8941AD95fFDB48f"},
|
||||
},
|
||||
},
|
||||
},
|
||||
CoinSpecificData: completeTransaction{
|
||||
Tx: &rpcTransaction{
|
||||
AccountNonce: "0xb26c",
|
||||
GasPrice: "0x430e23400",
|
||||
GasLimit: "0x5208",
|
||||
To: "0x555Ee11FBDDc0E49A9bAB358A8941AD95fFDB48f",
|
||||
Value: "0x1bc0159d530e6000",
|
||||
Payload: "0x",
|
||||
Hash: "0xcd647151552b5132b2aef7c9be00dc6f73afc5901dde157aab131335baaa853b",
|
||||
BlockNumber: "0x41eee8",
|
||||
From: "0x3E3a3D69dc66bA10737F531ed088954a9EC89d97",
|
||||
TransactionIndex: "0xa",
|
||||
},
|
||||
Receipt: &rpcReceipt{
|
||||
GasUsed: "0x5208",
|
||||
Status: "0x1",
|
||||
Logs: []*rpcLog{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
testTx2 = bchain.Tx{
|
||||
Blocktime: 1521533434,
|
||||
Hex: "7b226e6f6e6365223a22307862323663222c226761735072696365223a223078343330653233343030222c22676173223a22307835323038222c22746f223a22307835353565653131666264646330653439613962616233353861383934316164393566666462343866222c2276616c7565223a2230783231222c22696e707574223a223078222c2268617368223a22307863643634373135313535326235313332623261656637633962653030646336663733616663353930316464653135376161623133313333356261616138353362222c22626c6f636b4e756d626572223a223078326263663038222c2266726f6d223a22307833653361336436396463363662613130373337663533316564303838393534613965633839643937222c227472616e73616374696f6e496e646578223a22307861222c2276223a2230783239222c2272223a22307866373136316331373064343335373361643963386437303163646166373134666632613534386135363262306463363339323330643137383839666364343035222c2273223a22307833633439373766633930333835613237656661303033326531376234396664353735623238323663623536653364316563663231353234663261393466393135227d",
|
||||
Time: 1521533434,
|
||||
Txid: "0xcd647151552b5132b2aef7c9be00dc6f73afc5901dde157aab131335baaa853b",
|
||||
Blocktime: 1534858022,
|
||||
Time: 1534858022,
|
||||
Txid: "0xa9cd088aba2131000da6f38a33c20169baee476218deea6b78720700b895b101",
|
||||
Vin: []bchain.Vin{
|
||||
{
|
||||
Addresses: []string{"0x3e3a3d69dc66ba10737f531ed088954a9ec89d97"},
|
||||
Addresses: []string{"0x20cD153de35D469BA46127A0C8F18626b59a256A"},
|
||||
},
|
||||
},
|
||||
Vout: []bchain.Vout{
|
||||
{
|
||||
ValueSat: *big.NewInt(33),
|
||||
ValueSat: *big.NewInt(0),
|
||||
ScriptPubKey: bchain.ScriptPubKey{
|
||||
Addresses: []string{"0x555ee11fbddc0e49a9bab358a8941ad95ffdb48f"},
|
||||
Addresses: []string{"0x4af4114F73d1c1C903aC9E0361b379D1291808A2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
CoinSpecificData: completeTransaction{
|
||||
Tx: &rpcTransaction{
|
||||
AccountNonce: "0xd0",
|
||||
GasPrice: "0x9502f9000",
|
||||
GasLimit: "0x130d5",
|
||||
To: "0x4af4114F73d1c1C903aC9E0361b379D1291808A2",
|
||||
Value: "0x0",
|
||||
Payload: "0xa9059cbb000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f00000000000000000000000000000000000000000000021e19e0c9bab2400000",
|
||||
Hash: "0xa9cd088aba2131000da6f38a33c20169baee476218deea6b78720700b895b101",
|
||||
BlockNumber: "0x41eee8",
|
||||
From: "0x20cD153de35D469BA46127A0C8F18626b59a256A",
|
||||
TransactionIndex: "0x0"},
|
||||
Receipt: &rpcReceipt{
|
||||
GasUsed: "0xcb39",
|
||||
Status: "0x1",
|
||||
Logs: []*rpcLog{
|
||||
{
|
||||
Address: "0x4af4114F73d1c1C903aC9E0361b379D1291808A2",
|
||||
Data: "0x00000000000000000000000000000000000000000000021e19e0c9bab2400000",
|
||||
Topics: []string{
|
||||
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
|
||||
"0x00000000000000000000000020cd153de35d469ba46127a0c8f18626b59a256a",
|
||||
"0x000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
testTx1Failed = bchain.Tx{
|
||||
Blocktime: 1534858022,
|
||||
Time: 1534858022,
|
||||
Txid: "0xcd647151552b5132b2aef7c9be00dc6f73afc5901dde157aab131335baaa853b",
|
||||
Vin: []bchain.Vin{
|
||||
{
|
||||
Addresses: []string{"0x3E3a3D69dc66bA10737F531ed088954a9EC89d97"},
|
||||
},
|
||||
},
|
||||
Vout: []bchain.Vout{
|
||||
{
|
||||
ValueSat: *big.NewInt(1999622000000000000),
|
||||
ScriptPubKey: bchain.ScriptPubKey{
|
||||
Addresses: []string{"0x555Ee11FBDDc0E49A9bAB358A8941AD95fFDB48f"},
|
||||
},
|
||||
},
|
||||
},
|
||||
CoinSpecificData: completeTransaction{
|
||||
Tx: &rpcTransaction{
|
||||
AccountNonce: "0xb26c",
|
||||
GasPrice: "0x430e23400",
|
||||
GasLimit: "0x5208",
|
||||
To: "0x555Ee11FBDDc0E49A9bAB358A8941AD95fFDB48f",
|
||||
Value: "0x1bc0159d530e6000",
|
||||
Payload: "0x",
|
||||
Hash: "0xcd647151552b5132b2aef7c9be00dc6f73afc5901dde157aab131335baaa853b",
|
||||
BlockNumber: "0x41eee8",
|
||||
From: "0x3E3a3D69dc66bA10737F531ed088954a9EC89d97",
|
||||
TransactionIndex: "0xa",
|
||||
},
|
||||
Receipt: &rpcReceipt{
|
||||
GasUsed: "0x5208",
|
||||
Status: "0x0",
|
||||
Logs: []*rpcLog{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
testTx1NoStatus = bchain.Tx{
|
||||
Blocktime: 1534858022,
|
||||
Time: 1534858022,
|
||||
Txid: "0xcd647151552b5132b2aef7c9be00dc6f73afc5901dde157aab131335baaa853b",
|
||||
Vin: []bchain.Vin{
|
||||
{
|
||||
Addresses: []string{"0x3E3a3D69dc66bA10737F531ed088954a9EC89d97"},
|
||||
},
|
||||
},
|
||||
Vout: []bchain.Vout{
|
||||
{
|
||||
ValueSat: *big.NewInt(1999622000000000000),
|
||||
ScriptPubKey: bchain.ScriptPubKey{
|
||||
Addresses: []string{"0x555Ee11FBDDc0E49A9bAB358A8941AD95fFDB48f"},
|
||||
},
|
||||
},
|
||||
},
|
||||
CoinSpecificData: completeTransaction{
|
||||
Tx: &rpcTransaction{
|
||||
AccountNonce: "0xb26c",
|
||||
GasPrice: "0x430e23400",
|
||||
GasLimit: "0x5208",
|
||||
To: "0x555Ee11FBDDc0E49A9bAB358A8941AD95fFDB48f",
|
||||
Value: "0x1bc0159d530e6000",
|
||||
Payload: "0x",
|
||||
Hash: "0xcd647151552b5132b2aef7c9be00dc6f73afc5901dde157aab131335baaa853b",
|
||||
BlockNumber: "0x41eee8",
|
||||
From: "0x3E3a3D69dc66bA10737F531ed088954a9EC89d97",
|
||||
TransactionIndex: "0xa",
|
||||
},
|
||||
Receipt: &rpcReceipt{
|
||||
GasUsed: "0x5208",
|
||||
Status: "",
|
||||
Logs: []*rpcLog{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestEthereumParser_PackTx(t *testing.T) {
|
||||
|
@ -130,22 +252,40 @@ func TestEthereumParser_PackTx(t *testing.T) {
|
|||
name: "1",
|
||||
args: args{
|
||||
tx: &testTx1,
|
||||
height: 2870000,
|
||||
blockTime: 1521515026,
|
||||
height: 4321000,
|
||||
blockTime: 1534858022,
|
||||
},
|
||||
want: testTxPacked1,
|
||||
want: dbtestdata.EthTx1Packed,
|
||||
},
|
||||
{
|
||||
name: "2",
|
||||
args: args{
|
||||
tx: &testTx2,
|
||||
height: 2871048,
|
||||
blockTime: 1521533434,
|
||||
height: 4321000,
|
||||
blockTime: 1534858022,
|
||||
},
|
||||
want: testTxPacked2,
|
||||
want: dbtestdata.EthTx2Packed,
|
||||
},
|
||||
{
|
||||
name: "3",
|
||||
args: args{
|
||||
tx: &testTx1Failed,
|
||||
height: 4321000,
|
||||
blockTime: 1534858022,
|
||||
},
|
||||
want: dbtestdata.EthTx1FailedPacked,
|
||||
},
|
||||
{
|
||||
name: "4",
|
||||
args: args{
|
||||
tx: &testTx1NoStatus,
|
||||
height: 4321000,
|
||||
blockTime: 1534858022,
|
||||
},
|
||||
want: dbtestdata.EthTx1NoStatusPacked,
|
||||
},
|
||||
}
|
||||
p := NewEthereumParser()
|
||||
p := NewEthereumParser(1)
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := p.PackTx(tt.args.tx, tt.args.height, tt.args.blockTime)
|
||||
|
@ -175,18 +315,30 @@ func TestEthereumParser_UnpackTx(t *testing.T) {
|
|||
}{
|
||||
{
|
||||
name: "1",
|
||||
args: args{hex: testTxPacked1},
|
||||
args: args{hex: dbtestdata.EthTx1Packed},
|
||||
want: &testTx1,
|
||||
want1: 2870000,
|
||||
want1: 4321000,
|
||||
},
|
||||
{
|
||||
name: "2",
|
||||
args: args{hex: testTxPacked2},
|
||||
args: args{hex: dbtestdata.EthTx2Packed},
|
||||
want: &testTx2,
|
||||
want1: 2871048,
|
||||
want1: 4321000,
|
||||
},
|
||||
{
|
||||
name: "3",
|
||||
args: args{hex: dbtestdata.EthTx1FailedPacked},
|
||||
want: &testTx1Failed,
|
||||
want1: 4321000,
|
||||
},
|
||||
{
|
||||
name: "4",
|
||||
args: args{hex: dbtestdata.EthTx1NoStatusPacked},
|
||||
want: &testTx1NoStatus,
|
||||
want1: 4321000,
|
||||
},
|
||||
}
|
||||
p := NewEthereumParser()
|
||||
p := NewEthereumParser(1)
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
b, err := hex.DecodeString(tt.args.hex)
|
||||
|
@ -198,8 +350,22 @@ func TestEthereumParser_UnpackTx(t *testing.T) {
|
|||
t.Errorf("EthereumParser.UnpackTx() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("EthereumParser.UnpackTx() got = %v, want %v", got, tt.want)
|
||||
// DeepEqual has problems with pointers in completeTransaction
|
||||
gs := got.CoinSpecificData.(completeTransaction)
|
||||
ws := tt.want.CoinSpecificData.(completeTransaction)
|
||||
gc := *got
|
||||
wc := *tt.want
|
||||
gc.CoinSpecificData = nil
|
||||
wc.CoinSpecificData = nil
|
||||
if fmt.Sprint(gc) != fmt.Sprint(wc) {
|
||||
// if !reflect.DeepEqual(gc, wc) {
|
||||
t.Errorf("EthereumParser.UnpackTx() gc got = %+v, want %+v", gc, wc)
|
||||
}
|
||||
if !reflect.DeepEqual(gs.Tx, ws.Tx) {
|
||||
t.Errorf("EthereumParser.UnpackTx() gs.Tx got = %+v, want %+v", gs.Tx, ws.Tx)
|
||||
}
|
||||
if !reflect.DeepEqual(gs.Receipt, ws.Receipt) {
|
||||
t.Errorf("EthereumParser.UnpackTx() gs.Receipt got = %+v, want %+v", gs.Receipt, ws.Receipt)
|
||||
}
|
||||
if got1 != tt.want1 {
|
||||
t.Errorf("EthereumParser.UnpackTx() got1 = %v, want %v", got1, tt.want1)
|
||||
|
@ -207,3 +373,30 @@ func TestEthereumParser_UnpackTx(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEthereumParser_GetEthereumTxData(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
tx *bchain.Tx
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "Test empty data",
|
||||
tx: &testTx1,
|
||||
want: "0x",
|
||||
},
|
||||
{
|
||||
name: "Test non empty data",
|
||||
tx: &testTx2,
|
||||
want: "0xa9059cbb000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f00000000000000000000000000000000000000000000021e19e0c9bab2400000",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := GetEthereumTxData(tt.tx)
|
||||
if got.Data != tt.want {
|
||||
t.Errorf("EthereumParser.GetEthereumTxData() = %v, want %v", got.Data, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package eth
|
||||
|
||||
import (
|
||||
"blockbook/bchain"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
@ -10,14 +9,16 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/juju/errors"
|
||||
|
||||
ethereum "github.com/ethereum/go-ethereum"
|
||||
ethcommon "github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
ethtypes "github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/ethclient"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/golang/glog"
|
||||
"github.com/juju/errors"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
"spacecruft.org/spacecruft/blockbook/common"
|
||||
)
|
||||
|
||||
// EthereumNet type specifies the type of ethereum network
|
||||
|
@ -28,25 +29,31 @@ const (
|
|||
MainNet EthereumNet = 1
|
||||
// TestNet is Ropsten test network
|
||||
TestNet EthereumNet = 3
|
||||
// TestNetGoerli is Goerli test network
|
||||
TestNetGoerli EthereumNet = 5
|
||||
)
|
||||
|
||||
// Configuration represents json config file
|
||||
type Configuration struct {
|
||||
CoinName string `json:"coin_name"`
|
||||
CoinShortcut string `json:"coin_shortcut"`
|
||||
RPCURL string `json:"rpc_url"`
|
||||
RPCTimeout int `json:"rpc_timeout"`
|
||||
CoinName string `json:"coin_name"`
|
||||
CoinShortcut string `json:"coin_shortcut"`
|
||||
RPCURL string `json:"rpc_url"`
|
||||
RPCTimeout int `json:"rpc_timeout"`
|
||||
BlockAddressesToKeep int `json:"block_addresses_to_keep"`
|
||||
MempoolTxTimeoutHours int `json:"mempoolTxTimeoutHours"`
|
||||
QueryBackendOnMempoolResync bool `json:"queryBackendOnMempoolResync"`
|
||||
}
|
||||
|
||||
// EthereumRPC is an interface to JSON-RPC eth service.
|
||||
type EthereumRPC struct {
|
||||
*bchain.BaseChain
|
||||
client *ethclient.Client
|
||||
rpc *rpc.Client
|
||||
timeout time.Duration
|
||||
Parser *EthereumParser
|
||||
Testnet bool
|
||||
Network string
|
||||
Mempool *bchain.NonUTXOMempool
|
||||
bestHeaderMu sync.Mutex
|
||||
Mempool *bchain.MempoolEthereumType
|
||||
mempoolInitialized bool
|
||||
bestHeaderLock sync.Mutex
|
||||
bestHeader *ethtypes.Header
|
||||
bestHeaderTime time.Time
|
||||
chanNewBlock chan *ethtypes.Header
|
||||
|
@ -54,7 +61,6 @@ type EthereumRPC struct {
|
|||
chanNewTx chan ethcommon.Hash
|
||||
newTxSubscription *rpc.ClientSubscription
|
||||
ChainConfig *Configuration
|
||||
isETC bool
|
||||
}
|
||||
|
||||
// NewEthereumRPC returns new EthRPC instance.
|
||||
|
@ -65,25 +71,27 @@ func NewEthereumRPC(config json.RawMessage, pushHandler func(bchain.Notification
|
|||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "Invalid configuration file")
|
||||
}
|
||||
rc, err := rpc.Dial(c.RPCURL)
|
||||
// keep at least 100 mappings block->addresses to allow rollback
|
||||
if c.BlockAddressesToKeep < 100 {
|
||||
c.BlockAddressesToKeep = 100
|
||||
}
|
||||
|
||||
rc, ec, err := openRPC(c.RPCURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ec := ethclient.NewClient(rc)
|
||||
|
||||
s := &EthereumRPC{
|
||||
BaseChain: &bchain.BaseChain{},
|
||||
client: ec,
|
||||
rpc: rc,
|
||||
ChainConfig: &c,
|
||||
}
|
||||
|
||||
// always create parser
|
||||
s.Parser = NewEthereumParser()
|
||||
s.Parser = NewEthereumParser(c.BlockAddressesToKeep)
|
||||
s.timeout = time.Duration(c.RPCTimeout) * time.Second
|
||||
|
||||
// detect ethereum classic
|
||||
s.isETC = s.ChainConfig.CoinName == "Ethereum Classic"
|
||||
|
||||
// new blocks notifications handling
|
||||
// the subscription is done in Initialize
|
||||
s.chanNewBlock = make(chan *ethtypes.Header)
|
||||
|
@ -95,10 +103,10 @@ func NewEthereumRPC(config json.RawMessage, pushHandler func(bchain.Notification
|
|||
}
|
||||
glog.V(2).Info("rpc: new block header ", h.Number)
|
||||
// update best header to the new header
|
||||
s.bestHeaderMu.Lock()
|
||||
s.bestHeaderLock.Lock()
|
||||
s.bestHeader = h
|
||||
s.bestHeaderTime = time.Now()
|
||||
s.bestHeaderMu.Unlock()
|
||||
s.bestHeaderLock.Unlock()
|
||||
// notify blockbook
|
||||
pushHandler(bchain.NotificationNewBlock)
|
||||
}
|
||||
|
@ -113,9 +121,11 @@ func NewEthereumRPC(config json.RawMessage, pushHandler func(bchain.Notification
|
|||
if !ok {
|
||||
break
|
||||
}
|
||||
hex := t.Hex()
|
||||
if glog.V(2) {
|
||||
glog.Info("rpc: new tx ", t.Hex())
|
||||
glog.Info("rpc: new tx ", hex)
|
||||
}
|
||||
s.Mempool.AddTransactionToMempool(hex)
|
||||
pushHandler(bchain.NotificationNewTx)
|
||||
}
|
||||
}()
|
||||
|
@ -123,6 +133,15 @@ func NewEthereumRPC(config json.RawMessage, pushHandler func(bchain.Notification
|
|||
return s, nil
|
||||
}
|
||||
|
||||
func openRPC(url string) (*rpc.Client, *ethclient.Client, error) {
|
||||
rc, err := rpc.Dial(url)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
ec := ethclient.NewClient(rc)
|
||||
return rc, ec, nil
|
||||
}
|
||||
|
||||
// Initialize initializes ethereum rpc interface
|
||||
func (b *EthereumRPC) Initialize() error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
|
||||
|
@ -143,32 +162,72 @@ func (b *EthereumRPC) Initialize() error {
|
|||
b.Testnet = true
|
||||
b.Network = "testnet"
|
||||
break
|
||||
case TestNetGoerli:
|
||||
b.Testnet = true
|
||||
b.Network = "goerli"
|
||||
default:
|
||||
return errors.Errorf("Unknown network id %v", id)
|
||||
}
|
||||
glog.Info("rpc: block chain ", b.Network)
|
||||
|
||||
if b.isETC {
|
||||
glog.Info(b.ChainConfig.CoinName, " does not support subscription to newHeads")
|
||||
} else {
|
||||
// subscriptions
|
||||
if err = b.subscribe(func() (*rpc.ClientSubscription, error) {
|
||||
// invalidate the previous subscription - it is either the first one or there was an error
|
||||
b.newBlockSubscription = nil
|
||||
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
|
||||
defer cancel()
|
||||
sub, err := b.rpc.EthSubscribe(ctx, b.chanNewBlock, "newHeads")
|
||||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "EthSubscribe newHeads")
|
||||
}
|
||||
b.newBlockSubscription = sub
|
||||
glog.Info("Subscribed to newHeads")
|
||||
return sub, nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateMempool creates mempool if not already created, however does not initialize it
|
||||
func (b *EthereumRPC) CreateMempool(chain bchain.BlockChain) (bchain.Mempool, error) {
|
||||
if b.Mempool == nil {
|
||||
b.Mempool = bchain.NewMempoolEthereumType(chain, b.ChainConfig.MempoolTxTimeoutHours, b.ChainConfig.QueryBackendOnMempoolResync)
|
||||
glog.Info("mempool created, MempoolTxTimeoutHours=", b.ChainConfig.MempoolTxTimeoutHours, ", QueryBackendOnMempoolResync=", b.ChainConfig.QueryBackendOnMempoolResync)
|
||||
}
|
||||
if err = b.subscribe(func() (*rpc.ClientSubscription, error) {
|
||||
return b.Mempool, nil
|
||||
}
|
||||
|
||||
// InitializeMempool creates subscriptions to newHeads and newPendingTransactions
|
||||
func (b *EthereumRPC) InitializeMempool(addrDescForOutpoint bchain.AddrDescForOutpointFunc, onNewTxAddr bchain.OnNewTxAddrFunc, onNewTx bchain.OnNewTxFunc) error {
|
||||
if b.Mempool == nil {
|
||||
return errors.New("Mempool not created")
|
||||
}
|
||||
|
||||
// get initial mempool transactions
|
||||
txs, err := b.GetMempoolTransactions()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, txid := range txs {
|
||||
b.Mempool.AddTransactionToMempool(txid)
|
||||
}
|
||||
|
||||
b.Mempool.OnNewTxAddr = onNewTxAddr
|
||||
b.Mempool.OnNewTx = onNewTx
|
||||
|
||||
if err = b.subscribeEvents(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.mempoolInitialized = true
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *EthereumRPC) subscribeEvents() error {
|
||||
// subscriptions
|
||||
if err := b.subscribe(func() (*rpc.ClientSubscription, error) {
|
||||
// invalidate the previous subscription - it is either the first one or there was an error
|
||||
b.newBlockSubscription = nil
|
||||
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
|
||||
defer cancel()
|
||||
sub, err := b.rpc.EthSubscribe(ctx, b.chanNewBlock, "newHeads")
|
||||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "EthSubscribe newHeads")
|
||||
}
|
||||
b.newBlockSubscription = sub
|
||||
glog.Info("Subscribed to newHeads")
|
||||
return sub, nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := b.subscribe(func() (*rpc.ClientSubscription, error) {
|
||||
// invalidate the previous subscription - it is either the first one or there was an error
|
||||
b.newTxSubscription = nil
|
||||
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
|
||||
|
@ -184,9 +243,6 @@ func (b *EthereumRPC) Initialize() error {
|
|||
return err
|
||||
}
|
||||
|
||||
// create mempool
|
||||
b.Mempool = bchain.NewNonUTXOMempool(b)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -206,8 +262,8 @@ func (b *EthereumRPC) subscribe(f func() (*rpc.ClientSubscription, error)) error
|
|||
return
|
||||
}
|
||||
glog.Error("Subscription error ", e)
|
||||
timer := time.NewTimer(time.Second)
|
||||
// try in 1 second interval to resubscribe
|
||||
timer := time.NewTimer(time.Second * 2)
|
||||
// try in 2 second interval to resubscribe
|
||||
for {
|
||||
select {
|
||||
case e = <-s.Err():
|
||||
|
@ -221,7 +277,8 @@ func (b *EthereumRPC) subscribe(f func() (*rpc.ClientSubscription, error)) error
|
|||
s = ns
|
||||
continue Loop
|
||||
}
|
||||
timer.Reset(time.Second)
|
||||
glog.Error("Resubscribe error ", err)
|
||||
timer.Reset(time.Second * 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -229,8 +286,7 @@ func (b *EthereumRPC) subscribe(f func() (*rpc.ClientSubscription, error)) error
|
|||
return nil
|
||||
}
|
||||
|
||||
// Shutdown cleans up rpc interface to ethereum
|
||||
func (b *EthereumRPC) Shutdown(ctx context.Context) error {
|
||||
func (b *EthereumRPC) closeRPC() {
|
||||
if b.newBlockSubscription != nil {
|
||||
b.newBlockSubscription.Unsubscribe()
|
||||
}
|
||||
|
@ -240,54 +296,80 @@ func (b *EthereumRPC) Shutdown(ctx context.Context) error {
|
|||
if b.rpc != nil {
|
||||
b.rpc.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (b *EthereumRPC) reconnectRPC() error {
|
||||
glog.Info("Reconnecting RPC")
|
||||
b.closeRPC()
|
||||
rc, ec, err := openRPC(b.ChainConfig.RPCURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.rpc = rc
|
||||
b.client = ec
|
||||
return b.subscribeEvents()
|
||||
}
|
||||
|
||||
// Shutdown cleans up rpc interface to ethereum
|
||||
func (b *EthereumRPC) Shutdown(ctx context.Context) error {
|
||||
b.closeRPC()
|
||||
close(b.chanNewBlock)
|
||||
glog.Info("rpc: shutdown")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *EthereumRPC) IsTestnet() bool {
|
||||
return b.Testnet
|
||||
}
|
||||
|
||||
func (b *EthereumRPC) GetNetworkName() string {
|
||||
return b.Network
|
||||
}
|
||||
|
||||
// GetCoinName returns coin name
|
||||
func (b *EthereumRPC) GetCoinName() string {
|
||||
return b.ChainConfig.CoinName
|
||||
}
|
||||
|
||||
// GetSubversion returns empty string, ethereum does not have subversion
|
||||
func (b *EthereumRPC) GetSubversion() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetChainInfo returns information about the connected backend
|
||||
func (b *EthereumRPC) GetChainInfo() (*bchain.ChainInfo, error) {
|
||||
h, err := b.getBestHeader()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
|
||||
defer cancel()
|
||||
id, err := b.client.NetworkID(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rv := &bchain.ChainInfo{}
|
||||
var ver string
|
||||
if err := b.rpc.CallContext(ctx, &ver, "web3_clientVersion"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rv := &bchain.ChainInfo{
|
||||
Blocks: int(h.Number.Int64()),
|
||||
Bestblockhash: h.Hash().Hex(),
|
||||
Difficulty: h.Difficulty.String(),
|
||||
Version: ver,
|
||||
}
|
||||
idi := int(id.Uint64())
|
||||
if idi == 1 {
|
||||
rv.Chain = "mainnet"
|
||||
} else {
|
||||
rv.Chain = "testnet " + strconv.Itoa(idi)
|
||||
}
|
||||
// TODO - return more information about the chain
|
||||
return rv, nil
|
||||
}
|
||||
|
||||
func (b *EthereumRPC) getBestHeader() (*ethtypes.Header, error) {
|
||||
b.bestHeaderMu.Lock()
|
||||
defer b.bestHeaderMu.Unlock()
|
||||
// ETC does not have newBlocks subscription, bestHeader must be updated very often (each 1 second)
|
||||
if b.isETC {
|
||||
if b.bestHeaderTime.Add(1 * time.Second).Before(time.Now()) {
|
||||
b.bestHeader = nil
|
||||
b.bestHeaderLock.Lock()
|
||||
defer b.bestHeaderLock.Unlock()
|
||||
// if the best header was not updated for 15 minutes, there could be a subscription problem, reconnect RPC
|
||||
// do it only in case of normal operation, not initial synchronization
|
||||
if b.bestHeaderTime.Add(15*time.Minute).Before(time.Now()) && !b.bestHeaderTime.IsZero() && b.mempoolInitialized {
|
||||
err := b.reconnectRPC()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b.bestHeader = nil
|
||||
}
|
||||
if b.bestHeader == nil {
|
||||
var err error
|
||||
|
@ -295,6 +377,7 @@ func (b *EthereumRPC) getBestHeader() (*ethtypes.Header, error) {
|
|||
defer cancel()
|
||||
b.bestHeader, err = b.client.HeaderByNumber(ctx, nil)
|
||||
if err != nil {
|
||||
b.bestHeader = nil
|
||||
return nil, err
|
||||
}
|
||||
b.bestHeaderTime = time.Now()
|
||||
|
@ -302,23 +385,25 @@ func (b *EthereumRPC) getBestHeader() (*ethtypes.Header, error) {
|
|||
return b.bestHeader, nil
|
||||
}
|
||||
|
||||
// GetBestBlockHash returns hash of the tip of the best-block-chain
|
||||
func (b *EthereumRPC) GetBestBlockHash() (string, error) {
|
||||
h, err := b.getBestHeader()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return ethHashToHash(h.Hash()), nil
|
||||
return h.Hash().Hex(), nil
|
||||
}
|
||||
|
||||
// GetBestBlockHeight returns height of the tip of the best-block-chain
|
||||
func (b *EthereumRPC) GetBestBlockHeight() (uint32, error) {
|
||||
h, err := b.getBestHeader()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
// TODO - can it grow over 2^32 ?
|
||||
return uint32(h.Number.Uint64()), nil
|
||||
}
|
||||
|
||||
// GetBlockHash returns hash of block in best-block-chain at given height
|
||||
func (b *EthereumRPC) GetBlockHash(height uint32) (string, error) {
|
||||
var n big.Int
|
||||
n.SetUint64(uint64(height))
|
||||
|
@ -331,36 +416,47 @@ func (b *EthereumRPC) GetBlockHash(height uint32) (string, error) {
|
|||
}
|
||||
return "", errors.Annotatef(err, "height %v", height)
|
||||
}
|
||||
return ethHashToHash(h.Hash()), nil
|
||||
return h.Hash().Hex(), nil
|
||||
}
|
||||
|
||||
func (b *EthereumRPC) ethHeaderToBlockHeader(h *ethtypes.Header) (*bchain.BlockHeader, error) {
|
||||
hn := h.Number.Uint64()
|
||||
c, err := b.computeConfirmations(hn)
|
||||
func (b *EthereumRPC) ethHeaderToBlockHeader(h *rpcHeader) (*bchain.BlockHeader, error) {
|
||||
height, err := ethNumber(h.Number)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c, err := b.computeConfirmations(uint64(height))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
time, err := ethNumber(h.Time)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
size, err := ethNumber(h.Size)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &bchain.BlockHeader{
|
||||
Hash: ethHashToHash(h.Hash()),
|
||||
Height: uint32(hn),
|
||||
Hash: h.Hash,
|
||||
Prev: h.ParentHash,
|
||||
Height: uint32(height),
|
||||
Confirmations: int(c),
|
||||
Time: int64(h.Time.Uint64()),
|
||||
// Next
|
||||
// Prev
|
||||
Time: time,
|
||||
Size: int(size),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetBlockHeader returns header of block with given hash
|
||||
func (b *EthereumRPC) GetBlockHeader(hash string) (*bchain.BlockHeader, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
|
||||
defer cancel()
|
||||
h, err := b.client.HeaderByHash(ctx, ethcommon.HexToHash(hash))
|
||||
raw, err := b.getBlockRaw(hash, 0, false)
|
||||
if err != nil {
|
||||
if err == ethereum.NotFound {
|
||||
return nil, bchain.ErrBlockNotFound
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
var h rpcHeader
|
||||
if err := json.Unmarshal(raw, &h); err != nil {
|
||||
return nil, errors.Annotatef(err, "hash %v", hash)
|
||||
}
|
||||
return b.ethHeaderToBlockHeader(h)
|
||||
return b.ethHeaderToBlockHeader(&h)
|
||||
}
|
||||
|
||||
func (b *EthereumRPC) computeConfirmations(n uint64) (uint32, error) {
|
||||
|
@ -373,61 +469,82 @@ func (b *EthereumRPC) computeConfirmations(n uint64) (uint32, error) {
|
|||
return uint32(bn - n + 1), nil
|
||||
}
|
||||
|
||||
// GetBlock returns block with given hash or height, hash has precedence if both passed
|
||||
func (b *EthereumRPC) GetBlock(hash string, height uint32) (*bchain.Block, error) {
|
||||
func (b *EthereumRPC) getBlockRaw(hash string, height uint32, fullTxs bool) (json.RawMessage, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
|
||||
defer cancel()
|
||||
var raw json.RawMessage
|
||||
var err error
|
||||
if hash != "" {
|
||||
err = b.rpc.CallContext(ctx, &raw, "eth_getBlockByHash", ethcommon.HexToHash(hash), true)
|
||||
if hash == "pending" {
|
||||
err = b.rpc.CallContext(ctx, &raw, "eth_getBlockByNumber", hash, fullTxs)
|
||||
} else {
|
||||
err = b.rpc.CallContext(ctx, &raw, "eth_getBlockByHash", ethcommon.HexToHash(hash), fullTxs)
|
||||
}
|
||||
} else {
|
||||
|
||||
err = b.rpc.CallContext(ctx, &raw, "eth_getBlockByNumber", fmt.Sprintf("%#x", height), true)
|
||||
err = b.rpc.CallContext(ctx, &raw, "eth_getBlockByNumber", fmt.Sprintf("%#x", height), fullTxs)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "hash %v, height %v", hash, height)
|
||||
} else if len(raw) == 0 {
|
||||
return nil, bchain.ErrBlockNotFound
|
||||
}
|
||||
// Decode header and transactions.
|
||||
var head *ethtypes.Header
|
||||
var body rpcBlock
|
||||
return raw, nil
|
||||
}
|
||||
|
||||
func (b *EthereumRPC) getERC20EventsForBlock(blockNumber string) (map[string][]*rpcLog, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
|
||||
defer cancel()
|
||||
var logs []rpcLogWithTxHash
|
||||
err := b.rpc.CallContext(ctx, &logs, "eth_getLogs", map[string]interface{}{
|
||||
"fromBlock": blockNumber,
|
||||
"toBlock": blockNumber,
|
||||
"topics": []string{erc20TransferEventSignature},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "blockNumber %v", blockNumber)
|
||||
}
|
||||
r := make(map[string][]*rpcLog)
|
||||
for i := range logs {
|
||||
l := &logs[i]
|
||||
r[l.Hash] = append(r[l.Hash], &l.rpcLog)
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// GetBlock returns block with given hash or height, hash has precedence if both passed
|
||||
func (b *EthereumRPC) GetBlock(hash string, height uint32) (*bchain.Block, error) {
|
||||
raw, err := b.getBlockRaw(hash, height, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var head rpcHeader
|
||||
if err := json.Unmarshal(raw, &head); err != nil {
|
||||
return nil, errors.Annotatef(err, "hash %v, height %v", hash, height)
|
||||
}
|
||||
if head == nil {
|
||||
return nil, bchain.ErrBlockNotFound
|
||||
}
|
||||
var body rpcBlockTransactions
|
||||
if err := json.Unmarshal(raw, &body); err != nil {
|
||||
return nil, errors.Annotatef(err, "hash %v, height %v", hash, height)
|
||||
}
|
||||
// Quick-verify transaction and uncle lists. This mostly helps with debugging the server.
|
||||
if head.UncleHash == ethtypes.EmptyUncleHash && len(body.UncleHashes) > 0 {
|
||||
return nil, errors.Annotatef(fmt.Errorf("server returned non-empty uncle list but block header indicates no uncles"), "hash %v, height %v", hash, height)
|
||||
}
|
||||
if head.UncleHash != ethtypes.EmptyUncleHash && len(body.UncleHashes) == 0 {
|
||||
return nil, errors.Annotatef(fmt.Errorf("server returned empty uncle list but block header indicates uncles"), "hash %v, height %v", hash, height)
|
||||
}
|
||||
if head.TxHash == ethtypes.EmptyRootHash && len(body.Transactions) > 0 {
|
||||
return nil, errors.Annotatef(fmt.Errorf("server returned non-empty transaction list but block header indicates no transactions"), "hash %v, height %v", hash, height)
|
||||
}
|
||||
if head.TxHash != ethtypes.EmptyRootHash && len(body.Transactions) == 0 {
|
||||
return nil, errors.Annotatef(fmt.Errorf("server returned empty transaction list but block header indicates transactions"), "hash %v, height %v", hash, height)
|
||||
}
|
||||
bbh, err := b.ethHeaderToBlockHeader(head)
|
||||
bbh, err := b.ethHeaderToBlockHeader(&head)
|
||||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "hash %v, height %v", hash, height)
|
||||
}
|
||||
// TODO - this is probably not the correct size
|
||||
bbh.Size = len(raw)
|
||||
// get ERC20 events
|
||||
logs, err := b.getERC20EventsForBlock(head.Number)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
btxs := make([]bchain.Tx, len(body.Transactions))
|
||||
for i, tx := range body.Transactions {
|
||||
btx, err := b.Parser.ethTxToTx(&tx, int64(head.Time.Uint64()), uint32(bbh.Confirmations))
|
||||
for i := range body.Transactions {
|
||||
tx := &body.Transactions[i]
|
||||
btx, err := b.Parser.ethTxToTx(tx, &rpcReceipt{Logs: logs[tx.Hash]}, bbh.Time, uint32(bbh.Confirmations), true)
|
||||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "hash %v, height %v, txid %v", hash, height, tx.Hash.String())
|
||||
return nil, errors.Annotatef(err, "hash %v, height %v, txid %v", hash, height, tx.Hash)
|
||||
}
|
||||
btxs[i] = *btx
|
||||
if b.mempoolInitialized {
|
||||
b.Mempool.RemoveTransactionFromMempool(tx.Hash)
|
||||
}
|
||||
}
|
||||
bbk := bchain.Block{
|
||||
BlockHeader: *bbh,
|
||||
|
@ -438,8 +555,28 @@ func (b *EthereumRPC) GetBlock(hash string, height uint32) (*bchain.Block, error
|
|||
|
||||
// GetBlockInfo returns extended header (more info than in bchain.BlockHeader) with a list of txids
|
||||
func (b *EthereumRPC) GetBlockInfo(hash string) (*bchain.BlockInfo, error) {
|
||||
// TODO - implement
|
||||
return nil, errors.New("Not implemented yet")
|
||||
raw, err := b.getBlockRaw(hash, 0, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var head rpcHeader
|
||||
var txs rpcBlockTxids
|
||||
if err := json.Unmarshal(raw, &head); err != nil {
|
||||
return nil, errors.Annotatef(err, "hash %v", hash)
|
||||
}
|
||||
if err = json.Unmarshal(raw, &txs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bch, err := b.ethHeaderToBlockHeader(&head)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &bchain.BlockInfo{
|
||||
BlockHeader: *bch,
|
||||
Difficulty: common.JSONNumber(head.Difficulty),
|
||||
Nonce: common.JSONNumber(head.Nonce),
|
||||
Txids: txs.Transactions,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetTransactionForMempool returns a transaction by the transaction ID.
|
||||
|
@ -453,32 +590,45 @@ func (b *EthereumRPC) GetTransaction(txid string) (*bchain.Tx, error) {
|
|||
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
|
||||
defer cancel()
|
||||
var tx *rpcTransaction
|
||||
err := b.rpc.CallContext(ctx, &tx, "eth_getTransactionByHash", ethcommon.HexToHash(txid))
|
||||
hash := ethcommon.HexToHash(txid)
|
||||
err := b.rpc.CallContext(ctx, &tx, "eth_getTransactionByHash", hash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if tx == nil {
|
||||
return nil, ethereum.NotFound
|
||||
} else if tx.R == "" {
|
||||
if !b.isETC {
|
||||
return nil, errors.Annotatef(fmt.Errorf("server returned transaction without signature"), "txid %v", txid)
|
||||
} else {
|
||||
glog.Warning("server returned transaction without signature, txid ", txid)
|
||||
if b.mempoolInitialized {
|
||||
b.Mempool.RemoveTransactionFromMempool(txid)
|
||||
}
|
||||
return nil, bchain.ErrTxNotFound
|
||||
}
|
||||
var btx *bchain.Tx
|
||||
if tx.BlockNumber == "" {
|
||||
// mempool tx
|
||||
btx, err = b.Parser.ethTxToTx(tx, 0, 0)
|
||||
btx, err = b.Parser.ethTxToTx(tx, nil, 0, 0, true)
|
||||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "txid %v", txid)
|
||||
}
|
||||
} else {
|
||||
// non mempool tx - we must read the block header to get the block time
|
||||
n, err := ethNumber(tx.BlockNumber)
|
||||
// non mempool tx - read the block header to get the block time
|
||||
raw, err := b.getBlockRaw(tx.BlockHash, 0, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var ht struct {
|
||||
Time string `json:"timestamp"`
|
||||
}
|
||||
if err := json.Unmarshal(raw, &ht); err != nil {
|
||||
return nil, errors.Annotatef(err, "hash %v", hash)
|
||||
}
|
||||
var time int64
|
||||
if time, err = ethNumber(ht.Time); err != nil {
|
||||
return nil, errors.Annotatef(err, "txid %v", txid)
|
||||
}
|
||||
var receipt rpcReceipt
|
||||
err = b.rpc.CallContext(ctx, &receipt, "eth_getTransactionReceipt", hash)
|
||||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "txid %v", txid)
|
||||
}
|
||||
h, err := b.client.HeaderByHash(ctx, *tx.BlockHash)
|
||||
n, err := ethNumber(tx.BlockNumber)
|
||||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "txid %v", txid)
|
||||
}
|
||||
|
@ -486,46 +636,46 @@ func (b *EthereumRPC) GetTransaction(txid string) (*bchain.Tx, error) {
|
|||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "txid %v", txid)
|
||||
}
|
||||
btx, err = b.Parser.ethTxToTx(tx, h.Time.Int64(), confirmations)
|
||||
btx, err = b.Parser.ethTxToTx(tx, &receipt, time, confirmations, true)
|
||||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "txid %v", txid)
|
||||
}
|
||||
// remove tx from mempool if it is there
|
||||
if b.mempoolInitialized {
|
||||
b.Mempool.RemoveTransactionFromMempool(txid)
|
||||
}
|
||||
}
|
||||
return btx, nil
|
||||
}
|
||||
|
||||
// GetTransactionSpecific returns json as returned by backend, with all coin specific data
|
||||
func (b *EthereumRPC) GetTransactionSpecific(txid string) (json.RawMessage, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
|
||||
defer cancel()
|
||||
var tx json.RawMessage
|
||||
err := b.rpc.CallContext(ctx, &tx, "eth_getTransactionByHash", ethcommon.HexToHash(txid))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if tx == nil {
|
||||
return nil, ethereum.NotFound
|
||||
func (b *EthereumRPC) GetTransactionSpecific(tx *bchain.Tx) (json.RawMessage, error) {
|
||||
csd, ok := tx.CoinSpecificData.(completeTransaction)
|
||||
if !ok {
|
||||
ntx, err := b.GetTransaction(tx.Txid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
csd, ok = ntx.CoinSpecificData.(completeTransaction)
|
||||
if !ok {
|
||||
return nil, errors.New("Cannot get CoinSpecificData")
|
||||
}
|
||||
}
|
||||
return tx, nil
|
||||
m, err := json.Marshal(&csd)
|
||||
return json.RawMessage(m), err
|
||||
}
|
||||
|
||||
type rpcMempoolBlock struct {
|
||||
Transactions []string `json:"transactions"`
|
||||
}
|
||||
|
||||
func (b *EthereumRPC) GetMempool() ([]string, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
|
||||
defer cancel()
|
||||
var raw json.RawMessage
|
||||
var err error
|
||||
err = b.rpc.CallContext(ctx, &raw, "eth_getBlockByNumber", "pending", false)
|
||||
// GetMempoolTransactions returns transactions in mempool
|
||||
func (b *EthereumRPC) GetMempoolTransactions() ([]string, error) {
|
||||
raw, err := b.getBlockRaw("pending", 0, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if len(raw) == 0 {
|
||||
return nil, bchain.ErrBlockNotFound
|
||||
}
|
||||
var body rpcMempoolBlock
|
||||
if err := json.Unmarshal(raw, &body); err != nil {
|
||||
return nil, err
|
||||
var body rpcBlockTxids
|
||||
if len(raw) > 0 {
|
||||
if err := json.Unmarshal(raw, &body); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return body.Transactions, nil
|
||||
}
|
||||
|
@ -539,21 +689,57 @@ func (b *EthereumRPC) EstimateFee(blocks int) (big.Int, error) {
|
|||
func (b *EthereumRPC) EstimateSmartFee(blocks int, conservative bool) (big.Int, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
|
||||
defer cancel()
|
||||
// TODO - what parameters of msg to use to get better estimate, maybe more data from the wallet are needed
|
||||
a := ethcommon.HexToAddress("0x1234567890123456789012345678901234567890")
|
||||
msg := ethereum.CallMsg{
|
||||
To: &a,
|
||||
}
|
||||
g, err := b.client.EstimateGas(ctx, msg)
|
||||
var r big.Int
|
||||
if err != nil {
|
||||
return r, err
|
||||
gp, err := b.client.SuggestGasPrice(ctx)
|
||||
if err == nil && b != nil {
|
||||
r = *gp
|
||||
}
|
||||
r.SetUint64(g)
|
||||
return r, nil
|
||||
return r, err
|
||||
}
|
||||
|
||||
// SendRawTransaction sends raw transaction.
|
||||
func getStringFromMap(p string, params map[string]interface{}) (string, bool) {
|
||||
v, ok := params[p]
|
||||
if ok {
|
||||
s, ok := v.(string)
|
||||
return s, ok
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
// EthereumTypeEstimateGas returns estimation of gas consumption for given transaction parameters
|
||||
func (b *EthereumRPC) EthereumTypeEstimateGas(params map[string]interface{}) (uint64, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
|
||||
defer cancel()
|
||||
msg := ethereum.CallMsg{}
|
||||
s, ok := getStringFromMap("from", params)
|
||||
if ok && len(s) > 0 {
|
||||
msg.From = ethcommon.HexToAddress(s)
|
||||
}
|
||||
s, ok = getStringFromMap("to", params)
|
||||
if ok && len(s) > 0 {
|
||||
a := ethcommon.HexToAddress(s)
|
||||
msg.To = &a
|
||||
}
|
||||
s, ok = getStringFromMap("data", params)
|
||||
if ok && len(s) > 0 {
|
||||
msg.Data = ethcommon.FromHex(s)
|
||||
}
|
||||
s, ok = getStringFromMap("value", params)
|
||||
if ok && len(s) > 0 {
|
||||
msg.Value, _ = hexutil.DecodeBig(s)
|
||||
}
|
||||
s, ok = getStringFromMap("gas", params)
|
||||
if ok && len(s) > 0 {
|
||||
msg.Gas, _ = hexutil.DecodeUint64(s)
|
||||
}
|
||||
s, ok = getStringFromMap("gasPrice", params)
|
||||
if ok && len(s) > 0 {
|
||||
msg.GasPrice, _ = hexutil.DecodeBig(s)
|
||||
}
|
||||
return b.client.EstimateGas(ctx, msg)
|
||||
}
|
||||
|
||||
// SendRawTransaction sends raw transaction
|
||||
func (b *EthereumRPC) SendRawTransaction(hex string) (string, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
|
||||
defer cancel()
|
||||
|
@ -574,24 +760,21 @@ func (b *EthereumRPC) SendRawTransaction(hex string) (string, error) {
|
|||
return result, nil
|
||||
}
|
||||
|
||||
func (b *EthereumRPC) ResyncMempool(onNewTxAddr bchain.OnNewTxAddrFunc) (int, error) {
|
||||
return b.Mempool.Resync(onNewTxAddr)
|
||||
// EthereumTypeGetBalance returns current balance of an address
|
||||
func (b *EthereumRPC) EthereumTypeGetBalance(addrDesc bchain.AddressDescriptor) (*big.Int, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
|
||||
defer cancel()
|
||||
return b.client.BalanceAt(ctx, ethcommon.BytesToAddress(addrDesc), nil)
|
||||
}
|
||||
|
||||
// GetMempoolTransactions returns slice of mempool transactions for given address
|
||||
func (b *EthereumRPC) GetMempoolTransactions(address string) ([]string, error) {
|
||||
return b.Mempool.GetTransactions(address)
|
||||
}
|
||||
|
||||
// GetMempoolTransactionsForAddrDesc returns slice of mempool transactions for given address descriptor
|
||||
func (b *EthereumRPC) GetMempoolTransactionsForAddrDesc(addrDesc bchain.AddressDescriptor) ([]string, error) {
|
||||
return b.Mempool.GetAddrDescTransactions(addrDesc)
|
||||
}
|
||||
|
||||
func (b *EthereumRPC) GetMempoolEntry(txid string) (*bchain.MempoolEntry, error) {
|
||||
return nil, errors.New("GetMempoolEntry: not implemented")
|
||||
// EthereumTypeGetNonce returns current balance of an address
|
||||
func (b *EthereumRPC) EthereumTypeGetNonce(addrDesc bchain.AddressDescriptor) (uint64, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
|
||||
defer cancel()
|
||||
return b.client.NonceAt(ctx, ethcommon.BytesToAddress(addrDesc), nil)
|
||||
}
|
||||
|
||||
// GetChainParser returns ethereum BlockChainParser
|
||||
func (b *EthereumRPC) GetChainParser() bchain.BlockChainParser {
|
||||
return b.Parser
|
||||
}
|
||||
|
|
|
@ -0,0 +1,261 @@
|
|||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// source: bchain/coins/eth/ethtx.proto
|
||||
|
||||
/*
|
||||
Package eth is a generated protocol buffer package.
|
||||
|
||||
It is generated from these files:
|
||||
bchain/coins/eth/ethtx.proto
|
||||
|
||||
It has these top-level messages:
|
||||
ProtoCompleteTransaction
|
||||
*/
|
||||
package eth
|
||||
|
||||
import proto "github.com/golang/protobuf/proto"
|
||||
import fmt "fmt"
|
||||
import math "math"
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
|
||||
|
||||
type ProtoCompleteTransaction struct {
|
||||
BlockNumber uint32 `protobuf:"varint,1,opt,name=BlockNumber" json:"BlockNumber,omitempty"`
|
||||
BlockTime uint64 `protobuf:"varint,2,opt,name=BlockTime" json:"BlockTime,omitempty"`
|
||||
Tx *ProtoCompleteTransaction_TxType `protobuf:"bytes,3,opt,name=Tx" json:"Tx,omitempty"`
|
||||
Receipt *ProtoCompleteTransaction_ReceiptType `protobuf:"bytes,4,opt,name=Receipt" json:"Receipt,omitempty"`
|
||||
}
|
||||
|
||||
func (m *ProtoCompleteTransaction) Reset() { *m = ProtoCompleteTransaction{} }
|
||||
func (m *ProtoCompleteTransaction) String() string { return proto.CompactTextString(m) }
|
||||
func (*ProtoCompleteTransaction) ProtoMessage() {}
|
||||
func (*ProtoCompleteTransaction) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
|
||||
|
||||
func (m *ProtoCompleteTransaction) GetBlockNumber() uint32 {
|
||||
if m != nil {
|
||||
return m.BlockNumber
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *ProtoCompleteTransaction) GetBlockTime() uint64 {
|
||||
if m != nil {
|
||||
return m.BlockTime
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *ProtoCompleteTransaction) GetTx() *ProtoCompleteTransaction_TxType {
|
||||
if m != nil {
|
||||
return m.Tx
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *ProtoCompleteTransaction) GetReceipt() *ProtoCompleteTransaction_ReceiptType {
|
||||
if m != nil {
|
||||
return m.Receipt
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type ProtoCompleteTransaction_TxType struct {
|
||||
AccountNonce uint64 `protobuf:"varint,1,opt,name=AccountNonce" json:"AccountNonce,omitempty"`
|
||||
GasPrice []byte `protobuf:"bytes,2,opt,name=GasPrice,proto3" json:"GasPrice,omitempty"`
|
||||
GasLimit uint64 `protobuf:"varint,3,opt,name=GasLimit" json:"GasLimit,omitempty"`
|
||||
Value []byte `protobuf:"bytes,4,opt,name=Value,proto3" json:"Value,omitempty"`
|
||||
Payload []byte `protobuf:"bytes,5,opt,name=Payload,proto3" json:"Payload,omitempty"`
|
||||
Hash []byte `protobuf:"bytes,6,opt,name=Hash,proto3" json:"Hash,omitempty"`
|
||||
To []byte `protobuf:"bytes,7,opt,name=To,proto3" json:"To,omitempty"`
|
||||
From []byte `protobuf:"bytes,8,opt,name=From,proto3" json:"From,omitempty"`
|
||||
TransactionIndex uint32 `protobuf:"varint,9,opt,name=TransactionIndex" json:"TransactionIndex,omitempty"`
|
||||
}
|
||||
|
||||
func (m *ProtoCompleteTransaction_TxType) Reset() { *m = ProtoCompleteTransaction_TxType{} }
|
||||
func (m *ProtoCompleteTransaction_TxType) String() string { return proto.CompactTextString(m) }
|
||||
func (*ProtoCompleteTransaction_TxType) ProtoMessage() {}
|
||||
func (*ProtoCompleteTransaction_TxType) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor0, []int{0, 0}
|
||||
}
|
||||
|
||||
func (m *ProtoCompleteTransaction_TxType) GetAccountNonce() uint64 {
|
||||
if m != nil {
|
||||
return m.AccountNonce
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *ProtoCompleteTransaction_TxType) GetGasPrice() []byte {
|
||||
if m != nil {
|
||||
return m.GasPrice
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *ProtoCompleteTransaction_TxType) GetGasLimit() uint64 {
|
||||
if m != nil {
|
||||
return m.GasLimit
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *ProtoCompleteTransaction_TxType) GetValue() []byte {
|
||||
if m != nil {
|
||||
return m.Value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *ProtoCompleteTransaction_TxType) GetPayload() []byte {
|
||||
if m != nil {
|
||||
return m.Payload
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *ProtoCompleteTransaction_TxType) GetHash() []byte {
|
||||
if m != nil {
|
||||
return m.Hash
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *ProtoCompleteTransaction_TxType) GetTo() []byte {
|
||||
if m != nil {
|
||||
return m.To
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *ProtoCompleteTransaction_TxType) GetFrom() []byte {
|
||||
if m != nil {
|
||||
return m.From
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *ProtoCompleteTransaction_TxType) GetTransactionIndex() uint32 {
|
||||
if m != nil {
|
||||
return m.TransactionIndex
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type ProtoCompleteTransaction_ReceiptType struct {
|
||||
GasUsed []byte `protobuf:"bytes,1,opt,name=GasUsed,proto3" json:"GasUsed,omitempty"`
|
||||
Status []byte `protobuf:"bytes,2,opt,name=Status,proto3" json:"Status,omitempty"`
|
||||
Log []*ProtoCompleteTransaction_ReceiptType_LogType `protobuf:"bytes,3,rep,name=Log" json:"Log,omitempty"`
|
||||
}
|
||||
|
||||
func (m *ProtoCompleteTransaction_ReceiptType) Reset() { *m = ProtoCompleteTransaction_ReceiptType{} }
|
||||
func (m *ProtoCompleteTransaction_ReceiptType) String() string { return proto.CompactTextString(m) }
|
||||
func (*ProtoCompleteTransaction_ReceiptType) ProtoMessage() {}
|
||||
func (*ProtoCompleteTransaction_ReceiptType) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor0, []int{0, 1}
|
||||
}
|
||||
|
||||
func (m *ProtoCompleteTransaction_ReceiptType) GetGasUsed() []byte {
|
||||
if m != nil {
|
||||
return m.GasUsed
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *ProtoCompleteTransaction_ReceiptType) GetStatus() []byte {
|
||||
if m != nil {
|
||||
return m.Status
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *ProtoCompleteTransaction_ReceiptType) GetLog() []*ProtoCompleteTransaction_ReceiptType_LogType {
|
||||
if m != nil {
|
||||
return m.Log
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type ProtoCompleteTransaction_ReceiptType_LogType struct {
|
||||
Address []byte `protobuf:"bytes,1,opt,name=Address,proto3" json:"Address,omitempty"`
|
||||
Data []byte `protobuf:"bytes,2,opt,name=Data,proto3" json:"Data,omitempty"`
|
||||
Topics [][]byte `protobuf:"bytes,3,rep,name=Topics,proto3" json:"Topics,omitempty"`
|
||||
}
|
||||
|
||||
func (m *ProtoCompleteTransaction_ReceiptType_LogType) Reset() {
|
||||
*m = ProtoCompleteTransaction_ReceiptType_LogType{}
|
||||
}
|
||||
func (m *ProtoCompleteTransaction_ReceiptType_LogType) String() string {
|
||||
return proto.CompactTextString(m)
|
||||
}
|
||||
func (*ProtoCompleteTransaction_ReceiptType_LogType) ProtoMessage() {}
|
||||
func (*ProtoCompleteTransaction_ReceiptType_LogType) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor0, []int{0, 1, 0}
|
||||
}
|
||||
|
||||
func (m *ProtoCompleteTransaction_ReceiptType_LogType) GetAddress() []byte {
|
||||
if m != nil {
|
||||
return m.Address
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *ProtoCompleteTransaction_ReceiptType_LogType) GetData() []byte {
|
||||
if m != nil {
|
||||
return m.Data
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *ProtoCompleteTransaction_ReceiptType_LogType) GetTopics() [][]byte {
|
||||
if m != nil {
|
||||
return m.Topics
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*ProtoCompleteTransaction)(nil), "eth.ProtoCompleteTransaction")
|
||||
proto.RegisterType((*ProtoCompleteTransaction_TxType)(nil), "eth.ProtoCompleteTransaction.TxType")
|
||||
proto.RegisterType((*ProtoCompleteTransaction_ReceiptType)(nil), "eth.ProtoCompleteTransaction.ReceiptType")
|
||||
proto.RegisterType((*ProtoCompleteTransaction_ReceiptType_LogType)(nil), "eth.ProtoCompleteTransaction.ReceiptType.LogType")
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("bchain/coins/eth/ethtx.proto", fileDescriptor0) }
|
||||
|
||||
var fileDescriptor0 = []byte{
|
||||
// 409 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x92, 0xdf, 0x8a, 0xd4, 0x30,
|
||||
0x18, 0xc5, 0xe9, 0x9f, 0x99, 0xd9, 0xfd, 0xa6, 0x8a, 0x04, 0x91, 0x30, 0xec, 0x45, 0x59, 0xbc,
|
||||
0x18, 0xbd, 0xe8, 0xe2, 0xea, 0x0b, 0xac, 0x23, 0xae, 0xc2, 0xb0, 0x0e, 0x31, 0x7a, 0x9f, 0x49,
|
||||
0xc3, 0x36, 0x38, 0x6d, 0x4a, 0x93, 0x42, 0xf7, 0x8d, 0x7c, 0x21, 0xdf, 0xc5, 0x4b, 0xc9, 0xd7,
|
||||
0x74, 0x1d, 0x11, 0x65, 0x2f, 0x0a, 0xf9, 0x9d, 0x7e, 0xa7, 0x39, 0x27, 0x29, 0x9c, 0xed, 0x65,
|
||||
0x25, 0x74, 0x73, 0x21, 0x8d, 0x6e, 0xec, 0x85, 0x72, 0x95, 0x7f, 0xdc, 0x50, 0xb4, 0x9d, 0x71,
|
||||
0x86, 0x24, 0xca, 0x55, 0xe7, 0xdf, 0x67, 0x40, 0x77, 0x1e, 0x37, 0xa6, 0x6e, 0x0f, 0xca, 0x29,
|
||||
0xde, 0x89, 0xc6, 0x0a, 0xe9, 0xb4, 0x69, 0x48, 0x0e, 0xcb, 0xb7, 0x07, 0x23, 0xbf, 0xdd, 0xf4,
|
||||
0xf5, 0x5e, 0x75, 0x34, 0xca, 0xa3, 0xf5, 0x23, 0x76, 0x2c, 0x91, 0x33, 0x38, 0x45, 0xe4, 0xba,
|
||||
0x56, 0x34, 0xce, 0xa3, 0x75, 0xca, 0x7e, 0x0b, 0xe4, 0x0d, 0xc4, 0x7c, 0xa0, 0x49, 0x1e, 0xad,
|
||||
0x97, 0x97, 0xcf, 0x0b, 0xe5, 0xaa, 0xe2, 0x5f, 0x5b, 0x15, 0x7c, 0xe0, 0x77, 0xad, 0x62, 0x31,
|
||||
0x1f, 0xc8, 0x06, 0x16, 0x4c, 0x49, 0xa5, 0x5b, 0x47, 0x53, 0xb4, 0xbe, 0xf8, 0xbf, 0x35, 0x0c,
|
||||
0xa3, 0x7f, 0x72, 0xae, 0x7e, 0x46, 0x30, 0x1f, 0xbf, 0x49, 0xce, 0x21, 0xbb, 0x92, 0xd2, 0xf4,
|
||||
0x8d, 0xbb, 0x31, 0x8d, 0x54, 0x58, 0x23, 0x65, 0x7f, 0x68, 0x64, 0x05, 0x27, 0xd7, 0xc2, 0xee,
|
||||
0x3a, 0x2d, 0xc7, 0x1a, 0x19, 0xbb, 0xe7, 0xf0, 0x6e, 0xab, 0x6b, 0xed, 0xb0, 0x4b, 0xca, 0xee,
|
||||
0x99, 0x3c, 0x85, 0xd9, 0x57, 0x71, 0xe8, 0x15, 0x26, 0xcd, 0xd8, 0x08, 0x84, 0xc2, 0x62, 0x27,
|
||||
0xee, 0x0e, 0x46, 0x94, 0x74, 0x86, 0xfa, 0x84, 0x84, 0x40, 0xfa, 0x41, 0xd8, 0x8a, 0xce, 0x51,
|
||||
0xc6, 0x35, 0x79, 0x0c, 0x31, 0x37, 0x74, 0x81, 0x4a, 0xcc, 0x8d, 0x9f, 0x79, 0xdf, 0x99, 0x9a,
|
||||
0x9e, 0x8c, 0x33, 0x7e, 0x4d, 0x5e, 0xc2, 0x93, 0xa3, 0xca, 0x1f, 0x9b, 0x52, 0x0d, 0xf4, 0x14,
|
||||
0xaf, 0xe3, 0x2f, 0x7d, 0xf5, 0x23, 0x82, 0xe5, 0xd1, 0x99, 0xf8, 0x34, 0xd7, 0xc2, 0x7e, 0xb1,
|
||||
0xaa, 0xc4, 0xea, 0x19, 0x9b, 0x90, 0x3c, 0x83, 0xf9, 0x67, 0x27, 0x5c, 0x6f, 0x43, 0xe7, 0x40,
|
||||
0x64, 0x03, 0xc9, 0xd6, 0xdc, 0xd2, 0x24, 0x4f, 0xd6, 0xcb, 0xcb, 0x57, 0x0f, 0x3e, 0xfd, 0x62,
|
||||
0x6b, 0x6e, 0xf1, 0x16, 0xbc, 0x7b, 0xf5, 0x09, 0x16, 0x81, 0x7d, 0x82, 0xab, 0xb2, 0xec, 0x94,
|
||||
0xb5, 0x53, 0x82, 0x80, 0xbe, 0xeb, 0x3b, 0xe1, 0x44, 0xd8, 0x1f, 0xd7, 0x3e, 0x15, 0x37, 0xad,
|
||||
0x96, 0x16, 0x03, 0x64, 0x2c, 0xd0, 0x7e, 0x8e, 0xbf, 0xed, 0xeb, 0x5f, 0x01, 0x00, 0x00, 0xff,
|
||||
0xff, 0xc2, 0x69, 0x8d, 0xdf, 0xd6, 0x02, 0x00, 0x00,
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
syntax = "proto3";
|
||||
package eth;
|
||||
|
||||
message ProtoCompleteTransaction {
|
||||
message TxType {
|
||||
uint64 AccountNonce = 1;
|
||||
bytes GasPrice = 2;
|
||||
uint64 GasLimit = 3;
|
||||
bytes Value = 4;
|
||||
bytes Payload = 5;
|
||||
bytes Hash = 6;
|
||||
bytes To = 7;
|
||||
bytes From = 8;
|
||||
uint32 TransactionIndex = 9;
|
||||
}
|
||||
message ReceiptType {
|
||||
message LogType {
|
||||
bytes Address = 1;
|
||||
bytes Data = 2;
|
||||
repeated bytes Topics = 3;
|
||||
}
|
||||
bytes GasUsed = 1;
|
||||
bytes Status = 2;
|
||||
repeated LogType Log = 3;
|
||||
}
|
||||
uint32 BlockNumber = 1;
|
||||
uint64 BlockTime = 2;
|
||||
TxType Tx = 3;
|
||||
ReceiptType Receipt = 4;
|
||||
}
|
|
@ -1,175 +0,0 @@
|
|||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// source: tx.proto
|
||||
|
||||
/*
|
||||
Package eth is a generated protocol buffer package.
|
||||
|
||||
It is generated from these files:
|
||||
tx.proto
|
||||
|
||||
It has these top-level messages:
|
||||
ProtoTransaction
|
||||
*/
|
||||
package eth
|
||||
|
||||
import proto "github.com/golang/protobuf/proto"
|
||||
import fmt "fmt"
|
||||
import math "math"
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
|
||||
|
||||
type ProtoTransaction struct {
|
||||
AccountNonce uint64 `protobuf:"varint,1,opt,name=AccountNonce" json:"AccountNonce,omitempty"`
|
||||
Price []byte `protobuf:"bytes,2,opt,name=Price,proto3" json:"Price,omitempty"`
|
||||
GasLimit uint64 `protobuf:"varint,3,opt,name=GasLimit" json:"GasLimit,omitempty"`
|
||||
Value []byte `protobuf:"bytes,4,opt,name=Value,proto3" json:"Value,omitempty"`
|
||||
Payload []byte `protobuf:"bytes,5,opt,name=Payload,proto3" json:"Payload,omitempty"`
|
||||
Hash []byte `protobuf:"bytes,6,opt,name=Hash,proto3" json:"Hash,omitempty"`
|
||||
BlockNumber uint32 `protobuf:"varint,7,opt,name=BlockNumber" json:"BlockNumber,omitempty"`
|
||||
BlockTime uint64 `protobuf:"varint,8,opt,name=BlockTime" json:"BlockTime,omitempty"`
|
||||
To []byte `protobuf:"bytes,9,opt,name=To,proto3" json:"To,omitempty"`
|
||||
From []byte `protobuf:"bytes,10,opt,name=From,proto3" json:"From,omitempty"`
|
||||
TransactionIndex uint32 `protobuf:"varint,11,opt,name=TransactionIndex" json:"TransactionIndex,omitempty"`
|
||||
V []byte `protobuf:"bytes,12,opt,name=V,proto3" json:"V,omitempty"`
|
||||
R []byte `protobuf:"bytes,13,opt,name=R,proto3" json:"R,omitempty"`
|
||||
S []byte `protobuf:"bytes,14,opt,name=S,proto3" json:"S,omitempty"`
|
||||
}
|
||||
|
||||
func (m *ProtoTransaction) Reset() { *m = ProtoTransaction{} }
|
||||
func (m *ProtoTransaction) String() string { return proto.CompactTextString(m) }
|
||||
func (*ProtoTransaction) ProtoMessage() {}
|
||||
func (*ProtoTransaction) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
|
||||
|
||||
func (m *ProtoTransaction) GetAccountNonce() uint64 {
|
||||
if m != nil {
|
||||
return m.AccountNonce
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *ProtoTransaction) GetPrice() []byte {
|
||||
if m != nil {
|
||||
return m.Price
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *ProtoTransaction) GetGasLimit() uint64 {
|
||||
if m != nil {
|
||||
return m.GasLimit
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *ProtoTransaction) GetValue() []byte {
|
||||
if m != nil {
|
||||
return m.Value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *ProtoTransaction) GetPayload() []byte {
|
||||
if m != nil {
|
||||
return m.Payload
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *ProtoTransaction) GetHash() []byte {
|
||||
if m != nil {
|
||||
return m.Hash
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *ProtoTransaction) GetBlockNumber() uint32 {
|
||||
if m != nil {
|
||||
return m.BlockNumber
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *ProtoTransaction) GetBlockTime() uint64 {
|
||||
if m != nil {
|
||||
return m.BlockTime
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *ProtoTransaction) GetTo() []byte {
|
||||
if m != nil {
|
||||
return m.To
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *ProtoTransaction) GetFrom() []byte {
|
||||
if m != nil {
|
||||
return m.From
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *ProtoTransaction) GetTransactionIndex() uint32 {
|
||||
if m != nil {
|
||||
return m.TransactionIndex
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *ProtoTransaction) GetV() []byte {
|
||||
if m != nil {
|
||||
return m.V
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *ProtoTransaction) GetR() []byte {
|
||||
if m != nil {
|
||||
return m.R
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *ProtoTransaction) GetS() []byte {
|
||||
if m != nil {
|
||||
return m.S
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*ProtoTransaction)(nil), "eth.ProtoTransaction")
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("tx.proto", fileDescriptor0) }
|
||||
|
||||
var fileDescriptor0 = []byte{
|
||||
// 262 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x64, 0x90, 0xbd, 0x6a, 0xeb, 0x40,
|
||||
0x10, 0x85, 0x59, 0x59, 0xb6, 0xe5, 0xb1, 0x6c, 0xcc, 0x70, 0x8b, 0xe1, 0x92, 0x42, 0xb8, 0x12,
|
||||
0x29, 0xd2, 0xe4, 0x09, 0x92, 0x22, 0x3f, 0x10, 0x8c, 0x90, 0x85, 0xfa, 0xf5, 0x7a, 0xc1, 0x22,
|
||||
0x92, 0x26, 0x48, 0x2b, 0x70, 0x5e, 0x38, 0xcf, 0x11, 0x76, 0x44, 0x12, 0x87, 0x74, 0xf3, 0x7d,
|
||||
0x70, 0xf6, 0x2c, 0x07, 0x22, 0x77, 0xbe, 0x79, 0xeb, 0xd8, 0x31, 0x4e, 0xac, 0x3b, 0x6d, 0x3f,
|
||||
0x02, 0xd8, 0x64, 0x1e, 0x8b, 0x4e, 0xb7, 0xbd, 0x36, 0xae, 0xe2, 0x16, 0xb7, 0x10, 0xdf, 0x19,
|
||||
0xc3, 0x43, 0xeb, 0x76, 0xdc, 0x1a, 0x4b, 0x2a, 0x51, 0x69, 0x98, 0xff, 0x72, 0xf8, 0x0f, 0xa6,
|
||||
0x59, 0x57, 0x19, 0x4b, 0x41, 0xa2, 0xd2, 0x38, 0x1f, 0x01, 0xff, 0x43, 0xf4, 0xa8, 0xfb, 0x97,
|
||||
0xaa, 0xa9, 0x1c, 0x4d, 0x24, 0xf5, 0xcd, 0x3e, 0x51, 0xea, 0x7a, 0xb0, 0x14, 0x8e, 0x09, 0x01,
|
||||
0x24, 0x98, 0x67, 0xfa, 0xbd, 0x66, 0x7d, 0xa4, 0xa9, 0xf8, 0x2f, 0x44, 0x84, 0xf0, 0x49, 0xf7,
|
||||
0x27, 0x9a, 0x89, 0x96, 0x1b, 0x13, 0x58, 0xde, 0xd7, 0x6c, 0x5e, 0x77, 0x43, 0x73, 0xb0, 0x1d,
|
||||
0xcd, 0x13, 0x95, 0xae, 0xf2, 0x4b, 0x85, 0x57, 0xb0, 0x10, 0x2c, 0xaa, 0xc6, 0x52, 0x24, 0x5f,
|
||||
0xf8, 0x11, 0xb8, 0x86, 0xa0, 0x60, 0x5a, 0xc8, 0x8b, 0x41, 0xc1, 0xbe, 0xe3, 0xa1, 0xe3, 0x86,
|
||||
0x60, 0xec, 0xf0, 0x37, 0x5e, 0xc3, 0xe6, 0x62, 0x8c, 0xe7, 0xf6, 0x68, 0xcf, 0xb4, 0x94, 0xa2,
|
||||
0x3f, 0x1e, 0x63, 0x50, 0x25, 0xc5, 0x12, 0x56, 0xa5, 0xa7, 0x9c, 0x56, 0x23, 0xe5, 0x9e, 0xf6,
|
||||
0xb4, 0x1e, 0x69, 0x7f, 0x98, 0xc9, 0xe8, 0xb7, 0x9f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x15, 0xc8,
|
||||
0xe4, 0x30, 0x80, 0x01, 0x00, 0x00,
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
syntax = "proto3";
|
||||
package eth;
|
||||
|
||||
message ProtoTransaction {
|
||||
uint64 AccountNonce = 1;
|
||||
bytes Price = 2;
|
||||
uint64 GasLimit = 3;
|
||||
bytes Value = 4;
|
||||
bytes Payload = 5;
|
||||
bytes Hash = 6;
|
||||
uint32 BlockNumber = 7;
|
||||
uint64 BlockTime = 8;
|
||||
bytes To = 9;
|
||||
bytes From = 10;
|
||||
uint32 TransactionIndex = 11;
|
||||
bytes V = 12;
|
||||
bytes R = 13;
|
||||
bytes S = 14;
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
package firo
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
|
||||
"github.com/martinboehm/btcd/chaincfg/chainhash"
|
||||
"github.com/martinboehm/btcd/wire"
|
||||
)
|
||||
|
||||
// FiroMsgTx encapsulate firo tx and extra
|
||||
type FiroMsgTx struct {
|
||||
wire.MsgTx
|
||||
Extra []byte
|
||||
}
|
||||
|
||||
// TxHash calculate hash of transaction
|
||||
func (msg *FiroMsgTx) TxHash() chainhash.Hash {
|
||||
extraSize := uint64(len(msg.Extra))
|
||||
sizeOfExtraSize := 0
|
||||
if extraSize != 0 {
|
||||
sizeOfExtraSize = wire.VarIntSerializeSize(extraSize)
|
||||
}
|
||||
|
||||
// Original payload
|
||||
buf := bytes.NewBuffer(make([]byte, 0,
|
||||
msg.SerializeSizeStripped()+sizeOfExtraSize+len(msg.Extra)))
|
||||
_ = msg.SerializeNoWitness(buf)
|
||||
|
||||
// Extra payload
|
||||
if extraSize != 0 {
|
||||
wire.WriteVarInt(buf, 0, extraSize)
|
||||
buf.Write(msg.Extra)
|
||||
}
|
||||
|
||||
return chainhash.DoubleHashH(buf.Bytes())
|
||||
}
|
||||
|
||||
// FiroDecode to decode bitcoin tx and extra
|
||||
func (msg *FiroMsgTx) FiroDecode(r io.Reader, pver uint32, enc wire.MessageEncoding) error {
|
||||
if err := msg.MsgTx.BtcDecode(r, pver, enc); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// extra
|
||||
version := uint32(msg.Version)
|
||||
txVersion := version & 0xffff
|
||||
txType := (version >> 16) & 0xffff
|
||||
if txVersion == 3 && txType != 0 {
|
||||
|
||||
extraSize, err := wire.ReadVarInt(r, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
msg.Extra = make([]byte, extraSize)
|
||||
_, err = io.ReadFull(r, msg.Extra[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,312 @@
|
|||
package firo
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"io"
|
||||
|
||||
"github.com/martinboehm/btcd/chaincfg/chainhash"
|
||||
"github.com/martinboehm/btcd/wire"
|
||||
"github.com/martinboehm/btcutil/chaincfg"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
|
||||
)
|
||||
|
||||
const (
|
||||
OpZeroCoinMint = 0xc1
|
||||
OpZeroCoinSpend = 0xc2
|
||||
OpSigmaMint = 0xc3
|
||||
OpSigmaSpend = 0xc4
|
||||
OpLelantusMint = 0xc5
|
||||
OpLelantusJMint = 0xc6
|
||||
OpLelantusJoinSplit = 0xc7
|
||||
|
||||
MainnetMagic wire.BitcoinNet = 0xe3d9fef1
|
||||
TestnetMagic wire.BitcoinNet = 0xcffcbeea
|
||||
RegtestMagic wire.BitcoinNet = 0xfabfb5da
|
||||
|
||||
GenesisBlockTime = 1414776286
|
||||
SwitchToMTPBlockHeader = 1544443200
|
||||
MTPL = 64
|
||||
|
||||
SpendTxID = "0000000000000000000000000000000000000000000000000000000000000000"
|
||||
|
||||
TransactionQuorumCommitmentType = 6
|
||||
)
|
||||
|
||||
var (
|
||||
MainNetParams chaincfg.Params
|
||||
TestNetParams chaincfg.Params
|
||||
RegtestParams chaincfg.Params
|
||||
)
|
||||
|
||||
func init() {
|
||||
// mainnet
|
||||
MainNetParams = chaincfg.MainNetParams
|
||||
MainNetParams.Net = MainnetMagic
|
||||
|
||||
MainNetParams.AddressMagicLen = 1
|
||||
MainNetParams.PubKeyHashAddrID = []byte{0x52}
|
||||
MainNetParams.ScriptHashAddrID = []byte{0x07}
|
||||
|
||||
// testnet
|
||||
TestNetParams = chaincfg.TestNet3Params
|
||||
TestNetParams.Net = TestnetMagic
|
||||
|
||||
TestNetParams.AddressMagicLen = 1
|
||||
TestNetParams.PubKeyHashAddrID = []byte{0x41}
|
||||
TestNetParams.ScriptHashAddrID = []byte{0xb2}
|
||||
|
||||
// regtest
|
||||
RegtestParams = chaincfg.RegressionNetParams
|
||||
RegtestParams.Net = RegtestMagic
|
||||
}
|
||||
|
||||
// FiroParser handle
|
||||
type FiroParser struct {
|
||||
*btc.BitcoinParser
|
||||
}
|
||||
|
||||
// NewFiroParser returns new FiroParser instance
|
||||
func NewFiroParser(params *chaincfg.Params, c *btc.Configuration) *FiroParser {
|
||||
return &FiroParser{
|
||||
BitcoinParser: btc.NewBitcoinParser(params, c),
|
||||
}
|
||||
}
|
||||
|
||||
// GetChainParams contains network parameters for the main Firo network,
|
||||
// the regression test Firo network, the test Firo network and
|
||||
// the simulation test Firo network, in this order
|
||||
func GetChainParams(chain string) *chaincfg.Params {
|
||||
if !chaincfg.IsRegistered(&MainNetParams) {
|
||||
err := chaincfg.Register(&MainNetParams)
|
||||
if err == nil {
|
||||
err = chaincfg.Register(&TestNetParams)
|
||||
}
|
||||
if err == nil {
|
||||
err = chaincfg.Register(&RegtestParams)
|
||||
}
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
switch chain {
|
||||
case "test":
|
||||
return &TestNetParams
|
||||
case "regtest":
|
||||
return &RegtestParams
|
||||
default:
|
||||
return &MainNetParams
|
||||
}
|
||||
}
|
||||
|
||||
// GetAddressesFromAddrDesc returns addresses for given address descriptor with flag if the addresses are searchable
|
||||
func (p *FiroParser) GetAddressesFromAddrDesc(addrDesc bchain.AddressDescriptor) ([]string, bool, error) {
|
||||
|
||||
if len(addrDesc) > 0 {
|
||||
switch addrDesc[0] {
|
||||
case OpZeroCoinMint:
|
||||
return []string{"Zeromint"}, false, nil
|
||||
case OpZeroCoinSpend:
|
||||
return []string{"Zerospend"}, false, nil
|
||||
case OpSigmaMint:
|
||||
return []string{"Sigmamint"}, false, nil
|
||||
case OpSigmaSpend:
|
||||
return []string{"Sigmaspend"}, false, nil
|
||||
case OpLelantusMint:
|
||||
return []string{"LelantusMint"}, false, nil
|
||||
case OpLelantusJMint:
|
||||
return []string{"LelantusJMint"}, false, nil
|
||||
case OpLelantusJoinSplit:
|
||||
return []string{"LelantusJoinSplit"}, false, nil
|
||||
}
|
||||
}
|
||||
|
||||
return p.OutputScriptToAddressesFunc(addrDesc)
|
||||
}
|
||||
|
||||
// PackTx packs transaction to byte array using protobuf
|
||||
func (p *FiroParser) PackTx(tx *bchain.Tx, height uint32, blockTime int64) ([]byte, error) {
|
||||
return p.BaseParser.PackTx(tx, height, blockTime)
|
||||
}
|
||||
|
||||
// UnpackTx unpacks transaction from protobuf byte array
|
||||
func (p *FiroParser) UnpackTx(buf []byte) (*bchain.Tx, uint32, error) {
|
||||
return p.BaseParser.UnpackTx(buf)
|
||||
}
|
||||
|
||||
// TxFromFiroMsgTx converts bitcoin wire Tx to bchain.Tx
|
||||
func (p *FiroParser) TxFromFiroMsgTx(t *FiroMsgTx, parseAddresses bool) bchain.Tx {
|
||||
btx := p.TxFromMsgTx(&t.MsgTx, parseAddresses)
|
||||
|
||||
// NOTE: wire.MsgTx.TxHash() doesn't include extra
|
||||
btx.Txid = t.TxHash().String()
|
||||
|
||||
return btx
|
||||
}
|
||||
|
||||
// ParseBlock parses raw block to our Block struct
|
||||
func (p *FiroParser) ParseBlock(b []byte) (*bchain.Block, error) {
|
||||
reader := bytes.NewReader(b)
|
||||
|
||||
// parse standard block header first
|
||||
header, err := parseBlockHeader(reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// then MTP header
|
||||
if isMTP(header) {
|
||||
mtpHeader := MTPBlockHeader{}
|
||||
mtpHashData := MTPHashData{}
|
||||
|
||||
// header
|
||||
err = binary.Read(reader, binary.LittleEndian, &mtpHeader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// hash data
|
||||
err = binary.Read(reader, binary.LittleEndian, &mtpHashData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// proof
|
||||
for i := 0; i < MTPL*3; i++ {
|
||||
var numberProofBlocks uint8
|
||||
|
||||
err = binary.Read(reader, binary.LittleEndian, &numberProofBlocks)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for j := uint8(0); j < numberProofBlocks; j++ {
|
||||
var mtpData [16]uint8
|
||||
|
||||
err = binary.Read(reader, binary.LittleEndian, mtpData[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// parse txs
|
||||
ntx, err := wire.ReadVarInt(reader, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
txs := make([]bchain.Tx, ntx)
|
||||
|
||||
for i := uint64(0); i < ntx; i++ {
|
||||
tx := FiroMsgTx{}
|
||||
|
||||
// read version and seek back
|
||||
var version uint32 = 0
|
||||
if err = binary.Read(reader, binary.LittleEndian, &version); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err = reader.Seek(-4, io.SeekCurrent); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
txVersion := version & 0xffff
|
||||
txType := (version >> 16) & 0xffff
|
||||
|
||||
enc := wire.WitnessEncoding
|
||||
|
||||
// transaction quorum commitment could not be parsed with witness flag
|
||||
if txVersion == 3 && txType == TransactionQuorumCommitmentType {
|
||||
enc = wire.BaseEncoding
|
||||
}
|
||||
|
||||
if err = tx.FiroDecode(reader, 0, enc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
btx := p.TxFromFiroMsgTx(&tx, false)
|
||||
|
||||
if err = p.parseFiroTx(&btx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
txs[i] = btx
|
||||
}
|
||||
|
||||
return &bchain.Block{
|
||||
BlockHeader: bchain.BlockHeader{
|
||||
Size: len(b),
|
||||
Time: header.Timestamp.Unix(),
|
||||
},
|
||||
Txs: txs,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ParseTxFromJson parses JSON message containing transaction and returns Tx struct
|
||||
func (p *FiroParser) ParseTxFromJson(msg json.RawMessage) (*bchain.Tx, error) {
|
||||
var tx bchain.Tx
|
||||
err := json.Unmarshal(msg, &tx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i := range tx.Vout {
|
||||
vout := &tx.Vout[i]
|
||||
// convert vout.JsonValue to big.Int and clear it, it is only temporary value used for unmarshal
|
||||
vout.ValueSat, err = p.AmountToBigInt(vout.JsonValue)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vout.JsonValue = ""
|
||||
}
|
||||
|
||||
p.parseFiroTx(&tx)
|
||||
|
||||
return &tx, nil
|
||||
}
|
||||
|
||||
func (p *FiroParser) parseFiroTx(tx *bchain.Tx) error {
|
||||
for i := range tx.Vin {
|
||||
vin := &tx.Vin[i]
|
||||
|
||||
// FIXME: right now we treat zerocoin spend vin as coinbase
|
||||
// change this after blockbook support special type of vin
|
||||
if vin.Txid == SpendTxID {
|
||||
vin.Coinbase = vin.Txid
|
||||
vin.Txid = ""
|
||||
vin.Sequence = 0
|
||||
vin.Vout = 0
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseBlockHeader(r io.Reader) (*wire.BlockHeader, error) {
|
||||
h := &wire.BlockHeader{}
|
||||
err := h.Deserialize(r)
|
||||
return h, err
|
||||
}
|
||||
|
||||
func isMTP(h *wire.BlockHeader) bool {
|
||||
epoch := h.Timestamp.Unix()
|
||||
|
||||
// the genesis block never be MTP block
|
||||
return epoch > GenesisBlockTime && epoch >= SwitchToMTPBlockHeader
|
||||
}
|
||||
|
||||
type MTPHashData struct {
|
||||
HashRootMTP [16]uint8
|
||||
BlockMTP [128][128]uint64
|
||||
}
|
||||
|
||||
type MTPBlockHeader struct {
|
||||
VersionMTP int32
|
||||
MTPHashValue chainhash.Hash
|
||||
Reserved1 chainhash.Hash
|
||||
Reserved2 chainhash.Hash
|
||||
}
|
|
@ -0,0 +1,981 @@
|
|||
// +build unittest
|
||||
|
||||
package firo
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/martinboehm/btcd/wire"
|
||||
"github.com/martinboehm/btcutil/chaincfg"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
|
||||
)
|
||||
|
||||
var (
|
||||
testTx1, testTx2, testTx3, testTx4, testTx5, testTx6 bchain.Tx
|
||||
rawTestTx1, rawTestTx2, rawTestTx3, rawTestTx4, rawTestTx5, rawTestTx6 string
|
||||
testTxPacked1, testTxPacked2, testTxPacked3, testTxPacked4, testTxPacked5, testTxPacked6 string
|
||||
rawBlock1, rawBlock2, rawBlock3 string
|
||||
jsonTx json.RawMessage
|
||||
)
|
||||
|
||||
func readHexs(path string) []string {
|
||||
raw, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
rawStr := string(raw)
|
||||
raws := strings.Split(rawStr, "\n")
|
||||
return raws
|
||||
}
|
||||
|
||||
func init() {
|
||||
rawBlocks := readHexs("./testdata/rawblock.hex")
|
||||
rawBlock1 = rawBlocks[0]
|
||||
rawBlock2 = rawBlocks[1]
|
||||
rawBlock3 = rawBlocks[2]
|
||||
|
||||
hextxs := readHexs("./testdata/txs.hex")
|
||||
rawTestTx1 = hextxs[0]
|
||||
rawTestTx2 = hextxs[1]
|
||||
rawTestTx3 = hextxs[2]
|
||||
rawTestTx4 = hextxs[3]
|
||||
rawTestTx5 = hextxs[4]
|
||||
rawTestTx6 = hextxs[5]
|
||||
|
||||
rawSpendHex := readHexs("./testdata/rawspend.hex")[0]
|
||||
|
||||
rawSpendTx, err := ioutil.ReadFile("./testdata/spendtx.json")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
jsonTx = json.RawMessage(rawSpendTx)
|
||||
|
||||
testTxPackeds := readHexs("./testdata/packedtxs.hex")
|
||||
testTxPacked1 = testTxPackeds[0]
|
||||
testTxPacked2 = testTxPackeds[1]
|
||||
testTxPacked3 = testTxPackeds[2]
|
||||
testTxPacked4 = testTxPackeds[3]
|
||||
testTxPacked5 = testTxPackeds[4]
|
||||
testTxPacked6 = testTxPackeds[5]
|
||||
|
||||
testTx1 = bchain.Tx{
|
||||
Hex: rawTestTx1,
|
||||
Blocktime: 1533980594,
|
||||
Time: 1533980594,
|
||||
Txid: "9d9e759dd970d86df9e105a7d4f671543bc16a03b6c5d2b48895f2a00aa7dd23",
|
||||
LockTime: 99688,
|
||||
Vin: []bchain.Vin{
|
||||
{
|
||||
ScriptSig: bchain.ScriptSig{
|
||||
Hex: "47304402205b7d9c9aae790b69017651e10134735928df3b4a4a2feacc9568eb4fa133ed5902203f21a399385ce29dd79831ea34aa535612aa4314c5bd0b002bbbc9bcd2de1436012102b8d462740c99032a00083ac7028879acec244849e54ad0a04ea87f632f54b1d2",
|
||||
},
|
||||
Txid: "463a2d66b04636a014da35724909425f3403d9d786dd4f79780de50d47b18716",
|
||||
Vout: 1,
|
||||
Sequence: 4294967294,
|
||||
},
|
||||
},
|
||||
Vout: []bchain.Vout{
|
||||
{
|
||||
ValueSat: *big.NewInt(100000000),
|
||||
N: 0,
|
||||
ScriptPubKey: bchain.ScriptPubKey{
|
||||
Hex: "c10280004c80f767f3ee79953c67a7ed386dcccf1243619eb4bbbe414a3982dd94a83c1b69ac52d6ab3b653a3e05c4e4516c8dfe1e58ada40461bc5835a4a0d0387a51c29ac11b72ae25bbcdef745f50ad08f08b3e9bc2c31a35444398a490e65ac090e9f341f1abdebe47e57e8237ac25d098e951b4164a35caea29f30acb50b12e4425df28",
|
||||
},
|
||||
},
|
||||
{
|
||||
ValueSat: *big.NewInt(871824000),
|
||||
N: 1,
|
||||
ScriptPubKey: bchain.ScriptPubKey{
|
||||
Hex: "76a914c963f917c7f23cb4243e079db33107571b87690588ac",
|
||||
Addresses: []string{
|
||||
"aK5KKi8qqDbspcXFfDjx8UBGMouhYbYZVp",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
testTx2 = bchain.Tx{
|
||||
Hex: rawTestTx2,
|
||||
Blocktime: 1481277009,
|
||||
Time: 1481277009,
|
||||
Txid: "3d721fdce2855e2b4a54b74a26edd58a7262e1f195b5acaaae7832be6e0b3d32",
|
||||
LockTime: 0,
|
||||
Vin: []bchain.Vin{
|
||||
{
|
||||
Coinbase: rawSpendHex,
|
||||
Txid: "0000000000000000000000000000000000000000000000000000000000000000",
|
||||
Vout: 4294967295,
|
||||
Sequence: 2,
|
||||
},
|
||||
},
|
||||
Vout: []bchain.Vout{
|
||||
{
|
||||
ValueSat: *big.NewInt(5000000000),
|
||||
N: 0,
|
||||
ScriptPubKey: bchain.ScriptPubKey{
|
||||
Hex: "76a914b9e262e30df03e88ccea312652bc83ca7290c8fc88ac",
|
||||
Addresses: []string{
|
||||
"aHfKwzFZMiSxDuNL4jts819nh57t2yJG1h",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
testTx3 = bchain.Tx{
|
||||
Hex: rawTestTx3,
|
||||
Blocktime: 1547091829,
|
||||
Time: 1547091829,
|
||||
Txid: "96ae951083651f141d1fb2719c76d47e5a3ad421b81905f679c0edb60f2de0ff",
|
||||
LockTime: 126200,
|
||||
Vin: []bchain.Vin{
|
||||
{
|
||||
ScriptSig: bchain.ScriptSig{
|
||||
Hex: "483045022100bdc6b51c114617e29e28390dc9b3ad95b833ca3d1f0429ba667c58a667f9124702204ca2ed362dd9ef723ddbdcf4185b47c28b127a36f46bc4717662be863309b3e601210387e7ff08b953e3736955408fc6ebcd8aa84a04cc4b45758ea29cc2cfe1820535",
|
||||
},
|
||||
Txid: "448ccfd9c3f375be8701b86aff355a230dbe240334233f2ed476fcae6abd295d",
|
||||
Vout: 1,
|
||||
Sequence: 4294967294,
|
||||
},
|
||||
},
|
||||
Vout: []bchain.Vout{
|
||||
{
|
||||
ValueSat: *big.NewInt(42000000000),
|
||||
N: 0,
|
||||
ScriptPubKey: bchain.ScriptPubKey{
|
||||
Hex: "76a91429bef7962c5c65a2f0f4f7d9ec791866c54f851688ac",
|
||||
Addresses: []string{
|
||||
"a4XCDQ7AnRH9opZ4h6LcG3g7ocSV2SbBmS",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ValueSat: *big.NewInt(107300000000),
|
||||
N: 1,
|
||||
ScriptPubKey: bchain.ScriptPubKey{
|
||||
Hex: "76a914e2cee7b71c3a4637dbdfe613f19f4b4f2d070d7f88ac",
|
||||
Addresses: []string{
|
||||
"aMPiKHB3E1AGPi8kKLknx6j1L4JnKCGkLw",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
testTx4 = bchain.Tx{
|
||||
Hex: rawTestTx4,
|
||||
Blocktime: 1533977563,
|
||||
Time: 1533977563,
|
||||
Txid: "914ccbdb72f593e5def15978cf5891e1384a1b85e89374fc1c440c074c6dd286",
|
||||
LockTime: 0,
|
||||
Vin: []bchain.Vin{
|
||||
{
|
||||
Coinbase: "03a1860104dba36e5b082a00077c00000000052f6d70682f",
|
||||
Sequence: 0,
|
||||
},
|
||||
},
|
||||
Vout: []bchain.Vout{
|
||||
{
|
||||
ValueSat: *big.NewInt(2800200000),
|
||||
N: 0,
|
||||
ScriptPubKey: bchain.ScriptPubKey{
|
||||
Hex: "76a91436e086acf6561a68ba64196e7b92b606d0b8516688ac",
|
||||
Addresses: []string{
|
||||
"a5idCcHN8WYxvFCeBXSXvMPrZHuBkZmqEJ",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ValueSat: *big.NewInt(1500000000),
|
||||
N: 1,
|
||||
ScriptPubKey: bchain.ScriptPubKey{
|
||||
Hex: "76a914381a5dd1a279e8e63e67cde39ecfa61a99dd2ba288ac",
|
||||
Addresses: []string{
|
||||
"a5q7Ad4okSFFVh5adyqx5DT21RTxJykpUM",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ValueSat: *big.NewInt(100000000),
|
||||
N: 2,
|
||||
ScriptPubKey: bchain.ScriptPubKey{
|
||||
Hex: "76a9147d9ed014fc4e603fca7c2e3f9097fb7d0fb487fc88ac",
|
||||
Addresses: []string{
|
||||
"aCAgTPgtYcA4EysU4UKC86EQd5cTtHtCcr",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ValueSat: *big.NewInt(100000000),
|
||||
N: 3,
|
||||
ScriptPubKey: bchain.ScriptPubKey{
|
||||
Hex: "76a914bc7e5a5234db3ab82d74c396ad2b2af419b7517488ac",
|
||||
Addresses: []string{
|
||||
"aHu897ivzmeFuLNB6956X6gyGeVNHUBRgD",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ValueSat: *big.NewInt(100000000),
|
||||
N: 4,
|
||||
ScriptPubKey: bchain.ScriptPubKey{
|
||||
Hex: "76a914ff71b0c9c2a90c6164a50a2fb523eb54a8a6b55088ac",
|
||||
Addresses: []string{
|
||||
"aQ18FBVFtnueucZKeVg4srhmzbpAeb1KoN",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ValueSat: *big.NewInt(300000000),
|
||||
N: 5,
|
||||
ScriptPubKey: bchain.ScriptPubKey{
|
||||
Hex: "76a9140654dd9b856f2ece1d56cb4ee5043cd9398d962c88ac",
|
||||
Addresses: []string{
|
||||
"a1HwTdCmQV3NspP2QqCGpehoFpi8NY4Zg3",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ValueSat: *big.NewInt(100000000),
|
||||
N: 6,
|
||||
ScriptPubKey: bchain.ScriptPubKey{
|
||||
Hex: "76a9140b4bfb256ef4bfa360e3b9e66e53a0bd84d196bc88ac",
|
||||
Addresses: []string{
|
||||
"a1kCCGddf5pMXSipLVD9hBG2MGGVNaJ15U",
|
||||
},
|
||||
},
|
||||
},
|
||||
// TODO: test segwit
|
||||
},
|
||||
}
|
||||
|
||||
testTx5 = bchain.Tx{
|
||||
Hex: rawTestTx5,
|
||||
Blocktime: 1591752749,
|
||||
Time: 1591752749,
|
||||
Txid: "8d1f32f35c32d2c127a7400dc1ec52049fbf0b8bcdf284cfaa3da59b6169a22d",
|
||||
LockTime: 0,
|
||||
Vin: []bchain.Vin{},
|
||||
Vout: []bchain.Vout{},
|
||||
}
|
||||
|
||||
testTx6 = bchain.Tx{
|
||||
Hex: rawTestTx6,
|
||||
Blocktime: 1591762049,
|
||||
Time: 1591762049,
|
||||
Txid: "e5767d3606230a65f150837a6f28b4f0e4c2702a683045df3883d57702739c61",
|
||||
LockTime: 0,
|
||||
Vin: []bchain.Vin{
|
||||
{
|
||||
Coinbase: "02b4140101",
|
||||
Sequence: 4294967295,
|
||||
},
|
||||
},
|
||||
Vout: []bchain.Vout{
|
||||
{
|
||||
ValueSat: *big.NewInt(1400000000),
|
||||
N: 0,
|
||||
ScriptPubKey: bchain.ScriptPubKey{
|
||||
Hex: "2103fb09a216761d5e7f248294970c2370f7f84ce1ad564b8e7096b1e19116af1d52ac",
|
||||
Addresses: []string{
|
||||
"TAn9Ghkp31myXRgejCj11wWVHT14Lsj349",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ValueSat: *big.NewInt(50000000),
|
||||
N: 1,
|
||||
ScriptPubKey: bchain.ScriptPubKey{
|
||||
Hex: "76a914296134d2415bf1f2b518b3f673816d7e603b160088ac",
|
||||
Addresses: []string{
|
||||
"TDk19wPKYq91i18qmY6U9FeTdTxwPeSveo",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ValueSat: *big.NewInt(50000000),
|
||||
N: 2,
|
||||
ScriptPubKey: bchain.ScriptPubKey{
|
||||
Hex: "76a914e1e1dc06a889c1b6d3eb00eef7a96f6a7cfb884888ac",
|
||||
Addresses: []string{
|
||||
"TWZZcDGkNixTAMtRBqzZkkMHbq1G6vUTk5",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ValueSat: *big.NewInt(50000000),
|
||||
N: 3,
|
||||
ScriptPubKey: bchain.ScriptPubKey{
|
||||
Hex: "76a914ab03ecfddee6330497be894d16c29ae341c123aa88ac",
|
||||
Addresses: []string{
|
||||
"TRZTFdNCKCKbLMQV8cZDkQN9Vwuuq4gDzT",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ValueSat: *big.NewInt(150000000),
|
||||
N: 4,
|
||||
ScriptPubKey: bchain.ScriptPubKey{
|
||||
Hex: "76a9144281a58a1d5b2d3285e00cb45a8492debbdad4c588ac",
|
||||
Addresses: []string{
|
||||
"TG2ruj59E5b1u9G3F7HQVs6pCcVDBxrQve",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ValueSat: *big.NewInt(50000000),
|
||||
N: 5,
|
||||
ScriptPubKey: bchain.ScriptPubKey{
|
||||
Hex: "76a9141fd264c0bb53bd9fef18e2248ddf1383d6e811ae88ac",
|
||||
Addresses: []string{
|
||||
"TCsTzQZKVn4fao8jDmB9zQBk9YQNEZ3XfS",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ValueSat: *big.NewInt(750000000),
|
||||
N: 6,
|
||||
ScriptPubKey: bchain.ScriptPubKey{
|
||||
Hex: "76a91471a3892d164ffa3829078bf9ad5f114a3908ce5588ac",
|
||||
Addresses: []string{
|
||||
"TLL5GQULX4uBfz7yXL6VcZyvzdKVv1RGxm",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
c := m.Run()
|
||||
chaincfg.ResetParams()
|
||||
os.Exit(c)
|
||||
}
|
||||
|
||||
func TestGetAddrDesc(t *testing.T) {
|
||||
type args struct {
|
||||
tx bchain.Tx
|
||||
parser *FiroParser
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
}{
|
||||
{
|
||||
name: "firo-1",
|
||||
args: args{
|
||||
tx: testTx1,
|
||||
parser: NewFiroParser(GetChainParams("main"), &btc.Configuration{}),
|
||||
},
|
||||
},
|
||||
// FIXME: work around handle zerocoin spend as coinbase
|
||||
// {
|
||||
// name: "firo-2",
|
||||
// args: args{
|
||||
// tx: testTx2,
|
||||
// parser: NewFiroParser(GetChainParams("main"), &btc.Configuration{}),
|
||||
// },
|
||||
// },
|
||||
{
|
||||
name: "firo-3",
|
||||
args: args{
|
||||
tx: testTx3,
|
||||
parser: NewFiroParser(GetChainParams("main"), &btc.Configuration{}),
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
for n, vout := range tt.args.tx.Vout {
|
||||
got1, err := tt.args.parser.GetAddrDescFromVout(&vout)
|
||||
if err != nil {
|
||||
t.Errorf("getAddrDescFromVout() error = %v, vout = %d", err, n)
|
||||
return
|
||||
}
|
||||
|
||||
// normal vout
|
||||
if len(vout.ScriptPubKey.Addresses) >= 1 {
|
||||
got2, err := tt.args.parser.GetAddrDescFromAddress(vout.ScriptPubKey.Addresses[0])
|
||||
if err != nil {
|
||||
t.Errorf("getAddrDescFromAddress() error = %v, vout = %d", err, n)
|
||||
return
|
||||
}
|
||||
if !bytes.Equal(got1, got2) {
|
||||
t.Errorf("Address descriptors mismatch: got1 = %v, got2 = %v", got1, got2)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetAddrDescFromVoutForMint(t *testing.T) {
|
||||
type args struct {
|
||||
vout bchain.Vout
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "OP_RETURN",
|
||||
args: args{vout: bchain.Vout{ScriptPubKey: bchain.ScriptPubKey{Hex: "6a072020f1686f6a20"}}},
|
||||
want: "6a072020f1686f6a20",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "OP_ZEROCOINMINT",
|
||||
args: args{vout: bchain.Vout{ScriptPubKey: bchain.ScriptPubKey{Hex: "c10280004c80f767f3ee79953c67a7ed386dcccf1243619eb4bbbe414a3982dd94a83c1b69ac52d6ab3b653a3e05c4e4516c8dfe1e58ada40461bc5835a4a0d0387a51c29ac11b72ae25bbcdef745f50ad08f08b3e9bc2c31a35444398a490e65ac090e9f341f1abdebe47e57e8237ac25d098e951b4164a35caea29f30acb50b12e4425df28"}}},
|
||||
want: "c10280004c80f767f3ee79953c67a7ed386dcccf1243619eb4bbbe414a3982dd94a83c1b69ac52d6ab3b653a3e05c4e4516c8dfe1e58ada40461bc5835a4a0d0387a51c29ac11b72ae25bbcdef745f50ad08f08b3e9bc2c31a35444398a490e65ac090e9f341f1abdebe47e57e8237ac25d098e951b4164a35caea29f30acb50b12e4425df28",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "OP_SIGMAMINT",
|
||||
args: args{vout: bchain.Vout{ScriptPubKey: bchain.ScriptPubKey{Hex: "c317dcee5b8b2c5b79728abc3a39abc54682b31a4e18f5abb6f34dc8089544763b0000"}}},
|
||||
want: "c317dcee5b8b2c5b79728abc3a39abc54682b31a4e18f5abb6f34dc8089544763b0000",
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
parser := NewFiroParser(GetChainParams("main"), &btc.Configuration{})
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := parser.GetAddrDescFromVout(&tt.args.vout)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("GetAddrDescFromVout() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
h := hex.EncodeToString(got)
|
||||
if !reflect.DeepEqual(h, tt.want) {
|
||||
t.Errorf("GetAddrDescFromVout() = %v, want %v", h, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetAddressesFromAddrDescForMint(t *testing.T) {
|
||||
type args struct {
|
||||
script string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []string
|
||||
want2 bool
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "OP_RETURN hex",
|
||||
args: args{script: "6a072020f1686f6a20"},
|
||||
want: []string{"OP_RETURN 2020f1686f6a20"},
|
||||
want2: false,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "OP_ZEROCOINMINT size hex",
|
||||
args: args{script: "c10280004c80f767f3ee79953c67a7ed386dcccf1243619eb4bbbe414a3982dd94a83c1b69ac52d6ab3b653a3e05c4e4516c8dfe1e58ada40461bc5835a4a0d0387a51c29ac11b72ae25bbcdef745f50ad08f08b3e9bc2c31a35444398a490e65ac090e9f341f1abdebe47e57e8237ac25d098e951b4164a35caea29f30acb50b12e4425df28"},
|
||||
want: []string{"Zeromint"},
|
||||
want2: false,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "OP_SIGMAMINT size hex",
|
||||
args: args{script: "c317dcee5b8b2c5b79728abc3a39abc54682b31a4e18f5abb6f34dc8089544763b0000"},
|
||||
want: []string{"Sigmamint"},
|
||||
want2: false,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
parser := NewFiroParser(GetChainParams("main"), &btc.Configuration{})
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
b, _ := hex.DecodeString(tt.args.script)
|
||||
got, got2, err := parser.GetAddressesFromAddrDesc(b)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("GetAddressesFromAddrDesc() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("GetAddressesFromAddrDesc() = %v, want %v", got, tt.want)
|
||||
}
|
||||
if !reflect.DeepEqual(got2, tt.want2) {
|
||||
t.Errorf("GetAddressesFromAddrDesc() = %v, want %v", got2, tt.want2)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPackTx(t *testing.T) {
|
||||
type args struct {
|
||||
tx bchain.Tx
|
||||
height uint32
|
||||
blockTime int64
|
||||
parser *FiroParser
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "firo-1",
|
||||
args: args{
|
||||
tx: testTx1,
|
||||
height: 100002,
|
||||
blockTime: 1533980594,
|
||||
parser: NewFiroParser(GetChainParams("main"), &btc.Configuration{}),
|
||||
},
|
||||
want: testTxPacked1,
|
||||
wantErr: false,
|
||||
},
|
||||
// FIXME: work around handle zerocoin spend as coinbase
|
||||
// {
|
||||
// name: "firo-2",
|
||||
// args: args{
|
||||
// tx: testTx2,
|
||||
// height: 11002,
|
||||
// blockTime: 1481277009,
|
||||
// parser: NewFiroParser(GetChainParams("main"), &btc.Configuration{}),
|
||||
// },
|
||||
// want: testTxPacked2,
|
||||
// wantErr: true,
|
||||
// },
|
||||
{
|
||||
name: "firo-3",
|
||||
args: args{
|
||||
tx: testTx3,
|
||||
height: 126202,
|
||||
blockTime: 1547091829,
|
||||
parser: NewFiroParser(GetChainParams("main"), &btc.Configuration{}),
|
||||
},
|
||||
want: testTxPacked3,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "firo-coinbase",
|
||||
args: args{
|
||||
tx: testTx4,
|
||||
height: 100001,
|
||||
blockTime: 1533977563,
|
||||
parser: NewFiroParser(GetChainParams("main"), &btc.Configuration{}),
|
||||
},
|
||||
want: testTxPacked4,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "firo-quorum-commitment-tx",
|
||||
args: args{
|
||||
tx: testTx5,
|
||||
height: 5268,
|
||||
blockTime: 1591752749,
|
||||
parser: NewFiroParser(GetChainParams("test"), &btc.Configuration{}),
|
||||
},
|
||||
want: testTxPacked5,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "firo-special-coinbase-tx",
|
||||
args: args{
|
||||
tx: testTx6,
|
||||
height: 5300,
|
||||
blockTime: 1591762049,
|
||||
parser: NewFiroParser(GetChainParams("test"), &btc.Configuration{}),
|
||||
},
|
||||
want: testTxPacked6,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := tt.args.parser.PackTx(&tt.args.tx, tt.args.height, tt.args.blockTime)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("packTx() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !tt.wantErr {
|
||||
h := hex.EncodeToString(got)
|
||||
if !reflect.DeepEqual(h, tt.want) {
|
||||
t.Errorf("packTx() = %v, want %v", h, tt.want)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnpackTx(t *testing.T) {
|
||||
type args struct {
|
||||
packedTx string
|
||||
parser *FiroParser
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *bchain.Tx
|
||||
want1 uint32
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "firo-1",
|
||||
args: args{
|
||||
packedTx: testTxPacked1,
|
||||
parser: NewFiroParser(GetChainParams("main"), &btc.Configuration{}),
|
||||
},
|
||||
want: &testTx1,
|
||||
want1: 100002,
|
||||
wantErr: false,
|
||||
},
|
||||
// FIXME: work around handle zerocoin spend as coinbase
|
||||
// {
|
||||
// name: "firo-2",
|
||||
// args: args{
|
||||
// packedTx: testTxPacked2,
|
||||
// parser: NewFiroParser(GetChainParams("main"), &btc.Configuration{}),
|
||||
// },
|
||||
// want: &testTx2,
|
||||
// want1: 11002,
|
||||
// wantErr: true,
|
||||
// },
|
||||
{
|
||||
name: "firo-3",
|
||||
args: args{
|
||||
packedTx: testTxPacked3,
|
||||
parser: NewFiroParser(GetChainParams("main"), &btc.Configuration{}),
|
||||
},
|
||||
want: &testTx3,
|
||||
want1: 126202,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "firo-coinbase",
|
||||
args: args{
|
||||
packedTx: testTxPacked4,
|
||||
parser: NewFiroParser(GetChainParams("main"), &btc.Configuration{}),
|
||||
},
|
||||
want: &testTx4,
|
||||
want1: 100001,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "firo-special-tx",
|
||||
args: args{
|
||||
packedTx: testTxPacked5,
|
||||
parser: NewFiroParser(GetChainParams("test"), &btc.Configuration{}),
|
||||
},
|
||||
want: &testTx5,
|
||||
want1: 5268,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "firo-special-coinbase-tx",
|
||||
args: args{
|
||||
packedTx: testTxPacked6,
|
||||
parser: NewFiroParser(GetChainParams("test"), &btc.Configuration{}),
|
||||
},
|
||||
want: &testTx6,
|
||||
want1: 5300,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
b, _ := hex.DecodeString(tt.args.packedTx)
|
||||
got, got1, err := tt.args.parser.UnpackTx(b)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("unpackTx() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
|
||||
if !tt.wantErr {
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("unpackTx() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
if got1 != tt.want1 {
|
||||
t.Errorf("unpackTx() got1 = %v, want %v", got1, tt.want1)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseBlock(t *testing.T) {
|
||||
type args struct {
|
||||
rawBlock string
|
||||
parser *FiroParser
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *bchain.Block
|
||||
wantTxs int
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "normal-block",
|
||||
args: args{
|
||||
rawBlock: rawBlock1,
|
||||
parser: NewFiroParser(GetChainParams("main"), &btc.Configuration{}),
|
||||
},
|
||||
want: &bchain.Block{
|
||||
BlockHeader: bchain.BlockHeader{
|
||||
Size: 200286,
|
||||
Time: 1547120622,
|
||||
},
|
||||
},
|
||||
wantTxs: 3,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "spend-block",
|
||||
args: args{
|
||||
rawBlock: rawBlock2,
|
||||
parser: NewFiroParser(GetChainParams("main"), &btc.Configuration{}),
|
||||
},
|
||||
want: &bchain.Block{
|
||||
BlockHeader: bchain.BlockHeader{
|
||||
Size: 25298,
|
||||
Time: 1482107572,
|
||||
},
|
||||
},
|
||||
wantTxs: 4,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "special-tx-block",
|
||||
args: args{
|
||||
rawBlock: rawBlock3,
|
||||
parser: NewFiroParser(GetChainParams("test"), &btc.Configuration{}),
|
||||
},
|
||||
want: &bchain.Block{
|
||||
BlockHeader: bchain.BlockHeader{
|
||||
Size: 200062,
|
||||
Time: 1591752749,
|
||||
},
|
||||
},
|
||||
wantTxs: 3,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
b, _ := hex.DecodeString(tt.args.rawBlock)
|
||||
got, err := tt.args.parser.ParseBlock(b)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("parseBlock() error = %+v", err)
|
||||
}
|
||||
|
||||
if got != nil {
|
||||
|
||||
if !reflect.DeepEqual(got.BlockHeader, tt.want.BlockHeader) {
|
||||
t.Errorf("parseBlock() got = %v, want %v", got.BlockHeader, tt.want.BlockHeader)
|
||||
}
|
||||
|
||||
if len(got.Txs) != tt.wantTxs {
|
||||
t.Errorf("parseBlock() txs length got = %d, want %d", len(got.Txs), tt.wantTxs)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeTransaction(t *testing.T) {
|
||||
type args struct {
|
||||
enc wire.MessageEncoding
|
||||
rawTransaction string
|
||||
parser *FiroParser
|
||||
privacyType byte // 0 as non privacy
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want bchain.Tx
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "normal-transaction",
|
||||
args: args{
|
||||
enc: wire.WitnessEncoding,
|
||||
rawTransaction: rawTestTx1,
|
||||
parser: NewFiroParser(GetChainParams("main"), &btc.Configuration{}),
|
||||
},
|
||||
want: testTx1,
|
||||
},
|
||||
{
|
||||
name: "coinbase-firospend",
|
||||
args: args{
|
||||
enc: wire.WitnessEncoding,
|
||||
rawTransaction: rawTestTx2,
|
||||
parser: NewFiroParser(GetChainParams("main"), &btc.Configuration{}),
|
||||
privacyType: OpSigmaSpend,
|
||||
},
|
||||
want: testTx2,
|
||||
},
|
||||
{
|
||||
name: "normal-transaction-2",
|
||||
args: args{
|
||||
enc: wire.WitnessEncoding,
|
||||
rawTransaction: rawTestTx3,
|
||||
parser: NewFiroParser(GetChainParams("main"), &btc.Configuration{}),
|
||||
},
|
||||
want: testTx3,
|
||||
},
|
||||
{
|
||||
name: "coinbase-transaction",
|
||||
args: args{
|
||||
enc: wire.WitnessEncoding,
|
||||
rawTransaction: rawTestTx4,
|
||||
parser: NewFiroParser(GetChainParams("main"), &btc.Configuration{}),
|
||||
},
|
||||
want: testTx4,
|
||||
},
|
||||
{
|
||||
name: "quorum-commitment-transaction",
|
||||
args: args{
|
||||
enc: wire.BaseEncoding,
|
||||
rawTransaction: rawTestTx5,
|
||||
parser: NewFiroParser(GetChainParams("main"), &btc.Configuration{}),
|
||||
},
|
||||
want: testTx5,
|
||||
},
|
||||
{
|
||||
name: "quorum-commitment-transaction-witness",
|
||||
args: args{
|
||||
enc: wire.WitnessEncoding,
|
||||
rawTransaction: rawTestTx5,
|
||||
parser: NewFiroParser(GetChainParams("main"), &btc.Configuration{}),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "special-coinbase",
|
||||
args: args{
|
||||
enc: wire.WitnessEncoding,
|
||||
rawTransaction: rawTestTx6,
|
||||
parser: NewFiroParser(GetChainParams("test"), &btc.Configuration{}),
|
||||
},
|
||||
want: testTx6,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
b, _ := hex.DecodeString(tt.args.rawTransaction)
|
||||
r := bytes.NewReader(b)
|
||||
|
||||
msg := FiroMsgTx{}
|
||||
err := msg.FiroDecode(r, 0, tt.args.enc)
|
||||
|
||||
if tt.wantErr {
|
||||
if err == nil {
|
||||
t.Errorf("Want error")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
got := tt.args.parser.TxFromFiroMsgTx(&msg, true)
|
||||
if pErr := tt.args.parser.parseFiroTx(&got); pErr != nil {
|
||||
t.Fatal(pErr)
|
||||
}
|
||||
|
||||
if r.Len() != 0 {
|
||||
t.Errorf("Expected EOF but there are remaining %d bytes to read", r.Len())
|
||||
}
|
||||
|
||||
if len(got.Vin) != len(tt.want.Vin) {
|
||||
t.Errorf("Check vin size, got %v, want %v", len(got.Vin), len(tt.want.Vin))
|
||||
}
|
||||
|
||||
for i := 0; i != len(got.Vin); i++ {
|
||||
if !reflect.DeepEqual(got.Vin[i].Addresses, tt.want.Vin[i].Addresses) {
|
||||
t.Errorf("Check Addresses at input %d, got %v, want %v",
|
||||
i, got.Vin[i].Addresses, tt.want.Vin[i].Addresses)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(got.Vin[i].Coinbase, tt.want.Vin[i].Coinbase) {
|
||||
t.Errorf("Check Coinbase at input %d, got %v, want %v",
|
||||
i, got.Vin[i].Coinbase, tt.want.Vin[i].Coinbase)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(got.Vin[i].ScriptSig, tt.want.Vin[i].ScriptSig) {
|
||||
t.Errorf("Check ScriptSig at input %d, got %v, want %v",
|
||||
i, got.Vin[i].ScriptSig, tt.want.Vin[i].ScriptSig)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(got.Vin[i].Sequence, tt.want.Vin[i].Sequence) {
|
||||
t.Errorf("Check Sequence at input %d, got %v, want %v",
|
||||
i, got.Vin[i].Sequence, tt.want.Vin[i].Sequence)
|
||||
}
|
||||
|
||||
if tt.args.privacyType == 0 && !reflect.DeepEqual(got.Vin[i].Txid, tt.want.Vin[i].Txid) {
|
||||
t.Errorf("Check Txid at input %d, got %v, want %v",
|
||||
i, got.Vin[i].Txid, tt.want.Vin[i].Txid)
|
||||
}
|
||||
|
||||
if tt.args.privacyType == 0 && !reflect.DeepEqual(got.Vin[i].Vout, tt.want.Vin[i].Vout) {
|
||||
t.Errorf("Check Vout at input %d, got %v, want %v",
|
||||
i, got.Vin[i].Vout, tt.want.Vin[i].Vout)
|
||||
}
|
||||
}
|
||||
|
||||
if len(got.Vout) != len(tt.want.Vout) {
|
||||
t.Errorf("Check vout size, got %v, want %v", len(got.Vout), len(tt.want.Vout))
|
||||
}
|
||||
|
||||
for i := 0; i != len(got.Vout); i++ {
|
||||
if !reflect.DeepEqual(got.Vout[i].JsonValue, tt.want.Vout[i].JsonValue) {
|
||||
t.Errorf("Check JsonValue at output %d, got %v, want %v",
|
||||
i, got.Vout[i].JsonValue, tt.want.Vout[i].JsonValue)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(got.Vout[i].N, tt.want.Vout[i].N) {
|
||||
t.Errorf("Check N at output %d, got %v, want %v",
|
||||
i, got.Vout[i].N, tt.want.Vout[i].N)
|
||||
}
|
||||
|
||||
// empty addresses and null should be the same
|
||||
if !((len(got.Vout[i].ScriptPubKey.Addresses) == 0 && len(got.Vout[i].ScriptPubKey.Addresses) == len(tt.want.Vout[i].ScriptPubKey.Addresses)) ||
|
||||
reflect.DeepEqual(got.Vout[i].ScriptPubKey.Addresses, tt.want.Vout[i].ScriptPubKey.Addresses)) {
|
||||
t.Errorf("Check ScriptPubKey.Addresses at output %d, got %v, want %v",
|
||||
i, got.Vout[i].ScriptPubKey.Addresses, tt.want.Vout[i].ScriptPubKey.Addresses)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(got.Vout[i].ScriptPubKey.Hex, tt.want.Vout[i].ScriptPubKey.Hex) {
|
||||
t.Errorf("Check ScriptPubKey.Hex at output %d, got %v, want %v",
|
||||
i, got.Vout[i].ScriptPubKey.Hex, tt.want.Vout[i].ScriptPubKey.Hex)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(got.Vout[i].ValueSat, tt.want.Vout[i].ValueSat) {
|
||||
t.Errorf("Check ValueSat at output %d, got %v, want %v",
|
||||
i, got.Vout[i].ValueSat, tt.want.Vout[i].ValueSat)
|
||||
}
|
||||
}
|
||||
|
||||
if got.LockTime != tt.want.LockTime {
|
||||
t.Errorf("Check LockTime, got %v, want %v", got.LockTime, tt.want.LockTime)
|
||||
}
|
||||
|
||||
if got.Txid != tt.want.Txid {
|
||||
t.Errorf("Check TxId, got %v, want %v", got.Txid, tt.want.Txid)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,243 @@
|
|||
package firo
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/juju/errors"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
|
||||
)
|
||||
|
||||
type FiroRPC struct {
|
||||
*btc.BitcoinRPC
|
||||
}
|
||||
|
||||
func NewFiroRPC(config json.RawMessage, pushHandler func(bchain.NotificationType)) (bchain.BlockChain, error) {
|
||||
// init base implementation
|
||||
bc, err := btc.NewBitcoinRPC(config, pushHandler)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// init firo implementation
|
||||
zc := &FiroRPC{
|
||||
BitcoinRPC: bc.(*btc.BitcoinRPC),
|
||||
}
|
||||
|
||||
zc.ChainConfig.Parse = true
|
||||
zc.ChainConfig.SupportsEstimateFee = true
|
||||
zc.ChainConfig.SupportsEstimateSmartFee = false
|
||||
zc.ParseBlocks = true
|
||||
zc.RPCMarshaler = btc.JSONMarshalerV1{}
|
||||
|
||||
return zc, nil
|
||||
}
|
||||
|
||||
func (zc *FiroRPC) Initialize() error {
|
||||
ci, err := zc.GetChainInfo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
chainName := ci.Chain
|
||||
|
||||
params := GetChainParams(chainName)
|
||||
|
||||
// always create parser
|
||||
zc.Parser = NewFiroParser(params, zc.ChainConfig)
|
||||
|
||||
// parameters for getInfo request
|
||||
if params.Net == MainnetMagic {
|
||||
zc.Testnet = false
|
||||
zc.Network = "livenet"
|
||||
} else {
|
||||
zc.Testnet = true
|
||||
zc.Network = "testnet"
|
||||
}
|
||||
|
||||
glog.Info("rpc: block chain ", params.Name)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (zc *FiroRPC) GetBlock(hash string, height uint32) (*bchain.Block, error) {
|
||||
var err error
|
||||
|
||||
if hash == "" {
|
||||
hash, err = zc.GetBlockHash(height)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// optimization
|
||||
if height > 0 {
|
||||
return zc.GetBlockWithoutHeader(hash, height)
|
||||
}
|
||||
|
||||
header, err := zc.GetBlockHeader(hash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, err := zc.GetBlockRaw(hash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
block, err := zc.Parser.ParseBlock(data)
|
||||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "hash %v", hash)
|
||||
}
|
||||
|
||||
block.BlockHeader = *header
|
||||
|
||||
return block, nil
|
||||
}
|
||||
|
||||
func (zc *FiroRPC) GetBlockInfo(hash string) (*bchain.BlockInfo, error) {
|
||||
glog.V(1).Info("rpc: getblock (verbosity=true) ", hash)
|
||||
|
||||
res := btc.ResGetBlockInfo{}
|
||||
req := cmdGetBlock{Method: "getblock"}
|
||||
req.Params.BlockHash = hash
|
||||
req.Params.Verbosity = true
|
||||
err := zc.Call(&req, &res)
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "hash %v", hash)
|
||||
}
|
||||
if res.Error != nil {
|
||||
if btc.IsErrBlockNotFound(res.Error) {
|
||||
return nil, bchain.ErrBlockNotFound
|
||||
}
|
||||
return nil, errors.Annotatef(res.Error, "hash %v", hash)
|
||||
}
|
||||
return &res.Result, nil
|
||||
}
|
||||
|
||||
func (zc *FiroRPC) GetBlockWithoutHeader(hash string, height uint32) (*bchain.Block, error) {
|
||||
data, err := zc.GetBlockRaw(hash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
block, err := zc.Parser.ParseBlock(data)
|
||||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "%v %v", height, hash)
|
||||
}
|
||||
|
||||
block.BlockHeader.Hash = hash
|
||||
block.BlockHeader.Height = height
|
||||
|
||||
return block, nil
|
||||
}
|
||||
|
||||
func (zc *FiroRPC) GetBlockRaw(hash string) ([]byte, error) {
|
||||
glog.V(1).Info("rpc: getblock (verbosity=false) ", hash)
|
||||
|
||||
res := btc.ResGetBlockRaw{}
|
||||
req := cmdGetBlock{Method: "getblock"}
|
||||
req.Params.BlockHash = hash
|
||||
req.Params.Verbosity = false
|
||||
err := zc.Call(&req, &res)
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "hash %v", hash)
|
||||
}
|
||||
if res.Error != nil {
|
||||
if btc.IsErrBlockNotFound(res.Error) {
|
||||
return nil, bchain.ErrBlockNotFound
|
||||
}
|
||||
return nil, errors.Annotatef(res.Error, "hash %v", hash)
|
||||
}
|
||||
return hex.DecodeString(res.Result)
|
||||
}
|
||||
|
||||
func (zc *FiroRPC) GetTransactionForMempool(txid string) (*bchain.Tx, error) {
|
||||
glog.V(1).Info("rpc: getrawtransaction nonverbose ", txid)
|
||||
|
||||
res := btc.ResGetRawTransactionNonverbose{}
|
||||
req := cmdGetRawTransaction{Method: "getrawtransaction"}
|
||||
req.Params.Txid = txid
|
||||
req.Params.Verbose = 0
|
||||
err := zc.Call(&req, &res)
|
||||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "txid %v", txid)
|
||||
}
|
||||
if res.Error != nil {
|
||||
if btc.IsMissingTx(res.Error) {
|
||||
return nil, bchain.ErrTxNotFound
|
||||
}
|
||||
return nil, errors.Annotatef(res.Error, "txid %v", txid)
|
||||
}
|
||||
data, err := hex.DecodeString(res.Result)
|
||||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "txid %v", txid)
|
||||
}
|
||||
tx, err := zc.Parser.ParseTx(data)
|
||||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "txid %v", txid)
|
||||
}
|
||||
return tx, nil
|
||||
}
|
||||
|
||||
func (zc *FiroRPC) GetTransaction(txid string) (*bchain.Tx, error) {
|
||||
r, err := zc.getRawTransaction(txid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tx, err := zc.Parser.ParseTxFromJson(r)
|
||||
tx.CoinSpecificData = r
|
||||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "txid %v", txid)
|
||||
}
|
||||
|
||||
return tx, nil
|
||||
}
|
||||
|
||||
func (zc *FiroRPC) GetTransactionSpecific(tx *bchain.Tx) (json.RawMessage, error) {
|
||||
if csd, ok := tx.CoinSpecificData.(json.RawMessage); ok {
|
||||
return csd, nil
|
||||
}
|
||||
return zc.getRawTransaction(tx.Txid)
|
||||
}
|
||||
|
||||
func (zc *FiroRPC) getRawTransaction(txid string) (json.RawMessage, error) {
|
||||
glog.V(1).Info("rpc: getrawtransaction ", txid)
|
||||
|
||||
res := btc.ResGetRawTransaction{}
|
||||
req := cmdGetRawTransaction{Method: "getrawtransaction"}
|
||||
req.Params.Txid = txid
|
||||
req.Params.Verbose = 1
|
||||
err := zc.Call(&req, &res)
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "txid %v", txid)
|
||||
}
|
||||
if res.Error != nil {
|
||||
if btc.IsMissingTx(res.Error) {
|
||||
return nil, bchain.ErrTxNotFound
|
||||
}
|
||||
return nil, errors.Annotatef(res.Error, "txid %v", txid)
|
||||
}
|
||||
return res.Result, nil
|
||||
}
|
||||
|
||||
type cmdGetBlock struct {
|
||||
Method string `json:"method"`
|
||||
Params struct {
|
||||
BlockHash string `json:"blockhash"`
|
||||
Verbosity bool `json:"verbosity"`
|
||||
} `json:"params"`
|
||||
}
|
||||
|
||||
type cmdGetRawTransaction struct {
|
||||
Method string `json:"method"`
|
||||
Params struct {
|
||||
Txid string `json:"txid"`
|
||||
Verbose int `json:"verbose"`
|
||||
} `json:"params"`
|
||||
}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,79 @@
|
|||
package flo
|
||||
|
||||
import (
|
||||
"github.com/martinboehm/btcd/wire"
|
||||
"github.com/martinboehm/btcutil/chaincfg"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
|
||||
)
|
||||
|
||||
// magic numbers
|
||||
const (
|
||||
MainnetMagic wire.BitcoinNet = 0xf1a5c0fd
|
||||
TestnetMagic wire.BitcoinNet = 0xf25ac0fd
|
||||
RegtestMagic wire.BitcoinNet = 0xdab5bffa
|
||||
)
|
||||
|
||||
// chain parameters
|
||||
var (
|
||||
MainNetParams chaincfg.Params
|
||||
TestNetParams chaincfg.Params
|
||||
)
|
||||
|
||||
func init() {
|
||||
MainNetParams = chaincfg.MainNetParams
|
||||
MainNetParams.Net = MainnetMagic
|
||||
MainNetParams.PubKeyHashAddrID = []byte{35}
|
||||
MainNetParams.ScriptHashAddrID = []byte{94}
|
||||
MainNetParams.Bech32HRPSegwit = "flo"
|
||||
|
||||
TestNetParams = chaincfg.TestNet3Params
|
||||
TestNetParams.Net = TestnetMagic
|
||||
TestNetParams.PubKeyHashAddrID = []byte{115}
|
||||
TestNetParams.ScriptHashAddrID = []byte{198}
|
||||
TestNetParams.Bech32HRPSegwit = "tflo"
|
||||
}
|
||||
|
||||
// FloParser handle
|
||||
type FloParser struct {
|
||||
*btc.BitcoinParser
|
||||
baseparser *bchain.BaseParser
|
||||
}
|
||||
|
||||
// NewFloParser returns new FloParser instance
|
||||
func NewFloParser(params *chaincfg.Params, c *btc.Configuration) *FloParser {
|
||||
return &FloParser{
|
||||
BitcoinParser: btc.NewBitcoinParser(params, c),
|
||||
baseparser: &bchain.BaseParser{},
|
||||
}
|
||||
}
|
||||
|
||||
// GetChainParams contains network parameters for the main Flo network,
|
||||
// and the test Flo network
|
||||
func GetChainParams(chain string) *chaincfg.Params {
|
||||
if !chaincfg.IsRegistered(&MainNetParams) {
|
||||
err := chaincfg.Register(&MainNetParams)
|
||||
if err == nil {
|
||||
err = chaincfg.Register(&TestNetParams)
|
||||
}
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
switch chain {
|
||||
case "test":
|
||||
return &TestNetParams
|
||||
default:
|
||||
return &MainNetParams
|
||||
}
|
||||
}
|
||||
|
||||
// PackTx packs transaction to byte array using protobuf
|
||||
func (p *FloParser) PackTx(tx *bchain.Tx, height uint32, blockTime int64) ([]byte, error) {
|
||||
return p.baseparser.PackTx(tx, height, blockTime)
|
||||
}
|
||||
|
||||
// UnpackTx unpacks transaction from protobuf byte array
|
||||
func (p *FloParser) UnpackTx(buf []byte) (*bchain.Tx, uint32, error) {
|
||||
return p.baseparser.UnpackTx(buf)
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
// +build unittest
|
||||
|
||||
package flo
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/martinboehm/btcutil/chaincfg"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
c := m.Run()
|
||||
chaincfg.ResetParams()
|
||||
os.Exit(c)
|
||||
}
|
||||
|
||||
func Test_GetAddrDescFromAddress_Testnet(t *testing.T) {
|
||||
type args struct {
|
||||
address string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "P2PKH1",
|
||||
args: args{address: "oX8YUMFwRhQdqGvuVGpLJz8BcYmM87e9ee"},
|
||||
want: "76a9149c708c27ce34ead174a7a9b4f47afafb3d906d0d88ac",
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
parser := NewFloParser(GetChainParams("test"), &btc.Configuration{})
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := parser.GetAddrDescFromAddress(tt.args.address)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("GetAddrDescFromAddress() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
h := hex.EncodeToString(got)
|
||||
if !reflect.DeepEqual(h, tt.want) {
|
||||
t.Errorf("GetAddrDescFromAddress() = %v, want %v", h, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_GetAddrDescFromAddress_Mainnet(t *testing.T) {
|
||||
type args struct {
|
||||
address string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "P2PKH1",
|
||||
args: args{address: "FAPiw7EFMYmYK1mUuQQekyLsmimUBQT9zd"},
|
||||
want: "76a914320b6c674c8bc353942046981ff7ac73f5ceae4688ac",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "P2PKH2",
|
||||
args: args{address: "FMg9M7GPuUAGKvhWmgWjoqYtMqmckD4tRF"},
|
||||
want: "76a914adcfd792793fb204ec4e8cf2d0215fea6963b97388ac",
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
parser := NewFloParser(GetChainParams("main"), &btc.Configuration{})
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := parser.GetAddrDescFromAddress(tt.args.address)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("GetAddrDescFromAddress() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
h := hex.EncodeToString(got)
|
||||
if !reflect.DeepEqual(h, tt.want) {
|
||||
t.Errorf("GetAddrDescFromAddress() = %v, want %v", h, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
package flo
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/juju/errors"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
|
||||
)
|
||||
|
||||
// FloRPC is an interface to JSON-RPC bitcoind service.
|
||||
type FloRPC struct {
|
||||
*btc.BitcoinRPC
|
||||
}
|
||||
|
||||
// NewFloRPC returns new FloRPC instance.
|
||||
func NewFloRPC(config json.RawMessage, pushHandler func(bchain.NotificationType)) (bchain.BlockChain, error) {
|
||||
b, err := btc.NewBitcoinRPC(config, pushHandler)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s := &FloRPC{
|
||||
b.(*btc.BitcoinRPC),
|
||||
}
|
||||
s.RPCMarshaler = btc.JSONMarshalerV2{}
|
||||
s.ChainConfig.SupportsEstimateFee = false
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// Initialize initializes FloRPC instance.
|
||||
func (f *FloRPC) Initialize() error {
|
||||
ci, err := f.GetChainInfo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
chainName := ci.Chain
|
||||
|
||||
glog.Info("Chain name ", chainName)
|
||||
params := GetChainParams(chainName)
|
||||
|
||||
// always create parser
|
||||
f.Parser = NewFloParser(params, f.ChainConfig)
|
||||
|
||||
// parameters for getInfo request
|
||||
if params.Net == MainnetMagic {
|
||||
f.Testnet = false
|
||||
f.Network = "livenet"
|
||||
} else {
|
||||
f.Testnet = true
|
||||
f.Network = "testnet"
|
||||
}
|
||||
|
||||
glog.Info("rpc: block chain ", params.Name)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetBlock returns block with given hash.
|
||||
func (f *FloRPC) GetBlock(hash string, height uint32) (*bchain.Block, error) {
|
||||
var err error
|
||||
if hash == "" {
|
||||
hash, err = f.GetBlockHash(height)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if !f.ParseBlocks {
|
||||
return f.GetBlockFull(hash)
|
||||
}
|
||||
// optimization
|
||||
if height > 0 {
|
||||
return f.GetBlockWithoutHeader(hash, height)
|
||||
}
|
||||
header, err := f.GetBlockHeader(hash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data, err := f.GetBlockRaw(hash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
block, err := f.Parser.ParseBlock(data)
|
||||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "hash %v", hash)
|
||||
}
|
||||
block.BlockHeader = *header
|
||||
return block, nil
|
||||
}
|
||||
|
||||
// GetBlockFull returns block with given hash
|
||||
func (f *FloRPC) GetBlockFull(hash string) (*bchain.Block, error) {
|
||||
glog.V(1).Info("rpc: getblock (verbosity=2) ", hash)
|
||||
|
||||
res := btc.ResGetBlockFull{}
|
||||
req := btc.CmdGetBlock{Method: "getblock"}
|
||||
req.Params.BlockHash = hash
|
||||
req.Params.Verbosity = 2
|
||||
err := f.Call(&req, &res)
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "hash %v", hash)
|
||||
}
|
||||
if res.Error != nil {
|
||||
if btc.IsErrBlockNotFound(res.Error) {
|
||||
return nil, bchain.ErrBlockNotFound
|
||||
}
|
||||
return nil, errors.Annotatef(res.Error, "hash %v", hash)
|
||||
}
|
||||
|
||||
for i := range res.Result.Txs {
|
||||
tx := &res.Result.Txs[i]
|
||||
for j := range tx.Vout {
|
||||
vout := &tx.Vout[j]
|
||||
// convert vout.JsonValue to big.Int and clear it, it is only temporary value used for unmarshal
|
||||
vout.ValueSat, err = f.Parser.AmountToBigInt(vout.JsonValue)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vout.JsonValue = ""
|
||||
}
|
||||
}
|
||||
|
||||
return &res.Result, nil
|
||||
}
|
||||
|
||||
// GetTransactionForMempool returns a transaction by the transaction ID.
|
||||
// It could be optimized for mempool, i.e. without block time and confirmations
|
||||
func (f *FloRPC) GetTransactionForMempool(txid string) (*bchain.Tx, error) {
|
||||
return f.GetTransaction(txid)
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
package fujicoin
|
||||
|
||||
import (
|
||||
"github.com/martinboehm/btcd/wire"
|
||||
"github.com/martinboehm/btcutil/chaincfg"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
|
||||
)
|
||||
|
||||
const (
|
||||
// MainnetMagic is mainnet network constant
|
||||
MainnetMagic wire.BitcoinNet = 0x696a7566
|
||||
// TestnetMagic is testnet network constant
|
||||
TestnetMagic wire.BitcoinNet = 0x66756a69
|
||||
// RegtestMagic is regtest network constant
|
||||
RegtestMagic wire.BitcoinNet = 0x66756a69
|
||||
)
|
||||
|
||||
var (
|
||||
// MainNetParams are parser parameters for mainnet
|
||||
MainNetParams chaincfg.Params
|
||||
// TestNetParams are parser parameters for testnet
|
||||
TestNetParams chaincfg.Params
|
||||
)
|
||||
|
||||
func init() {
|
||||
MainNetParams = chaincfg.MainNetParams
|
||||
MainNetParams.Net = MainnetMagic
|
||||
MainNetParams.PubKeyHashAddrID = []byte{36}
|
||||
MainNetParams.ScriptHashAddrID = []byte{16}
|
||||
MainNetParams.Bech32HRPSegwit = "fc"
|
||||
|
||||
TestNetParams = chaincfg.TestNet3Params
|
||||
TestNetParams.Net = TestnetMagic
|
||||
TestNetParams.PubKeyHashAddrID = []byte{74}
|
||||
TestNetParams.ScriptHashAddrID = []byte{196}
|
||||
TestNetParams.Bech32HRPSegwit = "tfc"
|
||||
}
|
||||
|
||||
// FujicoinParser handle
|
||||
type FujicoinParser struct {
|
||||
*btc.BitcoinParser
|
||||
}
|
||||
|
||||
// NewFujicoinParser returns new FujicoinParser instance
|
||||
func NewFujicoinParser(params *chaincfg.Params, c *btc.Configuration) *FujicoinParser {
|
||||
return &FujicoinParser{BitcoinParser: btc.NewBitcoinParser(params, c)}
|
||||
}
|
||||
|
||||
// GetChainParams contains network parameters for the main Fujicoin network,
|
||||
// and the test Fujicoin network
|
||||
func GetChainParams(chain string) *chaincfg.Params {
|
||||
if !chaincfg.IsRegistered(&MainNetParams) {
|
||||
err := chaincfg.Register(&MainNetParams)
|
||||
if err == nil {
|
||||
err = chaincfg.Register(&TestNetParams)
|
||||
}
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
switch chain {
|
||||
case "test":
|
||||
return &TestNetParams
|
||||
default:
|
||||
return &MainNetParams
|
||||
}
|
||||
}
|
|
@ -0,0 +1,208 @@
|
|||
// +build unittest
|
||||
|
||||
package fujicoin
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"math/big"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/martinboehm/btcutil/chaincfg"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
c := m.Run()
|
||||
chaincfg.ResetParams()
|
||||
os.Exit(c)
|
||||
}
|
||||
|
||||
func Test_GetAddrDescFromAddress_Mainnet(t *testing.T) {
|
||||
type args struct {
|
||||
address string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "pubkeyhash1",
|
||||
args: args{address: "FgKfZA7QB5xHGTtpPqTF1RQRyHCCTr5NXg"},
|
||||
want: "76a9147a5ab68256ac6bd8dc6d78b6b94af69e414dffd588ac",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "pubkeyhash2",
|
||||
args: args{address: "FsjXKBTE9nQinJnc1r2iLEb8SX7ok1eC7V"},
|
||||
want: "76a914f787334ddbda3e65fe7d579f03842b5607cf0db888ac",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "scripthash1",
|
||||
args: args{address: "7ojX5QT534BBDPwNoYXjx1MdbHWS2ueMk3"},
|
||||
want: "a914e9ec22a509631fa3c8c09ebb1b41f59c81ec669f87",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "scripthash2",
|
||||
args: args{address: "7XJah1ejjogvQS7UkfiH577R1yzkVzNBn4"},
|
||||
want: "a91435b2bd909a0b9b6ca35fef9276675f568f42a4e387",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "witness_v0_keyhash",
|
||||
args: args{address: "fc1q2ze0qtna2a42qqam3k9xsu3xamrrcp8rsczdve"},
|
||||
want: "001450b2f02e7d576aa003bb8d8a687226eec63c04e3",
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
parser := NewFujicoinParser(GetChainParams("main"), &btc.Configuration{})
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := parser.GetAddrDescFromAddress(tt.args.address)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("GetAddrDescFromAddress() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
h := hex.EncodeToString(got)
|
||||
if !reflect.DeepEqual(h, tt.want) {
|
||||
t.Errorf("GetAddrDescFromAddress() = %v, want %v", h, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
testTx1 bchain.Tx
|
||||
|
||||
testTxPacked1 = "0023f3648bc2d3dd5c0100000001ec5666d4e842926c6e872fd7f12a87183b298464716dec1b03fc956777e06a70010000006a473044022062949ffe184af6bdc8d77e07c2caf1bd4e36d977bc27e24ab6d24c9e735d76b802201f42950f6fa005126f1fbd5cb9d6f0838d94adb70a05517b3eebd144a0ec2fd6012102534f95a0a13bbc3e261f966edd057a1dc9819a4e6bb21d81a633d5500d184a7bfeffffff0200e876481700000017a9146b8a69f8b43bc06b886c0668c93197be5152e8598740494d552e0000001976a914df783f980b38d7a64cee23202af95609b547fde888ac62f32300"
|
||||
)
|
||||
|
||||
func init() {
|
||||
testTx1 = bchain.Tx{
|
||||
Hex: "0100000001ec5666d4e842926c6e872fd7f12a87183b298464716dec1b03fc956777e06a70010000006a473044022062949ffe184af6bdc8d77e07c2caf1bd4e36d977bc27e24ab6d24c9e735d76b802201f42950f6fa005126f1fbd5cb9d6f0838d94adb70a05517b3eebd144a0ec2fd6012102534f95a0a13bbc3e261f966edd057a1dc9819a4e6bb21d81a633d5500d184a7bfeffffff0200e876481700000017a9146b8a69f8b43bc06b886c0668c93197be5152e8598740494d552e0000001976a914df783f980b38d7a64cee23202af95609b547fde888ac62f32300",
|
||||
Blocktime: 1546286958,
|
||||
Txid: "7367db1a3073146f0b6060ce4a6dbb96b67b7aadcbaa450bff8d7dedda52ec13",
|
||||
LockTime: 2356066,
|
||||
Version: 1,
|
||||
Vin: []bchain.Vin{
|
||||
{
|
||||
ScriptSig: bchain.ScriptSig{
|
||||
Hex: "473044022062949ffe184af6bdc8d77e07c2caf1bd4e36d977bc27e24ab6d24c9e735d76b802201f42950f6fa005126f1fbd5cb9d6f0838d94adb70a05517b3eebd144a0ec2fd6012102534f95a0a13bbc3e261f966edd057a1dc9819a4e6bb21d81a633d5500d184a7b",
|
||||
},
|
||||
Txid: "706ae0776795fc031bec6d716484293b18872af1d72f876e6c9242e8d46656ec",
|
||||
Vout: 1,
|
||||
Sequence: 4294967294,
|
||||
},
|
||||
},
|
||||
Vout: []bchain.Vout{
|
||||
{
|
||||
ValueSat: *big.NewInt(100000000000),
|
||||
N: 0,
|
||||
ScriptPubKey: bchain.ScriptPubKey{
|
||||
Hex: "a9146b8a69f8b43bc06b886c0668c93197be5152e85987",
|
||||
Addresses: []string{
|
||||
"7cDGsS8Dum191ZMD19UaLzstMtf29YMXBL",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ValueSat: *big.NewInt(198999624000),
|
||||
N: 1,
|
||||
ScriptPubKey: bchain.ScriptPubKey{
|
||||
Hex: "76a914df783f980b38d7a64cee23202af95609b547fde888ac",
|
||||
Addresses: []string{
|
||||
"FqYKBhEF4zKkDGkVyufGVGajhttuD4Usc2",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func Test_PackTx(t *testing.T) {
|
||||
type args struct {
|
||||
tx bchain.Tx
|
||||
height uint32
|
||||
blockTime int64
|
||||
parser *FujicoinParser
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "fujicoin-1",
|
||||
args: args{
|
||||
tx: testTx1,
|
||||
height: 2356068,
|
||||
blockTime: 1546286958,
|
||||
parser: NewFujicoinParser(GetChainParams("main"), &btc.Configuration{}),
|
||||
},
|
||||
want: testTxPacked1,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := tt.args.parser.PackTx(&tt.args.tx, tt.args.height, tt.args.blockTime)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("packTx() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
h := hex.EncodeToString(got)
|
||||
if !reflect.DeepEqual(h, tt.want) {
|
||||
t.Errorf("packTx() = %v, want %v", h, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_UnpackTx(t *testing.T) {
|
||||
type args struct {
|
||||
packedTx string
|
||||
parser *FujicoinParser
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *bchain.Tx
|
||||
want1 uint32
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "fujicoin-1",
|
||||
args: args{
|
||||
packedTx: testTxPacked1,
|
||||
parser: NewFujicoinParser(GetChainParams("main"), &btc.Configuration{}),
|
||||
},
|
||||
want: &testTx1,
|
||||
want1: 2356068,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
b, _ := hex.DecodeString(tt.args.packedTx)
|
||||
got, got1, err := tt.args.parser.UnpackTx(b)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("unpackTx() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("unpackTx() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
if got1 != tt.want1 {
|
||||
t.Errorf("unpackTx() got1 = %v, want %v", got1, tt.want1)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
package fujicoin
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
|
||||
)
|
||||
|
||||
// FujicoinRPC is an interface to JSON-RPC bitcoind service.
|
||||
type FujicoinRPC struct {
|
||||
*btc.BitcoinRPC
|
||||
}
|
||||
|
||||
// NewFujicoinRPC returns new FujicoinRPC instance.
|
||||
func NewFujicoinRPC(config json.RawMessage, pushHandler func(bchain.NotificationType)) (bchain.BlockChain, error) {
|
||||
b, err := btc.NewBitcoinRPC(config, pushHandler)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s := &FujicoinRPC{
|
||||
b.(*btc.BitcoinRPC),
|
||||
}
|
||||
s.RPCMarshaler = btc.JSONMarshalerV2{}
|
||||
s.ChainConfig.SupportsEstimateFee = false
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// Initialize initializes FujicoinRPC instance.
|
||||
func (b *FujicoinRPC) Initialize() error {
|
||||
ci, err := b.GetChainInfo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
chainName := ci.Chain
|
||||
|
||||
glog.Info("Chain name ", chainName)
|
||||
params := GetChainParams(chainName)
|
||||
|
||||
// always create parser
|
||||
b.Parser = NewFujicoinParser(params, b.ChainConfig)
|
||||
|
||||
// parameters for getInfo request
|
||||
if params.Net == MainnetMagic {
|
||||
b.Testnet = false
|
||||
b.Network = "livenet"
|
||||
} else {
|
||||
b.Testnet = true
|
||||
b.Network = "testnet"
|
||||
}
|
||||
|
||||
glog.Info("rpc: block chain ", params.Name)
|
||||
|
||||
return nil
|
||||
}
|
|
@ -1,18 +1,19 @@
|
|||
package gamecredits
|
||||
|
||||
import (
|
||||
"blockbook/bchain/coins/btc"
|
||||
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/jakm/btcutil/chaincfg"
|
||||
"github.com/martinboehm/btcd/wire"
|
||||
"github.com/martinboehm/btcutil/chaincfg"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
|
||||
)
|
||||
|
||||
// magic numbers
|
||||
const (
|
||||
MainnetMagic wire.BitcoinNet = 0xdbb6c0fb
|
||||
TestnetMagic wire.BitcoinNet = 0x0709110b
|
||||
RegtestMagic wire.BitcoinNet = 0xdab5bffa
|
||||
)
|
||||
|
||||
// chain parameters
|
||||
var (
|
||||
MainNetParams chaincfg.Params
|
||||
TestNetParams chaincfg.Params
|
||||
|
|
|
@ -3,15 +3,15 @@
|
|||
package gamecredits
|
||||
|
||||
import (
|
||||
"blockbook/bchain"
|
||||
"blockbook/bchain/coins/btc"
|
||||
"encoding/hex"
|
||||
"math/big"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/jakm/btcutil/chaincfg"
|
||||
"github.com/martinboehm/btcutil/chaincfg"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
package gamecredits
|
||||
|
||||
import (
|
||||
"blockbook/bchain"
|
||||
"blockbook/bchain/coins/btc"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
|
||||
)
|
||||
|
||||
// GameCreditsRPC is an interface to JSON-RPC bitcoind service.
|
||||
|
@ -31,10 +31,11 @@ func NewGameCreditsRPC(config json.RawMessage, pushHandler func(bchain.Notificat
|
|||
|
||||
// Initialize initializes GameCreditsRPC instance.
|
||||
func (b *GameCreditsRPC) Initialize() error {
|
||||
chainName, err := b.GetChainInfoAndInitializeMempool(b)
|
||||
ci, err := b.GetChainInfo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
chainName := ci.Chain
|
||||
|
||||
glog.Info("Chain name ", chainName)
|
||||
params := GetChainParams(chainName)
|
||||
|
|
|
@ -1,19 +1,20 @@
|
|||
package grs
|
||||
|
||||
import (
|
||||
"blockbook/bchain"
|
||||
"blockbook/bchain/coins/btc"
|
||||
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/jakm/btcutil/base58"
|
||||
"github.com/jakm/btcutil/chaincfg"
|
||||
"github.com/martinboehm/btcd/wire"
|
||||
"github.com/martinboehm/btcutil/base58"
|
||||
"github.com/martinboehm/btcutil/chaincfg"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
|
||||
)
|
||||
|
||||
// magic numbers
|
||||
const (
|
||||
MainnetMagic wire.BitcoinNet = 0xd4b4bef9
|
||||
TestnetMagic wire.BitcoinNet = 0x0709110b
|
||||
)
|
||||
|
||||
// chain parameters
|
||||
var (
|
||||
MainNetParams chaincfg.Params
|
||||
TestNetParams chaincfg.Params
|
||||
|
|
|
@ -3,8 +3,6 @@
|
|||
package grs
|
||||
|
||||
import (
|
||||
"blockbook/bchain"
|
||||
"blockbook/bchain/coins/btc"
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"math/big"
|
||||
|
@ -12,14 +10,16 @@ import (
|
|||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/jakm/btcutil/chaincfg"
|
||||
"github.com/martinboehm/btcutil/chaincfg"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
|
||||
)
|
||||
|
||||
var (
|
||||
testTx1, testTx2 bchain.Tx
|
||||
|
||||
testTxPacked1 = "0a20f56521b17b828897f72b30dd21b0192fd942342e89acbb06abf1d446282c30f512bf0101000000014a9d1fdba915e0907ab02f04f88898863112a2b4fdcf872c7414588c47c874cb000000006a47304402201fb96d20d0778f54520ab59afe70d5fb20e500ecc9f02281cf57934e8029e8e10220383d5a3e80f2e1eb92765b6da0f23d454aecbd8236f083d483e9a7430236876101210331693756f749180aeed0a65a0fab0625a2250bd9abca502282a4cf0723152e67ffffffff01a0330300000000001976a914fe40329c95c5598ac60752a5310b320cb52d18e688ac0000000018ffff87da05200028a6f383013298010a001220cb74c8478c5814742c87cffdb4a21231869888f8042fb07a90e015a9db1f9d4a1800226a47304402201fb96d20d0778f54520ab59afe70d5fb20e500ecc9f02281cf57934e8029e8e10220383d5a3e80f2e1eb92765b6da0f23d454aecbd8236f083d483e9a7430236876101210331693756f749180aeed0a65a0fab0625a2250bd9abca502282a4cf0723152e6728ffffffff0f3a460a030333a010001a1976a914fe40329c95c5598ac60752a5310b320cb52d18e688ac222246744d347a416e39615659674867786d616d5742675750795a7362365268766b4139"
|
||||
testTxPacked2 = "0a209b5c4859a8a31e69788cb4402812bb28f14ad71cbd8c60b09903478bc56f79a312e00101000000000101d1613f483f2086d076c82fe34674385a86beb08f052d5405fe1aed397f852f4f0000000000feffffff02404b4c000000000017a9147a55d61848e77ca266e79a39bfc85c580a6426c987a8386f0000000000160014cc8067093f6f843d6d3e22004a4290cd0c0f336b02483045022100ea8780bc1e60e14e945a80654a41748bbf1aa7d6f2e40a88d91dfc2de1f34bd10220181a474a3420444bd188501d8d270736e1e9fe379da9970de992ff445b0972e3012103adc58245cf28406af0ef5cc24b8afba7f1be6c72f279b642d85c48798685f862d9ed090018caa384da0520d9db2728dadb27322c0a0012204f2f857f39ed1afe05542d058fb0be865a387446e32fc876d086203f483f61d1180028feffffff0f3a450a034c4b4010001a17a9147a55d61848e77ca266e79a39bfc85c580a6426c9872223324e345135466855323439374272794666556762716b414a453837614b4476335633653a4d0a036f38a810011a160014cc8067093f6f843d6d3e22004a4290cd0c0f336b222c746772733171656a7178777a666c64377a72366d663779677179357335736535787137766d74396c6b643537"
|
||||
testTxPacked1 = "0a20f56521b17b828897f72b30dd21b0192fd942342e89acbb06abf1d446282c30f512bf0101000000014a9d1fdba915e0907ab02f04f88898863112a2b4fdcf872c7414588c47c874cb000000006a47304402201fb96d20d0778f54520ab59afe70d5fb20e500ecc9f02281cf57934e8029e8e10220383d5a3e80f2e1eb92765b6da0f23d454aecbd8236f083d483e9a7430236876101210331693756f749180aeed0a65a0fab0625a2250bd9abca502282a4cf0723152e67ffffffff01a0330300000000001976a914fe40329c95c5598ac60752a5310b320cb52d18e688ac0000000018ffff87da05200028a6f383013298010a001220cb74c8478c5814742c87cffdb4a21231869888f8042fb07a90e015a9db1f9d4a1800226a47304402201fb96d20d0778f54520ab59afe70d5fb20e500ecc9f02281cf57934e8029e8e10220383d5a3e80f2e1eb92765b6da0f23d454aecbd8236f083d483e9a7430236876101210331693756f749180aeed0a65a0fab0625a2250bd9abca502282a4cf0723152e6728ffffffff0f3a460a030333a010001a1976a914fe40329c95c5598ac60752a5310b320cb52d18e688ac222246744d347a416e39615659674867786d616d5742675750795a7362365268766b41394000"
|
||||
testTxPacked2 = "0a209b5c4859a8a31e69788cb4402812bb28f14ad71cbd8c60b09903478bc56f79a312e00101000000000101d1613f483f2086d076c82fe34674385a86beb08f052d5405fe1aed397f852f4f0000000000feffffff02404b4c000000000017a9147a55d61848e77ca266e79a39bfc85c580a6426c987a8386f0000000000160014cc8067093f6f843d6d3e22004a4290cd0c0f336b02483045022100ea8780bc1e60e14e945a80654a41748bbf1aa7d6f2e40a88d91dfc2de1f34bd10220181a474a3420444bd188501d8d270736e1e9fe379da9970de992ff445b0972e3012103adc58245cf28406af0ef5cc24b8afba7f1be6c72f279b642d85c48798685f862d9ed090018caa384da0520d9db2728dadb27322c0a0012204f2f857f39ed1afe05542d058fb0be865a387446e32fc876d086203f483f61d1180028feffffff0f3a450a034c4b4010001a17a9147a55d61848e77ca266e79a39bfc85c580a6426c9872223324e345135466855323439374272794666556762716b414a453837614b4476335633653a4d0a036f38a810011a160014cc8067093f6f843d6d3e22004a4290cd0c0f336b222c746772733171656a7178777a666c64377a72366d663779677179357335736535787137766d74396c6b6435374000"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
|
|
@ -1,18 +1,20 @@
|
|||
package grs
|
||||
|
||||
import (
|
||||
"blockbook/bchain"
|
||||
"blockbook/bchain/coins/btc"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/juju/errors"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
|
||||
)
|
||||
|
||||
// GroestlcoinRPC is an interface to JSON-RPC service
|
||||
type GroestlcoinRPC struct {
|
||||
*btc.BitcoinRPC
|
||||
}
|
||||
|
||||
// NewGroestlcoinRPC returns new GroestlcoinRPC instance
|
||||
func NewGroestlcoinRPC(config json.RawMessage, pushHandler func(bchain.NotificationType)) (bchain.BlockChain, error) {
|
||||
b, err := btc.NewBitcoinRPC(config, pushHandler)
|
||||
if err != nil {
|
||||
|
@ -27,10 +29,11 @@ func NewGroestlcoinRPC(config json.RawMessage, pushHandler func(bchain.Notificat
|
|||
|
||||
// Initialize initializes GroestlcoinRPC instance.
|
||||
func (g *GroestlcoinRPC) Initialize() error {
|
||||
chainName, err := g.GetChainInfoAndInitializeMempool(g)
|
||||
ci, err := g.GetChainInfo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
chainName := ci.Chain
|
||||
|
||||
params := GetChainParams(chainName)
|
||||
|
||||
|
@ -79,7 +82,7 @@ func (g *GroestlcoinRPC) GetBlock(hash string, height uint32) (*bchain.Block, er
|
|||
for _, txid := range res.Result.Txids {
|
||||
tx, err := g.GetTransaction(txid)
|
||||
if err != nil {
|
||||
if isInvalidTx(err) {
|
||||
if err == bchain.ErrTxNotFound {
|
||||
glog.Errorf("rpc: getblock: skipping transanction in block %s due error: %s", hash, err)
|
||||
continue
|
||||
}
|
||||
|
@ -94,20 +97,6 @@ func (g *GroestlcoinRPC) GetBlock(hash string, height uint32) (*bchain.Block, er
|
|||
return block, nil
|
||||
}
|
||||
|
||||
func isInvalidTx(err error) bool {
|
||||
switch e1 := err.(type) {
|
||||
case *errors.Err:
|
||||
switch e2 := e1.Cause().(type) {
|
||||
case *bchain.RPCError:
|
||||
if e2.Code == -5 { // "No information available about transaction"
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// GetTransactionForMempool returns a transaction by the transaction ID.
|
||||
// It could be optimized for mempool, i.e. without block time and confirmations
|
||||
func (g *GroestlcoinRPC) GetTransactionForMempool(txid string) (*bchain.Tx, error) {
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
package koto
|
||||
|
||||
import (
|
||||
"github.com/martinboehm/btcd/wire"
|
||||
"github.com/martinboehm/btcutil/chaincfg"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
|
||||
)
|
||||
|
||||
// magic numbers
|
||||
const (
|
||||
MainnetMagic wire.BitcoinNet = 0x6f746f4b
|
||||
TestnetMagic wire.BitcoinNet = 0x6f6b6f54
|
||||
RegtestMagic wire.BitcoinNet = 0x6f6b6552
|
||||
)
|
||||
|
||||
// chain parameters
|
||||
var (
|
||||
MainNetParams chaincfg.Params
|
||||
TestNetParams chaincfg.Params
|
||||
RegtestParams chaincfg.Params
|
||||
)
|
||||
|
||||
func init() {
|
||||
MainNetParams = chaincfg.MainNetParams
|
||||
MainNetParams.Net = MainnetMagic
|
||||
|
||||
// Address encoding magics
|
||||
MainNetParams.AddressMagicLen = 2
|
||||
MainNetParams.PubKeyHashAddrID = []byte{0x18, 0x36} // base58 prefix: k1
|
||||
MainNetParams.ScriptHashAddrID = []byte{0x18, 0x3B} // base58 prefix: k3
|
||||
|
||||
TestNetParams = chaincfg.TestNet3Params
|
||||
TestNetParams.Net = TestnetMagic
|
||||
|
||||
// Address encoding magics
|
||||
TestNetParams.AddressMagicLen = 2
|
||||
TestNetParams.PubKeyHashAddrID = []byte{0x18, 0xA4} // base58 prefix: km
|
||||
TestNetParams.ScriptHashAddrID = []byte{0x18, 0x39} // base58 prefix: k2
|
||||
|
||||
RegtestParams = chaincfg.RegressionNetParams
|
||||
RegtestParams.Net = RegtestMagic
|
||||
}
|
||||
|
||||
// KotoParser handle
|
||||
type KotoParser struct {
|
||||
*btc.BitcoinParser
|
||||
baseparser *bchain.BaseParser
|
||||
}
|
||||
|
||||
// NewKotoParser returns new KotoParser instance
|
||||
func NewKotoParser(params *chaincfg.Params, c *btc.Configuration) *KotoParser {
|
||||
return &KotoParser{
|
||||
BitcoinParser: btc.NewBitcoinParser(params, c),
|
||||
baseparser: &bchain.BaseParser{},
|
||||
}
|
||||
}
|
||||
|
||||
// GetChainParams contains network parameters for the main Koto network,
|
||||
// the regression test Koto network, the test Koto network and
|
||||
// the simulation test Koto network, in this order
|
||||
func GetChainParams(chain string) *chaincfg.Params {
|
||||
if !chaincfg.IsRegistered(&MainNetParams) {
|
||||
err := chaincfg.Register(&MainNetParams)
|
||||
if err == nil {
|
||||
err = chaincfg.Register(&TestNetParams)
|
||||
}
|
||||
if err == nil {
|
||||
err = chaincfg.Register(&RegtestParams)
|
||||
}
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
switch chain {
|
||||
case "test":
|
||||
return &TestNetParams
|
||||
case "regtest":
|
||||
return &RegtestParams
|
||||
default:
|
||||
return &MainNetParams
|
||||
}
|
||||
}
|
||||
|
||||
// PackTx packs transaction to byte array using protobuf
|
||||
func (p *KotoParser) PackTx(tx *bchain.Tx, height uint32, blockTime int64) ([]byte, error) {
|
||||
return p.baseparser.PackTx(tx, height, blockTime)
|
||||
}
|
||||
|
||||
// UnpackTx unpacks transaction from protobuf byte array
|
||||
func (p *KotoParser) UnpackTx(buf []byte) (*bchain.Tx, uint32, error) {
|
||||
return p.baseparser.UnpackTx(buf)
|
||||
}
|
|
@ -0,0 +1,239 @@
|
|||
// +build unittest
|
||||
|
||||
package koto
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"math/big"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/martinboehm/btcutil/chaincfg"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
|
||||
)
|
||||
|
||||
var (
|
||||
testTx1, testTx2 bchain.Tx
|
||||
|
||||
testTxPacked1 = "0a2097f944e3558cc784f4013b3753ce9570fe4707893eda724b12eb4c69686113a612970f020000000001036b2048020000001976a9142df466d79cf4be0f7d1091512f1c297e4988fdd188ac000000000100000000000000001392204802000000a8a3d9a54a3fe3b5ae208fa2d96faea96dac6cb76b03bb2e32c5dd892a5d6f6490f05d8f6fb4401228df1be9f22c5ad69706c461bbc9253ffbc3531770e5075e149ec6af7f2cda0cb87862620792340bc425095115adbf0f16f4d3ece3f5c467cbcb02b01ced7192ab644f96fa01f04a7a450a42da2bfad1748634f7bc141467d3c94961ed6250dd23fca62b30b4a2ca6fbad0724372a429bcb97a28954e1681c0bb974877dac26eb2b994eaa23d56ecfdedd93f8331f9432f12adc37da9f049585179b7bbb370b76c6c37a438a20bc3b410a6a72ff8d11408a337a37bbc73dd15df8a34f2c878dc6d6db4f0cee504680fe53e0a158f1e1c82b84e065a4764fc57f8bf75d28899126917a05bf3230036f2d6b38f8a51214d1c2d0588a95e82f0032c2dfa6916c689f8daa648c01517bbf0826d2d4082067b0d17071920eb6dab6c0307603b825f3aae347db349628d4fcaa97a155ef1a2c601170fe825609efa964f0a06700afc135542ea7b06fc989424d7e100652c0ad5be4ca01c1fd676530e6f60606c5feb7de5da0d69544d8b7be8de06b27ba96a1bfa6bc07cc0269982acb722032a938ab36089c0eeda7a4076cd258a1752486a3d52af16db8dff072bcc61f17503185b5bc8aa0ea3a181663bb0ddd3cca1a19293764b01569b9878a60c0ce82e21020751a4ecc1a2a9b9a123042ee8d8a5d4d3f4764b1cdb13d57d2a77c3b56bd89102302d118ebc14969ade27bf83f0e707a97b26c7292a6c20e850abce5fad0ab59d032fec0d6278bbe0f2fb3fbc61697ba10b6ec3f2c4196e46e98dfb65bb28ec6afff81de5a4be7be8c4f56ab4c03043a3cf9987b630ac4b6d8aa74ce8ed5b61040262239172c6450f9ab642e2f2d258c200c3bb4ad69011ef2dfe1dd63d758c86270ac4925b248b9bb4b6c9ff7bf2e56260cba02b2429648dc20eb034c8f9b18e1f6a38b8651c236554546585b4dd0f07fd5ee1696bf792527ce84b22012439300797103f22d3969df725e4414d899ec32a2ebbb857cc911e374e84738f4e007ee5260ff666a286e45c465525e2e3fc5e5e0e9ad82e53d364e4fd355619711c616d508470b997af44f62f283871cd892552128135aadb40c6f8cf69ee72acf349e9f4d33e8673450b9f69d4022a8d886b0cdbaba0798e0bf57b42dedfafb0bbd5495ca1c0030bbf460b48f9a138f6ab748df9046d9995f895062583ce0818e40afb9704653e11d58ca42bd3f60f4e908589ad9144c76067dc433cad13a5bbd9c168691b8c6cddb19d812f3e3f98e2cdd20dbb170936fd5cd2ef0bca72af8931a1b01d6081ffbef5be4416e696a7c762a375b368f71dc31362a4005750992a48e55311dde8d2013180d62e507ffc3e468c4a27acc763a9651b19f37e1ffca7e656225617368e79c1d18f9b14d770993d3d1dc42dcfd9adfa02a8ddf0ebc8fbb850fab307fd1d239cf6ad4e5ff40992dd974bc43fa351ce807cc0036c2f7d80bbd052f496216304fbc63e8d728bf129acaedb0073aff077e584ce04bc1ccf9c91f41f3c8804dd65da4ecdfdba32590e04b4d1b6895dea8edacd1f40313e8a1d4900d0dba54056eee72e3d155e9c67e7a51df581c33cbd39f16549d590ae5387fe2c5ad3484ad5c7da320066b79083c49879e45938b3bbb063726008a2ebb8847c9e57be6ec489c7aaa80f5e8e430040cb8d60298363df850cb7b4e98e97192882d10d2fd96cc490dd18b263d96aac6aa4f5583770e0917fa9b566dd0e0b218c6684007ec10cf11747e8f039fac5250170de2835ab88fea356b6a7d0f5e81ffe9b78d191a745e0237a256a2a840880689d83503b72462e3955b61e22afde947c1f1527ea94151c5b7d3a72ce68979603911c08bcec01097899fd30347be7f2e246f70d2af6a1e29b54988978a91f79b2ed8be76ffd62f79de5418933ed166be919d9bbb7524347d87d31afaed05e71a82b09c18c196b3ba6e226939b375903f7d889422863567203814484af89fd223ce1c959b1fdffaf26461630c630d2bbf99228a096ea6cb0d61df70d24414c76bf9371c4abff0ad257098189af6ea32200fbe092d875aa4d3f72a7ec138439e4b08fb1dcde6a90f25fde1498773e693c9b21c40505d42edcbcaed8a2dc4642750e9df73e169f9986ddb3a57991ac2cb3b540d788e2c2c22c2c51b2d74a98ed59a8cec89ba54342fb9660449a116f8691da60cb447afe4d5e80f37b4669e6007c1cadc41933fb27bab41afd312c37e5cb43715cb4013efcd91221ed06249540b733c05e81131aba75ba0f427d9bd975554b2d49a8048f0b3a84477e75290235fd3bbcaee6c4438ba72299dd960f3f6ee9241f7e399684e894d7bb1c302ecbe24d0f19dca982a82ee44f36211d23b0ea623c9c9f4f527f4e452fd06ebb943cadeea3d7fa42cabd25324bc5851e40f9952823f56b50b97729e6561f2100c2b5922860c6cf447a668324ca931f2f35a5edb7d306f8b8802f98cf67140a3fe73099ab86bb65c439a8593e64816bcd46aa4c254918f4a3a0f3f47b4ebcfb2824703f9a7d163824484ce6fe1852c4ba131ee2635de14822a8cb3782697fecc6f69514edd3f42fcf2751075b838bf14ae91e9dcff517bf3cec4db1b986b4c966a4fa40d38f2ebb7fc60218c397a2d705200028d09a0c3a490a050248206b0310001a1976a9142df466d79cf4be0f7d1091512f1c297e4988fdd188ac22236b31323257767a46415565444b3667353238563376726547673870614a5137624248364000"
|
||||
testTxPacked2 = "0a203aebcf5a223450bca3c0312d3d87b6070447e795d09a266a3a01c70e44c7cc4812e1010100000001cbc2c0b14b26f563ceee8201971b2caae2a4f964d0fd91267290c51a6a171411010000006a473044022032dd5d573c3a7f729da1cb9d9ba02a08e05d50b4f74d5aeb7cb22284526f70340220661ca4a192d02684f0b6b52768b9e9ae5fad41b962aa918537b91bba275e92e70121024e98e62782ba44e5677b52b1e4e973a027c7d873915a6d62ba967b2c07467224ffffffff02c0c62d00000000001976a914dd985697513887236c484acc605ece839e2204ac88ac989e8ce0000000001976a91482bfe75940a6d46238f55e258fcae5bef4e847ea88ac0000000018ff98a2d705200028d49a0c3298010a0012201114176a1ac590722691fdd064f9a4e2aa2c1b970182eece63f5264bb1c0c2cb1801226a473044022032dd5d573c3a7f729da1cb9d9ba02a08e05d50b4f74d5aeb7cb22284526f70340220661ca4a192d02684f0b6b52768b9e9ae5fad41b962aa918537b91bba275e92e70121024e98e62782ba44e5677b52b1e4e973a027c7d873915a6d62ba967b2c0746722428ffffffff0f3a470a032dc6c010001a1976a914dd985697513887236c484acc605ece839e2204ac88ac22236b314a334461347236356653616b6571555953616a6f506f74656376633768384861513a480a04e08c9e9810011a1976a91482bfe75940a6d46238f55e258fcae5bef4e847ea88ac22236b31396b7355666462355139584b556a3565645570314451686e6343503868396845374000"
|
||||
)
|
||||
|
||||
func init() {
|
||||
testTx1 = bchain.Tx{
|
||||
Hex: "020000000001036b2048020000001976a9142df466d79cf4be0f7d1091512f1c297e4988fdd188ac000000000100000000000000001392204802000000a8a3d9a54a3fe3b5ae208fa2d96faea96dac6cb76b03bb2e32c5dd892a5d6f6490f05d8f6fb4401228df1be9f22c5ad69706c461bbc9253ffbc3531770e5075e149ec6af7f2cda0cb87862620792340bc425095115adbf0f16f4d3ece3f5c467cbcb02b01ced7192ab644f96fa01f04a7a450a42da2bfad1748634f7bc141467d3c94961ed6250dd23fca62b30b4a2ca6fbad0724372a429bcb97a28954e1681c0bb974877dac26eb2b994eaa23d56ecfdedd93f8331f9432f12adc37da9f049585179b7bbb370b76c6c37a438a20bc3b410a6a72ff8d11408a337a37bbc73dd15df8a34f2c878dc6d6db4f0cee504680fe53e0a158f1e1c82b84e065a4764fc57f8bf75d28899126917a05bf3230036f2d6b38f8a51214d1c2d0588a95e82f0032c2dfa6916c689f8daa648c01517bbf0826d2d4082067b0d17071920eb6dab6c0307603b825f3aae347db349628d4fcaa97a155ef1a2c601170fe825609efa964f0a06700afc135542ea7b06fc989424d7e100652c0ad5be4ca01c1fd676530e6f60606c5feb7de5da0d69544d8b7be8de06b27ba96a1bfa6bc07cc0269982acb722032a938ab36089c0eeda7a4076cd258a1752486a3d52af16db8dff072bcc61f17503185b5bc8aa0ea3a181663bb0ddd3cca1a19293764b01569b9878a60c0ce82e21020751a4ecc1a2a9b9a123042ee8d8a5d4d3f4764b1cdb13d57d2a77c3b56bd89102302d118ebc14969ade27bf83f0e707a97b26c7292a6c20e850abce5fad0ab59d032fec0d6278bbe0f2fb3fbc61697ba10b6ec3f2c4196e46e98dfb65bb28ec6afff81de5a4be7be8c4f56ab4c03043a3cf9987b630ac4b6d8aa74ce8ed5b61040262239172c6450f9ab642e2f2d258c200c3bb4ad69011ef2dfe1dd63d758c86270ac4925b248b9bb4b6c9ff7bf2e56260cba02b2429648dc20eb034c8f9b18e1f6a38b8651c236554546585b4dd0f07fd5ee1696bf792527ce84b22012439300797103f22d3969df725e4414d899ec32a2ebbb857cc911e374e84738f4e007ee5260ff666a286e45c465525e2e3fc5e5e0e9ad82e53d364e4fd355619711c616d508470b997af44f62f283871cd892552128135aadb40c6f8cf69ee72acf349e9f4d33e8673450b9f69d4022a8d886b0cdbaba0798e0bf57b42dedfafb0bbd5495ca1c0030bbf460b48f9a138f6ab748df9046d9995f895062583ce0818e40afb9704653e11d58ca42bd3f60f4e908589ad9144c76067dc433cad13a5bbd9c168691b8c6cddb19d812f3e3f98e2cdd20dbb170936fd5cd2ef0bca72af8931a1b01d6081ffbef5be4416e696a7c762a375b368f71dc31362a4005750992a48e55311dde8d2013180d62e507ffc3e468c4a27acc763a9651b19f37e1ffca7e656225617368e79c1d18f9b14d770993d3d1dc42dcfd9adfa02a8ddf0ebc8fbb850fab307fd1d239cf6ad4e5ff40992dd974bc43fa351ce807cc0036c2f7d80bbd052f496216304fbc63e8d728bf129acaedb0073aff077e584ce04bc1ccf9c91f41f3c8804dd65da4ecdfdba32590e04b4d1b6895dea8edacd1f40313e8a1d4900d0dba54056eee72e3d155e9c67e7a51df581c33cbd39f16549d590ae5387fe2c5ad3484ad5c7da320066b79083c49879e45938b3bbb063726008a2ebb8847c9e57be6ec489c7aaa80f5e8e430040cb8d60298363df850cb7b4e98e97192882d10d2fd96cc490dd18b263d96aac6aa4f5583770e0917fa9b566dd0e0b218c6684007ec10cf11747e8f039fac5250170de2835ab88fea356b6a7d0f5e81ffe9b78d191a745e0237a256a2a840880689d83503b72462e3955b61e22afde947c1f1527ea94151c5b7d3a72ce68979603911c08bcec01097899fd30347be7f2e246f70d2af6a1e29b54988978a91f79b2ed8be76ffd62f79de5418933ed166be919d9bbb7524347d87d31afaed05e71a82b09c18c196b3ba6e226939b375903f7d889422863567203814484af89fd223ce1c959b1fdffaf26461630c630d2bbf99228a096ea6cb0d61df70d24414c76bf9371c4abff0ad257098189af6ea32200fbe092d875aa4d3f72a7ec138439e4b08fb1dcde6a90f25fde1498773e693c9b21c40505d42edcbcaed8a2dc4642750e9df73e169f9986ddb3a57991ac2cb3b540d788e2c2c22c2c51b2d74a98ed59a8cec89ba54342fb9660449a116f8691da60cb447afe4d5e80f37b4669e6007c1cadc41933fb27bab41afd312c37e5cb43715cb4013efcd91221ed06249540b733c05e81131aba75ba0f427d9bd975554b2d49a8048f0b3a84477e75290235fd3bbcaee6c4438ba72299dd960f3f6ee9241f7e399684e894d7bb1c302ecbe24d0f19dca982a82ee44f36211d23b0ea623c9c9f4f527f4e452fd06ebb943cadeea3d7fa42cabd25324bc5851e40f9952823f56b50b97729e6561f2100c2b5922860c6cf447a668324ca931f2f35a5edb7d306f8b8802f98cf67140a3fe73099ab86bb65c439a8593e64816bcd46aa4c254918f4a3a0f3f47b4ebcfb2824703f9a7d163824484ce6fe1852c4ba131ee2635de14822a8cb3782697fecc6f69514edd3f42fcf2751075b838bf14ae91e9dcff517bf3cec4db1b986b4c966a4fa40d38f2ebb7fc602",
|
||||
Blocktime: 1525189571,
|
||||
Time: 1525189571,
|
||||
Txid: "97f944e3558cc784f4013b3753ce9570fe4707893eda724b12eb4c69686113a6",
|
||||
LockTime: 0,
|
||||
Vin: []bchain.Vin{},
|
||||
Vout: []bchain.Vout{
|
||||
{
|
||||
ValueSat: *big.NewInt(9800018691),
|
||||
N: 0,
|
||||
ScriptPubKey: bchain.ScriptPubKey{
|
||||
Hex: "76a9142df466d79cf4be0f7d1091512f1c297e4988fdd188ac",
|
||||
Addresses: []string{
|
||||
"k122WvzFAUeDK6g528V3vreGg8paJQ7bBH6",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
testTx2 = bchain.Tx{
|
||||
Hex: "0100000001cbc2c0b14b26f563ceee8201971b2caae2a4f964d0fd91267290c51a6a171411010000006a473044022032dd5d573c3a7f729da1cb9d9ba02a08e05d50b4f74d5aeb7cb22284526f70340220661ca4a192d02684f0b6b52768b9e9ae5fad41b962aa918537b91bba275e92e70121024e98e62782ba44e5677b52b1e4e973a027c7d873915a6d62ba967b2c07467224ffffffff02c0c62d00000000001976a914dd985697513887236c484acc605ece839e2204ac88ac989e8ce0000000001976a91482bfe75940a6d46238f55e258fcae5bef4e847ea88ac00000000",
|
||||
Blocktime: 1525189759,
|
||||
Time: 1525189759,
|
||||
Txid: "3aebcf5a223450bca3c0312d3d87b6070447e795d09a266a3a01c70e44c7cc48",
|
||||
LockTime: 0,
|
||||
Vin: []bchain.Vin{
|
||||
{
|
||||
ScriptSig: bchain.ScriptSig{
|
||||
Hex: "473044022032dd5d573c3a7f729da1cb9d9ba02a08e05d50b4f74d5aeb7cb22284526f70340220661ca4a192d02684f0b6b52768b9e9ae5fad41b962aa918537b91bba275e92e70121024e98e62782ba44e5677b52b1e4e973a027c7d873915a6d62ba967b2c07467224",
|
||||
},
|
||||
Txid: "1114176a1ac590722691fdd064f9a4e2aa2c1b970182eece63f5264bb1c0c2cb",
|
||||
Vout: 1,
|
||||
Sequence: 4294967295,
|
||||
},
|
||||
},
|
||||
Vout: []bchain.Vout{
|
||||
{
|
||||
ValueSat: *big.NewInt(3000000),
|
||||
N: 0,
|
||||
ScriptPubKey: bchain.ScriptPubKey{
|
||||
Hex: "76a914dd985697513887236c484acc605ece839e2204ac88ac",
|
||||
Addresses: []string{
|
||||
"k1J3Da4r65fSakeqUYSajoPotecvc7h8HaQ",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ValueSat: *big.NewInt(3767312024),
|
||||
N: 1,
|
||||
ScriptPubKey: bchain.ScriptPubKey{
|
||||
Hex: "76a91482bfe75940a6d46238f55e258fcae5bef4e847ea88ac",
|
||||
Addresses: []string{
|
||||
"k19ksUfdb5Q9XKUj5edUp1DQhncCP8h9hE7",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
c := m.Run()
|
||||
chaincfg.ResetParams()
|
||||
os.Exit(c)
|
||||
}
|
||||
|
||||
func TestGetAddrDesc(t *testing.T) {
|
||||
type args struct {
|
||||
tx bchain.Tx
|
||||
parser *KotoParser
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
}{
|
||||
{
|
||||
name: "koto-1",
|
||||
args: args{
|
||||
tx: testTx1,
|
||||
parser: NewKotoParser(GetChainParams("main"), &btc.Configuration{}),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "koto-2",
|
||||
args: args{
|
||||
tx: testTx2,
|
||||
parser: NewKotoParser(GetChainParams("main"), &btc.Configuration{}),
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
for n, vout := range tt.args.tx.Vout {
|
||||
got1, err := tt.args.parser.GetAddrDescFromVout(&vout)
|
||||
if err != nil {
|
||||
t.Errorf("getAddrDescFromVout() error = %v, vout = %d", err, n)
|
||||
return
|
||||
}
|
||||
got2, err := tt.args.parser.GetAddrDescFromAddress(vout.ScriptPubKey.Addresses[0])
|
||||
if err != nil {
|
||||
t.Errorf("getAddrDescFromAddress() error = %v, vout = %d", err, n)
|
||||
return
|
||||
}
|
||||
if !bytes.Equal(got1, got2) {
|
||||
t.Errorf("Address descriptors mismatch: got1 = %v, got2 = %v", got1, got2)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPackTx(t *testing.T) {
|
||||
type args struct {
|
||||
tx bchain.Tx
|
||||
height uint32
|
||||
blockTime int64
|
||||
parser *KotoParser
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "koto-1",
|
||||
args: args{
|
||||
tx: testTx1,
|
||||
height: 200016,
|
||||
blockTime: 1525189571,
|
||||
parser: NewKotoParser(GetChainParams("main"), &btc.Configuration{}),
|
||||
},
|
||||
want: testTxPacked1,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "koto-2",
|
||||
args: args{
|
||||
tx: testTx2,
|
||||
height: 200020,
|
||||
blockTime: 1525189759,
|
||||
parser: NewKotoParser(GetChainParams("main"), &btc.Configuration{}),
|
||||
},
|
||||
want: testTxPacked2,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := tt.args.parser.PackTx(&tt.args.tx, tt.args.height, tt.args.blockTime)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("packTx() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
h := hex.EncodeToString(got)
|
||||
if !reflect.DeepEqual(h, tt.want) {
|
||||
t.Errorf("packTx() = %v, want %v", h, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnpackTx(t *testing.T) {
|
||||
type args struct {
|
||||
packedTx string
|
||||
parser *KotoParser
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *bchain.Tx
|
||||
want1 uint32
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "koto-1",
|
||||
args: args{
|
||||
packedTx: testTxPacked1,
|
||||
parser: NewKotoParser(GetChainParams("main"), &btc.Configuration{}),
|
||||
},
|
||||
want: &testTx1,
|
||||
want1: 200016,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "koto-2",
|
||||
args: args{
|
||||
packedTx: testTxPacked2,
|
||||
parser: NewKotoParser(GetChainParams("main"), &btc.Configuration{}),
|
||||
},
|
||||
want: &testTx2,
|
||||
want1: 200020,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
b, _ := hex.DecodeString(tt.args.packedTx)
|
||||
got, got1, err := tt.args.parser.UnpackTx(b)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("unpackTx() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("unpackTx() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
if got1 != tt.want1 {
|
||||
t.Errorf("unpackTx() got1 = %v, want %v", got1, tt.want1)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
package koto
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/juju/errors"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
|
||||
)
|
||||
|
||||
// KotoRPC is an interface to JSON-RPC bitcoind service
|
||||
type KotoRPC struct {
|
||||
*btc.BitcoinRPC
|
||||
}
|
||||
|
||||
// NewKotoRPC returns new LitecoinRPC instance
|
||||
func NewKotoRPC(config json.RawMessage, pushHandler func(bchain.NotificationType)) (bchain.BlockChain, error) {
|
||||
b, err := btc.NewBitcoinRPC(config, pushHandler)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
z := &KotoRPC{
|
||||
BitcoinRPC: b.(*btc.BitcoinRPC),
|
||||
}
|
||||
z.RPCMarshaler = btc.JSONMarshalerV1{}
|
||||
z.ChainConfig.SupportsEstimateSmartFee = false
|
||||
return z, nil
|
||||
}
|
||||
|
||||
// Initialize initializes KotoRPC instance.
|
||||
func (z *KotoRPC) Initialize() error {
|
||||
ci, err := z.GetChainInfo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
chainName := ci.Chain
|
||||
|
||||
params := GetChainParams(chainName)
|
||||
|
||||
z.Parser = NewKotoParser(params, z.ChainConfig)
|
||||
|
||||
// parameters for getInfo request
|
||||
if params.Net == MainnetMagic {
|
||||
z.Testnet = false
|
||||
z.Network = "livenet"
|
||||
} else {
|
||||
z.Testnet = true
|
||||
z.Network = "testnet"
|
||||
}
|
||||
|
||||
glog.Info("rpc: block chain ", params.Name)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetBlock returns block with given hash.
|
||||
func (z *KotoRPC) GetBlock(hash string, height uint32) (*bchain.Block, error) {
|
||||
var err error
|
||||
if hash == "" && height > 0 {
|
||||
hash, err = z.GetBlockHash(height)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
glog.V(1).Info("rpc: getblock (verbosity=1) ", hash)
|
||||
|
||||
res := btc.ResGetBlockThin{}
|
||||
req := btc.CmdGetBlock{Method: "getblock"}
|
||||
req.Params.BlockHash = hash
|
||||
req.Params.Verbosity = 1
|
||||
err = z.Call(&req, &res)
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "hash %v", hash)
|
||||
}
|
||||
if res.Error != nil {
|
||||
return nil, errors.Annotatef(res.Error, "hash %v", hash)
|
||||
}
|
||||
|
||||
txs := make([]bchain.Tx, 0, len(res.Result.Txids))
|
||||
for _, txid := range res.Result.Txids {
|
||||
tx, err := z.GetTransaction(txid)
|
||||
if err != nil {
|
||||
if err == bchain.ErrTxNotFound {
|
||||
glog.Errorf("rpc: getblock: skipping transanction in block %s due error: %s", hash, err)
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
txs = append(txs, *tx)
|
||||
}
|
||||
block := &bchain.Block{
|
||||
BlockHeader: res.Result.BlockHeader,
|
||||
Txs: txs,
|
||||
}
|
||||
return block, nil
|
||||
}
|
||||
|
||||
// GetTransactionForMempool returns a transaction by the transaction ID.
|
||||
// It could be optimized for mempool, i.e. without block time and confirmations
|
||||
func (z *KotoRPC) GetTransactionForMempool(txid string) (*bchain.Tx, error) {
|
||||
return z.GetTransaction(txid)
|
||||
}
|
||||
|
||||
// GetMempoolEntry returns mempool data for given transaction
|
||||
func (z *KotoRPC) GetMempoolEntry(txid string) (*bchain.MempoolEntry, error) {
|
||||
return nil, errors.New("GetMempoolEntry: not implemented")
|
||||
}
|
||||
|
||||
func isErrBlockNotFound(err *bchain.RPCError) bool {
|
||||
return err.Message == "Block not found" ||
|
||||
err.Message == "Block height out of range"
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue