Compare commits
391 Commits
v0.3.0
...
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 |
|
@ -8,6 +8,7 @@ debug*
|
|||
docker/blockbook
|
||||
build/pkg-defs
|
||||
build/blockbook
|
||||
build/blockchaincfg.json
|
||||
build/ldb
|
||||
build/sst_dump
|
||||
build/*.deb
|
||||
|
|
|
@ -104,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 = "225ed00dbbd5cb8d8b3949a0ee7c9ea540754585"
|
||||
|
||||
[[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).
|
93
README.md
93
README.md
|
@ -1,74 +1,57 @@
|
|||
[![Go Report Card](https://goreportcard.com/badge/trezor/blockbook)](https://goreportcard.com/report/trezor/blockbook)
|
||||
# Fork
|
||||
Fork of Trezor Blockbook.
|
||||
|
||||
# Blockbook
|
||||
|
||||
**Blockbook** is back-end service for Trezor wallet. Main features of **Blockbook** are:
|
||||
The differences:
|
||||
|
||||
- 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
|
||||
* Just for Ethereum.
|
||||
|
||||
## Build and installation instructions
|
||||
* Use existing `geth --full` server.
|
||||
|
||||
Officially supported platform is **Debian Linux** and **AMD64** architecture.
|
||||
* Don't require `backend-*` package.
|
||||
|
||||
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.
|
||||
* Minimal UI, dark theme.
|
||||
|
||||
User installation guide is [here](https://wiki.trezor.io/User_manual:Running_a_local_instance_of_Trezor_Wallet_backend_(Blockbook)).
|
||||
* Listen only on localhost, no SSL.
|
||||
|
||||
Developer build guide is [here](/docs/build.md).
|
||||
* Use spacecruft repos, not github.
|
||||
|
||||
Contribution guide is [here](CONTRIBUTING.md).
|
||||
* Don't use CDN.
|
||||
|
||||
## Implemented coins
|
||||
# Install
|
||||
|
||||
Blockbook currently supports over 30 coins. The Trezor team implemented
|
||||
```
|
||||
# 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
|
||||
```
|
||||
|
||||
- Bitcoin, Bitcoin Cash, Zcash, Dash, Litecoin, Bitcoin Gold, Ethereum, Ethereum Classic, Dogecoin, Namecoin, Vertcoin, DigiByte, Liquid
|
||||
Edit config:
|
||||
```
|
||||
vim /opt/coins/blockbook/ethereum/config/blockchaincfg.json
|
||||
```
|
||||
|
||||
the rest of coins were implemented by the community.
|
||||
XXX Hardcoded into systemd script, set `geth` node:
|
||||
|
||||
Testnets for some coins are also supported, for example:
|
||||
- Bitcoin Testnet, Bitcoin Cash Testnet, ZCash Testnet, Ethereum Testnet Ropsten
|
||||
```
|
||||
vim /lib/systemd/system/blockbook-ethereum.service
|
||||
systemctl daemon-reload
|
||||
```
|
||||
|
||||
List of all implemented coins is in [the registry of ports](/docs/ports.md).
|
||||
Start:
|
||||
```
|
||||
systemctl start blockbook-ethereum.service
|
||||
```
|
||||
|
||||
## Common issues when running Blockbook or implementing additional coins
|
||||
Logs:
|
||||
```
|
||||
tail -f /opt/coins/blockbook/ethereum/logs/blockbook.INFO
|
||||
```
|
||||
|
||||
#### Out of memory when doing initial synchronization
|
||||
# Upstream
|
||||
Fork of Trezor Blockbook. See `README-upstream.md`.
|
||||
|
||||
How to reduce memory footprint of the initial sync:
|
||||
* https://github.com/trezor/blockbook
|
||||
|
||||
- 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).
|
||||
|
|
117
api/types.go
117
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)
|
||||
|
@ -107,7 +110,7 @@ type Vin struct {
|
|||
N int `json:"n"`
|
||||
AddrDesc bchain.AddressDescriptor `json:"-"`
|
||||
Addresses []string `json:"addresses,omitempty"`
|
||||
Searchable bool `json:"-"`
|
||||
IsAddress bool `json:"isAddress"`
|
||||
ValueSat *Amount `json:"value,omitempty"`
|
||||
Hex string `json:"hex,omitempty"`
|
||||
Asm string `json:"asm,omitempty"`
|
||||
|
@ -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,11 +171,12 @@ type TokenTransfer struct {
|
|||
|
||||
// EthereumSpecific contains ethereum specific transaction data
|
||||
type EthereumSpecific struct {
|
||||
Status int `json:"status"` // 1 OK, 0 Fail, -1 pending
|
||||
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
|
||||
|
@ -191,12 +195,20 @@ type Tx struct {
|
|||
ValueInSat *Amount `json:"valueIn,omitempty"`
|
||||
FeesSat *Amount `json:"fees,omitempty"`
|
||||
Hex string `json:"hex,omitempty"`
|
||||
CoinSpecificData interface{} `json:"-"`
|
||||
CoinSpecificJSON json.RawMessage `json:"-"`
|
||||
Rbf bool `json:"rbf,omitempty"`
|
||||
CoinSpecificData json.RawMessage `json:"coinSpecificData,omitempty"`
|
||||
TokenTransfers []TokenTransfer `json:"tokenTransfers,omitempty"`
|
||||
EthereumSpecific *EthereumSpecific `json:"ethereumSpecific,omitempty"`
|
||||
}
|
||||
|
||||
// FeeStats contains detailed block fee statistics
|
||||
type FeeStats struct {
|
||||
TxCount int `json:"txCount"`
|
||||
TotalFeesSat *Amount `json:"totalFeesSat"`
|
||||
AverageFeePerKb int64 `json:"averageFeePerKb"`
|
||||
DecilesFeePerKb [11]int64 `json:"decilesFeePerKb"`
|
||||
}
|
||||
|
||||
// Paging contains information about paging for address, blocks and block
|
||||
type Paging struct {
|
||||
Page int `json:"page,omitempty"`
|
||||
|
@ -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
|
||||
|
@ -266,6 +280,7 @@ type Utxo struct {
|
|||
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
|
||||
|
@ -286,6 +301,73 @@ 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
|
||||
|
@ -301,7 +383,7 @@ type BlockInfo struct {
|
|||
Confirmations int `json:"confirmations"`
|
||||
Size int `json:"size"`
|
||||
Time int64 `json:"time,omitempty"`
|
||||
Version json.Number `json:"version"`
|
||||
Version common.JSONNumber `json:"version"`
|
||||
MerkleRoot string `json:"merkleRoot"`
|
||||
Nonce string `json:"nonce"`
|
||||
Bits string `json:"bits"`
|
||||
|
@ -339,25 +421,10 @@ type BlockbookInfo struct {
|
|||
About string `json:"about"`
|
||||
}
|
||||
|
||||
// BackendInfo is used to get information about blockchain
|
||||
type BackendInfo struct {
|
||||
Chain string `json:"chain"`
|
||||
Blocks int `json:"blocks"`
|
||||
Headers int `json:"headers"`
|
||||
Bestblockhash string `json:"bestBlockHash"`
|
||||
Difficulty string `json:"difficulty"`
|
||||
SizeOnDisk int64 `json:"sizeOnDisk"`
|
||||
Version string `json:"version"`
|
||||
Subversion string `json:"subversion"`
|
||||
ProtocolVersion string `json:"protocolVersion"`
|
||||
Timeoffset float64 `json:"timeOffset"`
|
||||
Warnings string `json:"warnings"`
|
||||
}
|
||||
|
||||
// SystemInfo contains information about the running blockbook and backend instance
|
||||
type SystemInfo struct {
|
||||
Blockbook *BlockbookInfo `json:"blockbook"`
|
||||
Backend *BackendInfo `json:"backend"`
|
||||
Backend *common.BackendInfo `json:"backend"`
|
||||
}
|
||||
|
||||
// MempoolTxid contains information about a transaction in mempool
|
||||
|
|
|
@ -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
|
||||
|
@ -20,7 +21,7 @@ type VinV1 struct {
|
|||
ScriptSig ScriptSigV1 `json:"scriptSig"`
|
||||
AddrDesc bchain.AddressDescriptor `json:"-"`
|
||||
Addresses []string `json:"addresses"`
|
||||
Searchable bool `json:"-"`
|
||||
IsAddress bool `json:"-"`
|
||||
Value string `json:"value"`
|
||||
ValueSat big.Int `json:"-"`
|
||||
}
|
||||
|
@ -31,7 +32,7 @@ type ScriptPubKeyV1 struct {
|
|||
Asm string `json:"asm,omitempty"`
|
||||
AddrDesc bchain.AddressDescriptor `json:"-"`
|
||||
Addresses []string `json:"addresses"`
|
||||
Searchable bool `json:"-"`
|
||||
IsAddress bool `json:"-"`
|
||||
Type string `json:"type,omitempty"`
|
||||
}
|
||||
|
||||
|
@ -115,7 +116,7 @@ func (w *Worker) TxToV1(tx *Tx) *TxV1 {
|
|||
Asm: v.Asm,
|
||||
Hex: v.Hex,
|
||||
},
|
||||
Searchable: v.Searchable,
|
||||
IsAddress: v.IsAddress,
|
||||
Sequence: v.Sequence,
|
||||
Txid: v.Txid,
|
||||
Value: v.ValueSat.DecimalString(d),
|
||||
|
@ -133,7 +134,7 @@ func (w *Worker) TxToV1(tx *Tx) *TxV1 {
|
|||
Addresses: v.Addresses,
|
||||
Asm: v.Asm,
|
||||
Hex: v.Hex,
|
||||
Searchable: v.Searchable,
|
||||
IsAddress: v.IsAddress,
|
||||
Type: v.Type,
|
||||
},
|
||||
Spent: v.Spent,
|
||||
|
|
919
api/worker.go
919
api/worker.go
File diff suppressed because it is too large
Load Diff
61
api/xpub.go
61
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
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -418,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)
|
||||
|
@ -590,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
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package bchain
|
|||
import (
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type addrIndex struct {
|
||||
|
@ -27,6 +28,7 @@ type BaseMempool struct {
|
|||
txEntries map[string]txEntry
|
||||
addrDescToTx map[string][]Outpoint
|
||||
OnNewTxAddr OnNewTxAddrFunc
|
||||
OnNewTx OnNewTxFunc
|
||||
}
|
||||
|
||||
// GetTransactions returns slice of mempool transactions for given address
|
||||
|
@ -113,3 +115,22 @@ func (m *BaseMempool) GetTransactionTime(txid string) uint32 {
|
|||
}
|
||||
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
|
||||
|
|
|
@ -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
|
||||
|
@ -151,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
|
||||
}
|
||||
|
|
|
@ -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,8 +1,6 @@
|
|||
package bch
|
||||
|
||||
import (
|
||||
"blockbook/bchain"
|
||||
"blockbook/bchain/coins/btc"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"math/big"
|
||||
|
@ -10,6 +8,8 @@ import (
|
|||
"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.
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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,37 +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/monetaryunit"
|
||||
"blockbook/bchain/coins/myriad"
|
||||
"blockbook/bchain/coins/namecoin"
|
||||
"blockbook/bchain/coins/nuls"
|
||||
"blockbook/bchain/coins/pivx"
|
||||
"blockbook/bchain/coins/polis"
|
||||
"blockbook/bchain/coins/qtum"
|
||||
"blockbook/bchain/coins/ravencoin"
|
||||
"blockbook/bchain/coins/vertcoin"
|
||||
"blockbook/bchain/coins/viacoin"
|
||||
"blockbook/bchain/coins/vipstarcoin"
|
||||
"blockbook/bchain/coins/xzc"
|
||||
"blockbook/bchain/coins/zec"
|
||||
"blockbook/common"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
@ -41,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)
|
||||
|
@ -51,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
|
||||
|
@ -74,6 +90,7 @@ func init() {
|
|||
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
|
||||
|
@ -81,7 +98,7 @@ func init() {
|
|||
BlockChainFactories["PIVX"] = pivx.NewPivXRPC
|
||||
BlockChainFactories["PIVX Testnet"] = pivx.NewPivXRPC
|
||||
BlockChainFactories["Polis"] = polis.NewPolisRPC
|
||||
BlockChainFactories["Zcoin"] = xzc.NewZcoinRPC
|
||||
BlockChainFactories["Firo"] = firo.NewFiroRPC
|
||||
BlockChainFactories["Fujicoin"] = fujicoin.NewFujicoinRPC
|
||||
BlockChainFactories["Flo"] = flo.NewFloRPC
|
||||
BlockChainFactories["Bellcoin"] = bellcoin.NewBellcoinRPC
|
||||
|
@ -92,6 +109,17 @@ func init() {
|
|||
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
|
||||
|
@ -150,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
|
||||
}
|
||||
|
@ -163,8 +191,8 @@ func (c *blockChainWithMetrics) CreateMempool(chain bchain.BlockChain) (bchain.M
|
|||
return c.b.CreateMempool(chain)
|
||||
}
|
||||
|
||||
func (c *blockChainWithMetrics) InitializeMempool(addrDescForOutpoint bchain.AddrDescForOutpointFunc, onNewTxAddr bchain.OnNewTxAddrFunc) error {
|
||||
return c.b.InitializeMempool(addrDescForOutpoint, onNewTxAddr)
|
||||
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 {
|
||||
|
@ -299,7 +327,7 @@ type mempoolWithMetrics struct {
|
|||
func (c *mempoolWithMetrics) 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
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package btc
|
||||
|
||||
import (
|
||||
"blockbook/bchain"
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
|
@ -16,8 +15,25 @@ 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)
|
||||
|
||||
|
@ -30,6 +46,7 @@ type BitcoinParser struct {
|
|||
XPubMagicSegwitP2sh uint32
|
||||
XPubMagicSegwitNative uint32
|
||||
Slip44 uint32
|
||||
minimumCoinbaseConfirmations int
|
||||
}
|
||||
|
||||
// NewBitcoinParser returns new BitcoinParser instance
|
||||
|
@ -44,6 +61,7 @@ func NewBitcoinParser(params *chaincfg.Params, c *Configuration) *BitcoinParser
|
|||
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
|
||||
|
@ -326,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
|
||||
|
|
|
@ -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) {
|
||||
|
@ -259,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() {
|
||||
|
@ -335,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) {
|
||||
|
@ -372,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) {
|
||||
|
@ -420,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.
|
||||
|
@ -55,8 +56,9 @@ type Configuration struct {
|
|||
XPubMagicSegwitP2sh uint32 `json:"xpub_magic_segwit_p2sh,omitempty"`
|
||||
XPubMagicSegwitNative uint32 `json:"xpub_magic_segwit_native,omitempty"`
|
||||
Slip44 uint32 `json:"slip44,omitempty"`
|
||||
AlternativeEstimateFee string `json:"alternativeEstimateFee,omitempty"`
|
||||
AlternativeEstimateFeeParams string `json:"alternativeEstimateFeeParams,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.
|
||||
|
@ -71,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
|
||||
|
@ -149,12 +155,13 @@ func (b *BitcoinRPC) CreateMempool(chain bchain.BlockChain) (bchain.Mempool, err
|
|||
}
|
||||
|
||||
// InitializeMempool creates ZeroMQ subscription and sets AddrDescForOutpointFunc to the Mempool
|
||||
func (b *BitcoinRPC) InitializeMempool(addrDescForOutpoint bchain.AddrDescForOutpointFunc, onNewTxAddr bchain.OnNewTxAddrFunc) error {
|
||||
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 {
|
||||
|
@ -236,7 +243,7 @@ type ResGetBlockChainInfo struct {
|
|||
Blocks int `json:"blocks"`
|
||||
Headers int `json:"headers"`
|
||||
Bestblockhash string `json:"bestblockhash"`
|
||||
Difficulty json.Number `json:"difficulty"`
|
||||
Difficulty common.JSONNumber `json:"difficulty"`
|
||||
SizeOnDisk int64 `json:"size_on_disk"`
|
||||
Warnings string `json:"warnings"`
|
||||
} `json:"result"`
|
||||
|
@ -251,9 +258,9 @@ 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"`
|
||||
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"`
|
||||
|
@ -353,7 +360,7 @@ type CmdEstimateSmartFee struct {
|
|||
type ResEstimateSmartFee struct {
|
||||
Error *bchain.RPCError `json:"error"`
|
||||
Result struct {
|
||||
Feerate json.Number `json:"feerate"`
|
||||
Feerate common.JSONNumber `json:"feerate"`
|
||||
Blocks int `json:"blocks"`
|
||||
} `json:"result"`
|
||||
}
|
||||
|
@ -369,7 +376,7 @@ type CmdEstimateFee struct {
|
|||
|
||||
type ResEstimateFee struct {
|
||||
Error *bchain.RPCError `json:"error"`
|
||||
Result json.Number `json:"result"`
|
||||
Result common.JSONNumber `json:"result"`
|
||||
}
|
||||
|
||||
// sendrawtransaction
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package btc
|
||||
|
||||
import (
|
||||
"blockbook/bchain"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
@ -12,8 +11,8 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"github.com/juju/errors"
|
||||
"spacecruft.org/spacecruft/blockbook/bchain"
|
||||
)
|
||||
|
||||
// https://whatthefee.io returns
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
package btg
|
||||
|
||||
import (
|
||||
"blockbook/bchain"
|
||||
"blockbook/bchain/coins/btc"
|
||||
"blockbook/bchain/coins/utils"
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
|
@ -11,6 +8,9 @@ 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 (
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,20 +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() {
|
||||
|
@ -23,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
|
||||
|
@ -30,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)
|
||||
}
|
||||
}
|
||||
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.
|
||||
|
|
|
@ -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,13 +1,13 @@
|
|||
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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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,9 +311,15 @@ 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 r.Receipt.Status != "" {
|
||||
if pt.Receipt.Status, err = hexDecodeBig(r.Receipt.Status); err != nil {
|
||||
return nil, errors.Annotatef(err, "Status %v", r.Receipt.Status)
|
||||
}
|
||||
} else {
|
||||
// unknown status, use 'U' as status bytes
|
||||
// there is a potential for conflict with value 0x55 but this is not used by any chain at this moment
|
||||
pt.Receipt.Status = []byte{'U'}
|
||||
}
|
||||
ptLogs := make([]*ProtoCompleteTransaction_ReceiptType_LogType, len(r.Receipt.Logs))
|
||||
for i, l := range r.Receipt.Logs {
|
||||
a, err := hexutil.Decode(l.Address)
|
||||
|
@ -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,6 +29,8 @@ 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
|
||||
|
@ -57,7 +61,6 @@ type EthereumRPC struct {
|
|||
chanNewTx chan ethcommon.Hash
|
||||
newTxSubscription *rpc.ClientSubscription
|
||||
ChainConfig *Configuration
|
||||
isETC bool
|
||||
}
|
||||
|
||||
// NewEthereumRPC returns new EthRPC instance.
|
||||
|
@ -72,11 +75,11 @@ 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{},
|
||||
|
@ -89,9 +92,6 @@ func NewEthereumRPC(config json.RawMessage, pushHandler func(bchain.Notification
|
|||
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)
|
||||
|
@ -133,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)
|
||||
|
@ -153,6 +162,9 @@ 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)
|
||||
}
|
||||
|
@ -171,7 +183,7 @@ func (b *EthereumRPC) CreateMempool(chain bchain.BlockChain) (bchain.Mempool, er
|
|||
}
|
||||
|
||||
// InitializeMempool creates subscriptions to newHeads and newPendingTransactions
|
||||
func (b *EthereumRPC) InitializeMempool(addrDescForOutpoint bchain.AddrDescForOutpointFunc, onNewTxAddr bchain.OnNewTxAddrFunc) error {
|
||||
func (b *EthereumRPC) InitializeMempool(addrDescForOutpoint bchain.AddrDescForOutpointFunc, onNewTxAddr bchain.OnNewTxAddrFunc, onNewTx bchain.OnNewTxFunc) error {
|
||||
if b.Mempool == nil {
|
||||
return errors.New("Mempool not created")
|
||||
}
|
||||
|
@ -186,10 +198,18 @@ func (b *EthereumRPC) InitializeMempool(addrDescForOutpoint bchain.AddrDescForOu
|
|||
}
|
||||
|
||||
b.Mempool.OnNewTxAddr = onNewTxAddr
|
||||
b.Mempool.OnNewTx = onNewTx
|
||||
|
||||
if b.isETC {
|
||||
glog.Info(b.ChainConfig.CoinName, " does not support subscription to newHeads")
|
||||
} else {
|
||||
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
|
||||
|
@ -206,7 +226,6 @@ func (b *EthereumRPC) InitializeMempool(addrDescForOutpoint bchain.AddrDescForOu
|
|||
}); 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
|
||||
|
@ -224,8 +243,6 @@ func (b *EthereumRPC) InitializeMempool(addrDescForOutpoint bchain.AddrDescForOu
|
|||
return err
|
||||
}
|
||||
|
||||
b.mempoolInitialized = true
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -246,7 +263,7 @@ func (b *EthereumRPC) subscribe(f func() (*rpc.ClientSubscription, error)) error
|
|||
}
|
||||
glog.Error("Subscription error ", e)
|
||||
timer := time.NewTimer(time.Second * 2)
|
||||
// try in 1 second interval to resubscribe
|
||||
// try in 2 second interval to resubscribe
|
||||
for {
|
||||
select {
|
||||
case e = <-s.Err():
|
||||
|
@ -260,7 +277,7 @@ func (b *EthereumRPC) subscribe(f func() (*rpc.ClientSubscription, error)) error
|
|||
s = ns
|
||||
continue Loop
|
||||
}
|
||||
glog.Error("Resubscribe error ", e)
|
||||
glog.Error("Resubscribe error ", err)
|
||||
timer.Reset(time.Second * 2)
|
||||
}
|
||||
}
|
||||
|
@ -269,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()
|
||||
}
|
||||
|
@ -280,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
|
||||
|
@ -297,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,
|
||||
}
|
||||
idi := int(id.Uint64())
|
||||
if idi == 1 {
|
||||
|
@ -333,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
|
||||
|
@ -345,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()
|
||||
|
@ -504,7 +537,7 @@ 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)
|
||||
}
|
||||
|
@ -540,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
|
||||
}
|
||||
|
@ -570,7 +603,7 @@ func (b *EthereumRPC) GetTransaction(txid string) (*bchain.Tx, error) {
|
|||
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)
|
||||
}
|
||||
|
@ -591,34 +624,10 @@ 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)
|
||||
}
|
||||
}
|
||||
n, err := ethNumber(tx.BlockNumber)
|
||||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "txid %v", txid)
|
||||
|
@ -627,7 +636,7 @@ 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)
|
||||
}
|
||||
|
@ -715,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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
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 {
|
||||
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,16 +13,17 @@ 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
|
||||
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
|
||||
)
|
||||
|
||||
|
@ -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,7 +35,7 @@ func NewZcoinRPC(config json.RawMessage, pushHandler func(bchain.NotificationTyp
|
|||
return zc, nil
|
||||
}
|
||||
|
||||
func (zc *ZcoinRPC) Initialize() error {
|
||||
func (zc *FiroRPC) Initialize() error {
|
||||
ci, err := zc.GetChainInfo()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -45,7 +45,7 @@ func (zc *ZcoinRPC) Initialize() error {
|
|||
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 {
|
||||
|
@ -61,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 == "" {
|
||||
|
@ -96,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{}
|
||||
|
@ -117,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
|
||||
|
@ -134,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{}
|
||||
|
@ -155,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{}
|
||||
|
@ -183,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
|
||||
|
@ -198,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,13 +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.
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
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 (
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
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
|
||||
|
|
|
@ -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,12 +1,12 @@
|
|||
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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
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 (
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -1,17 +1,18 @@
|
|||
package monacoin
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
// chain parameters
|
||||
var (
|
||||
MainNetParams chaincfg.Params
|
||||
TestNetParams chaincfg.Params
|
||||
|
|
|
@ -3,8 +3,6 @@
|
|||
package monacoin
|
||||
|
||||
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) {
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue