Compare commits
540 Commits
v0.2.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 |
|
@ -8,8 +8,10 @@ debug*
|
|||
docker/blockbook
|
||||
build/pkg-defs
|
||||
build/blockbook
|
||||
build/blockchaincfg.json
|
||||
build/ldb
|
||||
build/sst_dump
|
||||
build/*.deb
|
||||
.bin-image
|
||||
.deb-image
|
||||
.deb-image
|
||||
\.idea/
|
||||
|
|
|
@ -71,6 +71,17 @@ backend-deploy-and-test-dash:
|
|||
- 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:
|
||||
|
@ -93,6 +104,17 @@ backend-deploy-and-test-litecoin:
|
|||
- 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:
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
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 have a need to change some of the 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
|
||||
|
|
|
@ -1,213 +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/martinboehm/btcd"
|
||||
packages = ["blockchain","btcec","chaincfg","chaincfg/chainhash","database","txscript","wire"]
|
||||
revision = "8e7c0427fee5d4778c5d4eb987150369e3ca1d0e"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/btcsuite/btclog"
|
||||
packages = ["."]
|
||||
revision = "84c8d2346e9fc8c7b947e243b9c24e6df9fd206a"
|
||||
|
||||
[[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 = "8bbe72075e4e16442c4e28d999edee12e294329e"
|
||||
version = "v1.8.17"
|
||||
|
||||
[[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/martinboehm/bchutil"
|
||||
packages = ["."]
|
||||
revision = "6373f11b6efe1ea81e8713b8788a695b2c144d38"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/martinboehm/btcutil"
|
||||
packages = [".","base58","bech32","chaincfg","hdkeychain","txscript"]
|
||||
revision = "63034958e64b209cb9294128309dbaed497cde7b"
|
||||
|
||||
[[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/martinboehm/btcd"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/martinboehm/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/martinboehm/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).
|
66
README.md
66
README.md
|
@ -1,43 +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.
|
||||
|
||||
- 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
|
||||
* 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 160 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.
|
||||
* 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
|
||||
```
|
||||
|
||||
Blockbook currently supports over 20 coins, among them:
|
||||
- Bitcoin, Litecoin, Bitcoin Cash, Bgold, ZCash, Dash, Ethereum, Ethereum Classic
|
||||
Edit config:
|
||||
```
|
||||
vim /opt/coins/blockbook/ethereum/config/blockchaincfg.json
|
||||
```
|
||||
|
||||
Testnets for some coins are also supported, for example:
|
||||
- Bitcoin Testnet, Bitcoin Cash Testnet, ZCash Testnet, Ethereum Testnet Ropsten
|
||||
XXX Hardcoded into systemd script, set `geth` node:
|
||||
|
||||
List of all implemented coins is in [the registry of ports](/docs/ports.md).
|
||||
```
|
||||
vim /lib/systemd/system/blockbook-ethereum.service
|
||||
systemctl daemon-reload
|
||||
```
|
||||
|
||||
# Data storage in RocksDB
|
||||
Start:
|
||||
```
|
||||
systemctl start blockbook-ethereum.service
|
||||
```
|
||||
|
||||
Blockbook stores data the key-value store RocksDB. Database format is described [here](/docs/rocksdb.md).
|
||||
Logs:
|
||||
```
|
||||
tail -f /opt/coins/blockbook/ethereum/logs/blockbook.INFO
|
||||
```
|
||||
|
||||
# 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()))
|
||||
}
|
||||
|
|
189
api/types.go
189
api/types.go
|
@ -1,13 +1,16 @@
|
|||
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)
|
||||
|
@ -101,17 +104,17 @@ func (a *Amount) AsInt64() int64 {
|
|||
|
||||
// Vin contains information about single transaction input
|
||||
type Vin struct {
|
||||
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"`
|
||||
Searchable bool `json:"-"`
|
||||
ValueSat *Amount `json:"value,omitempty"`
|
||||
Hex string `json:"hex,omitempty"`
|
||||
Asm string `json:"asm,omitempty"`
|
||||
Coinbase string `json:"coinbase,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
|
||||
|
@ -126,7 +129,7 @@ type Vout struct {
|
|||
Asm string `json:"asm,omitempty"`
|
||||
AddrDesc bchain.AddressDescriptor `json:"-"`
|
||||
Addresses []string `json:"addresses"`
|
||||
Searchable bool `json:"-"`
|
||||
IsAddress bool `json:"isAddress"`
|
||||
Type string `json:"type,omitempty"`
|
||||
}
|
||||
|
||||
|
@ -168,33 +171,42 @@ type TokenTransfer struct {
|
|||
|
||||
// EthereumSpecific contains ethereum specific transaction data
|
||||
type EthereumSpecific struct {
|
||||
Status int `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"`
|
||||
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"`
|
||||
Locktime uint32 `json:"lockTime,omitempty"`
|
||||
Vin []Vin `json:"vin"`
|
||||
Vout []Vout `json:"vout"`
|
||||
Blockhash string `json:"blockhash,omitempty"`
|
||||
Blockheight int `json:"blockheight"`
|
||||
Blockhash string `json:"blockHash,omitempty"`
|
||||
Blockheight int `json:"blockHeight"`
|
||||
Confirmations uint32 `json:"confirmations"`
|
||||
Blocktime int64 `json:"blocktime"`
|
||||
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"`
|
||||
CoinSpecificData interface{} `json:"-"`
|
||||
CoinSpecificJSON json.RawMessage `json:"-"`
|
||||
TokenTransfers []TokenTransfer `json:"tokentransfers,omitempty"`
|
||||
EthereumSpecific *EthereumSpecific `json:"ethereumspecific,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
|
||||
|
@ -214,6 +226,8 @@ const (
|
|||
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
|
||||
|
@ -244,13 +258,13 @@ type Address struct {
|
|||
UnconfirmedBalanceSat *Amount `json:"unconfirmedBalance"`
|
||||
UnconfirmedTxs int `json:"unconfirmedTxs"`
|
||||
Txs int `json:"txs"`
|
||||
NonTokenTxs int `json:"nontokenTxs,omitempty"`
|
||||
NonTokenTxs int `json:"nonTokenTxs,omitempty"`
|
||||
Transactions []*Tx `json:"transactions,omitempty"`
|
||||
Txids []string `json:"txids,omitempty"`
|
||||
Nonce string `json:"nonce,omitempty"`
|
||||
TotalTokens int `json:"totalTokens,omitempty"`
|
||||
UsedTokens int `json:"usedTokens,omitempty"`
|
||||
Tokens []Token `json:"tokens,omitempty"`
|
||||
Erc20Contract *bchain.Erc20Contract `json:"erc20contract,omitempty"`
|
||||
Erc20Contract *bchain.Erc20Contract `json:"erc20Contract,omitempty"`
|
||||
// helpers for explorer
|
||||
Filter string `json:"-"`
|
||||
XPubAddresses map[string]struct{} `json:"-"`
|
||||
|
@ -265,6 +279,8 @@ type Utxo struct {
|
|||
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
|
||||
|
@ -285,17 +301,101 @@ func (a Utxos) Less(i, j int) bool {
|
|||
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
|
||||
type Blocks struct {
|
||||
Paging
|
||||
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"`
|
||||
}
|
||||
|
||||
|
@ -304,10 +404,10 @@ 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"`
|
||||
|
@ -323,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"`
|
||||
}
|
||||
|
|
|
@ -49,3 +49,126 @@ func TestAmount_MarshalJSON(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"blockbook/bchain"
|
||||
"math/big"
|
||||
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
)
|
||||
|
||||
// ScriptSigV1 is used for legacy api v1
|
||||
|
@ -13,26 +14,26 @@ type ScriptSigV1 struct {
|
|||
|
||||
// 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"`
|
||||
Searchable bool `json:"-"`
|
||||
Value string `json:"value"`
|
||||
ValueSat big.Int `json:"-"`
|
||||
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"`
|
||||
Searchable bool `json:"-"`
|
||||
Type string `json:"type,omitempty"`
|
||||
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
|
||||
|
@ -96,8 +97,8 @@ type AddressUtxoV1 struct {
|
|||
// BlockV1 contains information about block
|
||||
type BlockV1 struct {
|
||||
Paging
|
||||
bchain.BlockInfo
|
||||
TxCount int `json:"TxCount"`
|
||||
BlockInfo
|
||||
TxCount int `json:"txCount"`
|
||||
Transactions []*TxV1 `json:"txs,omitempty"`
|
||||
}
|
||||
|
||||
|
@ -115,12 +116,12 @@ func (w *Worker) TxToV1(tx *Tx) *TxV1 {
|
|||
Asm: v.Asm,
|
||||
Hex: v.Hex,
|
||||
},
|
||||
Searchable: v.Searchable,
|
||||
Sequence: v.Sequence,
|
||||
Txid: v.Txid,
|
||||
Value: v.ValueSat.DecimalString(d),
|
||||
ValueSat: v.ValueSat.AsBigInt(),
|
||||
Vout: v.Vout,
|
||||
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))
|
||||
|
@ -129,12 +130,12 @@ func (w *Worker) TxToV1(tx *Tx) *TxV1 {
|
|||
voutV1[i] = VoutV1{
|
||||
N: v.N,
|
||||
ScriptPubKey: ScriptPubKeyV1{
|
||||
AddrDesc: v.AddrDesc,
|
||||
Addresses: v.Addresses,
|
||||
Asm: v.Asm,
|
||||
Hex: v.Hex,
|
||||
Searchable: v.Searchable,
|
||||
Type: v.Type,
|
||||
AddrDesc: v.AddrDesc,
|
||||
Addresses: v.Addresses,
|
||||
Asm: v.Asm,
|
||||
Hex: v.Hex,
|
||||
IsAddress: v.IsAddress,
|
||||
Type: v.Type,
|
||||
},
|
||||
Spent: v.Spent,
|
||||
SpentHeight: v.SpentHeight,
|
||||
|
|
1166
api/worker.go
1166
api/worker.go
File diff suppressed because it is too large
Load Diff
99
api/xpub.go
99
api/xpub.go
|
@ -1,8 +1,6 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"blockbook/bchain"
|
||||
"blockbook/db"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"sort"
|
||||
|
@ -11,9 +9,10 @@ import (
|
|||
|
||||
"github.com/golang/glog"
|
||||
"github.com/juju/errors"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
"spacecruft.org/spacecruft/blockbook/db"
|
||||
)
|
||||
|
||||
const xpubLen = 111
|
||||
const defaultAddressesGap = 20
|
||||
const maxAddressesGap = 10000
|
||||
|
||||
|
@ -92,7 +91,7 @@ func (w *Worker) xpubGetAddressTxids(addrDesc bchain.AddressDescriptor, mempool
|
|||
}
|
||||
if mempool {
|
||||
uniqueTxs := make(map[string]int)
|
||||
o, err := w.chain.GetMempoolTransactionsForAddrDesc(addrDesc)
|
||||
o, err := w.mempool.GetAddrDescTransactions(addrDesc)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
@ -101,7 +100,7 @@ func (w *Worker) xpubGetAddressTxids(addrDesc bchain.AddressDescriptor, mempool
|
|||
l = len(txs)
|
||||
callback(m.Txid, 0, []int32{m.Vout})
|
||||
if len(txs) > l {
|
||||
uniqueTxs[m.Txid] = l - 1
|
||||
uniqueTxs[m.Txid] = l
|
||||
}
|
||||
} else {
|
||||
if m.Vout < 0 {
|
||||
|
@ -160,7 +159,7 @@ func (w *Worker) xpubCheckAndLoadTxids(ad *xpubAddress, filter *AddressFilter, m
|
|||
|
||||
func (w *Worker) xpubDerivedAddressBalance(data *xpubData, ad *xpubAddress) (bool, error) {
|
||||
var err error
|
||||
if ad.balance, err = w.db.GetAddrDescBalance(ad.addrDesc); err != nil {
|
||||
if ad.balance, err = w.db.GetAddrDescBalance(ad.addrDesc, db.AddressBalanceDetailUTXO); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if ad.balance != nil {
|
||||
|
@ -271,7 +270,7 @@ func evictXpubCacheItems() {
|
|||
}
|
||||
|
||||
func (w *Worker) getXpubData(xpub string, page int, txsOnPage int, option AccountDetails, filter *AddressFilter, gap int) (*xpubData, uint32, error) {
|
||||
if w.chainType != bchain.ChainBitcoinType || len(xpub) != xpubLen {
|
||||
if w.chainType != bchain.ChainBitcoinType {
|
||||
return nil, 0, ErrUnsupportedXpub
|
||||
}
|
||||
var (
|
||||
|
@ -305,8 +304,7 @@ func (w *Worker) getXpubData(xpub string, page int, txsOnPage int, option Accoun
|
|||
data = xpubData{gap: gap}
|
||||
data.basePath, err = w.chainParser.DerivationBasePath(xpub)
|
||||
if err != nil {
|
||||
glog.Warning("DerivationBasePath error", err)
|
||||
data.basePath = "unknown"
|
||||
return nil, 0, err
|
||||
}
|
||||
} else {
|
||||
hash, err := w.db.GetBlockHash(data.dataHeight)
|
||||
|
@ -383,13 +381,13 @@ func (w *Worker) GetXpubAddress(xpub string, page int, txsOnPage int, option Acc
|
|||
return nil, err
|
||||
}
|
||||
// setup filtering of txids
|
||||
var useTxids func(txid *xpubTxid, ad *xpubAddress) bool
|
||||
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
|
||||
}
|
||||
useTxids = func(txid *xpubTxid, ad *xpubAddress) bool {
|
||||
txidFilter = func(txid *xpubTxid, ad *xpubAddress) bool {
|
||||
if txid.height < filter.FromHeight || txid.height > toHeight {
|
||||
return false
|
||||
}
|
||||
|
@ -406,6 +404,7 @@ func (w *Worker) GetXpubAddress(xpub string, page int, txsOnPage int, option Acc
|
|||
// 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]
|
||||
|
@ -417,7 +416,7 @@ func (w *Worker) GetXpubAddress(xpub string, page int, txsOnPage int, option Acc
|
|||
// 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, false)
|
||||
tx, err = w.GetTransaction(txid.txid, false, true)
|
||||
// mempool transaction may fail
|
||||
if err != nil || tx == nil {
|
||||
glog.Warning("GetTransaction in mempool: ", err)
|
||||
|
@ -432,18 +431,23 @@ func (w *Worker) GetXpubAddress(xpub string, page int, txsOnPage int, option Acc
|
|||
}
|
||||
uBalSat.Add(&uBalSat, tx.getAddrVoutValue(ad.addrDesc))
|
||||
uBalSat.Sub(&uBalSat, tx.getAddrVinValue(ad.addrDesc))
|
||||
if page == 0 && !foundTx && (useTxids == nil || useTxids(&txid, ad)) {
|
||||
if option == AccountDetailsTxidHistory {
|
||||
txids = append(txids, tx.Txid)
|
||||
} else if option >= AccountDetailsTxHistoryLight {
|
||||
txs = append(txs, tx)
|
||||
}
|
||||
// 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)
|
||||
|
@ -459,7 +463,7 @@ func (w *Worker) GetXpubAddress(xpub string, page int, txsOnPage int, option Acc
|
|||
}
|
||||
// add tx only once
|
||||
if !added {
|
||||
add := useTxids == nil || useTxids(&txid, ad)
|
||||
add := txidFilter == nil || txidFilter(&txid, ad)
|
||||
txcMap[txid.txid] = add
|
||||
if add {
|
||||
txc = append(txc, txid)
|
||||
|
@ -489,7 +493,7 @@ func (w *Worker) GetXpubAddress(xpub string, page int, txsOnPage int, option Acc
|
|||
if option == AccountDetailsTxidHistory {
|
||||
txids = append(txids, xpubTxid.txid)
|
||||
} else {
|
||||
tx, err := w.txFromTxid(xpubTxid.txid, bestheight, option)
|
||||
tx, err := w.txFromTxid(xpubTxid.txid, bestheight, option, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -499,7 +503,7 @@ func (w *Worker) GetXpubAddress(xpub string, page int, txsOnPage int, option Acc
|
|||
} else {
|
||||
txCount = int(data.txCountEstimate)
|
||||
}
|
||||
totalTokens := 0
|
||||
usedTokens := 0
|
||||
var tokens []Token
|
||||
var xpubAddresses map[string]struct{}
|
||||
if option > AccountDetailsBasic {
|
||||
|
@ -510,7 +514,7 @@ func (w *Worker) GetXpubAddress(xpub string, page int, txsOnPage int, option Acc
|
|||
for i := range da {
|
||||
ad := &da[i]
|
||||
if ad.balance != nil {
|
||||
totalTokens++
|
||||
usedTokens++
|
||||
}
|
||||
if option > AccountDetailsBasic {
|
||||
token := w.tokenFromXpubAddress(data, ad, ci, i, option)
|
||||
|
@ -536,7 +540,7 @@ func (w *Worker) GetXpubAddress(xpub string, page int, txsOnPage int, option Acc
|
|||
UnconfirmedTxs: unconfirmedTxs,
|
||||
Transactions: txs,
|
||||
Txids: txids,
|
||||
TotalTokens: totalTokens,
|
||||
UsedTokens: usedTokens,
|
||||
Tokens: tokens,
|
||||
XPubAddresses: xpubAddresses,
|
||||
}
|
||||
|
@ -584,3 +588,50 @@ func (w *Worker) GetXpubUtxo(xpub string, onlyConfirmed bool, gap int) (Utxos, e
|
|||
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
|
||||
}
|
||||
|
|
|
@ -29,6 +29,11 @@ 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")
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -9,6 +9,7 @@ import (
|
|||
"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
|
||||
|
@ -39,9 +40,9 @@ func (p *BaseParser) GetAddrDescForUnknownInput(tx *Tx, input int) AddressDescri
|
|||
|
||||
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, '.')
|
||||
|
@ -161,6 +162,11 @@ 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
|
||||
func (p *BaseParser) PackTx(tx *Tx, height uint32, blockTime int64) ([]byte, error) {
|
||||
var err error
|
||||
|
@ -268,6 +274,12 @@ func (p *BaseParser) UnpackTx(buf []byte) (*Tx, uint32, error) {
|
|||
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")
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
// +build unittest
|
||||
|
||||
package bchain
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"spacecruft.org/spacecruft/blockbook/common"
|
||||
)
|
||||
|
||||
func NewBaseParser(adp int) *BaseParser {
|
||||
|
@ -44,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,8 +1,6 @@
|
|||
package bch
|
||||
|
||||
import (
|
||||
"blockbook/bchain"
|
||||
"blockbook/bchain/coins/btc"
|
||||
"fmt"
|
||||
|
||||
"github.com/martinboehm/bchutil"
|
||||
|
@ -10,6 +8,8 @@ import (
|
|||
"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
|
||||
|
@ -71,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
|
||||
|
@ -157,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
|
||||
}
|
||||
|
@ -166,7 +160,7 @@ 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
|
||||
}
|
||||
|
|
|
@ -3,8 +3,6 @@
|
|||
package bch
|
||||
|
||||
import (
|
||||
"blockbook/bchain"
|
||||
"blockbook/bchain/coins/btc"
|
||||
"encoding/hex"
|
||||
"math/big"
|
||||
"os"
|
||||
|
@ -12,6 +10,8 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/martinboehm/btcutil/chaincfg"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
package bch
|
||||
|
||||
import (
|
||||
"blockbook/bchain"
|
||||
"blockbook/bchain/coins/btc"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"math/big"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"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.
|
||||
|
@ -33,10 +34,11 @@ func NewBCashRPC(config json.RawMessage, pushHandler func(bchain.NotificationTyp
|
|||
|
||||
// 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)
|
||||
|
||||
|
@ -161,3 +163,41 @@ 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",
|
||||
}
|
||||
|
||||
err := b.Call(&req, &res)
|
||||
|
||||
var r big.Int
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
if res.Error != nil {
|
||||
return r, res.Error
|
||||
}
|
||||
r, err = b.Parser.AmountToBigInt(res.Result)
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
|
|
@ -1,17 +1,18 @@
|
|||
package bellcoin
|
||||
|
||||
import (
|
||||
"blockbook/bchain/coins/btc"
|
||||
|
||||
"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
|
||||
|
|
|
@ -3,8 +3,6 @@
|
|||
package bellcoin
|
||||
|
||||
import (
|
||||
"blockbook/bchain"
|
||||
"blockbook/bchain/coins/btc"
|
||||
"encoding/hex"
|
||||
"math/big"
|
||||
"os"
|
||||
|
@ -12,6 +10,8 @@ import (
|
|||
"testing"
|
||||
|
||||
"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 bellcoin
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
// BellcoinRPC is an interface to JSON-RPC bitcoind service.
|
||||
|
@ -31,10 +31,11 @@ func NewBellcoinRPC(config json.RawMessage, pushHandler func(bchain.Notification
|
|||
|
||||
// Initialize initializes BellcoinRPC instance.
|
||||
func (b *BellcoinRPC) 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,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,30 +1,6 @@
|
|||
package coins
|
||||
|
||||
import (
|
||||
"blockbook/bchain"
|
||||
"blockbook/bchain/coins/bch"
|
||||
"blockbook/bchain/coins/bellcoin"
|
||||
"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/flo"
|
||||
"blockbook/bchain/coins/fujicoin"
|
||||
"blockbook/bchain/coins/gamecredits"
|
||||
"blockbook/bchain/coins/grs"
|
||||
"blockbook/bchain/coins/koto"
|
||||
"blockbook/bchain/coins/liquid"
|
||||
"blockbook/bchain/coins/litecoin"
|
||||
"blockbook/bchain/coins/monacoin"
|
||||
"blockbook/bchain/coins/myriad"
|
||||
"blockbook/bchain/coins/namecoin"
|
||||
"blockbook/bchain/coins/pivx"
|
||||
"blockbook/bchain/coins/vertcoin"
|
||||
"blockbook/bchain/coins/xzc"
|
||||
"blockbook/bchain/coins/zec"
|
||||
"blockbook/common"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
@ -34,6 +10,48 @@ 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)
|
||||
|
@ -44,16 +62,21 @@ 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
|
||||
|
@ -65,17 +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["Zcoin"] = xzc.NewZcoinRPC
|
||||
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
|
||||
|
@ -96,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 {
|
||||
|
@ -130,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
|
||||
}
|
||||
|
@ -139,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)
|
||||
}
|
||||
|
@ -194,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) {
|
||||
|
@ -229,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 []bchain.Outpoint, 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 []bchain.Outpoint, 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)
|
||||
|
@ -281,3 +318,44 @@ func (c *blockChainWithMetrics) EthereumTypeGetErc20ContractBalance(addrDesc, co
|
|||
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,7 +1,6 @@
|
|||
package btc
|
||||
|
||||
import (
|
||||
"blockbook/bchain"
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
|
@ -16,20 +15,38 @@ import (
|
|||
"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
|
||||
XPubMagic uint32
|
||||
XPubMagicSegwitP2sh uint32
|
||||
XPubMagicSegwitNative uint32
|
||||
Slip44 uint32
|
||||
Params *chaincfg.Params
|
||||
OutputScriptToAddressesFunc OutputScriptToAddressesFunc
|
||||
XPubMagic uint32
|
||||
XPubMagicSegwitP2sh uint32
|
||||
XPubMagicSegwitNative uint32
|
||||
Slip44 uint32
|
||||
minimumCoinbaseConfirmations int
|
||||
}
|
||||
|
||||
// NewBitcoinParser returns new BitcoinParser instance
|
||||
|
@ -39,11 +56,12 @@ func NewBitcoinParser(params *chaincfg.Params, c *Configuration) *BitcoinParser
|
|||
BlockAddressesToKeep: c.BlockAddressesToKeep,
|
||||
AmountDecimalPoint: 8,
|
||||
},
|
||||
Params: params,
|
||||
XPubMagic: c.XPubMagic,
|
||||
XPubMagicSegwitP2sh: c.XPubMagicSegwitP2sh,
|
||||
XPubMagicSegwitNative: c.XPubMagicSegwitNative,
|
||||
Slip44: c.Slip44,
|
||||
Params: params,
|
||||
XPubMagic: c.XPubMagic,
|
||||
XPubMagicSegwitP2sh: c.XPubMagicSegwitP2sh,
|
||||
XPubMagicSegwitNative: c.XPubMagicSegwitNative,
|
||||
Slip44: c.Slip44,
|
||||
minimumCoinbaseConfirmations: c.MinimumCoinbaseConfirmations,
|
||||
}
|
||||
p.OutputScriptToAddressesFunc = p.outputScriptToAddresses
|
||||
return p
|
||||
|
@ -61,6 +79,8 @@ func GetChainParams(chain string) *chaincfg.Params {
|
|||
return &chaincfg.TestNet3Params
|
||||
case "regtest":
|
||||
return &chaincfg.RegressionNetParams
|
||||
case "signet":
|
||||
return &SigNetParams
|
||||
}
|
||||
return &chaincfg.MainNetParams
|
||||
}
|
||||
|
@ -73,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
|
||||
|
@ -91,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)
|
||||
|
@ -105,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>
|
||||
|
@ -124,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 {
|
||||
|
@ -131,7 +167,6 @@ func TryParseOPReturn(script []byte) string {
|
|||
break
|
||||
}
|
||||
}
|
||||
var ed string
|
||||
if isASCII {
|
||||
ed = "(" + string(data) + ")"
|
||||
} else {
|
||||
|
@ -143,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)
|
||||
|
@ -157,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}
|
||||
}
|
||||
|
@ -278,6 +346,11 @@ 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
|
||||
|
@ -304,7 +377,7 @@ func (p *BitcoinParser) addrDescFromExtKey(extKey *hdkeychain.ExtendedKey) (bcha
|
|||
|
||||
// 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)
|
||||
extKey, err := hdkeychain.NewKeyFromString(xpub, p.Params.Base58CksumHasher)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -331,7 +404,7 @@ func (p *BitcoinParser) DeriveAddressDescriptorsFromTo(xpub string, change uint3
|
|||
if toIndex <= fromIndex {
|
||||
return nil, errors.New("toIndex<=fromIndex")
|
||||
}
|
||||
extKey, err := hdkeychain.NewKeyFromString(xpub)
|
||||
extKey, err := hdkeychain.NewKeyFromString(xpub, p.Params.Base58CksumHasher)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -355,7 +428,7 @@ func (p *BitcoinParser) DeriveAddressDescriptorsFromTo(xpub string, change uint3
|
|||
|
||||
// DerivationBasePath returns base path of xpub
|
||||
func (p *BitcoinParser) DerivationBasePath(xpub string) (string, error) {
|
||||
extKey, err := hdkeychain.NewKeyFromString(xpub)
|
||||
extKey, err := hdkeychain.NewKeyFromString(xpub, p.Params.Base58CksumHasher)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
package btc
|
||||
|
||||
import (
|
||||
"blockbook/bchain"
|
||||
"encoding/hex"
|
||||
"math/big"
|
||||
"os"
|
||||
|
@ -11,6 +10,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/martinboehm/btcutil/chaincfg"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
|
@ -215,6 +215,27 @@ func TestGetAddressesFromAddrDesc(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 TestGetAddressesFromAddrDesc(t *testing.T) {
|
|||
}
|
||||
|
||||
var (
|
||||
testTx1, testTx2 bchain.Tx
|
||||
testTx1, testTx2, testTx3 bchain.Tx
|
||||
|
||||
testTxPacked1 = "0001e2408ba8d7af5401000000017f9a22c9cbf54bd902400df746f138f37bcf5b4d93eb755820e974ba43ed5f42040000006a4730440220037f4ed5427cde81d55b9b6a2fd08c8a25090c2c2fff3a75c1a57625ca8a7118022076c702fe55969fa08137f71afd4851c48e31082dd3c40c919c92cdbc826758d30121029f6da5623c9f9b68a9baf9c1bc7511df88fa34c6c2f71f7c62f2f03ff48dca80feffffff019c9700000000000017a9146144d57c8aff48492c9dfb914e120b20bad72d6f8773d00700"
|
||||
testTxPacked2 = "0007c91a899ab7da6a010000000001019d64f0c72a0d206001decbffaa722eb1044534c74eee7a5df8318e42a4323ec10000000017160014550da1f5d25a9dae2eafd6902b4194c4c6500af6ffffffff02809698000000000017a914cd668d781ece600efa4b2404dc91fd26b8b8aed8870553d7360000000017a914246655bdbd54c7e477d0ea2375e86e0db2b8f80a8702473044022076aba4ad559616905fa51d4ddd357fc1fdb428d40cb388e042cdd1da4a1b7357022011916f90c712ead9a66d5f058252efd280439ad8956a967e95d437d246710bc9012102a80a5964c5612bb769ef73147b2cf3c149bc0fd4ecb02f8097629c94ab013ffd00000000"
|
||||
testTxPacked3 = "00003d818bfda9aa3e02000000000102deb1999a857ab0a13d6b12fbd95ea75b409edde5f2ff747507ce42d9986a8b9d0000000000fdffffff9fd2d3361e203b2375eba6438efbef5b3075531e7e583c7cc76b7294fe7f22980000000000fdffffff02a0860100000000001600148091746745464e7555c31e9a5afceac14a02978ae7fc1c0000000000160014565ea9ff4589d3e05ba149ae6e257752bfdc2a1e0247304402207d67d320a8e813f986b35e9791935fcb736754812b7038686f5de6cfdcda99cd02201c3bb2c178e0056016437ecfe365a7eef84aa9d293ebdc566177af82e22fcdd3012103abb30c1bbe878b07b58dc169b1d061d48c60be8107f632a59778b38bf7ceea5a02473044022044f54a478cfe086e870cb026c9dcd4e14e63778bef569a4d55a6332725cd9a9802202f0e94c04e6f328fc64ad9efe552888c299750d1b8d033324825a3ff29920e030121036fcd433428aa7dc65c4f5408fa31f208c54fe4b4c6c1ae9c39a825ed4f1ac039813d0000"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
@ -314,6 +336,54 @@ 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 TestPackTx(t *testing.T) {
|
||||
|
@ -351,6 +421,17 @@ func TestPackTx(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) {
|
||||
|
@ -399,6 +480,16 @@ func TestUnpackTx(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) {
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package btc
|
||||
|
||||
import (
|
||||
"blockbook/bchain"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
|
@ -17,6 +16,8 @@ import (
|
|||
"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.
|
||||
|
@ -36,25 +37,28 @@ type BitcoinRPC struct {
|
|||
|
||||
// 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"`
|
||||
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"`
|
||||
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.
|
||||
|
@ -69,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
|
||||
|
@ -101,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.NewMempoolBitcoinType(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)
|
||||
|
||||
|
@ -149,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 {
|
||||
|
@ -162,10 +184,12 @@ func (b *BitcoinRPC) Shutdown(ctx context.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
@ -215,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"`
|
||||
}
|
||||
|
||||
|
@ -234,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"`
|
||||
}
|
||||
|
||||
|
@ -336,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"`
|
||||
}
|
||||
|
||||
|
@ -351,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
|
||||
|
@ -458,6 +482,7 @@ func (b *BitcoinRPC) GetChainInfo() (*bchain.ChainInfo, error) {
|
|||
return rv, nil
|
||||
}
|
||||
|
||||
// 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"
|
||||
|
@ -634,8 +659,8 @@ func (b *BitcoinRPC) GetBlockFull(hash string) (*bchain.Block, error) {
|
|||
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{}
|
||||
|
@ -651,6 +676,7 @@ func (b *BitcoinRPC) GetMempool() ([]string, error) {
|
|||
return res.Result, nil
|
||||
}
|
||||
|
||||
// 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
|
||||
|
@ -732,23 +758,6 @@ func (b *BitcoinRPC) getRawTransaction(txid string) (json.RawMessage, error) {
|
|||
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.
|
||||
// Return value is 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) ([]bchain.Outpoint, error) {
|
||||
return b.Mempool.GetTransactions(address)
|
||||
}
|
||||
|
||||
// GetMempoolTransactionsForAddrDesc returns slice of mempool transactions for given address descriptor
|
||||
func (b *BitcoinRPC) GetMempoolTransactionsForAddrDesc(addrDesc bchain.AddressDescriptor) ([]bchain.Outpoint, 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
|
||||
|
@ -810,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")
|
||||
|
||||
|
|
|
@ -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,9 +1,6 @@
|
|||
package btg
|
||||
|
||||
import (
|
||||
"blockbook/bchain"
|
||||
"blockbook/bchain/coins/btc"
|
||||
"blockbook/bchain/coins/utils"
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
|
@ -11,15 +8,22 @@ import (
|
|||
"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"
|
||||
|
@ -13,6 +12,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/martinboehm/btcutil/chaincfg"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
|
|
|
@ -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,11 +1,10 @@
|
|||
package dash
|
||||
|
||||
import (
|
||||
"blockbook/bchain"
|
||||
"blockbook/bchain/coins/btc"
|
||||
|
||||
"github.com/martinboehm/btcd/wire"
|
||||
"github.com/martinboehm/btcutil/chaincfg"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
// build unittest
|
||||
// +build unittest
|
||||
|
||||
package dash
|
||||
|
||||
import (
|
||||
"blockbook/bchain"
|
||||
"blockbook/bchain/coins/btc"
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
|
@ -13,6 +11,9 @@ import (
|
|||
"path/filepath"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
|
||||
)
|
||||
|
||||
type testBlock struct {
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
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"
|
||||
)
|
||||
|
||||
const firstBlockWithSpecialTransactions = 1028160
|
||||
|
@ -34,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)
|
||||
|
||||
|
|
|
@ -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/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,8 +3,6 @@
|
|||
package digibyte
|
||||
|
||||
import (
|
||||
"blockbook/bchain"
|
||||
"blockbook/bchain/coins/btc"
|
||||
"encoding/hex"
|
||||
"math/big"
|
||||
"os"
|
||||
|
@ -12,6 +10,8 @@ import (
|
|||
"testing"
|
||||
|
||||
"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/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"
|
||||
|
@ -16,6 +14,8 @@ import (
|
|||
"testing"
|
||||
|
||||
"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"
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -1,17 +1,18 @@
|
|||
package eth
|
||||
|
||||
import (
|
||||
"blockbook/bchain"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"math/big"
|
||||
"strings"
|
||||
"sync"
|
||||
"unicode/utf8"
|
||||
|
||||
ethcommon "github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"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"},
|
||||
|
@ -73,9 +74,9 @@ func erc20GetTransfersFromLog(logs []*rpcLog) ([]bchain.Erc20Transfer, error) {
|
|||
return nil, err
|
||||
}
|
||||
r = append(r, bchain.Erc20Transfer{
|
||||
Contract: strings.ToLower(l.Address),
|
||||
From: strings.ToLower(from),
|
||||
To: strings.ToLower(to),
|
||||
Contract: EIP55AddressFromAddress(l.Address),
|
||||
From: EIP55AddressFromAddress(from),
|
||||
To: EIP55AddressFromAddress(to),
|
||||
Tokens: t,
|
||||
})
|
||||
}
|
||||
|
@ -96,9 +97,9 @@ func erc20GetTransfersFromTx(tx *rpcTransaction) ([]bchain.Erc20Transfer, error)
|
|||
return nil, errors.New("Data is not a number")
|
||||
}
|
||||
r = append(r, bchain.Erc20Transfer{
|
||||
Contract: strings.ToLower(tx.To),
|
||||
From: strings.ToLower(tx.From),
|
||||
To: strings.ToLower(to),
|
||||
Contract: EIP55AddressFromAddress(tx.To),
|
||||
From: EIP55AddressFromAddress(tx.From),
|
||||
To: EIP55AddressFromAddress(to),
|
||||
Tokens: t,
|
||||
})
|
||||
}
|
||||
|
@ -144,7 +145,7 @@ func parseErc20StringProperty(contractDesc bchain.AddressDescriptor, data string
|
|||
n := parseErc20NumericProperty(contractDesc, data[64:128])
|
||||
if n != nil {
|
||||
l := n.Uint64()
|
||||
if 2*int(l) <= len(data)-128 {
|
||||
if l > 0 && 2*int(l) <= len(data)-128 {
|
||||
b, err := hex.DecodeString(data[128 : 128+2*l])
|
||||
if err == nil {
|
||||
return string(b)
|
||||
|
@ -152,6 +153,20 @@ func parseErc20StringProperty(contractDesc bchain.AddressDescriptor, data string
|
|||
}
|
||||
}
|
||||
}
|
||||
// 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)
|
||||
}
|
||||
|
@ -165,21 +180,29 @@ func (b *EthereumRPC) EthereumTypeGetErc20ContractInfo(contractDesc bchain.Addre
|
|||
contract, found := cachedContracts[cds]
|
||||
cachedContractsMux.Unlock()
|
||||
if !found {
|
||||
address := hexutil.Encode(contractDesc)
|
||||
address := EIP55Address(contractDesc)
|
||||
data, err := b.ethCall(erc20NameSignature, address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
// 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 {
|
||||
return nil, err
|
||||
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 {
|
||||
return nil, err
|
||||
glog.Warning(errors.Annotatef(err, "erc20DecimalsSignature %v", address))
|
||||
// return nil, errors.Annotatef(err, "erc20DecimalsSignature %v", address)
|
||||
}
|
||||
contract = &bchain.Erc20Contract{
|
||||
Contract: address,
|
||||
|
@ -204,8 +227,8 @@ func (b *EthereumRPC) EthereumTypeGetErc20ContractInfo(contractDesc bchain.Addre
|
|||
|
||||
// EthereumTypeGetErc20ContractBalance returns balance of ERC20 contract for given address
|
||||
func (b *EthereumRPC) EthereumTypeGetErc20ContractBalance(addrDesc, contractDesc bchain.AddressDescriptor) (*big.Int, error) {
|
||||
addr := hexutil.Encode(addrDesc)
|
||||
contract := hexutil.Encode(contractDesc)
|
||||
addr := EIP55Address(addrDesc)
|
||||
contract := EIP55Address(contractDesc)
|
||||
req := erc20BalanceOf + "0000000000000000000000000000000000000000000000000000000000000000"[len(addr)-2:] + addr[2:]
|
||||
data, err := b.ethCall(req, contract)
|
||||
if err != nil {
|
||||
|
|
|
@ -3,12 +3,13 @@
|
|||
package eth
|
||||
|
||||
import (
|
||||
"blockbook/bchain"
|
||||
"blockbook/tests/dbtestdata"
|
||||
fmt "fmt"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
"spacecruft.org/spacecruft/blockbook/tests/dbtestdata"
|
||||
)
|
||||
|
||||
func TestErc20_erc20GetTransfersFromLog(t *testing.T) {
|
||||
|
@ -128,6 +129,26 @@ func TestErc20_parseErc20StringProperty(t *testing.T) {
|
|||
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) {
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package eth
|
||||
|
||||
import (
|
||||
"blockbook/bchain"
|
||||
"encoding/hex"
|
||||
"math/big"
|
||||
"strconv"
|
||||
|
@ -9,6 +8,8 @@ import (
|
|||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/juju/errors"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
"golang.org/x/crypto/sha3"
|
||||
)
|
||||
|
||||
// EthereumTypeAddressDescriptorLen - in case of EthereumType, the AddressDescriptor has fixed length
|
||||
|
@ -75,12 +76,6 @@ type rpcReceipt struct {
|
|||
Logs []*rpcLog `json:"logs"`
|
||||
}
|
||||
|
||||
type rpcEtcReceipt struct {
|
||||
GasUsed string `json:"gasUsed"`
|
||||
Status int `json:"status"`
|
||||
Logs []*rpcLog `json:"logs"`
|
||||
}
|
||||
|
||||
type completeTransaction struct {
|
||||
Tx *rpcTransaction `json:"tx"`
|
||||
Receipt *rpcReceipt `json:"receipt,omitempty"`
|
||||
|
@ -101,18 +96,31 @@ func ethNumber(n string) (int64, error) {
|
|||
return 0, errors.Errorf("Not a number: '%v'", n)
|
||||
}
|
||||
|
||||
func (p *EthereumParser) ethTxToTx(tx *rpcTransaction, receipt *rpcReceipt, blocktime int64, confirmations uint32) (*bchain.Tx, error) {
|
||||
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}
|
||||
}
|
||||
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,
|
||||
|
@ -176,9 +184,46 @@ func (p *EthereumParser) GetAddrDescFromAddress(address string) (bchain.AddressD
|
|||
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
|
||||
|
@ -266,8 +311,14 @@ func (p *EthereumParser) PackTx(tx *bchain.Tx, height uint32, blockTime int64) (
|
|||
if pt.Receipt.GasUsed, err = hexDecodeBig(r.Receipt.GasUsed); err != nil {
|
||||
return nil, errors.Annotatef(err, "GasUsed %v", r.Receipt.GasUsed)
|
||||
}
|
||||
if pt.Receipt.Status, err = hexDecodeBig(r.Receipt.Status); err != nil {
|
||||
return nil, errors.Annotatef(err, "Status %v", r.Receipt.Status)
|
||||
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 {
|
||||
|
@ -308,7 +359,7 @@ func (p *EthereumParser) UnpackTx(buf []byte) (*bchain.Tx, uint32, error) {
|
|||
rt := rpcTransaction{
|
||||
AccountNonce: hexutil.EncodeUint64(pt.Tx.AccountNonce),
|
||||
BlockNumber: hexutil.EncodeUint64(uint64(pt.BlockNumber)),
|
||||
From: hexutil.Encode(pt.Tx.From),
|
||||
From: EIP55Address(pt.Tx.From),
|
||||
GasLimit: hexutil.EncodeUint64(pt.Tx.GasLimit),
|
||||
Hash: hexutil.Encode(pt.Tx.Hash),
|
||||
Payload: hexutil.Encode(pt.Tx.Payload),
|
||||
|
@ -316,7 +367,7 @@ func (p *EthereumParser) UnpackTx(buf []byte) (*bchain.Tx, uint32, error) {
|
|||
// R: hexEncodeBig(pt.R),
|
||||
// S: hexEncodeBig(pt.S),
|
||||
// V: hexEncodeBig(pt.V),
|
||||
To: hexutil.Encode(pt.Tx.To),
|
||||
To: EIP55Address(pt.Tx.To),
|
||||
TransactionIndex: hexutil.EncodeUint64(uint64(pt.Tx.TransactionIndex)),
|
||||
Value: hexEncodeBig(pt.Tx.Value),
|
||||
}
|
||||
|
@ -329,18 +380,23 @@ func (p *EthereumParser) UnpackTx(buf []byte) (*bchain.Tx, uint32, error) {
|
|||
topics[j] = hexutil.Encode(t)
|
||||
}
|
||||
logs[i] = &rpcLog{
|
||||
Address: hexutil.Encode(l.Address),
|
||||
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: hexEncodeBig(pt.Receipt.Status),
|
||||
Status: status,
|
||||
Logs: logs,
|
||||
}
|
||||
}
|
||||
tx, err := p.ethTxToTx(&rt, rr, int64(pt.BlockTime), 0)
|
||||
tx, err := p.ethTxToTx(&rt, rr, int64(pt.BlockTime), 0, false)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
@ -416,40 +472,51 @@ func (p *EthereumParser) EthereumTypeGetErc20FromTx(tx *bchain.Tx) ([]bchain.Erc
|
|||
return r, nil
|
||||
}
|
||||
|
||||
// TxStatus is status of transaction
|
||||
type TxStatus int
|
||||
|
||||
// statuses of transaction
|
||||
const (
|
||||
txStatusUnknown = iota - 2
|
||||
txStatusPending
|
||||
txStatusFailure
|
||||
txStatusOK
|
||||
TxStatusUnknown = TxStatus(iota - 2)
|
||||
TxStatusPending
|
||||
TxStatusFailure
|
||||
TxStatusOK
|
||||
)
|
||||
|
||||
// EthereumTxData contains ethereum specific transaction data
|
||||
type EthereumTxData struct {
|
||||
Status int `json:"status"` // 1 OK, 0 Fail, -1 pending, -2 unknown
|
||||
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 {
|
||||
etd := EthereumTxData{Status: txStatusPending}
|
||||
csd, ok := tx.CoinSpecificData.(completeTransaction)
|
||||
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
|
||||
etd.Status = TxStatusOK
|
||||
case "": // old transactions did not set status
|
||||
etd.Status = txStatusUnknown
|
||||
etd.Status = TxStatusUnknown
|
||||
default:
|
||||
etd.Status = txStatusFailure
|
||||
etd.Status = TxStatusFailure
|
||||
}
|
||||
etd.GasUsed, _ = hexutil.DecodeBig(csd.Receipt.GasUsed)
|
||||
}
|
||||
|
|
|
@ -3,13 +3,14 @@
|
|||
package eth
|
||||
|
||||
import (
|
||||
"blockbook/bchain"
|
||||
"blockbook/tests/dbtestdata"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
"spacecruft.org/spacecruft/blockbook/tests/dbtestdata"
|
||||
)
|
||||
|
||||
func TestEthParser_GetAddrDescFromAddress(t *testing.T) {
|
||||
|
@ -67,7 +68,7 @@ func TestEthParser_GetAddrDescFromAddress(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
var testTx1, testTx2 bchain.Tx
|
||||
var testTx1, testTx2, testTx1Failed, testTx1NoStatus bchain.Tx
|
||||
|
||||
func init() {
|
||||
|
||||
|
@ -77,14 +78,14 @@ func init() {
|
|||
Txid: "0xcd647151552b5132b2aef7c9be00dc6f73afc5901dde157aab131335baaa853b",
|
||||
Vin: []bchain.Vin{
|
||||
{
|
||||
Addresses: []string{"0x3e3a3d69dc66ba10737f531ed088954a9ec89d97"},
|
||||
Addresses: []string{"0x3E3a3D69dc66bA10737F531ed088954a9EC89d97"},
|
||||
},
|
||||
},
|
||||
Vout: []bchain.Vout{
|
||||
{
|
||||
ValueSat: *big.NewInt(1999622000000000000),
|
||||
ScriptPubKey: bchain.ScriptPubKey{
|
||||
Addresses: []string{"0x555ee11fbddc0e49a9bab358a8941ad95ffdb48f"},
|
||||
Addresses: []string{"0x555Ee11FBDDc0E49A9bAB358A8941AD95fFDB48f"},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -93,12 +94,12 @@ func init() {
|
|||
AccountNonce: "0xb26c",
|
||||
GasPrice: "0x430e23400",
|
||||
GasLimit: "0x5208",
|
||||
To: "0x555ee11fbddc0e49a9bab358a8941ad95ffdb48f",
|
||||
To: "0x555Ee11FBDDc0E49A9bAB358A8941AD95fFDB48f",
|
||||
Value: "0x1bc0159d530e6000",
|
||||
Payload: "0x",
|
||||
Hash: "0xcd647151552b5132b2aef7c9be00dc6f73afc5901dde157aab131335baaa853b",
|
||||
BlockNumber: "0x41eee8",
|
||||
From: "0x3e3a3d69dc66ba10737f531ed088954a9ec89d97",
|
||||
From: "0x3E3a3D69dc66bA10737F531ed088954a9EC89d97",
|
||||
TransactionIndex: "0xa",
|
||||
},
|
||||
Receipt: &rpcReceipt{
|
||||
|
@ -115,14 +116,14 @@ func init() {
|
|||
Txid: "0xa9cd088aba2131000da6f38a33c20169baee476218deea6b78720700b895b101",
|
||||
Vin: []bchain.Vin{
|
||||
{
|
||||
Addresses: []string{"0x20cd153de35d469ba46127a0c8f18626b59a256a"},
|
||||
Addresses: []string{"0x20cD153de35D469BA46127A0C8F18626b59a256A"},
|
||||
},
|
||||
},
|
||||
Vout: []bchain.Vout{
|
||||
{
|
||||
ValueSat: *big.NewInt(0),
|
||||
ScriptPubKey: bchain.ScriptPubKey{
|
||||
Addresses: []string{"0x4af4114f73d1c1c903ac9e0361b379d1291808a2"},
|
||||
Addresses: []string{"0x4af4114F73d1c1C903aC9E0361b379D1291808A2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -131,19 +132,19 @@ func init() {
|
|||
AccountNonce: "0xd0",
|
||||
GasPrice: "0x9502f9000",
|
||||
GasLimit: "0x130d5",
|
||||
To: "0x4af4114f73d1c1c903ac9e0361b379d1291808a2",
|
||||
To: "0x4af4114F73d1c1C903aC9E0361b379D1291808A2",
|
||||
Value: "0x0",
|
||||
Payload: "0xa9059cbb000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f00000000000000000000000000000000000000000000021e19e0c9bab2400000",
|
||||
Hash: "0xa9cd088aba2131000da6f38a33c20169baee476218deea6b78720700b895b101",
|
||||
BlockNumber: "0x41eee8",
|
||||
From: "0x20cd153de35d469ba46127a0c8f18626b59a256a",
|
||||
From: "0x20cD153de35D469BA46127A0C8F18626b59a256A",
|
||||
TransactionIndex: "0x0"},
|
||||
Receipt: &rpcReceipt{
|
||||
GasUsed: "0xcb39",
|
||||
Status: "0x1",
|
||||
Logs: []*rpcLog{
|
||||
{
|
||||
Address: "0x4af4114f73d1c1c903ac9e0361b379d1291808a2",
|
||||
Address: "0x4af4114F73d1c1C903aC9E0361b379D1291808A2",
|
||||
Data: "0x00000000000000000000000000000000000000000000021e19e0c9bab2400000",
|
||||
Topics: []string{
|
||||
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
|
||||
|
@ -155,6 +156,83 @@ func init() {
|
|||
},
|
||||
},
|
||||
}
|
||||
|
||||
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) {
|
||||
|
@ -188,6 +266,24 @@ func TestEthereumParser_PackTx(t *testing.T) {
|
|||
},
|
||||
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(1)
|
||||
for _, tt := range tests {
|
||||
|
@ -229,6 +325,18 @@ func TestEthereumParser_UnpackTx(t *testing.T) {
|
|||
want: &testTx2,
|
||||
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(1)
|
||||
for _, tt := range tests {
|
||||
|
@ -265,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"
|
||||
|
@ -12,11 +11,14 @@ import (
|
|||
|
||||
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
|
||||
|
@ -27,36 +29,38 @@ 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"`
|
||||
BlockAddressesToKeep int `json:"block_addresses_to_keep"`
|
||||
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
|
||||
Mempool *bchain.MempoolEthereumType
|
||||
bestHeaderLock sync.Mutex
|
||||
bestHeader *ethtypes.Header
|
||||
bestHeaderTime time.Time
|
||||
chanNewBlock chan *ethtypes.Header
|
||||
newBlockSubscription *rpc.ClientSubscription
|
||||
chanNewTx chan ethcommon.Hash
|
||||
newTxSubscription *rpc.ClientSubscription
|
||||
pendingTransactions map[string]struct{}
|
||||
pendingTransactionsLock sync.Mutex
|
||||
ChainConfig *Configuration
|
||||
isETC bool
|
||||
client *ethclient.Client
|
||||
rpc *rpc.Client
|
||||
timeout time.Duration
|
||||
Parser *EthereumParser
|
||||
Mempool *bchain.MempoolEthereumType
|
||||
mempoolInitialized bool
|
||||
bestHeaderLock sync.Mutex
|
||||
bestHeader *ethtypes.Header
|
||||
bestHeaderTime time.Time
|
||||
chanNewBlock chan *ethtypes.Header
|
||||
newBlockSubscription *rpc.ClientSubscription
|
||||
chanNewTx chan ethcommon.Hash
|
||||
newTxSubscription *rpc.ClientSubscription
|
||||
ChainConfig *Configuration
|
||||
}
|
||||
|
||||
// NewEthereumRPC returns new EthRPC instance.
|
||||
|
@ -71,27 +75,23 @@ func NewEthereumRPC(config json.RawMessage, pushHandler func(bchain.Notification
|
|||
if c.BlockAddressesToKeep < 100 {
|
||||
c.BlockAddressesToKeep = 100
|
||||
}
|
||||
rc, err := rpc.Dial(c.RPCURL)
|
||||
|
||||
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,
|
||||
pendingTransactions: make(map[string]struct{}),
|
||||
BaseChain: &bchain.BaseChain{},
|
||||
client: ec,
|
||||
rpc: rc,
|
||||
ChainConfig: &c,
|
||||
}
|
||||
|
||||
// always create parser
|
||||
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)
|
||||
|
@ -125,9 +125,7 @@ func NewEthereumRPC(config json.RawMessage, pushHandler func(bchain.Notification
|
|||
if glog.V(2) {
|
||||
glog.Info("rpc: new tx ", hex)
|
||||
}
|
||||
s.pendingTransactionsLock.Lock()
|
||||
s.pendingTransactions[hex] = struct{}{}
|
||||
s.pendingTransactionsLock.Unlock()
|
||||
s.Mempool.AddTransactionToMempool(hex)
|
||||
pushHandler(bchain.NotificationNewTx)
|
||||
}
|
||||
}()
|
||||
|
@ -135,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)
|
||||
|
@ -155,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)
|
||||
|
@ -196,9 +243,6 @@ func (b *EthereumRPC) Initialize() error {
|
|||
return err
|
||||
}
|
||||
|
||||
// create mempool
|
||||
b.Mempool = bchain.NewMempoolEthereumType(b)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -218,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():
|
||||
|
@ -233,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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -241,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()
|
||||
}
|
||||
|
@ -252,6 +296,23 @@ 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
|
||||
|
@ -269,29 +330,25 @@ func (b *EthereumRPC) GetSubversion() string {
|
|||
|
||||
// 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
|
||||
}
|
||||
h, err := b.getBestHeader()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var ver, protocol string
|
||||
var ver string
|
||||
if err := b.rpc.CallContext(ctx, &ver, "web3_clientVersion"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := b.rpc.CallContext(ctx, &protocol, "eth_protocolVersion"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rv := &bchain.ChainInfo{
|
||||
Blocks: int(h.Number.Int64()),
|
||||
Bestblockhash: h.Hash().Hex(),
|
||||
Difficulty: h.Difficulty.String(),
|
||||
Version: ver,
|
||||
ProtocolVersion: protocol,
|
||||
Blocks: int(h.Number.Int64()),
|
||||
Bestblockhash: h.Hash().Hex(),
|
||||
Difficulty: h.Difficulty.String(),
|
||||
Version: ver,
|
||||
}
|
||||
idi := int(id.Uint64())
|
||||
if idi == 1 {
|
||||
|
@ -305,11 +362,14 @@ func (b *EthereumRPC) GetChainInfo() (*bchain.ChainInfo, error) {
|
|||
func (b *EthereumRPC) getBestHeader() (*ethtypes.Header, error) {
|
||||
b.bestHeaderLock.Lock()
|
||||
defer b.bestHeaderLock.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
|
||||
// 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
|
||||
|
@ -317,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()
|
||||
|
@ -476,14 +537,14 @@ func (b *EthereumRPC) GetBlock(hash string, height uint32) (*bchain.Block, error
|
|||
btxs := make([]bchain.Tx, len(body.Transactions))
|
||||
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))
|
||||
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)
|
||||
}
|
||||
btxs[i] = *btx
|
||||
b.pendingTransactionsLock.Lock()
|
||||
delete(b.pendingTransactions, tx.Hash)
|
||||
b.pendingTransactionsLock.Unlock()
|
||||
if b.mempoolInitialized {
|
||||
b.Mempool.RemoveTransactionFromMempool(tx.Hash)
|
||||
}
|
||||
}
|
||||
bbk := bchain.Block{
|
||||
BlockHeader: *bbh,
|
||||
|
@ -512,8 +573,8 @@ func (b *EthereumRPC) GetBlockInfo(hash string) (*bchain.BlockInfo, error) {
|
|||
}
|
||||
return &bchain.BlockInfo{
|
||||
BlockHeader: *bch,
|
||||
Difficulty: json.Number(head.Difficulty),
|
||||
Nonce: json.Number(head.Nonce),
|
||||
Difficulty: common.JSONNumber(head.Difficulty),
|
||||
Nonce: common.JSONNumber(head.Nonce),
|
||||
Txids: txs.Transactions,
|
||||
}, nil
|
||||
}
|
||||
|
@ -521,14 +582,7 @@ func (b *EthereumRPC) GetBlockInfo(hash string) (*bchain.BlockInfo, error) {
|
|||
// GetTransactionForMempool returns a transaction by the transaction ID.
|
||||
// It could be optimized for mempool, i.e. without block time and confirmations
|
||||
func (b *EthereumRPC) GetTransactionForMempool(txid string) (*bchain.Tx, error) {
|
||||
tx, err := b.GetTransaction(txid)
|
||||
// if there is an error getting the tx or the tx is confirmed, remove it from pending transactions
|
||||
if err == bchain.ErrTxNotFound || (tx != nil && tx.Confirmations > 0) {
|
||||
b.pendingTransactionsLock.Lock()
|
||||
delete(b.pendingTransactions, txid)
|
||||
b.pendingTransactionsLock.Unlock()
|
||||
}
|
||||
return tx, err
|
||||
return b.GetTransaction(txid)
|
||||
}
|
||||
|
||||
// GetTransaction returns a transaction by the transaction ID.
|
||||
|
@ -541,12 +595,15 @@ func (b *EthereumRPC) GetTransaction(txid string) (*bchain.Tx, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
} else if tx == nil {
|
||||
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, nil, 0, 0)
|
||||
btx, err = b.Parser.ethTxToTx(tx, nil, 0, 0, true)
|
||||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "txid %v", txid)
|
||||
}
|
||||
|
@ -567,33 +624,9 @@ func (b *EthereumRPC) GetTransaction(txid string) (*bchain.Tx, error) {
|
|||
return nil, errors.Annotatef(err, "txid %v", txid)
|
||||
}
|
||||
var receipt rpcReceipt
|
||||
if b.isETC {
|
||||
var rawReceipt json.RawMessage
|
||||
var etcReceipt rpcEtcReceipt
|
||||
err = b.rpc.CallContext(ctx, &rawReceipt, "eth_getTransactionReceipt", hash)
|
||||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "txid %v", txid)
|
||||
}
|
||||
err = json.Unmarshal(rawReceipt, &etcReceipt)
|
||||
if err == nil {
|
||||
receipt.GasUsed = etcReceipt.GasUsed
|
||||
receipt.Logs = etcReceipt.Logs
|
||||
if etcReceipt.Status == 0 {
|
||||
receipt.Status = "0x0"
|
||||
} else {
|
||||
receipt.Status = "0x1"
|
||||
}
|
||||
} else {
|
||||
err = json.Unmarshal(rawReceipt, &receipt)
|
||||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "unmarshal receipt for txid %v, %v", txid, string(rawReceipt))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
err = b.rpc.CallContext(ctx, &receipt, "eth_getTransactionReceipt", hash)
|
||||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "txid %v", txid)
|
||||
}
|
||||
err = b.rpc.CallContext(ctx, &receipt, "eth_getTransactionReceipt", hash)
|
||||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "txid %v", txid)
|
||||
}
|
||||
n, err := ethNumber(tx.BlockNumber)
|
||||
if err != nil {
|
||||
|
@ -603,10 +636,14 @@ 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, &receipt, time, 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
|
||||
}
|
||||
|
@ -628,8 +665,8 @@ func (b *EthereumRPC) GetTransactionSpecific(tx *bchain.Tx) (json.RawMessage, er
|
|||
return json.RawMessage(m), err
|
||||
}
|
||||
|
||||
// GetMempool returns transactions in mempool
|
||||
func (b *EthereumRPC) GetMempool() ([]string, error) {
|
||||
// GetMempoolTransactions returns transactions in mempool
|
||||
func (b *EthereumRPC) GetMempoolTransactions() ([]string, error) {
|
||||
raw, err := b.getBlockRaw("pending", 0, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -640,19 +677,7 @@ func (b *EthereumRPC) GetMempool() ([]string, error) {
|
|||
return nil, err
|
||||
}
|
||||
}
|
||||
b.pendingTransactionsLock.Lock()
|
||||
// join transactions returned by getBlockRaw with pendingTransactions from subscription
|
||||
for _, txid := range body.Transactions {
|
||||
b.pendingTransactions[txid] = struct{}{}
|
||||
}
|
||||
txids := make([]string, len(b.pendingTransactions))
|
||||
i := 0
|
||||
for txid := range b.pendingTransactions {
|
||||
txids[i] = txid
|
||||
i++
|
||||
}
|
||||
b.pendingTransactionsLock.Unlock()
|
||||
return txids, nil
|
||||
return body.Transactions, nil
|
||||
}
|
||||
|
||||
// EstimateFee returns fee estimation
|
||||
|
@ -699,6 +724,18 @@ func (b *EthereumRPC) EthereumTypeEstimateGas(params map[string]interface{}) (ui
|
|||
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)
|
||||
}
|
||||
|
||||
|
@ -737,28 +774,6 @@ func (b *EthereumRPC) EthereumTypeGetNonce(addrDesc bchain.AddressDescriptor) (u
|
|||
return b.client.NonceAt(ctx, ethcommon.BytesToAddress(addrDesc), nil)
|
||||
}
|
||||
|
||||
// ResyncMempool gets mempool transactions and maps output scripts to transactions.
|
||||
// ResyncMempool is not reentrant, it should be called from a single thread.
|
||||
// Return value is number of transactions in mempool
|
||||
func (b *EthereumRPC) ResyncMempool(onNewTxAddr bchain.OnNewTxAddrFunc) (int, error) {
|
||||
return b.Mempool.Resync(onNewTxAddr)
|
||||
}
|
||||
|
||||
// GetMempoolTransactions returns slice of mempool transactions for given address
|
||||
func (b *EthereumRPC) GetMempoolTransactions(address string) ([]bchain.Outpoint, error) {
|
||||
return b.Mempool.GetTransactions(address)
|
||||
}
|
||||
|
||||
// GetMempoolTransactionsForAddrDesc returns slice of mempool transactions for given address descriptor
|
||||
func (b *EthereumRPC) GetMempoolTransactionsForAddrDesc(addrDesc bchain.AddressDescriptor) ([]bchain.Outpoint, error) {
|
||||
return b.Mempool.GetAddrDescTransactions(addrDesc)
|
||||
}
|
||||
|
||||
// GetMempoolEntry is not supported by etherem
|
||||
func (b *EthereumRPC) GetMempoolEntry(txid string) (*bchain.MempoolEntry, error) {
|
||||
return nil, errors.New("GetMempoolEntry: not supported")
|
||||
}
|
||||
|
||||
// GetChainParser returns ethereum BlockChainParser
|
||||
func (b *EthereumRPC) GetChainParser() bchain.BlockChainParser {
|
||||
return b.Parser
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// source: tx.proto
|
||||
// source: bchain/coins/eth/ethtx.proto
|
||||
|
||||
/*
|
||||
Package eth is a generated protocol buffer package.
|
||||
|
||||
It is generated from these files:
|
||||
tx.proto
|
||||
bchain/coins/eth/ethtx.proto
|
||||
|
||||
It has these top-level messages:
|
||||
ProtoCompleteTransaction
|
||||
|
@ -228,33 +228,34 @@ func init() {
|
|||
proto.RegisterType((*ProtoCompleteTransaction_ReceiptType_LogType)(nil), "eth.ProtoCompleteTransaction.ReceiptType.LogType")
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("tx.proto", fileDescriptor0) }
|
||||
func init() { proto.RegisterFile("bchain/coins/eth/ethtx.proto", fileDescriptor0) }
|
||||
|
||||
var fileDescriptor0 = []byte{
|
||||
// 393 bytes of a gzipped FileDescriptorProto
|
||||
// 409 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x92, 0xdf, 0x8a, 0xd4, 0x30,
|
||||
0x14, 0xc6, 0xe9, 0x9f, 0xf9, 0xb3, 0xa7, 0x55, 0x24, 0x88, 0x84, 0xe2, 0x45, 0x59, 0xbc, 0xa8,
|
||||
0x5e, 0x14, 0x5c, 0x7d, 0x81, 0x75, 0xc4, 0x55, 0x18, 0xd6, 0x21, 0x46, 0xef, 0xb3, 0x69, 0xd8,
|
||||
0x29, 0xb6, 0x4d, 0x69, 0x52, 0xe8, 0xbe, 0x91, 0x2f, 0xe4, 0xbb, 0x78, 0x29, 0x39, 0x4d, 0xd7,
|
||||
0x11, 0x51, 0xbc, 0x3b, 0xbf, 0x6f, 0xce, 0x37, 0xf9, 0xbe, 0xa4, 0xb0, 0xb5, 0x53, 0xd9, 0x0f,
|
||||
0xda, 0x6a, 0x12, 0x29, 0x7b, 0x3c, 0xff, 0xb6, 0x02, 0x7a, 0x70, 0xb8, 0xd3, 0x6d, 0xdf, 0x28,
|
||||
0xab, 0xf8, 0x20, 0x3a, 0x23, 0xa4, 0xad, 0x75, 0x47, 0x72, 0x48, 0xde, 0x34, 0x5a, 0x7e, 0xbd,
|
||||
0x1e, 0xdb, 0x1b, 0x35, 0xd0, 0x20, 0x0f, 0x8a, 0x07, 0xec, 0x54, 0x22, 0x4f, 0xe1, 0x0c, 0x91,
|
||||
0xd7, 0xad, 0xa2, 0x61, 0x1e, 0x14, 0x31, 0xfb, 0x25, 0x90, 0xd7, 0x10, 0xf2, 0x89, 0x46, 0x79,
|
||||
0x50, 0x24, 0x17, 0xcf, 0x4a, 0x65, 0x8f, 0xe5, 0xdf, 0x8e, 0x2a, 0xf9, 0xc4, 0xef, 0x7a, 0xc5,
|
||||
0x42, 0x3e, 0x91, 0x1d, 0x6c, 0x98, 0x92, 0xaa, 0xee, 0x2d, 0x8d, 0xd1, 0xfa, 0xfc, 0xdf, 0x56,
|
||||
0xbf, 0x8c, 0xfe, 0xc5, 0x99, 0xfd, 0x08, 0x60, 0x3d, 0xff, 0x27, 0x39, 0x87, 0xf4, 0x52, 0x4a,
|
||||
0x3d, 0x76, 0xf6, 0x5a, 0x77, 0x52, 0x61, 0x8d, 0x98, 0xfd, 0xa6, 0x91, 0x0c, 0xb6, 0x57, 0xc2,
|
||||
0x1c, 0x86, 0x5a, 0xce, 0x35, 0x52, 0x76, 0xcf, 0xfe, 0xb7, 0x7d, 0xdd, 0xd6, 0x16, 0xbb, 0xc4,
|
||||
0xec, 0x9e, 0xc9, 0x63, 0x58, 0x7d, 0x11, 0xcd, 0xa8, 0x30, 0x69, 0xca, 0x66, 0x20, 0x14, 0x36,
|
||||
0x07, 0x71, 0xd7, 0x68, 0x51, 0xd1, 0x15, 0xea, 0x0b, 0x12, 0x02, 0xf1, 0x7b, 0x61, 0x8e, 0x74,
|
||||
0x8d, 0x32, 0xce, 0xe4, 0x21, 0x84, 0x5c, 0xd3, 0x0d, 0x2a, 0x21, 0xd7, 0x6e, 0xe7, 0xdd, 0xa0,
|
||||
0x5b, 0xba, 0x9d, 0x77, 0xdc, 0x4c, 0x5e, 0xc0, 0xa3, 0x93, 0xca, 0x1f, 0xba, 0x4a, 0x4d, 0xf4,
|
||||
0x0c, 0x9f, 0xe3, 0x0f, 0x3d, 0xfb, 0x1e, 0x40, 0x72, 0x72, 0x27, 0x2e, 0xcd, 0x95, 0x30, 0x9f,
|
||||
0x8d, 0xaa, 0xb0, 0x7a, 0xca, 0x16, 0x24, 0x4f, 0x60, 0xfd, 0xc9, 0x0a, 0x3b, 0x1a, 0xdf, 0xd9,
|
||||
0x13, 0xd9, 0x41, 0xb4, 0xd7, 0xb7, 0x34, 0xca, 0xa3, 0x22, 0xb9, 0x78, 0xf9, 0xdf, 0xb7, 0x5f,
|
||||
0xee, 0xf5, 0x2d, 0xbe, 0x82, 0x73, 0x67, 0x1f, 0x61, 0xe3, 0xd9, 0x25, 0xb8, 0xac, 0xaa, 0x41,
|
||||
0x19, 0xb3, 0x24, 0xf0, 0xe8, 0xba, 0xbe, 0x15, 0x56, 0xf8, 0xf3, 0x71, 0x76, 0xa9, 0xb8, 0xee,
|
||||
0x6b, 0x69, 0x30, 0x40, 0xca, 0x3c, 0xdd, 0xac, 0xf1, 0xb3, 0x7d, 0xf5, 0x33, 0x00, 0x00, 0xff,
|
||||
0xff, 0xde, 0xd5, 0x28, 0xa3, 0xc2, 0x02, 0x00, 0x00,
|
||||
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,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
|
||||
}
|
|
@ -1,8 +1,6 @@
|
|||
package xzc
|
||||
package firo
|
||||
|
||||
import (
|
||||
"blockbook/bchain"
|
||||
"blockbook/bchain/coins/btc"
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
|
@ -11,11 +9,18 @@ import (
|
|||
"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
|
||||
OpZeroCoinMint = 0xc1
|
||||
OpZeroCoinSpend = 0xc2
|
||||
OpSigmaMint = 0xc3
|
||||
OpSigmaSpend = 0xc4
|
||||
OpLelantusMint = 0xc5
|
||||
OpLelantusJMint = 0xc6
|
||||
OpLelantusJoinSplit = 0xc7
|
||||
|
||||
MainnetMagic wire.BitcoinNet = 0xe3d9fef1
|
||||
TestnetMagic wire.BitcoinNet = 0xcffcbeea
|
||||
|
@ -26,6 +31,8 @@ const (
|
|||
MTPL = 64
|
||||
|
||||
SpendTxID = "0000000000000000000000000000000000000000000000000000000000000000"
|
||||
|
||||
TransactionQuorumCommitmentType = 6
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -56,21 +63,21 @@ func init() {
|
|||
RegtestParams.Net = RegtestMagic
|
||||
}
|
||||
|
||||
// ZcoinParser handle
|
||||
type ZcoinParser struct {
|
||||
// FiroParser handle
|
||||
type FiroParser struct {
|
||||
*btc.BitcoinParser
|
||||
}
|
||||
|
||||
// NewZcoinParser returns new ZcoinParser instance
|
||||
func NewZcoinParser(params *chaincfg.Params, c *btc.Configuration) *ZcoinParser {
|
||||
return &ZcoinParser{
|
||||
// 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 Zcoin network,
|
||||
// the regression test Zcoin network, the test Zcoin network and
|
||||
// the simulation test Zcoin network, in this order
|
||||
// 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)
|
||||
|
@ -95,30 +102,52 @@ func GetChainParams(chain string) *chaincfg.Params {
|
|||
}
|
||||
|
||||
// GetAddressesFromAddrDesc returns addresses for given address descriptor with flag if the addresses are searchable
|
||||
func (p *ZcoinParser) GetAddressesFromAddrDesc(addrDesc bchain.AddressDescriptor) ([]string, bool, error) {
|
||||
if len(addrDesc) > 0 && addrDesc[0] == OpZeroCoinMint {
|
||||
return []string{"Zeromint"}, false, nil
|
||||
}
|
||||
func (p *FiroParser) GetAddressesFromAddrDesc(addrDesc bchain.AddressDescriptor) ([]string, bool, error) {
|
||||
|
||||
if len(addrDesc) > 0 && addrDesc[0] == OpZeroCoinSpend {
|
||||
return []string{"Zerospend"}, false, nil
|
||||
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 *ZcoinParser) PackTx(tx *bchain.Tx, height uint32, blockTime int64) ([]byte, error) {
|
||||
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 *ZcoinParser) UnpackTx(buf []byte) (*bchain.Tx, uint32, error) {
|
||||
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 *ZcoinParser) ParseBlock(b []byte) (*bchain.Block, error) {
|
||||
func (p *FiroParser) ParseBlock(b []byte) (*bchain.Block, error) {
|
||||
reader := bytes.NewReader(b)
|
||||
|
||||
// parse standard block header first
|
||||
|
@ -173,16 +202,37 @@ func (p *ZcoinParser) ParseBlock(b []byte) (*bchain.Block, error) {
|
|||
txs := make([]bchain.Tx, ntx)
|
||||
|
||||
for i := uint64(0); i < ntx; i++ {
|
||||
tx := wire.MsgTx{}
|
||||
tx := FiroMsgTx{}
|
||||
|
||||
err := tx.BtcDecode(reader, 0, wire.WitnessEncoding)
|
||||
if err != nil {
|
||||
// read version and seek back
|
||||
var version uint32 = 0
|
||||
if err = binary.Read(reader, binary.LittleEndian, &version); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
btx := p.TxFromMsgTx(&tx, false)
|
||||
if _, err = reader.Seek(-4, io.SeekCurrent); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p.parseZcoinTx(&btx)
|
||||
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
|
||||
}
|
||||
|
@ -197,7 +247,7 @@ func (p *ZcoinParser) ParseBlock(b []byte) (*bchain.Block, error) {
|
|||
}
|
||||
|
||||
// ParseTxFromJson parses JSON message containing transaction and returns Tx struct
|
||||
func (p *ZcoinParser) ParseTxFromJson(msg json.RawMessage) (*bchain.Tx, error) {
|
||||
func (p *FiroParser) ParseTxFromJson(msg json.RawMessage) (*bchain.Tx, error) {
|
||||
var tx bchain.Tx
|
||||
err := json.Unmarshal(msg, &tx)
|
||||
if err != nil {
|
||||
|
@ -214,12 +264,12 @@ func (p *ZcoinParser) ParseTxFromJson(msg json.RawMessage) (*bchain.Tx, error) {
|
|||
vout.JsonValue = ""
|
||||
}
|
||||
|
||||
p.parseZcoinTx(&tx)
|
||||
p.parseFiroTx(&tx)
|
||||
|
||||
return &tx, nil
|
||||
}
|
||||
|
||||
func (p *ZcoinParser) parseZcoinTx(tx *bchain.Tx) error {
|
||||
func (p *FiroParser) parseFiroTx(tx *bchain.Tx) error {
|
||||
for i := range tx.Vin {
|
||||
vin := &tx.Vin[i]
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
// +build unittest
|
||||
|
||||
package xzc
|
||||
package firo
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
@ -13,17 +13,18 @@ import (
|
|||
"strings"
|
||||
"testing"
|
||||
|
||||
"blockbook/bchain"
|
||||
"blockbook/bchain/coins/btc"
|
||||
|
||||
"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 bchain.Tx
|
||||
testTxPacked1, testTxPacked2, testTxPacked3, testTxPacked4 string
|
||||
rawBlock1, rawBlock2 string
|
||||
jsonTx json.RawMessage
|
||||
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 {
|
||||
|
@ -40,12 +41,15 @@ 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]
|
||||
rawTestTx1 = hextxs[0]
|
||||
rawTestTx2 = hextxs[1]
|
||||
rawTestTx3 = hextxs[2]
|
||||
rawTestTx4 = hextxs[3]
|
||||
rawTestTx5 = hextxs[4]
|
||||
rawTestTx6 = hextxs[5]
|
||||
|
||||
rawSpendHex := readHexs("./testdata/rawspend.hex")[0]
|
||||
|
||||
|
@ -60,13 +64,15 @@ func init() {
|
|||
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: 0,
|
||||
LockTime: 99688,
|
||||
Vin: []bchain.Vin{
|
||||
{
|
||||
ScriptSig: bchain.ScriptSig{
|
||||
|
@ -79,14 +85,14 @@ func init() {
|
|||
},
|
||||
Vout: []bchain.Vout{
|
||||
{
|
||||
ValueSat: *big.NewInt(18188266638),
|
||||
ValueSat: *big.NewInt(100000000),
|
||||
N: 0,
|
||||
ScriptPubKey: bchain.ScriptPubKey{
|
||||
Hex: "c10280004c80f767f3ee79953c67a7ed386dcccf1243619eb4bbbe414a3982dd94a83c1b69ac52d6ab3b653a3e05c4e4516c8dfe1e58ada40461bc5835a4a0d0387a51c29ac11b72ae25bbcdef745f50ad08f08b3e9bc2c31a35444398a490e65ac090e9f341f1abdebe47e57e8237ac25d098e951b4164a35caea29f30acb50b12e4425df28",
|
||||
},
|
||||
},
|
||||
{
|
||||
ValueSat: *big.NewInt(18188266638),
|
||||
ValueSat: *big.NewInt(871824000),
|
||||
N: 1,
|
||||
ScriptPubKey: bchain.ScriptPubKey{
|
||||
Hex: "76a914c963f917c7f23cb4243e079db33107571b87690588ac",
|
||||
|
@ -106,9 +112,7 @@ func init() {
|
|||
LockTime: 0,
|
||||
Vin: []bchain.Vin{
|
||||
{
|
||||
ScriptSig: bchain.ScriptSig{
|
||||
Hex: rawSpendHex,
|
||||
},
|
||||
Coinbase: rawSpendHex,
|
||||
Txid: "0000000000000000000000000000000000000000000000000000000000000000",
|
||||
Vout: 4294967295,
|
||||
Sequence: 2,
|
||||
|
@ -227,7 +231,7 @@ func init() {
|
|||
ScriptPubKey: bchain.ScriptPubKey{
|
||||
Hex: "76a914ff71b0c9c2a90c6164a50a2fb523eb54a8a6b55088ac",
|
||||
Addresses: []string{
|
||||
"a1HwTdCmQV3NspP2QqCGpehoFpi8NY4Zg3",
|
||||
"aQ18FBVFtnueucZKeVg4srhmzbpAeb1KoN",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -251,6 +255,103 @@ func init() {
|
|||
},
|
||||
},
|
||||
},
|
||||
// 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",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -264,32 +365,32 @@ func TestMain(m *testing.M) {
|
|||
func TestGetAddrDesc(t *testing.T) {
|
||||
type args struct {
|
||||
tx bchain.Tx
|
||||
parser *ZcoinParser
|
||||
parser *FiroParser
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
}{
|
||||
{
|
||||
name: "xzc-1",
|
||||
name: "firo-1",
|
||||
args: args{
|
||||
tx: testTx1,
|
||||
parser: NewZcoinParser(GetChainParams("main"), &btc.Configuration{}),
|
||||
parser: NewFiroParser(GetChainParams("main"), &btc.Configuration{}),
|
||||
},
|
||||
},
|
||||
// FIXME: work around handle zerocoin spend as coinbase
|
||||
// {
|
||||
// name: "xzc-2",
|
||||
// name: "firo-2",
|
||||
// args: args{
|
||||
// tx: testTx2,
|
||||
// parser: NewZcoinParser(GetChainParams("main"), &btc.Configuration{}),
|
||||
// parser: NewFiroParser(GetChainParams("main"), &btc.Configuration{}),
|
||||
// },
|
||||
// },
|
||||
{
|
||||
name: "xzc-3",
|
||||
name: "firo-3",
|
||||
args: args{
|
||||
tx: testTx3,
|
||||
parser: NewZcoinParser(GetChainParams("main"), &btc.Configuration{}),
|
||||
parser: NewFiroParser(GetChainParams("main"), &btc.Configuration{}),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -340,8 +441,14 @@ func TestGetAddrDescFromVoutForMint(t *testing.T) {
|
|||
want: "c10280004c80f767f3ee79953c67a7ed386dcccf1243619eb4bbbe414a3982dd94a83c1b69ac52d6ab3b653a3e05c4e4516c8dfe1e58ada40461bc5835a4a0d0387a51c29ac11b72ae25bbcdef745f50ad08f08b3e9bc2c31a35444398a490e65ac090e9f341f1abdebe47e57e8237ac25d098e951b4164a35caea29f30acb50b12e4425df28",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "OP_SIGMAMINT",
|
||||
args: args{vout: bchain.Vout{ScriptPubKey: bchain.ScriptPubKey{Hex: "c317dcee5b8b2c5b79728abc3a39abc54682b31a4e18f5abb6f34dc8089544763b0000"}}},
|
||||
want: "c317dcee5b8b2c5b79728abc3a39abc54682b31a4e18f5abb6f34dc8089544763b0000",
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
parser := NewZcoinParser(GetChainParams("main"), &btc.Configuration{})
|
||||
parser := NewFiroParser(GetChainParams("main"), &btc.Configuration{})
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
@ -383,8 +490,15 @@ func TestGetAddressesFromAddrDescForMint(t *testing.T) {
|
|||
want2: false,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "OP_SIGMAMINT size hex",
|
||||
args: args{script: "c317dcee5b8b2c5b79728abc3a39abc54682b31a4e18f5abb6f34dc8089544763b0000"},
|
||||
want: []string{"Sigmamint"},
|
||||
want2: false,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
parser := NewZcoinParser(GetChainParams("main"), &btc.Configuration{})
|
||||
parser := NewFiroParser(GetChainParams("main"), &btc.Configuration{})
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
@ -409,7 +523,7 @@ func TestPackTx(t *testing.T) {
|
|||
tx bchain.Tx
|
||||
height uint32
|
||||
blockTime int64
|
||||
parser *ZcoinParser
|
||||
parser *FiroParser
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
|
@ -418,50 +532,72 @@ func TestPackTx(t *testing.T) {
|
|||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "xzc-1",
|
||||
name: "firo-1",
|
||||
args: args{
|
||||
tx: testTx1,
|
||||
height: 100002,
|
||||
blockTime: 1533980594,
|
||||
parser: NewZcoinParser(GetChainParams("main"), &btc.Configuration{}),
|
||||
parser: NewFiroParser(GetChainParams("main"), &btc.Configuration{}),
|
||||
},
|
||||
want: testTxPacked1,
|
||||
wantErr: false,
|
||||
},
|
||||
// FIXME: work around handle zerocoin spend as coinbase
|
||||
// {
|
||||
// name: "xzc-2",
|
||||
// name: "firo-2",
|
||||
// args: args{
|
||||
// tx: testTx2,
|
||||
// height: 11002,
|
||||
// blockTime: 1481277009,
|
||||
// parser: NewZcoinParser(GetChainParams("main"), &btc.Configuration{}),
|
||||
// parser: NewFiroParser(GetChainParams("main"), &btc.Configuration{}),
|
||||
// },
|
||||
// want: testTxPacked2,
|
||||
// wantErr: true,
|
||||
// },
|
||||
{
|
||||
name: "xzc-3",
|
||||
name: "firo-3",
|
||||
args: args{
|
||||
tx: testTx3,
|
||||
height: 126202,
|
||||
blockTime: 1547091829,
|
||||
parser: NewZcoinParser(GetChainParams("main"), &btc.Configuration{}),
|
||||
parser: NewFiroParser(GetChainParams("main"), &btc.Configuration{}),
|
||||
},
|
||||
want: testTxPacked3,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "xzc-coinbase",
|
||||
name: "firo-coinbase",
|
||||
args: args{
|
||||
tx: testTx4,
|
||||
height: 100001,
|
||||
blockTime: 1533977563,
|
||||
parser: NewZcoinParser(GetChainParams("main"), &btc.Configuration{}),
|
||||
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) {
|
||||
|
@ -483,7 +619,7 @@ func TestPackTx(t *testing.T) {
|
|||
func TestUnpackTx(t *testing.T) {
|
||||
type args struct {
|
||||
packedTx string
|
||||
parser *ZcoinParser
|
||||
parser *FiroParser
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
|
@ -493,10 +629,10 @@ func TestUnpackTx(t *testing.T) {
|
|||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "xzc-1",
|
||||
name: "firo-1",
|
||||
args: args{
|
||||
packedTx: testTxPacked1,
|
||||
parser: NewZcoinParser(GetChainParams("main"), &btc.Configuration{}),
|
||||
parser: NewFiroParser(GetChainParams("main"), &btc.Configuration{}),
|
||||
},
|
||||
want: &testTx1,
|
||||
want1: 100002,
|
||||
|
@ -504,35 +640,55 @@ func TestUnpackTx(t *testing.T) {
|
|||
},
|
||||
// FIXME: work around handle zerocoin spend as coinbase
|
||||
// {
|
||||
// name: "xzc-2",
|
||||
// name: "firo-2",
|
||||
// args: args{
|
||||
// packedTx: testTxPacked2,
|
||||
// parser: NewZcoinParser(GetChainParams("main"), &btc.Configuration{}),
|
||||
// parser: NewFiroParser(GetChainParams("main"), &btc.Configuration{}),
|
||||
// },
|
||||
// want: &testTx2,
|
||||
// want1: 11002,
|
||||
// wantErr: true,
|
||||
// },
|
||||
{
|
||||
name: "xzc-3",
|
||||
name: "firo-3",
|
||||
args: args{
|
||||
packedTx: testTxPacked3,
|
||||
parser: NewZcoinParser(GetChainParams("main"), &btc.Configuration{}),
|
||||
parser: NewFiroParser(GetChainParams("main"), &btc.Configuration{}),
|
||||
},
|
||||
want: &testTx3,
|
||||
want1: 126202,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "xzc-coinbase",
|
||||
name: "firo-coinbase",
|
||||
args: args{
|
||||
packedTx: testTxPacked4,
|
||||
parser: NewZcoinParser(GetChainParams("main"), &btc.Configuration{}),
|
||||
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) {
|
||||
|
@ -558,7 +714,7 @@ func TestUnpackTx(t *testing.T) {
|
|||
func TestParseBlock(t *testing.T) {
|
||||
type args struct {
|
||||
rawBlock string
|
||||
parser *ZcoinParser
|
||||
parser *FiroParser
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
|
@ -571,7 +727,7 @@ func TestParseBlock(t *testing.T) {
|
|||
name: "normal-block",
|
||||
args: args{
|
||||
rawBlock: rawBlock1,
|
||||
parser: NewZcoinParser(GetChainParams("main"), &btc.Configuration{}),
|
||||
parser: NewFiroParser(GetChainParams("main"), &btc.Configuration{}),
|
||||
},
|
||||
want: &bchain.Block{
|
||||
BlockHeader: bchain.BlockHeader{
|
||||
|
@ -586,7 +742,7 @@ func TestParseBlock(t *testing.T) {
|
|||
name: "spend-block",
|
||||
args: args{
|
||||
rawBlock: rawBlock2,
|
||||
parser: NewZcoinParser(GetChainParams("main"), &btc.Configuration{}),
|
||||
parser: NewFiroParser(GetChainParams("main"), &btc.Configuration{}),
|
||||
},
|
||||
want: &bchain.Block{
|
||||
BlockHeader: bchain.BlockHeader{
|
||||
|
@ -597,6 +753,21 @@ func TestParseBlock(t *testing.T) {
|
|||
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 {
|
||||
|
@ -620,3 +791,191 @@ func TestParseBlock(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,28 +1,28 @@
|
|||
package xzc
|
||||
package firo
|
||||
|
||||
import (
|
||||
"blockbook/bchain"
|
||||
"blockbook/bchain/coins/btc"
|
||||
"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 ZcoinRPC struct {
|
||||
type FiroRPC struct {
|
||||
*btc.BitcoinRPC
|
||||
}
|
||||
|
||||
func NewZcoinRPC(config json.RawMessage, pushHandler func(bchain.NotificationType)) (bchain.BlockChain, error) {
|
||||
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 zcoin implementation
|
||||
zc := &ZcoinRPC{
|
||||
// init firo implementation
|
||||
zc := &FiroRPC{
|
||||
BitcoinRPC: bc.(*btc.BitcoinRPC),
|
||||
}
|
||||
|
||||
|
@ -35,16 +35,17 @@ func NewZcoinRPC(config json.RawMessage, pushHandler func(bchain.NotificationTyp
|
|||
return zc, nil
|
||||
}
|
||||
|
||||
func (zc *ZcoinRPC) Initialize() error {
|
||||
chainName, err := zc.GetChainInfoAndInitializeMempool(zc)
|
||||
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 = NewZcoinParser(params, zc.ChainConfig)
|
||||
zc.Parser = NewFiroParser(params, zc.ChainConfig)
|
||||
|
||||
// parameters for getInfo request
|
||||
if params.Net == MainnetMagic {
|
||||
|
@ -60,7 +61,7 @@ func (zc *ZcoinRPC) Initialize() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (zc *ZcoinRPC) GetBlock(hash string, height uint32) (*bchain.Block, error) {
|
||||
func (zc *FiroRPC) GetBlock(hash string, height uint32) (*bchain.Block, error) {
|
||||
var err error
|
||||
|
||||
if hash == "" {
|
||||
|
@ -95,7 +96,7 @@ func (zc *ZcoinRPC) GetBlock(hash string, height uint32) (*bchain.Block, error)
|
|||
return block, nil
|
||||
}
|
||||
|
||||
func (zc *ZcoinRPC) GetBlockInfo(hash string) (*bchain.BlockInfo, error) {
|
||||
func (zc *FiroRPC) GetBlockInfo(hash string) (*bchain.BlockInfo, error) {
|
||||
glog.V(1).Info("rpc: getblock (verbosity=true) ", hash)
|
||||
|
||||
res := btc.ResGetBlockInfo{}
|
||||
|
@ -116,7 +117,7 @@ func (zc *ZcoinRPC) GetBlockInfo(hash string) (*bchain.BlockInfo, error) {
|
|||
return &res.Result, nil
|
||||
}
|
||||
|
||||
func (zc *ZcoinRPC) GetBlockWithoutHeader(hash string, height uint32) (*bchain.Block, error) {
|
||||
func (zc *FiroRPC) GetBlockWithoutHeader(hash string, height uint32) (*bchain.Block, error) {
|
||||
data, err := zc.GetBlockRaw(hash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -133,7 +134,7 @@ func (zc *ZcoinRPC) GetBlockWithoutHeader(hash string, height uint32) (*bchain.B
|
|||
return block, nil
|
||||
}
|
||||
|
||||
func (zc *ZcoinRPC) GetBlockRaw(hash string) ([]byte, error) {
|
||||
func (zc *FiroRPC) GetBlockRaw(hash string) ([]byte, error) {
|
||||
glog.V(1).Info("rpc: getblock (verbosity=false) ", hash)
|
||||
|
||||
res := btc.ResGetBlockRaw{}
|
||||
|
@ -154,7 +155,7 @@ func (zc *ZcoinRPC) GetBlockRaw(hash string) ([]byte, error) {
|
|||
return hex.DecodeString(res.Result)
|
||||
}
|
||||
|
||||
func (zc *ZcoinRPC) GetTransactionForMempool(txid string) (*bchain.Tx, error) {
|
||||
func (zc *FiroRPC) GetTransactionForMempool(txid string) (*bchain.Tx, error) {
|
||||
glog.V(1).Info("rpc: getrawtransaction nonverbose ", txid)
|
||||
|
||||
res := btc.ResGetRawTransactionNonverbose{}
|
||||
|
@ -182,7 +183,7 @@ func (zc *ZcoinRPC) GetTransactionForMempool(txid string) (*bchain.Tx, error) {
|
|||
return tx, nil
|
||||
}
|
||||
|
||||
func (zc *ZcoinRPC) GetTransaction(txid string) (*bchain.Tx, error) {
|
||||
func (zc *FiroRPC) GetTransaction(txid string) (*bchain.Tx, error) {
|
||||
r, err := zc.getRawTransaction(txid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -197,14 +198,14 @@ func (zc *ZcoinRPC) GetTransaction(txid string) (*bchain.Tx, error) {
|
|||
return tx, nil
|
||||
}
|
||||
|
||||
func (zc *ZcoinRPC) GetTransactionSpecific(tx *bchain.Tx) (json.RawMessage, error) {
|
||||
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 *ZcoinRPC) getRawTransaction(txid string) (json.RawMessage, error) {
|
||||
func (zc *FiroRPC) getRawTransaction(txid string) (json.RawMessage, error) {
|
||||
glog.V(1).Info("rpc: getrawtransaction ", txid)
|
||||
|
||||
res := btc.ResGetRawTransaction{}
|
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
|
@ -1,18 +1,20 @@
|
|||
package flo
|
||||
|
||||
import (
|
||||
"blockbook/bchain"
|
||||
"blockbook/bchain/coins/btc"
|
||||
"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
|
||||
|
|
|
@ -3,13 +3,13 @@
|
|||
package flo
|
||||
|
||||
import (
|
||||
"blockbook/bchain/coins/btc"
|
||||
"encoding/hex"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/martinboehm/btcutil/chaincfg"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
package flo
|
||||
|
||||
import (
|
||||
"blockbook/bchain"
|
||||
"blockbook/bchain/coins/btc"
|
||||
"encoding/json"
|
||||
"github.com/juju/errors"
|
||||
|
||||
"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.
|
||||
|
@ -32,10 +32,11 @@ func NewFloRPC(config json.RawMessage, pushHandler func(bchain.NotificationType)
|
|||
|
||||
// Initialize initializes FloRPC instance.
|
||||
func (f *FloRPC) Initialize() error {
|
||||
chainName, err := f.GetChainInfoAndInitializeMempool(f)
|
||||
ci, err := f.GetChainInfo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
chainName := ci.Chain
|
||||
|
||||
glog.Info("Chain name ", chainName)
|
||||
params := GetChainParams(chainName)
|
||||
|
|
|
@ -1,20 +1,24 @@
|
|||
package fujicoin
|
||||
|
||||
import (
|
||||
"blockbook/bchain/coins/btc"
|
||||
|
||||
"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
|
||||
)
|
||||
|
||||
|
|
|
@ -3,8 +3,6 @@
|
|||
package fujicoin
|
||||
|
||||
import (
|
||||
"blockbook/bchain"
|
||||
"blockbook/bchain/coins/btc"
|
||||
"encoding/hex"
|
||||
"math/big"
|
||||
"os"
|
||||
|
@ -12,6 +10,8 @@ import (
|
|||
"testing"
|
||||
|
||||
"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 fujicoin
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
// FujicoinRPC is an interface to JSON-RPC bitcoind service.
|
||||
|
@ -31,10 +31,11 @@ func NewFujicoinRPC(config json.RawMessage, pushHandler func(bchain.Notification
|
|||
|
||||
// Initialize initializes FujicoinRPC instance.
|
||||
func (b *FujicoinRPC) 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,18 +1,19 @@
|
|||
package gamecredits
|
||||
|
||||
import (
|
||||
"blockbook/bchain/coins/btc"
|
||||
|
||||
"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,8 +3,6 @@
|
|||
package gamecredits
|
||||
|
||||
import (
|
||||
"blockbook/bchain"
|
||||
"blockbook/bchain/coins/btc"
|
||||
"encoding/hex"
|
||||
"math/big"
|
||||
"os"
|
||||
|
@ -12,6 +10,8 @@ import (
|
|||
"testing"
|
||||
|
||||
"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/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"
|
||||
|
@ -13,6 +11,8 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/martinboehm/btcutil/chaincfg"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -1,19 +1,20 @@
|
|||
package koto
|
||||
|
||||
import (
|
||||
"blockbook/bchain"
|
||||
"blockbook/bchain/coins/btc"
|
||||
|
||||
"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
|
||||
|
|
|
@ -3,8 +3,6 @@
|
|||
package koto
|
||||
|
||||
import (
|
||||
"blockbook/bchain"
|
||||
"blockbook/bchain/coins/btc"
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"math/big"
|
||||
|
@ -13,6 +11,8 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/martinboehm/btcutil/chaincfg"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
|
@ -1,18 +1,20 @@
|
|||
package koto
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
// 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 {
|
||||
|
@ -28,10 +30,11 @@ func NewKotoRPC(config json.RawMessage, pushHandler func(bchain.NotificationType
|
|||
|
||||
// Initialize initializes KotoRPC instance.
|
||||
func (z *KotoRPC) Initialize() error {
|
||||
chainName, err := z.GetChainInfoAndInitializeMempool(z)
|
||||
ci, err := z.GetChainInfo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
chainName := ci.Chain
|
||||
|
||||
params := GetChainParams(chainName)
|
||||
|
||||
|
|
|
@ -1,23 +1,24 @@
|
|||
package liquid
|
||||
|
||||
import (
|
||||
"blockbook/bchain"
|
||||
"blockbook/bchain/coins/btc"
|
||||
"strconv"
|
||||
|
||||
vlq "github.com/bsm/go-vlq"
|
||||
"github.com/golang/glog"
|
||||
|
||||
"github.com/martinboehm/btcd/txscript"
|
||||
"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 = 0xdab5bffa
|
||||
)
|
||||
|
||||
var (
|
||||
// MainNetParams are parser parameters for mainnet
|
||||
MainNetParams chaincfg.Params
|
||||
)
|
||||
|
||||
|
|
|
@ -3,8 +3,6 @@
|
|||
package liquid
|
||||
|
||||
import (
|
||||
"blockbook/bchain"
|
||||
"blockbook/bchain/coins/btc"
|
||||
"encoding/hex"
|
||||
"math/big"
|
||||
"os"
|
||||
|
@ -12,6 +10,8 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/martinboehm/btcutil/chaincfg"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
package liquid
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
// LiquidRPC is an interface to JSON-RPC bitcoind service.
|
||||
|
@ -31,10 +31,11 @@ func NewLiquidRPC(config json.RawMessage, pushHandler func(bchain.NotificationTy
|
|||
|
||||
// Initialize initializes GameCreditsRPC instance.
|
||||
func (b *LiquidRPC) 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,18 +1,19 @@
|
|||
package litecoin
|
||||
|
||||
import (
|
||||
"blockbook/bchain/coins/btc"
|
||||
|
||||
"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 = 0xf1c8d2fd
|
||||
RegtestMagic wire.BitcoinNet = 0xdab5bffa
|
||||
)
|
||||
|
||||
// chain parameters
|
||||
var (
|
||||
MainNetParams chaincfg.Params
|
||||
TestNetParams chaincfg.Params
|
||||
|
|
|
@ -3,8 +3,6 @@
|
|||
package litecoin
|
||||
|
||||
import (
|
||||
"blockbook/bchain"
|
||||
"blockbook/bchain/coins/btc"
|
||||
"encoding/hex"
|
||||
"math/big"
|
||||
"os"
|
||||
|
@ -12,6 +10,8 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/martinboehm/btcutil/chaincfg"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
|
@ -100,6 +100,18 @@ func Test_GetAddrDescFromAddress_Mainnet(t *testing.T) {
|
|||
want: "76a914feda50542e61108cf53b93dbffa0959f91ccb32588ac",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "P2PKH3 - bech32 prefix",
|
||||
args: args{address: "LTC1eqUzePT9uvpvb413Ejd6P8Cx1Ei8Di"},
|
||||
want: "76a91457630115300a625f5deaab64100faa5506c1422f88ac",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "P2PKH4 - bech32 prefix",
|
||||
args: args{address: "LTC1f9gtb7bU6B4VjHXvPGDi8ACNZhkKPo"},
|
||||
want: "76a9145763023d3f02509644dacbfc45f2c9102129749788ac",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "P2SH1",
|
||||
args: args{address: "MLTQ8niHMnpJLNvK72zBeY91hQmUtoo8nX"},
|
||||
|
@ -142,6 +154,73 @@ func Test_GetAddrDescFromAddress_Mainnet(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestGetAddressesFromAddrDesc_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: "76a914feda50542e61108cf53b93dbffa0959f91ccb32588ac"},
|
||||
want: []string{"LiTVReQ6N8rWc2pNg2XMwCWq7A9P15teWg"},
|
||||
want2: true,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "P2PKH2",
|
||||
args: args{script: "76A9145763023D3F02509644DACBFC45F2C9102129749788AC"},
|
||||
want: []string{"LTC1f9gtb7bU6B4VjHXvPGDi8ACNZhkKPo"},
|
||||
want2: true,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "P2SH1",
|
||||
args: args{script: "a9141c6fbaf46d64221e80cbae182c33ddf81b9294ac87"},
|
||||
want: []string{"MAVWzxXm8KGkZTesqLtqywzrvbs96FEoKy"},
|
||||
want2: true,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "witness_v0_keyhash",
|
||||
args: args{script: "0014a2516e770582864a6a56ed21a102044e388c62e3"},
|
||||
want: []string{"ltc1q5fgkuac9s2ry56jka5s6zqsyfcugcchrqgz2yl"},
|
||||
want2: true,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "witness_v0_scripthashx",
|
||||
args: args{script: "0020e15a86a23178f433d514dbbce042e87d72662b8b5edcacfd2e37ab7a2d135f05"},
|
||||
want: []string{"ltc1qu9dgdg330r6r84g5mw7wqshg04exv2uttmw2elfwx74h5tgntuzsk3x5nd"},
|
||||
want2: true,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
parser := NewLitecoinParser(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
|
||||
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
package litecoin
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
// LitecoinRPC is an interface to JSON-RPC bitcoind service.
|
||||
|
@ -31,10 +31,11 @@ func NewLitecoinRPC(config json.RawMessage, pushHandler func(bchain.Notification
|
|||
|
||||
// Initialize initializes LitecoinRPC instance.
|
||||
func (b *LitecoinRPC) 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)
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue