Compare commits

...

726 Commits

Author SHA1 Message Date
jebba a8595591f0 README 2021-04-05 15:16:32 -06:00
jebba cd8161a10d dark theme, minimal 2021-04-05 15:05:12 -06:00
jebba f4f7612655 more minimal 2021-04-05 14:25:58 -06:00
jebba e2a3404b06 dont pull css from cdn 2021-04-05 14:21:23 -06:00
jebba afabba64a9 minimal interface 2021-04-05 14:17:49 -06:00
jebba 473df3d139 listen on localhost, use remote host for eth 2021-04-05 13:38:29 -06:00
jebba a337163a2d deepcrayon maintainer 2021-04-05 13:31:25 -06:00
jebba 65be7edda2 just build ethereum blockbook, not backend 2021-04-05 13:31:16 -06:00
jebba 0c93f8803d no public_test.go ... 2021-04-05 13:29:13 -06:00
jebba 8fd633c6d3 dont use ssl 2021-04-05 13:08:44 -06:00
jebba 2771784113 systemd, dont want backend 2021-04-05 13:07:38 -06:00
jebba f05eff6b13 dont require backend dep 2021-04-05 13:05:55 -06:00
jebba 7ba8d7f143 s/github/spacecruft 2021-04-05 12:59:11 -06:00
jebba 14c92e8bd5 make 2021-04-05 12:59:00 -06:00
jebba 4b352b5bbc template go use spacecruft 2021-04-05 12:50:52 -06:00
jebba 124717da69 spacecruft go, not github 2021-04-05 12:19:28 -06:00
jebba 2f1c4bc3af use spacecruft repo 2021-04-05 11:55:41 -06:00
jebba 0ce520d1d5 unbrand a bit 2021-04-05 11:41:26 -06:00
jebba 8ef0a2a3d5 star favicon 2021-04-05 11:37:51 -06:00
jebba 27c9434383 about/tos 2021-04-05 11:37:06 -06:00
jebba fb64efefdc fork 2021-04-05 11:34:28 -06:00
kaladin 1f6cddd4ab
Websocket new transaction (#574) 2021-03-21 21:55:25 +01:00
CodeFace 78c8a9d499 bump Qtum 0.20.2 2021-03-21 18:23:31 +01:00
Vitalij Dovhanyč d8640f4e2f
zec (+testnet): 4.2.0 → 4.3.0 (#580)
Co-authored-by: 1000101 <b1000101@pm.me>
2021-03-11 16:45:28 +01:00
vdovhanyc d588904fb3 dogecoin: 1.14.2 → 1.14.3 2021-03-11 13:37:22 +01:00
Martin Boehm e6e6e64351 Adjust ethereum backend CLI flags for geth version 1.10 2021-03-09 14:03:49 +01:00
jsimon 61c2834002 eth (+testnet): 1.9.24 → 1.10.1 2021-03-09 12:52:58 +01:00
Martin Boehm 0ae8ba57a2 Update for geth version 1.10
- remove workaround for to low maxRequestContentLength
- stop using obsolete eth_protocolVersion RPC call
2021-03-09 11:37:35 +01:00
Martin Boehm 8f3106d009 Return filtered token balance in case of an empty account #566 2021-03-04 16:17:47 +01:00
vdovhanych 099a158f8c etc: 1.11.20 → 1.11.22 2021-02-24 13:29:30 +01:00
Perlover 37c7f4fbd1 Ubuntu 20.04 doesn't work without this patch
The problem was described here:

https://github.com/trezor/blockbook/issues/568
2021-02-23 21:12:08 +01:00
Martin Boehm db597c1f66 Update build documentation 2021-02-18 09:14:58 +01:00
Martin Boehm dcbcb99055 Add possibility to build from BASE_IMAGE 2021-02-18 09:14:58 +01:00
Martin Boehm 212b767925 Add TCMALLOC option to build of rocksdb 2021-02-18 09:14:58 +01:00
WO 3fe28d185c Bump Koto to 4.2.0 2021-01-27 09:41:12 +01:00
Rikard Wissing d0a1cb29f6 Update myriad backend to 0.18.1.0 2021-01-27 09:23:50 +01:00
Martin Boehm 2be5930862 Update ports registry and copyright year #549 2021-01-21 21:43:09 +01:00
Yusaku Senga 5fdc26bc14
feat: Support Ethereum Goerli testnet (#550) 2021-01-21 21:39:37 +01:00
Martin Boehm 7e54336e0c Return info about backend in websocket getInfo request 2021-01-21 10:31:04 +01:00
Martin Boehm 7dffe2e0f9 Show consensus in explorer index page 2021-01-21 10:31:04 +01:00
Martin Boehm d992369426 Fix linting issues 2021-01-21 10:29:25 +01:00
David Hill d97b5e14e8
Update Decred (#385)
Co-authored-by: Martin <martin.boehm@1mbsoftware.net>
2021-01-21 09:25:07 +01:00
JoHnY 9df39273ea vtc (+testnet): 0.15.0.1 → 0.17.1 2021-01-18 12:37:16 +01:00
Pavol Rusnak 66c072bf25 btc (+testnet): 0.20.1 -> 0.21.0 2021-01-16 21:56:16 +01:00
Jin Eguchi 505d859f91
bitcoin_signet: 0.21.0rc2 -> 0.21.0 (#554) 2021-01-16 21:46:23 +01:00
Dehumanizer77 d631bf9265 bch (+testnet): 22.1.0 → 22.2.0 2021-01-07 19:22:59 +01:00
Martin Kuvandzhiev 295b630ec8
Adding Bitcoin Gold Testnet to the configurations (#532)
Co-authored-by: Martin <martin.boehm@1mbsoftware.net>
2020-12-29 23:19:53 +01:00
Dehumanizer77 b4149946bd zec (+testnet): 4.1.1 → 4.2.0 2020-12-29 21:21:48 +01:00
Dehumanizer77 6981222a43 etc: 1.11.18 → 1.11.20 2020-12-29 21:18:54 +01:00
Peter John Bushnell bb9fce02cb
Add Trezarcoin (TZC) (#423)
Co-authored-by: Martin <martin.boehm@1mbsoftware.net>
2020-12-29 01:39:37 +01:00
Jin Eguchi 077e637093
add bitcoin-signet (#533) 2020-12-29 00:47:50 +01:00
araarakelyan1985 15b88ef23d
Rebranding from Zcoin to Firo (#538) 2020-12-28 23:38:56 +01:00
Martin 4697d756e0 Fix display of RBF flag in tx view 2020-12-28 23:05:02 +01:00
Martin 4766110255 Add RBF info to txdetail 2020-12-28 23:05:02 +01:00
Tomas Susanka 360cac85f6 chore(static): show RBF in the transaction detail 2020-12-28 23:05:02 +01:00
jackielove4u 554041c32c Bump Groestlcoin backend version to 2.21.0 2020-12-28 22:36:30 +01:00
CodeFace d12e6551ea bump Qtum 0.20.1 2020-12-08 22:50:12 +01:00
Martin Boehm 96e8329171 Update documentation #483 2020-12-04 12:40:39 +01:00
Martin Boehm f094ee578d Merge branch 'braydonf-docs' 2020-12-04 12:09:32 +01:00
Martin Boehm 00352cb5fe Merge branch 'docs' of https://github.com/braydonf/blockbook into braydonf-docs 2020-12-04 12:08:36 +01:00
CryptoManiac c0c2dc4151 You have to link against libdl on Linux
Otherwise there will be linking error because rocksdb is importing ```dlclose```, ```dlopen``` and other functions from libdl.so.
2020-12-04 12:05:33 +01:00
Martin Boehm da1c0d762e Unify error handling of GetTransactionSpecific #395 2020-12-04 11:57:11 +01:00
Martin Boehm fc267ed2f4 Return for mempool transactions coinSpecificData #522 2020-12-04 11:57:11 +01:00
Martin Boehm 69d13e0688 Fix ETH Ropsten: websocket: read limit exceeded #490
Geth sets maxRequestContentLength to 5M.
However, Ropsten contains blocks of largers size (for example 599281).
These which cannot be fetched using API.

Fixed by hacky way of modifying the geth source before
the build of the project.
Will submit PR to go-ethereum with final fix.
2020-12-04 11:57:11 +01:00
Martin Boehm 248de3cb34 Detect fork in connectBlocks loop 2020-12-04 11:57:11 +01:00
Martin Boehm 636167c72a Store to txcache old eth transactions without status 2020-12-04 11:57:11 +01:00
Martin Boehm 24a783be50 Move websocket connection close out of channel close mutex 2020-12-04 11:57:11 +01:00
Martin Boehm 579b42cf27 Stop using mod vendor in Blockbook build 2020-12-04 11:57:11 +01:00
Martin Boehm 576b8b57b7 Upgrade to go 1.15.6, rocksdb 6.13.3 and other dependecies 2020-12-04 11:57:11 +01:00
Martin Kuvandzhiev 786047f8c2 Updating the API docs so it shows more information about the web socket communication 2020-12-04 11:53:03 +01:00
Dehumanizer77 3ccfd181b7 zcash (+testnet): 4.1.0 → 4.1.1 2020-11-23 12:00:01 +01:00
Dehumanizer77 6274f4b3d4 zcash (+testnet): 4.0.0 → 4.1.0 2020-11-16 17:06:24 +01:00
Dehumanizer77 2b786c9832 eth (+testnet): 1.9.21 → 1.9.24 2020-11-16 17:01:58 +01:00
Dehumanizer77 bc009454d0 dash (+testnet): 0.15.0.0 → 0.16.0.1 2020-11-16 16:01:37 +01:00
Dehumanizer77 5e7d0e9f75 etc: 1.11.15 → 1.11.18 2020-11-16 15:59:54 +01:00
Pavol Rusnak c915f35224
bch(+testnet): 0.22.6 -> 22.1.0 (switch to Bitcoin Cash Node)
use spoofed subversion to not confuse wallets
2020-11-16 12:26:42 +01:00
Pavol Rusnak 3369295e10
bch(+testnet): 0.22.0 -> 0.22.6 (switch to BCHN) (#511) 2020-11-16 11:43:52 +01:00
hewigovens 5534372e7c
[Zcash] Expose zcash consensus info (#508) 2020-11-12 19:56:41 +01:00
Martin Boehm fc25200ff8 Fix ineffassign errors 2020-11-12 15:41:51 +01:00
Martin Boehm 3d9954bf79 Improve locking and add panic handlers to websocket functionality 2020-11-12 15:31:59 +01:00
nezero 214d0144ef Ignore DeepOnion QT 2020-11-06 10:21:19 +01:00
Liam Alford ec79702bab Bump DeepOnion Version to v2.2 2020-11-06 10:21:19 +01:00
Scotty0448 e666e7c5a4 Bump Ritocoin backend to 2.4.2.0 2020-11-06 10:19:31 +01:00
JoHnY 4832205f45 etc: 1.11.12 -> 1.11.15 2020-10-06 14:23:07 +02:00
1000101 b05346b1a1 eth (+testnet): 1.9.20 -> 1.9.21 2020-09-15 00:51:23 +02:00
WO dcf77a5680 Bump Koto to 4.0.0 2020-09-15 00:35:17 +02:00
1000101 7f1cf09d05 zec (+testnet): 3.1.0 -> 4.0.0 2020-09-08 11:24:32 +02:00
Martin a1993173ab
Go ethereum v1.9.20 (#482) issue #481
Handle different behavior of geth from v1.9.15
Bump go-ethereum dependecy to v1.9.20
2020-09-03 10:11:37 +02:00
jackielove4u bea6b6230f Add fiat rates for Groestlcoin 2020-09-01 10:10:33 +02:00
jackielove4u 72486c606f Bump Groestlcoin backend version to 2.20.1 2020-08-26 13:28:36 +02:00
1000101 a8ee6aefb0 bch(+testnet): 0.21.10->0.22.0 2020-08-26 13:24:57 +02:00
1000101 52cbc7162d eth(+testnet): 1.9.19->1.9.20 2020-08-26 10:31:22 +02:00
Pavol Rusnak 81ce876d8b
nix: add dependencies to shell.nix 2020-08-21 16:11:21 +02:00
Braydon Fuller 7b70ee0ad0
Add blockchaincfg.json to .gitignore 2020-08-20 15:21:55 -07:00
Braydon Fuller a83cb7684f
Update build documentation 2020-08-20 15:21:23 -07:00
Pavol Rusnak 0f4eadd935
nix: add trivial shell.nix for development 2020-08-20 17:52:58 +02:00
1000101 e66fa79383 btg: 0.17.2->0.17.3 2020-08-20 10:34:33 +02:00
1000101 f99406e9cf zec(+testnet): 3.0.0->3.1.0 2020-08-20 10:34:04 +02:00
1000101 be73064223 eth(+testnet): 1.9.13->1.9.19 2020-08-20 10:24:28 +02:00
Panu 79907e7aa5
Update Zcoin transaction parser and bump binary version (#466) 2020-08-20 10:23:23 +02:00
1000101 2fb1e779c0 etc: 1.11.7->1.11.12 2020-08-20 10:15:17 +02:00
1000101 a530f5612a btc(+testnet): 0.20.0->0.20.1 2020-08-20 10:05:21 +02:00
Martin Boehm ab285c6b05 Increase max size of reorg of ETC to 10000 blocks 2020-08-06 10:27:08 +02:00
Martin Boehm 17c9080135 Include eth transactions in unknown status into balance history 2020-07-30 16:02:08 +02:00
1000101 791948623e bgold: 0.15.2->0.17.2 2020-07-16 00:46:35 +02:00
Scotty0448 af5e8f18ba Bump Ravencoin backend to 4.2.1.0 2020-07-15 23:55:12 +02:00
root 07ac3c8401 bcash (+testnet): Bump backend 0.21.0 -> 0.21.10 2020-07-07 16:56:44 +02:00
Martin Boehm 83616bce83 Fix integration tests script 2020-06-30 15:06:11 +02:00
codeface 22145d0cc2 bump Qtum 0.19.1 2020-06-29 17:35:30 +02:00
Dehumanizer77 92ae2052c3 etc: Bump backend 1.11.2->1.11.7 2020-06-29 17:17:36 +02:00
Dehumanizer77 30149e51d2 ltc (+testnet): Bump backend 0.17.1->0.18.1 2020-06-29 17:08:52 +02:00
Martin Boehm abb6453fb3 Fix dash testnet config #447 2020-06-22 18:40:54 +02:00
Martin Boehm eb4e10ac67 Bump Blockbook version to 0.3.4 2020-06-12 11:52:21 +02:00
1000101 5350027e1d btc (+testnet): Bump backend 0.19.0.1->0.20.0 2020-06-03 18:46:00 +02:00
1000101 7d6c61623e zec (+testnet): Bump backend 2.1.2->3.0.0 2020-05-29 20:03:01 +02:00
Martin Boehm 994567aed9 Add fee value to unspent transactions balance 2020-05-26 23:21:25 +02:00
Martin Boehm dd7964297d Suppress logging of MQ errors 2020-05-24 19:18:23 +02:00
Martin Boehm 3be3bb5c3d Regenerate registry of ports 2020-05-24 17:58:44 +02:00
Martin Boehm 0a3ea6e225 Send websocket notification on new tx for input addresses 2020-05-24 17:58:29 +02:00
Martin Boehm bc001ce3a3 Make logs cleaner by not logging public API errors in websocket 2020-05-22 11:19:37 +02:00
Martin Boehm 76324be8ec Modify logging 2020-05-21 22:43:18 +02:00
Martin Boehm 01d8e48e73 Unconfirmed eth balance not being updated #408 2020-05-21 18:05:16 +02:00
Martin Boehm ff607bc334 Check ERC20 contract balance if no transactions were done for address 2020-05-21 18:05:16 +02:00
Martin Boehm e60c320ae7 Allow parameters value, gasPrice and gas to be passed to ETH estimateFee 2020-05-21 18:05:16 +02:00
Martin Boehm dd2dc6b2ee Add sentToSelf to BalanceHistory 2020-05-21 18:05:16 +02:00
wakiyamap b957ed66ab Add fiat rate(monacoin) 2020-05-20 00:05:26 +02:00
Adam Collier 3ebe99edb2
Add support for DigiByte Testnet (#432) 2020-05-19 23:58:46 +02:00
v bad9f992e1 eth-like backends listen on localhost instead * 2020-05-19 23:56:35 +02:00
jackielove4u 707ac28954 Bump Groestlcoin backend version to 2.19.1 2020-05-12 21:56:47 +02:00
Martin Boehm b6961ca600 Bump Blockbook version to 0.3.3 2020-05-11 19:12:39 +02:00
Martin Boehm 5492a51534 Upgrade gorilla websocket library to v1.4.2 2020-05-11 19:10:51 +02:00
Martin Boehm a7d95a49df Use go1.14.2 to build Blockbook 2020-05-10 00:26:47 +02:00
Martin Boehm ee3217aba8 Use common.JSONNumber instead of json.Number 2020-05-10 00:14:14 +02:00
Martin Boehm c3d58f0649 Add common.JSONNumber type 2020-05-10 00:02:34 +02:00
Martin Boehm 180b5655d5 Update documentation 2020-05-09 22:54:57 +02:00
hewigovens 3ba7289587 Add data field to EthereumSpecific 2020-05-09 21:43:33 +02:00
ciripel a34ee217b5 Fixed issue with folder containing spaces in the path of the repo 2020-05-09 12:01:54 +02:00
WO 11e9de2ffe Bump Koto backend to 2.1.2 2020-05-09 11:59:05 +02:00
Martin Boehm 81a1acd6f8 Update documentation to reflect the use of go modules 2020-05-09 11:21:53 +02:00
Martin Boehm 997fa661e7 Merge branch 'master' into gomod 2020-05-09 01:50:51 +02:00
Martin Boehm 8a926a0799 Merge branch 'master' of github.com:trezor/blockbook 2020-05-02 17:25:10 +02:00
Martin Boehm 828e10b629 Parsing of ERC20 name, symbol and decimals in different format #400 2020-05-02 17:24:56 +02:00
Scotty0448 a6fd137283 Bump Ravencoin backend to 4.1.0.0 2020-05-02 00:48:17 +02:00
Dehumanizer77 f97d23590c zec (+testnet): Bump backend 2.1.1-1->2.1.2 2020-05-02 00:41:00 +02:00
Dehumanizer77 945827c330 vtc (+testnet): Bump backend 0.14.0->0.15.0.1 2020-04-30 13:13:52 +02:00
Dehumanizer77 42bc7d2ba3 eth (+testnet): Bump backend 1.9.11->1.9.13 2020-04-30 10:20:21 +02:00
Adam Collier ddd981a405 Add in xpub magic to vertcoin testnet 2020-04-16 16:29:09 +02:00
Dehumanizer77 ea6a7e0db6 etc: Bump backend 1.11.1 -> 1.11.2 2020-04-16 11:43:32 +02:00
Scotty0448 7eb4675f54 Update Ravencoin support 2020-04-16 09:55:34 +02:00
ciripel 124dee84fa
SnowGem Support (#342)
* SnowGem Support

* changed bynary_url to debian9.11 source
The Debian Source contain also the fetch_params.sh

* changed sha256 source

* Fix integration tests config file after merge

* Implemented snowgem parser

* fixed paxkedTx

* Fixed testdata tx hex

Co-authored-by: Martin <martin.boehm@1mbsoftware.net>
2020-04-11 16:34:37 +02:00
Martin Boehm 5f1957b4ff Upgrade btcd and btcutil libraries 2020-04-11 14:59:50 +02:00
Martin Boehm c43a7a4feb Merge branch 'gomod' of https://github.com/dajohi/blockbook into gomod 2020-03-21 01:17:42 +01:00
ilmango e99a8eba65
Add BitZeny (ZNY) (#383)
* Add BitZeny

* Fix test packed tx

* Fix exec command template
2020-03-21 00:39:24 +01:00
Martin Boehm 2a3c5426ca Improve remove empty map on websocket unsubscribe 2020-03-17 00:08:00 +01:00
Kirill Fomichev c2e32b0a25 Remove empty map on address unsubscribe 2020-03-16 23:58:45 +01:00
Martin Boehm 4338d10dcb Use newer version of btcd and btcutil libraries 2020-03-14 00:14:52 +01:00
TheTrunk b4bddc8c0e
update zelcash binaries to 4.0.0 (#389) 2020-03-11 16:12:14 +01:00
JoHnY c705600aae
etc: Bump backend 1.11.0-core -> 1.11.1 #391 #387 2020-03-11 15:44:44 +01:00
Martin Boehm 48584b3070 Disable downloading of fiat rates for Bitcoin and Ethereum testnet 2020-03-06 14:34:58 +01:00
David Hill 395db88a60 Adjust test scripts 2020-03-05 11:54:42 -05:00
David Hill 8f8ade727c Adjust build scripts 2020-03-05 11:54:42 -05:00
David Hill 13527bda06 build: go module support 2020-03-05 11:54:42 -05:00
Martin Boehm d1fd66597b Bump Blockbook to version 0.3.2 2020-03-05 11:26:43 +01:00
Martin Boehm 6d1725717e Fix typos and texts 2020-03-05 11:18:21 +01:00
Martin Boehm 836568b716 Highlight our contributing policy 2020-03-05 10:44:11 +01:00
Martin Boehm 7d8ee6bc1d Add fiatRates DB column to documetation 2020-03-04 11:53:14 +01:00
Dehumanizer77 0de7475d89 etc: Bump backend 1.9.9 -> 1.11.0-core 2020-03-04 10:50:52 +01:00
Martin Boehm e47fd242cf Add option for lightweight tx details (txslight) to address API #381 2020-03-04 10:45:10 +01:00
Martin Boehm 53cc6237a7 Format all ethereum addresses as EIP55 2020-03-04 10:17:47 +01:00
Martin Boehm a6c01534f2 Add filter by contract to get address API #379 2020-03-04 00:37:16 +01:00
Martin Boehm f761dbec2a Fix linting errors 2020-03-03 10:22:11 +01:00
omotenashicoin-project 2d430aa80b added MTNS 2020-02-25 18:24:00 +01:00
Martin Boehm 1d0a985c9e Fix packBlockInfo 2020-02-25 00:55:11 +01:00
Martin Boehm a4da2f3865 Insert utxos in fixUtxo in the growing order 2020-02-24 23:11:46 +01:00
Martin Boehm 9feccfdb2e Automatically check for UTXO inconsitencies 2020-02-24 23:11:46 +01:00
Martin Boehm 1b713308a3 Fix order of utxos 2020-02-24 23:11:46 +01:00
Martin Boehm 7f46fbab0d Fix incorrect order of utxos 2020-02-24 23:11:46 +01:00
Martin Boehm d583028721 Check the order of utxos 2020-02-24 23:11:46 +01:00
Martin Boehm 2e37cbb974 Insert utxos in the right order in disconnect block 2020-02-24 23:11:46 +01:00
Martin Boehm 00b0a402ea Alter disconnect block procedure to avoid possible utxo db inconsistency 2020-02-24 23:11:46 +01:00
Martin Boehm fd4181d03f Alter logging of a possible db inconsitency in addresses column 2020-02-24 23:11:46 +01:00
Martin Boehm 47173774f6 Optimize and sort correctly fixed utxos 2020-02-24 23:11:46 +01:00
Martin Boehm 5a2b67bc9a Improve storing/loading of block info to db 2020-02-24 23:11:46 +01:00
Martin Boehm 2493c3d1af Fix errors in utxo db 2020-02-24 23:11:46 +01:00
Martin Boehm 58f426207e Handle possible address descriptor mismatch in db column addresses 2020-02-24 23:11:46 +01:00
Martin Boehm 0751ed452c Check for error in utxo DB 2020-02-24 23:11:46 +01:00
Martin Boehm 273b880245 Add load address by serialized address descriptor 2020-02-24 23:11:46 +01:00
judong d6de5b8048 Update bcashsv.json
excessiveblocksize,maxstackmemoryusageconsensus
look https://bitcoinsv.io/2020/01/15/genesis-1-0-0-stable-release/
The purpose of the daemon configuration is to sendrawtransaction
2020-02-24 21:34:45 +01:00
Martin Boehm c33ab2b8d5 Remove cashaddr address format from Bcash SV 2020-02-24 21:34:45 +01:00
f4r4 7a5881c7c9 Bump bchsv backend 0.2.1 -> 1.0.1 2020-02-22 23:16:59 +01:00
WO 55e1861eae Bump Koto backend to 2.1.1-1 2020-02-22 23:14:57 +01:00
JoHnY d5b304c76a
Bump zec, bch, eth and dash backends (#370)
* zec (+testnet): Bump backend 2.1.0-1 -> 2.1.1-1

* bch (+testnet): Bump backend 0.20.8 -> 0.21.0

* eth (+testnet): Bump backend 1.9.10 -> 1.9.11

* dash (+testnet): Bump backend 0.14.0.5 -> 0.15.0.0
2020-02-19 15:27:51 +01:00
Martin Boehm 7e35bac99c Remove etc specific code after upgrade to multi-geth backend 2020-02-11 12:08:03 +01:00
JoHnY 744fd45c06
etc: Switch from getc 6.0.9 to multi-geth/etclabscore 1.9.9 (#362)
* etc: Switch from getc 6.0.9 to multi-geth/etclabscore 1.9.9
* etc: Fix multi-geth to sync with correct chain
2020-02-11 12:07:47 +01:00
jackielove4u bd5726508f Bump Groestlcoin backend version to 2.18.2 2020-01-29 11:23:12 +01:00
Vladyslav Burzakovskyy 82debaa50e balanceHistory: remove empty currencies from the list, fix typo 2020-01-28 12:48:40 +01:00
Martin Boehm 95ac05b280 Improve error logging related to utxo requests 2020-01-22 16:38:29 +01:00
Martin Boehm aceadbb10c Fix formatting and linting errors 2020-01-22 16:07:30 +01:00
Vladyslav Burzakovskyy f0ccab3e01 getFiatRatesForTimestamps: remove empty currencies from the currency slice 2020-01-22 15:26:12 +01:00
Vladyslav Burzakovskyy 9455417e8b public_test.go: add another test case for /api/v2/tickers 2020-01-22 15:26:12 +01:00
Vladyslav Burzakovskyy 4b63b483e4 fiatRates: always return rates as a map, even if the ticker is unavailable 2020-01-22 15:26:12 +01:00
Vladyslav Burzakovskyy 357ad63bde test-websocket.html: remove debug log :) 2020-01-22 15:26:12 +01:00
Vladyslav Burzakovskyy 4ca66f3b1d fiatRates: accept an array of strings everywhere and return all available rates if it's empty 2020-01-22 15:26:12 +01:00
Vladyslav Burzakovskyy 87065d13ef balanceHistory: update docs 2020-01-22 15:26:12 +01:00
Vladyslav Burzakovskyy 4b564510e0 balanceHistory: return all currencies if the "currency" parameter is empty 2020-01-22 15:26:12 +01:00
Vladyslav Burzakovskyy 5b41a37da6 fiatRates, balanceHistory: update docs 2020-01-22 15:26:12 +01:00
Vladyslav Burzakovskyy 8ea853c388 balanceHistory: fix currencies list and update placeholders in test-websocket.html 2020-01-22 15:26:12 +01:00
Vladyslav Burzakovskyy 171b7f9b9d balanceHistory: accept a list of currencies, update tests 2020-01-22 15:26:12 +01:00
Vladyslav Burzakovskyy 6f06b549df balanceHistory: accept Unix timestamps instead of a date string 2020-01-22 15:26:12 +01:00
Vladyslav Burzakovskyy 729c35334a worker.go: use the correct data timestamp for getFiatRatesTickersList 2020-01-22 15:26:12 +01:00
Dehumanizer77 a8f436fe4c eth (+testnet): Bump backend 1.9.9 -> 1.9.10 2020-01-21 15:20:21 +01:00
WO a89a1fb85a Bump Koto backend to 2.1.0-1 2020-01-18 16:53:50 +01:00
Scotty0448 6429290fdc Bump Ravencoin backend to 3.3.1.0 2020-01-16 22:40:07 +01:00
Dali e4231a2eaa Add Bitcore (BTX) 2020-01-14 11:57:46 +01:00
Vladyslav Burzakovskyy 29af6eb34d currencyRates: make websocket "currency" arguments case-insensitive, like in REST API 2020-01-08 17:57:47 +01:00
Vladyslav Burzakovskyy acaebd41c0 docs/api: use a more adequate groupBy value in example 2020-01-08 17:57:47 +01:00
Vladyslav Burzakovskyy c7730de36a docs/api: remove "gap" parameter, add "groupBy" example response 2020-01-08 17:57:47 +01:00
Vladyslav Burzakovskyy 2399f245e4 docs/api: document the fiatRates and balanceHistory endpoints 2020-01-08 17:57:47 +01:00
Vladyslav Burzakovskyy 7f992e9eb8 test-websocket.html: add "groupBy" parameter to getBalanceHistory 2020-01-08 17:57:47 +01:00
Martin 5a604441b4 Fix formatting 2020-01-08 17:57:47 +01:00
Martin 20459a2f46 Fix groupBy parameter parsing 2020-01-08 17:57:47 +01:00
Vladyslav Burzakovskyy 94977dc5ef balanceHistory: fix groupBy parameter initialization 2020-01-08 17:57:47 +01:00
Vladyslav Burzakovskyy 6919055e30 balanceHistory: sanitize the groupBy parameter in websocket API 2020-01-08 17:57:47 +01:00
Vladyslav Burzakovskyy 918e032bfe balanceHistory: add "groupBy" parameter 2020-01-08 17:57:47 +01:00
Vladyslav Burzakovskyy d162348d36 fiatRates: update tests according to 9045a9ef64 2020-01-08 17:57:47 +01:00
Vladyslav Burzakovskyy db8e0938df fiatRates: rename "data_timestamp" field to "ts" 2020-01-08 17:57:47 +01:00
Vladyslav Burzakovskyy 9cec5424e7 fiatRates: fields to camelCase, update output format and tests 2020-01-08 17:57:47 +01:00
Vladyslav Burzakovskyy c58b1538d6 fiatRates: timestamps as int64, errors as -1, update field names and tests 2020-01-08 17:57:47 +01:00
Vladyslav Burzakovskyy bc0e95f174 websocket: make allFiatRates parameter string a constant 2020-01-08 17:57:47 +01:00
Vladyslav Burzakovskyy bef572c494 FiatRates: store rates as float64 instead of json.Number 2020-01-08 17:57:47 +01:00
Vladyslav Burzakovskyy 14c64410f7 FiatRates: change input & output time values to Unix timestamps
make currency parameter optional, return all rates if it's empty

update tests
2020-01-08 17:57:47 +01:00
Martin Boehm f2ff7985b1 Improve explorer responsiveness 2020-01-04 00:57:17 +01:00
random.zebra b7a430574f PIVX (+testnet): Bump backend 3.1.1 -> 4.0.0 (#340)
* PIVX: bump to 4.0.0

* PIVX: remove AccCheckpoint in block version 7

ref https://github.com/PIVX-Project/PIVX/pull/1022

* PIVX: Test block v7 in pivxparser_test
2020-01-03 12:45:40 +01:00
Martin Boehm 1ed7b3f2d9 Fix repeated utxos (unconfirmed and confirmed) #275 2020-01-03 11:53:49 +01:00
Martin Boehm 35c9da1ce8 Improve explorer navbar responsiveness, update copyright year #327, #343 2020-01-02 17:29:14 +01:00
Martin 1ce7bc1406
Merge pull request #337 from trezor/balanceHistory
Add API for account balance history #307
2019-12-18 00:05:34 +01:00
Martin Boehm 225ac85a2a Add optional fiat rate to balance history 2019-12-18 00:02:24 +01:00
Martin Boehm 0340cef13c Fix linting errors 2019-12-17 15:37:09 +01:00
Martin Boehm 15e2c0bf41 Add websocket method getBalanceHistory 2019-12-17 14:37:24 +01:00
Martin Boehm 1cec22ecba Merge branch 'master' into balanceHistory 2019-12-17 11:52:33 +01:00
Vladyslav Burzakovskyy f6111af5da Add fiat rates functionality (#316)
* Add initial commit for fiat rates functionality

* templates.go: use bash from current user's environment

* bitcoinrpc.go: add FiatRates and FiatRatesParams to config

* blockbook.go: add initFiatRatesDownloader kickoff

* bitcoin.json: add coingecko API URL

* rockdb.go: add FindTicker and StoreTicker functions

* rocksdb_test.go: add a simple test for storing and getting FiatRate tickers

* rocksdb: add FindLastTicker and convertDate, make FindTicker return strings

* rocksdb: add ConvertDate function and CoinGeckoTicker struct, update tests

* blockbook.go, fiat: finalize the CoinGecko downloader

* coingecko.go: do not stop syncing when encountered an error

* rocksdb_test: fix the exported function name

* worker.go: make getBlockInfoFromBlockID a public function

* public.go: apiTickers kickoff

* rocksdb_test: fix the unittest comment

* coingecko.go: update comments

* blockbook.go, fiat: reword CoinGecko -> FiatRates, fix binary search upper bound, remove assignment of goroutine call result

* rename coingecko -> fiat_rates

* fiat_rates: export only the necessary methods

* blockbook.go: update log message

* bitcoinrpc.go: remove fiatRates settings

* use CurrencyRatesTicker structure everywhere, fix time format string, update tests, use UTC time

* add /api/v2/tickers tests, store rates as strings (json.Number)

* fiat_rates: add more tests, metrics and tickers-list endpoint, make the "currency" parameter mandatory

* public, worker: move FiatRates API logic to worker.go

* fiat_rates: add a future date test, fix comments, add more checks, store time as a pointer

* rocksdb_test: remove unneeded code

* fiat_rates: add a "ping" call to check server availability

* fiat_rates: do not return empty ticker, return nil instead if not found

add a test for non-existent ticker

* rocksdb_test: remove Sleep from tests

* worker.go: do not propagate all API errors to the client

* move InitTestFiatRates from rocksdb.go to public_test.go

* public.go: fix FiatRatesFindLastTicker result check

* fiat_rates: mock API server responses

* remove commented-out code

* fiat_rates: add comment explaining what periodSeconds attribute is used for

* websocket.go: implement fiatRates websocket endpoints & add tests

* fiatRates: add getFiatRatesTickersList websocket endpoint & test

* fiatRates: make websocket getFiatRatesByDate accept an array of dates, add more tests

* fiatRates: remove getFiatRatesForBlockID from websocket endpoints

* fiatRates: remove "if test", use custom startTime instead

Update tests and mock data

* fiatRates: finalize websocket functionality

add "date" parameter to TickerList

return data timestamps where needed

fix sync bugs (nil timestamp, duplicate save)

* fiatRates: add FiatRates configs for different coins

* worker.go: make GetBlockInfoFromBlockID private again

* fiatRates: wait & retry on errors, remove Ping function

* websocket.go: remove incorrect comment

* fiatRates: move coingecko-related code to a separate file, use interface

* fiatRates: if the new rates are the same as previous, try five more times, and only then store them

* coingecko: fix getting actual rates, add a timestamp parameter to get uncached responses

* vertcoin_testnet.json: remove fiat rates parameters

* fiat_rates: add timestamp to log message about skipping the repeating rates
2019-12-17 10:40:02 +01:00
Jan Hrnko e2b34afb9c bch (+testnet): Bump backend 0.20.7 -> 0.20.8 2019-12-11 14:20:11 +01:00
Jan Hrnko 81ca37e51f dash (+testnet): Bump backend 0.14.0.4 -> 0.14.0.5 2019-12-10 22:53:40 +01:00
Jan Hrnko beeddef239 nmc: Bump backend 0.19.0 -> 0.19.0.1 2019-12-10 22:53:40 +01:00
Jan Hrnko cd3cad423a nmc: Bump backend 0.18.0 -> 0.19.0 2019-12-10 22:53:40 +01:00
Jan Hrnko 3ac22a7571 eth (+testnet): Bump backend 1.9.8 -> 1.9.9 2019-12-10 22:53:40 +01:00
thebevrishot 76beadef8b Bump Zcoin to 0.13.8.8 2019-12-10 22:53:40 +01:00
Jan Hrnko 0a177d554c bch (+testnet): Bump backend 0.20.6 -> 0.20.7 2019-12-10 22:53:40 +01:00
Jan Hrnko 5c4f1d2674 eth (+testnet): Bump backend 1.9.7 -> 1.9.8 2019-12-10 22:53:40 +01:00
Jan Hrnko 684712680f btc (+testnet): Bump backend 0.18.1 -> 0.19.0.1 2019-12-10 22:53:40 +01:00
Jan Hrnko 5c7b1c9a0c dash (+testnet): Bump backend 0.14.0.3 -> 0.14.0.4 2019-12-10 22:53:40 +01:00
Jan Hrnko 80aa7bc4a4 dash (+testnet): Bump backend 0.14.0.4 -> 0.14.0.5 2019-12-10 22:52:16 +01:00
Jan Hrnko d092c42e21 nmc: Bump backend 0.19.0 -> 0.19.0.1 2019-12-10 21:20:52 +01:00
Martin Boehm 6f294a6241 Add get balance history for ethereum type coins 2019-12-10 21:08:27 +01:00
Jan Hrnko 2a818d8780 nmc: Bump backend 0.18.0 -> 0.19.0 2019-12-07 12:05:31 +01:00
Jan Hrnko f09b8ef683 eth (+testnet): Bump backend 1.9.8 -> 1.9.9 2019-12-06 14:06:21 +01:00
Martin Boehm c913a022ef Add get balance history for xpub 2019-12-03 16:02:20 +01:00
thebevrishot 630ef1d308 Bump Zcoin to 0.13.8.8 2019-12-03 11:40:59 +01:00
Jan Hrnko f94878b234 bch (+testnet): Bump backend 0.20.6 -> 0.20.7 2019-11-30 18:40:31 +01:00
Martin Boehm 62208b9634 Add aggregation to balance history 2019-11-29 19:26:20 +01:00
Jan Hrnko b3367f8f8c eth (+testnet): Bump backend 1.9.7 -> 1.9.8 2019-11-28 19:10:27 +01:00
Martin Boehm bf3d822b87 Add filter from-to to balance history 2019-11-25 18:34:33 +01:00
Jan Hrnko 0baf02c6b8 btc (+testnet): Bump backend 0.18.1 -> 0.19.0.1 2019-11-25 00:39:27 +01:00
Jan Hrnko 0f4f71d2b0 dash (+testnet): Bump backend 0.14.0.3 -> 0.14.0.4 2019-11-24 18:28:53 +01:00
Martin Boehm da714b5299 Fix error message informing about missing Ethereum xpub support #314 2019-11-23 14:34:53 +01:00
Martin Boehm 5600e0d30a Fix formatting/linting issues 2019-11-23 14:34:53 +01:00
Martin Boehm e754a6c0fd Fix incorrect registration of network params in unobtanium and viacoin 2019-11-23 14:34:53 +01:00
Jan Hrnko d26e1fbb3b doge: Bump backend 0.14.0 -> 0.14.2 2019-11-23 14:34:53 +01:00
Liam Alford 3853520a0f Add DeepOnion (#298)
* Add DeepOnion

* Fix config bugs

* Use base pack/unpck TX

* Fix empty array causing test failure.

* Fix config files and executable.

* Fix sync issue

* Fix integration tetsts
2019-11-23 14:34:53 +01:00
Jan Hrnko f6ea86e52b bch (+testnet): Bump backend 0.20.5 -> 0.20.6 2019-11-23 14:34:53 +01:00
Martin Boehm 7ae81a704b Fix version in Zcash and Zcash-testnet configs 2019-11-23 14:34:53 +01:00
Mykola c9b72b7aee Return to release tag 2.0.1-1 2019-11-23 14:34:53 +01:00
Mykola a400ca188a Fix release number 2019-11-23 14:34:53 +01:00
Mykola c44ce00114 zcash (+testnet): Bump backend 2.0.7-3 -> 2.1.0-1 2019-11-23 14:34:53 +01:00
Jan Hrnko fa2ee739c6 eth (+testnet): Bump backend 1.9.6 -> 1.9.7 2019-11-23 14:34:53 +01:00
thebevrishot c3ba9a7b95 Bump Zcoin to 0.13.8.5 2019-11-23 14:34:53 +01:00
WO 0aadb241fb Bump Koto backend to 2.1.0 2019-11-23 14:34:53 +01:00
Jan Hrnko a7b42f1de0 bch (+testnet): Bump backend 0.20.4 -> 0.20.5 2019-11-23 14:34:53 +01:00
Martin Boehm e4c6d23389 Fix error message informing about missing Ethereum xpub support #314 2019-11-19 11:56:40 +01:00
Martin Boehm f7bbffa4c9 Fix formatting/linting issues 2019-11-19 11:15:00 +01:00
Martin Boehm c45312edf1 Fix incorrect registration of network params in unobtanium and viacoin 2019-11-19 11:13:15 +01:00
Jan Hrnko dfafd780dc doge: Bump backend 0.14.0 -> 0.14.2 2019-11-18 21:10:52 +01:00
Liam Alford 4134934031 Add DeepOnion (#298)
* Add DeepOnion

* Fix config bugs

* Use base pack/unpck TX

* Fix empty array causing test failure.

* Fix config files and executable.

* Fix sync issue

* Fix integration tetsts
2019-11-18 17:51:45 +01:00
Jan Hrnko bb1b909361 bch (+testnet): Bump backend 0.20.5 -> 0.20.6 2019-11-15 08:53:14 +01:00
Martin Boehm eb4f049912 Fix version in Zcash and Zcash-testnet configs 2019-11-13 13:43:06 +01:00
Mykola bd0848dbbe Return to release tag 2.0.1-1 2019-11-13 11:19:21 +01:00
Mykola ff415ae394 Fix release number 2019-11-13 11:19:21 +01:00
Mykola 42a208be15 zcash (+testnet): Bump backend 2.0.7-3 -> 2.1.0-1 2019-11-13 11:19:21 +01:00
Jan Hrnko 1c929f2a40 eth (+testnet): Bump backend 1.9.6 -> 1.9.7 2019-11-08 08:43:27 +01:00
thebevrishot 21fe8082dd Bump Zcoin to 0.13.8.5 2019-11-06 17:08:12 +01:00
WO f2e4e67c4d Bump Koto backend to 2.1.0 2019-11-06 17:07:37 +01:00
Martin Boehm 6cfb881a04 Add GetBalanceHistory for an address of bitcoin type 2019-11-04 10:55:00 +01:00
Jan Hrnko bc4b1905f5 bch (+testnet): Bump backend 0.20.4 -> 0.20.5 2019-11-01 17:37:33 +01:00
Martin Boehm 262ca3e2e4 Fix litecoin address parsing issue #254 2019-10-23 14:00:56 +02:00
Jan Hrnko f4501e7e1f bch (+testnet): Bump backend 0.20.2 -> 0.20.4 2019-10-18 21:36:45 +02:00
Min Khang Aung 6645178782 Bump CPUchain backend to 0.18.1 2019-10-17 20:17:28 +02:00
Scotty0448 5d398cc0d7 Bump Ritocoin backend to 2.4.1.0 2019-10-10 15:30:19 +02:00
Martin Boehm 1c192f6d0b Fix invalid BlockHeight for unconfirmed transactions #301 2019-10-09 14:49:06 +02:00
Jan Hrnko 83b1552dfa eth (+testnet): Bump backend 1.9.5 -> 1.9.6 2019-10-03 16:02:51 +02:00
Martin Boehm 4eff57189d Fix ETH address API - some tokens are missing #271 2019-09-30 17:28:10 +02:00
Martin Boehm ac9a721cc6 Format Ethereum addresses with EIP55 checksum 2019-09-30 17:11:17 +02:00
Jan Hrnko d3931953d5 zcash (+testnet): Bump backend 2.0.7-2 -> 2.0.7-3 2019-09-26 12:48:01 +02:00
Martin Boehm 8851c649d5 Fix linting errors 2019-09-23 09:38:48 +02:00
migwi f28c6bcf61 Check unindexed confirmed tx height for decred 2019-09-22 17:31:04 +02:00
vlddm 86a0b5783d Disable wallet functionality for bitcoind 2019-09-22 17:28:09 +02:00
Jan Hrnko 1bc9feb7fe eth (+testnet): Bump backend 1.9.3 -> 1.9.5 2019-09-20 16:59:37 +02:00
代码脸 fe24ec2913 bump Qtum to 0.18.1 (#294) 2019-09-20 11:36:05 +02:00
atomlab 28a8641c8e add decred.conf (#292)
* Update decred.json

change config path to decred.conf

* Create decred.conf

* Create decred_client.conf

* Update decred.json

* Update decred.conf

* Update decred.conf

* Update decred_client.conf
2019-09-20 11:35:26 +02:00
migwi d4a7fcabd9 Check if unindexed confirmed tx height exists 2019-09-18 22:35:53 +02:00
Jan Hrnko 0f39657006 bcash (+testnet): Bump backend 0.20.1 -> 0.20.2 2019-09-18 15:31:39 +02:00
Min Khang Aung 24725a21a9 Add CPUchain support (#288)
* Add CPUchain support

* Update cpuchain.json
2019-09-16 22:32:08 +02:00
TheCreator 12c4217f94 Add Unobtanium (#276)
* Create Unobtanium Configs

* Add Unobtanium

* Add Unobtanium

* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload

* Fix Unobtanium xpub_magic

* Fix Server/Client config files

TODO: Maybe remove reindex: 1 from additional params.

* Fix xpub

* Reduce the polling interval

As suggested by martinboehm, copied from viacoin

* Remove GetBlockHeader

Fails Integration tests.
2019-09-15 22:53:51 +02:00
JoHnY 8829cdf525 ethereum (+testnet): Bump backend 1.9.2 -> 1.9.3 (#287) 2019-09-04 18:46:06 +02:00
Jan Hrnko 485887a130 bcash (+testnet): Bump backend 0.20.0 -> 0.20.1 2019-09-04 18:41:18 +02:00
JoHnY 1d1b02388d zcash (+testnet): Bump backend 2.0.7 -> 2.0.7-2 (#285)
* Bump zcash backend to 2.0.7

* zcash (+testnet): Bump backend 2.0.5-2 -> 2.0.7

* zcash (+testnet): Bump backend 2.0.7 -> 2.0.7-2

* zcash (+testnet): Bump backend 2.0.7 -> 2.0.7-2
2019-09-04 12:57:53 +02:00
Enrique 00253004ee Bump Polis v1.4.18 2019-09-04 12:57:13 +02:00
Martin Boehm 9d5793a8e3 Add backend-deploy-and-test CI job for Namecoin 2019-08-30 18:16:39 +02:00
Jan Hrnko 1ee09331c9 nmc: Bump backend 0.16.3 -> 0.18.0 2019-08-30 18:02:11 +02:00
Scotty0448 2d62e3c306 Bump Ravencoin backend to 2.5.1.0 2019-08-29 23:41:22 +02:00
Martin Boehm a4594bf073 Fix ethereum-classic backend package 2019-08-28 17:26:12 +02:00
JoHnY 5431b19cef Bump zcash backend to 2.0.7 (#273)
* Bump zcash backend to 2.0.7

* zcash (+testnet): Bump backend 2.0.5-2 -> 2.0.7
2019-08-28 14:14:47 +02:00
wakiyamap c689eedece Add monacoin testnet params 2019-08-26 11:32:32 +02:00
Jan Hrnko 431473c30d etc: Bump backend 5.5.2 -> 6.0.9 2019-08-23 11:59:26 +02:00
Martin Boehm 3a8da67752 Fix linting errors 2019-08-21 23:11:50 +02:00
Jan Hrnko d089a84ba9 bch (+testnet): Bump backend 0.19.12 -> 0.20.0 2019-08-21 22:44:25 +02:00
Migwi Ndung'u 5ea4bbded6 Fix decred's xpub decoding (#249)
* Add the decred xpub decoding implementation

* Fix the address decoding

* Resolve the extended public key path

* Add tests for DeriveAddressDescriptors and DeriveAddressDescriptorsFromTo methods

* Add TestDerivationBasePath

* Add tests for pack and unpack methods
2019-08-21 19:31:23 +02:00
James Scaur d6375a19dd Add support for DIVI (#228)
* Add Divi Project (DIVI) support

* Remove PivX comments, add Divi RPC tests

* Merge with latest Blockbook state

* Fix permissions issue with automatic setup

* Recreate integration tests with block 430894

* Remove GetBlockHeader test

* Increase frequency of mempool sync
2019-08-21 19:07:35 +02:00
WO 44a0e5823f Bump Koto backend to 2.0.7 2019-08-21 19:06:43 +02:00
WakiyamaP 9f2160a654 Change backend config file(monacoin) 2019-08-21 19:06:19 +02:00
Jan Hrnko 91a26e4d40 dash (+testnet): Bump backend 0.14.0.1 -> 0.14.0.3 2019-08-21 15:38:06 +02:00
Martin Boehm 094be77ceb Add Bcash specific backend configuration after backend upgrade 2019-08-21 15:33:11 +02:00
Jan Hrnko e234186f6a bch (+testnet): Bump backend 0.19.10 -> 0.19.12 2019-08-21 14:26:37 +02:00
Jan Hrnko 7430fa6051 btc (+testnet): Bump backend 0.18.0 -> 0.18.1 2019-08-21 11:25:51 +02:00
Jan Hrnko a986082fda eth (+testnet): Bump backend 1.9.1 -> 1.9.2 2019-08-21 11:19:10 +02:00
Mohak Shah 5341b3ebde Fixes duplicated entry in `docs/rocksdb.md`
Under the description of the `addressBalance` column, in the list of data in the value field, `block_height` was duplicated.
2019-08-19 13:46:54 +02:00
Vladyslav Burzakovskyy 4224aab5f2 Implement the "feestats" endpoint (#250) 2019-08-07 13:13:45 +02:00
Martin Boehm 298ec5ea35 Handle empty address slices in RPC integration tests 2019-08-07 11:00:50 +02:00
Martin Boehm be393c6d5d Update dependency of btcutil library 2019-08-04 12:54:54 +02:00
Scotty0448 a89be9cc07 Bump Ritocoin backend to 2.4.0.0 2019-08-04 11:55:48 +02:00
Martin Boehm d01fc3d914 Detect RBF transactions in explorer and API #237 2019-07-30 16:43:13 +02:00
Martin Boehm d450f1d315 Update dependency of btcutil library 2019-07-26 15:08:58 +02:00
Jan Hrnko 231291cbc6 Bump eth backend (+testnet) 1.9.0 -> 1.9.1 2019-07-24 15:31:52 +02:00
Martin Boehm 39f2c73f3e Add coinbase flag (boolean) to UTXO response #236 2019-07-23 12:52:18 +02:00
Jan Hrnko 45f5d115d6 Bump bchsv backend 0.1.1 -> 0.2.1 2019-07-23 12:20:05 +02:00
Jan Hrnko b33b1771d8 Bump eth backend (+testnet) 1.8.27 -> 1.9.0 2019-07-23 11:45:31 +02:00
David Hill a6709d523f decred: use correct coin type (#240) 2019-07-22 17:32:34 +02:00
David Hill f49cc0719b Use proper public key id values for Decred (#239)
* decred: remove white space

* decred: correct xpub magic values
2019-07-22 16:42:20 +02:00
Panu 63b4719179 Bump Zcoin to 0.13.8.2 and support sigma transaction (#235)
* Added support for sigma

* Bump Zcoin to 0.13.7.10

* Change exclude files list
2019-07-19 19:37:33 +02:00
Martin Boehm 4ba0769433 Add "ping" message to the websocket interface #238 2019-07-19 14:59:03 +02:00
Jan Hrnko 8b9fe50677 Bump bcash backend (+testnet) 0.19.9 -> 0.19.10 2019-07-18 17:42:18 +02:00
migwi 40316727ac Update additional params and the package maintainer 2019-07-15 23:15:17 +02:00
David Hill 6087b985bc decred fixes
- set service type to simple as dcrd runs in the foreground.
- fix test rpclisten port
- set mainnet to false on testnet
- update mainnet explorer url.
2019-07-15 22:45:55 +02:00
TheTrunk 0739825a6f zelcash 3.3.0 2019-07-12 15:59:26 +02:00
Scotty0448 a6de1a97c4 Bump Ravencoin backend to 2.4.0.0 2019-07-09 11:16:33 +02:00
wakiyamap ee1b71cd15 Bump Monacoin backend to 0.17.1 2019-07-08 23:12:12 +02:00
Martin Boehm 8b8669f7c9 Update ports registry 2019-07-07 22:43:30 +02:00
Migwi Ndung'u 91691ed7e7 Add decred support (#216) 2019-07-07 22:41:01 +02:00
Jan Hrnko 72143f027c Bump bcash backend (+testnet) 0.19.8 -> 0.19.9 2019-07-05 10:31:00 +02:00
TheTrunk 785dd0153d update zelcash binaries to 3.2.0 2019-07-02 23:55:47 +02:00
Martin Boehm d37a46f8e9 Bump go-ethereum dependency to v1.8.20 2019-07-01 16:50:17 +02:00
CodeFace 4eaeb25a5e qtum feerate check
it seems there is an issue with qtum core rpc that "estimatesmartfee" can return feerate lower than minFeeRate sometimes
2019-06-27 10:00:02 +02:00
CodeFace 5b2b7ce3d6 Bump Qtum to 0.17.6 2019-06-27 08:19:37 +02:00
Jan Hrnko b1810dc06b Bump dogecoin backend 0.14.RC1 -> 0.14.0 2019-06-25 15:37:32 +02:00
Martin Boehm 104f6f9a9d Add boolean flag isAddress vin and vout in API #209 2019-06-19 14:16:06 +02:00
Ján Hrnko d6883a5f35 Bump bcash backend (+testnet) 0.19.7 -> 0.19.8 (#208) (#199) 2019-06-19 11:09:26 +02:00
Martin Boehm bf461d0737 Add "gap" parameter to websocket "getAccountInfo" method #206 2019-06-18 12:21:22 +02:00
Martin Boehm 701348c96d Remove hardcoded xpub length #203 2019-06-17 18:43:13 +02:00
Vladyslav Burzakovskyy 20eed82e48 Use "#!/usr/bin/env bash" in shebangs instead of "#!/bin/bash" 2019-06-17 18:30:39 +02:00
Martin Boehm 6447cdb1b7 Switch from TREZOR to Trezor according to new design guidelines #201 2019-06-17 15:39:35 +02:00
Jan Hrnko 7284c7cbfb bump dash backend (+testnet) 0.14.0.0 -> 0.14.0.1 2019-06-17 10:46:56 +02:00
Jan Hrnko 25027ad92e bump btc cash backend (+testnet) 0.19.6 -> 0.19.7 2019-06-17 10:46:56 +02:00
artem 3464d1cf9b Add note on blockbook bechavior in case of backend(blockchain) reorganization 2019-06-17 10:46:16 +02:00
thebevrishot cbba7ab8c0 Bump Zcoin to 0.13.7.10 2019-06-17 10:37:41 +02:00
Martin Boehm 49b50f3353 Bump blockbook version to 0.3.1 2019-06-11 19:14:57 +02:00
Martin Boehm 480523a2b3 Fix linting issues 2019-06-11 17:22:24 +02:00
Martin Boehm d52bd0352d Handle error on ethereum openRPC 2019-06-11 16:30:21 +02:00
Martin Boehm d5f11561ac Catch and log panic in blockbook main 2019-06-11 16:27:47 +02:00
Martin Boehm c4487ac94b Try to reconnect ethereum RPC in case of network failure 2019-06-10 16:48:31 +02:00
Martin Boehm c916d46763 Log context in case of websocket and socketio error 2019-06-10 14:27:11 +02:00
Martin Boehm 34e5599362 Try to reconnect ethereum RPC in case of network failure 2019-06-10 13:48:46 +02:00
Martin Boehm d26995a1e4 Show blockbook status in case of backend error 2019-06-05 13:29:06 +02:00
Martin Boehm d7d596bf4b Stop passing error details to prometheus metrics 2019-06-04 13:09:15 +02:00
Martin Boehm c409a350c9 Try to reconnect ethereum RPC 2019-06-03 17:48:09 +02:00
Martin Boehm 5c2b9f763e Fix get mempool txs for xpub 2019-06-03 12:50:45 +02:00
WO 950520673d Bump Koto backend to 2.0.5 (#193) 2019-06-03 12:08:52 +02:00
Martin Boehm bbc6ea4cec Sync coin parameters from trezor-firmware monorepo 2019-05-31 13:51:08 +02:00
Jan Hrnko 5f6a8ca605 bump litecoin backend (+testnet) 0.16.3 -> 0.17.1 2019-05-31 08:47:48 +02:00
Enrique bf97a44987 Update Polis Core (#191)
* Update Polis Core v1.4.11

* Bump

* Fix

* Fix

* Update

* Bump Polis v1.4.15
2019-05-30 21:11:12 +02:00
Scotty0448 e87fb62b1d Add Ritocoin support 2019-05-29 10:47:45 +02:00
Sotiris Blad ce91b9e0f8 Fix monetaryunit checksum (#189) #188 2019-05-29 10:45:59 +02:00
kiss1987f4 a4e3db6fbb Some updates to NULS (#183) 2019-05-28 01:02:38 +02:00
Ján Hrnko 3e67307a5d bump dash backend (+testnet) 0.13.3.0 -> 0.14.0.0 (#187) 2019-05-27 14:40:09 +02:00
Martin Boehm b33414cdba Fix formatting and linting issues 2019-05-27 12:51:42 +02:00
Martin Boehm 7f63acaaff Wait for backend startup in integration test 2019-05-27 12:24:25 +02:00
Petr Kracík 97fc295ee4 Bump Digibyte backend to 7.17.2 and enable integration tests (#172)
* Bump Digibyte to 7.17.2

* Added digibyte to Integration tests
2019-05-27 12:24:25 +02:00
Petr Kracík ebf6de9dde Bump Bitcoin to 0.18.0 2019-05-27 12:24:25 +02:00
Martin Boehm 6f603e2424 Add prefix to socketio and websocket test names 2019-05-27 12:24:25 +02:00
Martin Boehm 6ec0175937 Refactor totalTokens to usedTokens in API 2019-05-27 12:24:25 +02:00
Martin Boehm 42d3ecdd41 Add test of websocket interface 2019-05-27 12:24:25 +02:00
Martin Boehm 8cd94070ce Return error when loading not existing block in ETH #176 2019-05-27 12:24:25 +02:00
Martin Boehm 749e534a26 Improve API requests when blockbook in sync with backend 2019-05-27 12:24:25 +02:00
Martin Boehm c7b36189cd Disable bitcoin whatthefee fee estimates 2019-05-27 12:24:25 +02:00
Martin Boehm 3f17333be6 Reduce logging of unimportant warnings during block import 2019-05-27 12:24:25 +02:00
Martin Boehm 9ba294da39 Update documentation 2019-05-27 12:24:25 +02:00
Martin Boehm 77bea0ecdc Retry sync in case of error 2019-05-27 12:24:25 +02:00
Martin Boehm b6e5055821 Wait for backend startup in integration test 2019-05-27 12:24:25 +02:00
Petr Kracík aa69121cc2 Bump Digibyte backend to 7.17.2 and enable integration tests (#172)
* Bump Digibyte to 7.17.2

* Added digibyte to Integration tests
2019-05-27 12:24:25 +02:00
Martin Boehm bb9bd7fea7 Tune bulk connect 2019-05-27 12:24:25 +02:00
Martin Boehm 9e98a4eb39 Improve performance of utxo indexing 2019-05-27 12:24:25 +02:00
Martin Boehm f793d3390c Wait for backend startup in integration test 2019-05-27 12:24:25 +02:00
Petr Kracík 3c9a0a3833 Bump Digibyte backend to 7.17.2 and enable integration tests (#172)
* Bump Digibyte to 7.17.2

* Added digibyte to Integration tests
2019-05-27 12:24:25 +02:00
Martin Boehm 0440a1d6bb Wait for backend startup in integration test 2019-05-27 12:24:25 +02:00
Petr Kracík 9a021b6723 Bump Digibyte backend to 7.17.2 and enable integration tests (#172)
* Bump Digibyte to 7.17.2

* Added digibyte to Integration tests
2019-05-27 12:24:25 +02:00
Martin Boehm 8b07783134 Bump golang to version 1.12.4 and rocskdb to 5.18.3 in docker build 2019-05-27 12:24:24 +02:00
Martin Boehm ce0529c130 Use utxo index in API 2019-05-27 12:24:24 +02:00
Martin Boehm 61e096b3f9 Fix computefeestats flag typo 2019-05-27 12:24:24 +02:00
Martin Boehm 733c966094 Maintain utxo index on disconnect block 2019-05-27 12:24:24 +02:00
Martin Boehm 995d5c66b5 Add utxos to addressBalance column 2019-05-27 12:24:24 +02:00
Martin Boehm 5689be20f3 Update coin configs 2019-05-27 12:24:24 +02:00
Martin Boehm 917840d6b2 Stop indexing OP_RETURN scripts 2019-05-27 12:24:24 +02:00
Martin Boehm 90d7a7f2da Bump required index version to 5 2019-05-27 12:24:24 +02:00
Martin Boehm 1e5ada2ebf Update coin configs 2019-05-27 12:24:24 +02:00
Martin Boehm 984618a0ed Add option to compute fee statistics for chosen blocks 2019-05-27 12:24:24 +02:00
Martin Boehm 228d40e7a5 Compare whatTheFee estimates to default 2019-05-27 12:24:24 +02:00
Martin Boehm 239ccdfd78 Make websocket interface fields camelCase 2019-05-27 12:24:24 +02:00
Martin Boehm 55e39f0ea4 Implement alternative estimateFee using whatthefee.io WIP #153 2019-05-27 12:24:24 +02:00
Martin Boehm 942c95add8 Update coin configs 2019-05-27 12:24:24 +02:00
Martin Boehm 34475ff5e8 Bump blockbook version to 0.3.0 2019-05-27 12:24:24 +02:00
Martin Boehm 0312db4d9d Fix formatting/linting errors 2019-05-27 12:24:24 +02:00
Martin Boehm b348a53664 Make all API fields camelCase 2019-05-27 12:24:24 +02:00
Jan Hrnko bed1a02a3e bump dogecoin backend 1.14-alpha-1 -> 1.14-RC1 2019-05-27 12:02:56 +02:00
JoHnY 0316ead232 bump zcash backend 2.0.4 -> 2.0.5-2 (#186)
* bump zcash backend 2.0.4 -> 2.0.5-2

* bump zcash_testnet backend 2.0.4 -> 2.0.5-2
2019-05-27 11:55:48 +02:00
Scotty0448 78ffee5d3d Add Ravencoin support 2019-05-21 11:29:15 +02:00
motty dcfb468d76 mod binary_url 2019-05-21 10:26:10 +02:00
Jan Hrnko 9da5f0cc35 Bump bitcoin cash (+testnet) backend 0.19.5 -> 0.19.6 2019-05-20 16:53:57 +02:00
Ján Hrnko 93e4b3f4e1 bump bitcoin cash backend 0.19.4 -> 0.19.5 (#178)
* bump bitcoin cash backend 0.19.4 -> 0.19.5

* bump bitcoin cash testnet backend 0.19.4 -> 0.19.5
2019-05-18 10:56:15 +02:00
TheTrunk 2969b06ce2 integration tests, xx58 port 2019-05-13 14:16:47 +02:00
TheTrunk f21ba5573b fix extract command and parameters download 2019-05-13 14:16:47 +02:00
TheTrunk da03baf83a ZelCash - reuse Zcash integration 2019-05-13 14:16:47 +02:00
CodeFace 18bea50b69 Bump Qtum to 0.17.5 2019-05-10 15:07:49 +02:00
Sotiris Blad beb8244570 fix getblock & tx for genesis 2019-05-10 15:01:08 +02:00
Martin Boehm 7c4e8c5750 Log error on eth subscription resubscribe 2019-05-07 12:23:50 +02:00
Martin Boehm 2d3e7c9612 Wait for backend startup in integration test 2019-05-06 22:34:07 +02:00
Petr Kracík 5322cea4a5 Bump Digibyte backend to 7.17.2 and enable integration tests (#172)
* Bump Digibyte to 7.17.2

* Added digibyte to Integration tests
2019-05-06 15:56:02 +02:00
Martin Boehm 629e07e6a4 Trim packr tos link 2019-05-03 14:50:08 +02:00
Petr Kracík 1da82a070b Bump Bitcoin to 0.18.0 2019-05-03 13:09:42 +02:00
Martin Boehm e8bda6cabe Update monetaryunit and polis config 2019-04-30 16:11:17 +02:00
Sotiris Blad ae4cf6b029 Add MonetaryUnit (MUE) Support (#166)
* MUE

* mue parser

* mue rpc

* mue

* mue tests

* mue ports

* update

* update

* Create monetaryunitparser_test.go

* Update monetaryunitparser_test.go

* update

* Update monetaryunitparser_test.go

* Update monetaryunitparser_test.go

* compiling

* Update monetaryunitparser_test.go

* update hex

* test

* mue test

* Update monetaryunitparser.go

* Update monetaryunitparser.go

* getblock add

* update sum

* removed testnet
2019-04-30 16:03:36 +02:00
Martin Boehm 10d08161ba Add common issues explanation to documentation 2019-04-29 15:22:37 +02:00
y-chan 39632a8d39 fix test data 2019-04-26 14:07:35 +02:00
y-chan 24b7dca29b fix miss 2019-04-26 14:07:35 +02:00
y-chan fc528a7056 fix tests for vipstarcoin 2019-04-26 14:07:35 +02:00
y-chan 591b1937dc fix port number and coin name 2019-04-26 14:07:35 +02:00
y-chan 5aabc52a62 fix port number xx90 to xx56 2019-04-26 14:07:35 +02:00
y-chan f791467efd fix import 2019-04-26 14:07:35 +02:00
y-chan 09fd01c0a5 fix username 2019-04-26 14:07:35 +02:00
y-chan 4dc5721384 fix errors 2019-04-26 14:07:35 +02:00
y-chan 03f910cc30 fix miss 2019-04-26 14:07:35 +02:00
y-chan 4a2e7602b1 fix tests 2019-04-26 14:07:35 +02:00
y-chan 70ace81011 Add VIPSTARCOIN 2019-04-26 14:07:35 +02:00
Petr Kracík 32885e54e7 Bump Ethereum to 1.8.27 2019-04-26 11:26:27 +02:00
Petr Kracík c34af64f0c Bump Bcash to 0.19.4 2019-04-25 11:03:38 +02:00
Martin Boehm d23d0a9e4f Return with non zero exit code in case of fatal error 2019-04-23 14:18:49 +02:00
ilmango 68fed56d12 Remove bellcoin-releases.asc 2019-04-23 12:15:07 +02:00
wakiyamap b51229a114 add monacoin testdata 2019-04-18 10:38:16 +02:00
romanornr 35f3278771 Viacoin add support 2019-04-18 10:25:48 +02:00
Cronos 54e5a76896 Update Polis Core v1.4.11 2019-04-17 11:08:23 +02:00
Martin Boehm 9642e306ac Merge branch 'mempool' 2019-04-15 12:27:24 +02:00
Martin Boehm 3ef9426229 Change OMNI simple send text 2019-04-15 12:15:03 +02:00
Martin Boehm dffcded306 Enable parallel sync only for initial sync 2019-04-12 16:46:54 +02:00
Martin Boehm 8fb4772331 Swich from Fatal log to Error log in Blockbook main 2019-04-12 15:44:11 +02:00
Martin Boehm 230b5e5d32 Fix synchronization issue in mempool 2019-04-11 14:58:22 +02:00
Petr Kracík 8c1691be8d Bump Bcash to 0.19.3 2019-04-11 14:58:22 +02:00
Petr Kracík 8b38d3b7ea Bump Ethereum to 1.8.25 2019-04-11 14:58:22 +02:00
Petr Kracík cd0202891b Bump Dash backend version to 0.13.3.0 2019-04-11 14:58:22 +02:00
Petr Kracík 9abff90701 Bump Bcash to 0.19.3 2019-04-11 14:56:33 +02:00
Martin Boehm 9f8cf071dd Stop displaying first seen tx time when unknown 2019-04-10 13:54:20 +02:00
Petr Kracík 974f31e135 Bump Ethereum to 1.8.25 2019-04-10 12:55:20 +02:00
Petr Kracík 8ae7a61dab Bump Dash backend version to 0.13.3.0 2019-04-10 12:55:20 +02:00
Martin Boehm b367e25194 Fix bcash xpub derivation path #146 2019-04-08 15:39:03 +02:00
Martin Boehm a6d5f4d421 Fix bcash xpub derivation path #146 2019-04-08 15:34:58 +02:00
Martin Boehm add504b57e Make ethereum type mempool parameters configurable 2019-04-08 14:39:29 +02:00
Martin Boehm 7ac877f160 Apply modified BlockChain to PolisRPC 2019-04-08 12:54:06 +02:00
Jin Eguchi 02c65bc67d Fix link path 2019-04-08 12:48:48 +02:00
Cronos f108559d7c Support Polis (#118)
* Update .gitignore Intelij IDEA

* Polis initial configuration

* Remove polis-qt from excluded files

* Fix

* Update v1.4.10

* Test files

* Fix

* Add PackedTxInfo

* Add Parsing Blocks test

* Exclude polis-qt

* Integration test data

* Fix
2019-04-08 12:48:48 +02:00
Jin Eguchi ef6078d14e Fix link path 2019-04-08 11:58:40 +02:00
Cronos 1392a884ca Support Polis (#118)
* Update .gitignore Intelij IDEA

* Polis initial configuration

* Remove polis-qt from excluded files

* Fix

* Update v1.4.10

* Test files

* Fix

* Add PackedTxInfo

* Add Parsing Blocks test

* Exclude polis-qt

* Integration test data

* Fix
2019-04-06 00:18:24 +02:00
Martin Boehm 127d24be06 Give option to show derived xpub addresses in explorer 2019-04-05 17:32:26 +02:00
Martin Boehm e394350e85 Update api documentation 2019-04-05 17:14:08 +02:00
Martin Boehm 4b0addbe98 Preserve socket.io BlockTimestamp backward compatibility 2019-04-05 17:13:56 +02:00
Martin Boehm b227a8e777 Modify bitcoin type mempool resync to preserve first seen time order 2019-04-05 16:04:26 +02:00
Martin Boehm 3f973bf47d Implement new ethereum mempool sync with tx timeout 2019-04-04 23:35:38 +02:00
Martin Boehm 4435dbbfb4 Pass correct blockchain object to mempool 2019-04-03 22:08:32 +02:00
Martin Boehm 4512a57134 Bump blockbook to v0.2.2 2019-04-03 14:09:27 +02:00
Martin Boehm b64d76d8f9 Show first seen date of mempool transaction in explorer 2019-04-03 14:09:27 +02:00
Martin Boehm 47f798dbaa Return mempool transactions for address in reverse order 2019-04-03 14:09:27 +02:00
Martin Boehm 870354bc90 Extract mempool common functionality to BaseMempool 2019-04-03 14:09:27 +02:00
Martin Boehm c19f6bfb42 Fix tryParseOmni 2019-04-03 14:09:27 +02:00
Martin Boehm 4bc196f599 Fix tryParseOmni 2019-04-03 12:51:51 +02:00
Martin Boehm 987aec47f9 Show mempool content in explorer 2019-04-02 11:39:38 +02:00
Martin Boehm f2dc4a56d8 Store time of mempool transaction 2019-04-01 17:00:53 +02:00
Yura Pakhuchiy 827cbcd1d8 Support Groestlcoin xpub 2019-04-01 17:00:53 +02:00
Yura Pakhuchiy 1c290db225 Support Groestlcoin xpub 2019-04-01 12:49:18 +02:00
Martin Boehm c813f76336 Try to load mempool inputs from db to speed up mempool sync 2019-03-29 17:01:20 +01:00
Martin Boehm d2928b3516 Refactor server init in program startup 2019-03-28 14:44:40 +01:00
Martin Boehm a8735c460d Update ports registry 2019-03-28 14:44:40 +01:00
kiss1987f4 954619efe9 Add NULS supported. (#135)
Add NULS supported.
2019-03-28 14:44:40 +01:00
Martin Boehm fa4a11c3a7 Fix bcash EstimateSmartFee functionality 2019-03-28 14:44:40 +01:00
Petr Kracík 9cd35b3616 Bump ZCash backend version to 2.0.4 2019-03-28 14:44:40 +01:00
Petr Kracík 72f4ad9101 Bump BCash backend version to 0.19.2 2019-03-28 14:44:40 +01:00
Martin Boehm 1cd4c207be Update xpub derivation documentation 2019-03-28 14:44:40 +01:00
Martin Boehm ee4ecc2bb0 Fix bcash EstimateFee after interface change in backend v0.19.1 2019-03-28 14:44:40 +01:00
Petr Kracík 57ce874208 Bump BCash backend version to 0.19.1 2019-03-28 14:44:40 +01:00
Petr Kracík e6863b0420 Bump Dash backend version to 0.13.2.0 2019-03-28 14:44:40 +01:00
Martin Boehm d734c7d489 Update xpub segwit native magic from trezor-common 2019-03-28 14:44:40 +01:00
Martin Boehm 35b24b03b9 Update ports registry 2019-03-28 14:32:33 +01:00
kiss1987f4 12fbdedf6e Add NULS supported. (#135)
Add NULS supported.
2019-03-28 14:31:29 +01:00
Martin Boehm f01273ab10 Fix bcash EstimateSmartFee functionality 2019-03-28 11:24:31 +01:00
Petr Kracík 0bfe1d5558 Bump ZCash backend version to 2.0.4 2019-03-28 11:12:53 +01:00
Petr Kracík 955209db6b Bump BCash backend version to 0.19.2 2019-03-28 11:11:42 +01:00
Martin Boehm c074873452 Update xpub derivation documentation 2019-03-28 11:09:57 +01:00
Martin Boehm 8c471ed20d Fix bcash EstimateFee after interface change in backend v0.19.1 2019-03-27 14:25:36 +01:00
Petr Kracík 713f928a57 Bump BCash backend version to 0.19.1 2019-03-27 13:27:38 +01:00
Petr Kracík 98a637e952 Bump Dash backend version to 0.13.2.0 2019-03-27 13:15:45 +01:00
Martin Boehm b8eca9c25e Update xpub segwit native magic from trezor-common 2019-03-26 13:04:30 +01:00
Martin Boehm ce3c7c5e66 Extract mempool interface from blockchain 2019-03-25 16:43:57 +01:00
Martin Boehm 68575b2786 Fix linter issues 2019-03-21 22:53:48 +01:00
Martin Boehm 283a039290 Resolve formatting issues 2019-03-21 21:05:14 +01:00
Matej Čamaj c0cebd4ce6 Implement Omni parser for Bitcoin 2019-03-21 20:46:25 +01:00
Martin Boehm 6ac14f163a Add TODO for omni parsing 2019-03-21 11:13:42 +01:00
Martin Boehm df952ffb05 Update api.GetBlock to support ethereum type coins 2019-03-21 09:36:18 +01:00
Martin Boehm c7e884dd81 Document API 2019-03-20 18:06:30 +01:00
Martin Boehm bae1050ce4 Log error from api json encode 2019-03-20 18:04:52 +01:00
Martin Boehm 6784ecd6b3 Fix api.Block json serialization 2019-03-20 17:49:21 +01:00
Martin Boehm 1d8389bb2c Return utxos by sorted by desceding block height 2019-03-20 17:32:10 +01:00
Martin Boehm 846b20c795 Unify processing of query parameters in address and xpub api 2019-03-18 18:23:03 +01:00
Martin Boehm 133d6f9c59 Specify default page size for websocket getAccountInfo 2019-03-14 21:03:08 +01:00
Martin Boehm 46e49daa9c Merge branch 'master' of github.com:trezor/blockbook 2019-03-13 14:14:25 +01:00
Martin Boehm e7b3421603 Bump Bitcoin Cash SV backend version to 0.1.1 #134 2019-03-13 14:14:18 +01:00
codeface 41433a9684 add qtum support 2019-03-11 13:43:54 +01:00
Martin Boehm efca04a3fb Correctly count unconfirmed txs in api GetXpub 2019-03-06 18:48:15 +01:00
Martin Boehm e06ff194de Resolve some linting issues 2019-03-06 17:50:20 +01:00
Martin Boehm 70330273c0 Fix format errors 2019-03-06 17:32:17 +01:00
Martin Boehm 3df1cc81ed Correctly count unconfirmed txs in api GetAddress and GetXpub 2019-03-06 17:23:06 +01:00
Martin Boehm 3d10d9c2c5 Ensure ordering of address and xpub txs in the same block 2019-03-05 13:48:11 +01:00
ilmango 3551c90590 Add Bellcoin (#116)
* Add Bellcoin

* Add integration tests

* Add bellcoinparser_test

* Fix testTxPacked

* Add tests files

* Fix indentation

* Fix indentation

* add bellcoin gpg key

* fix binary link

* Fix verification_type

* Fix verification_type

* fix username

* Fix verification
2019-03-04 22:29:30 +01:00
人畜無害 52308ad8af fix KotoTestnet asc link (#132)
* fix KotoTestnet asc link

* typo
2019-03-04 22:25:03 +01:00
Martin Boehm 3f7ac68865 Disable bcash unsupported EstimateSmartFee 2019-03-04 11:26:12 +01:00
Martin Boehm 1540dc940d Disable bcash unsupported EstimateSmartFee 2019-03-04 11:05:09 +01:00
Gruve_p 4b15e46602 Fix groestlcoin config 2019-03-01 16:29:34 +01:00
Martin Boehm bd89ab8256 Add getTransactionSpecific to websocket interface 2019-03-01 15:37:38 +01:00
Martin Boehm 1c35c632cb Add tokens to return level choice to websocket interface 2019-03-01 15:25:16 +01:00
Martin Boehm e54998dccc Sync with trezor common and fix formatting 2019-03-01 14:55:24 +01:00
Martin Boehm 39f9f46d3d Fix backend deploy and test script 2019-03-01 13:59:06 +01:00
Martin Boehm dda96b4a8f Merge branch 'xpub' 2019-03-01 11:12:40 +01:00
Martin Boehm 7b590d9958 Unify AccountDetails levels in GetAccount and GetXpub api calls 2019-02-28 15:07:07 +01:00
Martin Boehm 87b778d648 Merge branch 'backend-deploy-and-test' 2019-02-27 14:28:07 +01:00
Martin Boehm 66854715cf Fix zcoin parser test 2019-02-27 14:06:09 +01:00
Martin Boehm 881dab35f5 Fix packing of coinbase transactions for dash 2019-02-27 13:58:11 +01:00
Martin Boehm ffbbbb0b44 Fix dash after DIP2 update to the blockchain 2019-02-27 13:29:42 +01:00
Martin Boehm 46001a9fa5 Evict old cached xpubs 2019-02-26 16:27:28 +01:00
Martin Boehm 22e73c44cc Fix ethereum_testnet_ropsten integration test 2019-02-26 11:14:22 +01:00
Martin Boehm 0e7714dfd1 Add automatic backend deploy and integration test for all our coins 2019-02-26 11:05:19 +01:00
Martin Boehm c67c8d4008 Merge branch 'backend-deploy-and-test' of github.com:trezor/blockbook into backend-deploy-and-test 2019-02-26 10:42:05 +01:00
Martin Boehm bcd4b8ed4f Add service name to backend deploy and integration test 2019-02-26 10:41:23 +01:00
Martin Boehm 9382c7a9c0 Add automatic backend deploy and integration test to gitlab CI 2019-02-26 10:41:23 +01:00
Martin Boehm e7fc52760e Add service name to backend deploy and integration test 2019-02-25 18:40:12 +01:00
wlc- 0d26854c35 Bump myriad backend to 0.16.4.0 2019-02-25 17:40:44 +01:00
Gruve_p 16558d23c0 Bump Groestlcoin backend version to 2.17.2 (#128) 2019-02-25 17:34:06 +01:00
Martin Boehm c7e88ff2be Add automatic backend deploy and integration test to gitlab CI 2019-02-25 17:32:06 +01:00
Martin Boehm e1eadda6bf Make upper limit for gap in xpub address derivation 2019-02-25 14:09:24 +01:00
Petr Kracík 04d843f593 Bump ethereum backend to version 1.8.23 2019-02-21 21:31:48 +01:00
Petr Kracík 7042a45ba5 Bump Bcash backend to version 0.19.0 2019-02-21 21:31:48 +01:00
Martin Boehm 347e1d5967 Load ETH account balance also for accounts without any transactions #125 2019-02-19 20:16:16 +01:00
Martin Boehm c7808b87d5 Bump blockbook to version 0.2.1 2019-02-14 14:54:26 +01:00
Martin Boehm 88d9e09ad4 Highlight own ethereum addresses in explorer like in xpub explorer 2019-02-14 14:53:35 +01:00
Petr Kracík d41adf0a4e Bump zcash backend to 2.0.3 2019-02-14 11:30:09 +01:00
Martin Boehm 0546a2609d Count xpub transactions regardless of filter 2019-02-13 19:43:16 +01:00
Martin Boehm 593247c364 Fix public interface xpub test 2019-02-13 18:34:31 +01:00
Martin Boehm 3edfebca65 Stop logging warnings ErrTxNotFound in ethereum mempool 2019-02-13 18:24:38 +01:00
Martin Boehm b8672a0fff Add public interface test for xpub functionality 2019-02-13 18:10:29 +01:00
Martin Boehm f3ec1e6b77 Return unique transactions from GetXpubAddress 2019-02-13 17:57:30 +01:00
WO 6460ca3872 Fix typo 2019-02-13 12:09:40 +01:00
WO d28a0beab6 Revert Dcokerfile 2019-02-13 12:09:40 +01:00
WO 47a77c35c5 Update koto 2.0.3 2019-02-13 12:09:40 +01:00
WO bee0f8c709 Change OS for docker build 2019-02-13 12:09:40 +01:00
Petr Kracík 7609ab7c76 Bump DASH backend to version 0.13.1.0 2019-02-13 12:09:14 +01:00
Petr Kracík 166bf74162 Bump Bcash backend to version 0.18.8 2019-02-13 12:09:14 +01:00
Petr Kracík d314140dde Change DB size on disk Error to warning 2019-02-13 12:08:19 +01:00
Martin Boehm 4846af9f60 Control token detail level returned by xpub api 2019-02-12 15:15:10 +01:00
Martin Boehm 63fb910ecb Add xpub handling to websocket interface 2019-02-11 11:54:34 +01:00
Martin Boehm 6b0a4960fd Get utxo for xpub 2019-02-08 15:50:37 +01:00
Jeremiah Buddenhagen 28649b5d2c add flo 2019-02-08 08:50:59 +01:00
Martin Boehm 273436f109 Include mempool transactions in GetAddressForXpub 2019-02-08 00:42:38 +01:00
Martin Boehm 1ca4a0cfc7 Process mempool txs in api.GetAddress even for Basic level of details 2019-02-07 17:28:45 +01:00
Martin Boehm ae332547ba Fix loading of txids for xpub 2019-02-06 17:35:10 +01:00
Martin Boehm 64c8ae9a62 Estimate full derivation path from xpub 2019-02-05 20:47:54 +01:00
thebevrishot 94253c122f Update zcoin backend to version 0.13.7.7 2019-02-05 11:10:54 +01:00
Martin Boehm 266b0575b6 Highlight xpub addresses in explorer 2019-02-04 16:53:49 +01:00
Martin b2af4c1242
Merge pull request #113 from petrkr/versions
Update Ethereum and Bcash backends to new versions
2019-02-04 16:24:56 +01:00
Petr Kracík 42686166d8 Bump Bcash backend to version 0.18.7 2019-02-04 14:51:47 +01:00
Petr Kracík e64c0a52d4 Bump Ethereum to 1.8.22 - fork 2019-02-04 14:39:47 +01:00
Martin Boehm 9d3cd3b3e9 Implement loading of transactions in GetAddressForXpub - WIP 2019-02-03 23:42:44 +01:00
Martin Boehm 57b40ad6dc Hide xpub addresses with zero balance in explorer by default 2019-01-31 15:04:09 +01:00
Martin Boehm 8f1f1c87ac Modify the discovery of xpub addresses 2019-01-31 08:30:18 +01:00
Martin Boehm 225830d3e9 Implement GetAddressForXpub and xpub explorer view - WIP 2019-01-30 17:56:15 +01:00
Martin Boehm b670b4fede Add address derivation tests and benchmarks 2019-01-30 16:29:34 +01:00
Martin Boehm 27dba68319 Add DeriveAddressDescriptors for list of indexes 2019-01-29 12:11:35 +01:00
random.zebra 818288cd16 Add PIVX Testnet (#110)
* fix PIVX MainnetMagic byte order

* add PIVX Testnet

* PIVX: zerocoin address labels
2019-01-29 10:15:03 +01:00
Martin 8efaa4ebc3
Merge pull request #111 from bitspill/valueSatJson
Convert JsonValue to ValueSat when `"parse": false`
2019-01-29 10:14:20 +01:00
Jeremiah Buddenhagen 87624f44ca Convert JsonValue to ValueSat when `"parse": false` 2019-01-28 16:39:54 -08:00
Martin Boehm 986275bb76 Implement parser.DeriveAddressDescriptors from xpub 2019-01-28 18:29:12 +01:00
Martin Boehm dafe19cf29 Synchronize xpub magic numbers from trezor-common 2019-01-28 13:34:43 +01:00
Martin Boehm 42f6821cfa Update gitignore 2019-01-27 00:29:44 +01:00
Martin Boehm e27821f231 Update documentation to match version 0.2.0 2019-01-24 18:08:37 +01:00
Martin Boehm 499d65460f Fix coding style and formatting issues 2019-01-24 15:24:56 +01:00
WO e83511469b Add Koto (#107)
I have constructed blockbook for Koto at the following URL.
https://blockbook.kotocoin.info/
2019-01-23 22:08:28 +01:00
Martin Boehm e6d7cea761 Skip CoinSpecificData in mempool integration test 2019-01-23 17:26:45 +01:00
Martin Boehm 06a1e96190 Merge branch 'indexv4' of github.com:trezor/blockbook into indexv4 2019-01-23 16:34:56 +01:00
Martin Boehm 171e15c9f7 Fix unit test build tags 2019-01-23 16:34:35 +01:00
Martin 2975785de7
Merge pull request #109 from petrkr/versions
Bump backends bitcoin cash to 0.18.6, dash to 0.13.0.0
2019-01-23 16:02:54 +01:00
Petr Kracík 5b8954b74f Bump BCash version to 0.18.6 2019-01-23 15:14:12 +01:00
Petr Kracík 024b25dff7 Bump DASH version to 0.13.0.0, replaced DASH GPG key 2019-01-23 15:13:36 +01:00
Martin Boehm f3cbd5e20e Cleanup the test-websocket page 2019-01-21 10:05:44 +01:00
Martin Boehm b411ce881e Fix Groestlcoin rpc 2019-01-20 12:22:49 +01:00
Putta Khunchalee 8e82b3da0f Added Zcoin support (#106)
* Add zcoin configuration

* Update ports registry

* Change verification_type to gpg-sha256

* Fix incorrect zcoin configurations

* Change backend verification type to sha256 due to no public key for gpg-sha256

* Initializes Zcoin factory

* Add zcoin parser

* Finish BlockChain implementation for XZC

* Implement EstimateFee for XZC

* Add RPC integration tests for XZC

* Add unittest for parser && fix wrong network params

* Fix incorrect RPC marshaler for XZC

* Add sync integration test for zcoin

* Add zcoin block parser

* Add more testdata for sync integration test

* Remove EstimateSmartFee test for XZC due to it not supported

* Refactor and cleanup XZC parser

* Fix zerocoin spend vin error

* Fix display zerocoin mint and spend

* Support script parser for spend

* Fix build errors and bugs after rebase
2019-01-17 20:31:15 +01:00
Martin Boehm b593643f63 Return Coinbase from API and Websocket interface 2019-01-17 20:30:18 +01:00
Martin Boehm d1f30e27cf Handle nil amounts in socket.io interface 2019-01-17 19:58:59 +01:00
Martin Boehm 522e6528d3 Merge Fujicoin into indexv4 branch #104 2019-01-16 23:19:08 +01:00
Martin Boehm 9af314f7aa Add websocket method getTransaction 2019-01-16 16:10:30 +01:00
Martin Boehm 3a8e854384 Merge branch 'master' into indexv4 2019-01-16 11:39:15 +01:00
Martin 9069c25584
Merge pull request #108 from petrkr/ethfork
Ethfork
2019-01-16 00:08:01 +01:00
Petr Kracík 19d6869465 Bump Ethereum to 1.8.21 - emergency hotfix 2019-01-15 23:39:39 +01:00
Martin Boehm 1c29283bf0 Add getAccountUtxo method to websocket interface 2019-01-14 17:12:33 +01:00
Martin Boehm 05daf85c10 Fix hanling of missing tx in Zcash block 0 2019-01-14 14:49:21 +01:00
Martin Boehm ac0c359fbe Update PIVX ports to the next available #105 2019-01-13 23:53:32 +01:00
Martin aefbc33a01
Merge pull request #105 from rikardwissing/feature/pivx-indexv4
Add support for PIVX
2019-01-13 23:49:08 +01:00
Martin Boehm 210652328f Avoid showing already confirmed txs as mempool txs 2019-01-13 23:32:28 +01:00
rikardwissing ffa745d390 Validate getTxAddresses return 2019-01-11 21:31:49 +01:00
rikardwissing 24bedfec26 Add support for PIVX
Co-Authored-By: Emil Karlsson <emil.karl@gmail.com>
2019-01-11 21:31:49 +01:00
Martin Boehm c2a581ea72 Add bulk import of EthereumType chain 2019-01-11 12:37:04 +01:00
Martin Boehm f109513464 Add handling of unknown inputs in api.GetTransaction 2019-01-10 17:24:29 +01:00
Martin Boehm 4bd43c5f47 Fix integration tests 2019-01-10 17:00:09 +01:00
Martin Boehm 3ca593aff1 Handle error tx not found #94 2019-01-10 16:39:36 +01:00
Martin Boehm 8c4fcf4441 Stop indexing contracts of ETH zero address 2019-01-10 12:38:16 +01:00
Martin Boehm 341bf331c1 Add custom handling of unknown input txs during BitcoinType block import 2019-01-09 23:24:25 +01:00
Martin Boehm d77fb35bb5 Minify explorer html templates 2019-01-09 15:20:05 +01:00
Martin Boehm 51bb4fe109 Fix go dependencies 2019-01-09 13:11:45 +01:00
Martin Boehm 00c1fd6661 Limit number of found transactions for address by page size 2019-01-09 11:48:19 +01:00
Martin Boehm 2c3af5129e Generalize tokens instead of ERC20 tokens in API 2019-01-07 15:45:00 +01:00
Martin Boehm 07108b8c4f Store txs in column addresses in reverse order 2019-01-07 13:38:58 +01:00
Martin Boehm 1e8506bdf7 Add websocket method getBlockHash 2019-01-04 16:10:24 +01:00
Martin Boehm 97e0844a4b Update references to forked btcd, btcutil and bchutil libraries 2019-01-04 14:05:51 +01:00
Martin Boehm 1b7218530b Merge branch 'bchsvfix' of github.com:trezor/blockbook into bchsvfix 2019-01-04 13:47:46 +01:00
Martin Boehm ab077d882c Update references to forked btcd, btcutil and bchutil libraries 2019-01-04 13:47:28 +01:00
Martin Boehm d82bd8e7b8 Update references to forked btcd, btcutil and bchutil libraries 2019-01-04 13:36:16 +01:00
Martin Boehm 9518f94714 Merge branch 'master' into indexv4 2019-01-03 18:09:30 +01:00
Martin Boehm 26f293fc42 Bump golang to version 1.11.4 in docker build 2019-01-03 18:07:30 +01:00
Martin Boehm 44f07734ce Bump rocksdb to version 5.17.2 and use data format version 4 2019-01-03 18:05:06 +01:00
Martin Boehm 2552a429e8 Store data in addresses column in more compact way 2019-01-03 17:19:56 +01:00
Martin Boehm 4e040cb1f0 Store addresses in reverse order for newest blocks to be searched first 2018-12-20 17:33:13 +01:00
Martin Boehm e24115da83 Bump blockbook version to v0.2.0 and index version to 4 2018-12-20 16:16:51 +01:00
Martin Boehm a7b8994419 Merge branch 'ethereum' into indexv4 2018-12-20 16:07:34 +01:00
Martin Boehm 7da714bec4 Generalize erc20transfers to tokentransfers in api 2018-12-20 13:18:38 +01:00
Martin Boehm 81e105dd4f Refactor tx api 2018-12-19 13:59:18 +01:00
Martin Boehm e12641ae7d Use txids returned from pendingTransactions subscriptionfor mempool 2018-12-19 12:03:19 +01:00
Martin Boehm 9288a12f1d Fix unit and integration tests 2018-12-19 10:35:22 +01:00
Martin Boehm bab500d3f8 Notify on mempool erc20 transfer transaction 2018-12-19 10:06:25 +01:00
Martin Boehm 2e9f87e39d Parse ERC20 transfer from tx payload data 2018-12-18 13:14:07 +01:00
Martin Boehm 7edea80209 Add estimateFee method to websocket interface 2018-12-18 09:52:46 +01:00
Martin Boehm 83f9a525b0 Change Ethereum Testnet Ropsten shortcut 2018-12-17 15:08:48 +01:00
Martin Boehm 3ff1a86ab1 Add option TxHistoryLight to api.GetAddress 2018-12-17 13:20:10 +01:00
Martin Boehm f332c0aa9e Add getInfo websocket method 2018-12-16 00:26:41 +01:00
Martin Boehm a04b2b67b5 Filter address transactions by height from-to and contract 2018-12-14 16:42:35 +01:00
Martin Boehm 35a13e0647 Handle old style ethereum transactions that do not set status 2018-12-14 12:08:06 +01:00
Martin Boehm e9e6b472b6 Keep api v1 as compatible with blockbook 0.1.x, add api v2 2018-12-13 14:31:34 +01:00
Martin Boehm 9c142663ce Add type api.Amount for proper formatting of amounts in json 2018-12-13 00:41:58 +01:00
Martin Boehm d01081fd83 Allow websocket connections from all origins 2018-12-11 12:14:05 +01:00
Martin Boehm 4b39519750 Change websocket address subscription behavior 2018-12-11 11:50:43 +01:00
Martin Boehm 13f7b48ae6 Add websocket interface 2018-12-10 17:22:37 +01:00
Martin Boehm 75e2ffa025 Add websocket implementation WIP 2018-12-10 00:26:04 +01:00
Martin Boehm 5af08584ab Show nonce in address detail in explorer 2018-12-06 13:15:25 +01:00
Martin Boehm 70559ab9e0 Update api.GetAddress to return more ethereum specific data 2018-12-06 13:14:46 +01:00
Martin Boehm 45e1e32e18 Implement Liquid blockbook 2018-12-05 22:34:44 +01:00
Martin Boehm 0110fd0cf2 Fix protobuf serialization of transactions 2018-12-05 22:29:27 +01:00
Martin Boehm 874130ce4b Modify Liquid backend parameters 2018-12-05 21:25:24 +01:00
Martin Boehm d5c80db8f0 Add experimental Liquid support 2018-12-05 20:52:34 +01:00
Martin Boehm 9eb022238d Fix ETC handling of transaction receipt 2018-12-05 12:26:41 +01:00
Martin Boehm 0e1725321d Fix public_test 2018-12-05 12:02:41 +01:00
Martin Boehm 55ae22bab1 Add socketio method getAccountInfo 2018-12-05 11:41:07 +01:00
Martin Boehm 3e532e9130 Remove marshalling of ethereum tx to hex field 2018-12-05 01:10:00 +01:00
Martin Boehm 5e170b8dd8 Make worker more resistant to weird data in ethereum blockchain 2018-12-04 14:23:11 +01:00
Martin Boehm 4a216fa647 Fix parsing of erc20 properties 2018-12-04 13:56:25 +01:00
Martin Boehm ec1647c864 Show tx success/failure in txdetail 2018-12-04 12:20:05 +01:00
Martin Boehm 9a04c862d6 Filter address transactions by input/output or token 2018-12-04 11:54:15 +01:00
Martin Boehm a08f568353 Show block for EthereumType coins 2018-12-03 16:34:38 +01:00
Martin Boehm fead52881f Show ERC20 contracts for address 2018-12-03 15:48:07 +01:00
Martin Boehm c96c357013 Add Ethereum Classic specific handling in GetTransaction 2018-11-30 11:39:28 +01:00
Martin Boehm 61177c3750 Fix cleanup of column blockTxs 2018-11-29 15:37:04 +01:00
Martin Boehm 463eab9d2d Show ethereum specific data in tx detail in explorer 2018-11-28 14:56:45 +01:00
Martin Boehm 8ac57a3d56 Add ERC20 transfer information to ethereum transactions 2018-11-28 14:27:02 +01:00
Martin Boehm 1f32a39d16 Handle blocks with zero transactions 2018-11-26 13:55:29 +01:00
Martin Boehm 995d169172 Merge branch 'master' into ethereum 2018-11-26 13:04:13 +01:00
Martin Boehm 7a990f9b5b Implement explorer for EthereumType coin - WIP 2018-11-26 00:20:01 +01:00
Martin Boehm 8886256d0b Implement index v3 for ethereum type coin 2018-11-23 22:16:32 +01:00
Martin Boehm eb524c2226 Implement index v3 for ethereum type coin - WIP 2018-11-23 14:08:10 +01:00
Martin Boehm fad7ea326c Load ERC20 events in eth.GetBlock 2018-11-15 18:07:45 +01:00
Martin Boehm 089346a4bb Merge branch 'master' 2018-11-15 16:26:35 +01:00
Martin Boehm ab20a14d18 Modify loading of ethereum blocks 2018-11-15 16:18:29 +01:00
Martin Boehm 0f6fdf7f21 Fix integration tests 2018-11-15 16:18:29 +01:00
Martin Boehm 6072aa5e9e Handle coin specific tx data more efficiently 2018-11-15 16:18:29 +01:00
Martin Boehm 975c98b5b7 Pack eth transactions including receipt 2018-11-15 16:18:29 +01:00
Martin Boehm ef03abcd1c Process tx receipts and ERC20 tokens WIP 2018-11-15 16:18:29 +01:00
Martin Boehm 1ac7a7abca Fix api.GetTransaction for EthereumType blockchain 2018-11-15 16:18:29 +01:00
Martin Boehm 4448c57ba8 Introduce BitcoinType and EthereumType distinction of blockchains 2018-11-15 16:16:01 +01:00
Martin Boehm 28b3a4d1b4 Implement ETH GetChainInfo 2018-11-15 16:16:01 +01:00
Martin Boehm afb8926e1b Bump geth dependency to v1.8.17 2018-11-15 16:16:01 +01:00
Martin Boehm acb5d63afc Modify loading of ethereum blocks 2018-11-14 12:13:36 +01:00
Martin Boehm 1dbe7f42ba Fix integration tests 2018-11-13 10:55:28 +01:00
Martin Boehm 6d3e171b71 Handle coin specific tx data more efficiently 2018-11-13 10:31:27 +01:00
Martin Boehm edf7193df4 Pack eth transactions including receipt 2018-11-12 14:55:15 +01:00
Martin Boehm 4a05757321 Process tx receipts and ERC20 tokens WIP 2018-11-09 13:08:43 +01:00
Martin Boehm fac728bc51 Fix api.GetTransaction for EthereumType blockchain 2018-11-07 00:24:53 +01:00
Martin Boehm 38ba033654 Introduce BitcoinType and EthereumType distinction of blockchains 2018-11-06 18:41:13 +01:00
Martin Boehm 13acef41d4 Implement ETH GetChainInfo 2018-11-05 13:03:38 +01:00
Martin Boehm 9b14964900 Bump geth dependency to v1.8.17 2018-11-05 13:03:38 +01:00
403 changed files with 42374 additions and 4872 deletions

12
.gitignore vendored
View File

@ -6,8 +6,12 @@ notes.txt
debug*
.vscode
docker/blockbook
build
!build/templates
!build/docker
build/pkg-defs
build/blockbook
build/blockchaincfg.json
build/ldb
build/sst_dump
build/*.deb
.bin-image
.deb-image
.deb-image
\.idea/

View File

@ -1,6 +1,7 @@
stages:
- build
- test
- backend-deploy-and-test
build:
stage: build
@ -25,3 +26,135 @@ integration-test:
tags:
- blockbook
script: make test-integration ARGS="-run='TestIntegration/(bcash|bgold|bitcoin|dash|dogecoin|litecoin|vertcoin|zcash)=main/'"
backend-deploy-and-test-bcash:
stage: backend-deploy-and-test
only:
refs:
- master
changes:
- configs/coins/bcash.json
tags:
- blockbook
script: ./contrib/scripts/backend-deploy-and-test.sh bcash
backend-deploy-and-test-bgold:
stage: backend-deploy-and-test
only:
refs:
- master
changes:
- configs/coins/bgold.json
tags:
- blockbook
script: ./contrib/scripts/backend-deploy-and-test.sh bgold
backend-deploy-and-test-bitcoin:
stage: backend-deploy-and-test
only:
refs:
- master
changes:
- configs/coins/bitcoin.json
tags:
- blockbook
script: ./contrib/scripts/backend-deploy-and-test.sh bitcoin
backend-deploy-and-test-dash:
stage: backend-deploy-and-test
only:
refs:
- master
changes:
- configs/coins/dash.json
tags:
- blockbook
script: ./contrib/scripts/backend-deploy-and-test.sh dash
backend-deploy-and-test-digibyte:
stage: backend-deploy-and-test
only:
refs:
- master
changes:
- configs/coins/digibyte.json
tags:
- blockbook
script: ./contrib/scripts/backend-deploy-and-test.sh digibyte
backend-deploy-and-test-dogecoin:
stage: backend-deploy-and-test
only:
refs:
- master
changes:
- configs/coins/dogecoin.json
tags:
- blockbook
script: ./contrib/scripts/backend-deploy-and-test.sh dogecoin
backend-deploy-and-test-litecoin:
stage: backend-deploy-and-test
only:
refs:
- master
changes:
- configs/coins/litecoin.json
tags:
- blockbook
script: ./contrib/scripts/backend-deploy-and-test.sh litecoin
backend-deploy-and-test-namecoin:
stage: backend-deploy-and-test
only:
refs:
- master
changes:
- configs/coins/namecoin.json
tags:
- blockbook
script: ./contrib/scripts/backend-deploy-and-test.sh namecoin
backend-deploy-and-test-vertcoin:
stage: backend-deploy-and-test
only:
refs:
- master
changes:
- configs/coins/vertcoin.json
tags:
- blockbook
script: ./contrib/scripts/backend-deploy-and-test.sh vertcoin
backend-deploy-and-test-zcash:
stage: backend-deploy-and-test
only:
refs:
- master
changes:
- configs/coins/zcash.json
tags:
- blockbook
script: ./contrib/scripts/backend-deploy-and-test.sh zcash
backend-deploy-and-test-bitcoin_testnet:
stage: backend-deploy-and-test
only:
refs:
- master
changes:
- configs/coins/bitcoin_testnet.json
tags:
- blockbook
script: ./contrib/scripts/backend-deploy-and-test.sh bitcoin_testnet bitcoin-testnet bitcoin=test testnet3/debug.log
backend-deploy-and-test-ethereum_testnet_ropsten:
stage: backend-deploy-and-test
only:
refs:
- master
changes:
- configs/coins/ethereum_testnet_ropsten.json
tags:
- blockbook
script: ./contrib/scripts/backend-deploy-and-test.sh ethereum_testnet_ropsten ethereum-testnet-ropsten ethereum=test ethereum_testnet_ropsten.log

View File

@ -1,15 +1,15 @@
# Blockbook Contributor Guide
Blockbook is back-end service for Trezor wallet. Although it is open source, design and development of core packages
Blockbook is back-end service for Trezor wallet. Although it is open source, the design and development of the core packages
is done by Trezor developers in order to keep Blockbook compatible with Trezor.
Bug fixes and support for new coins are welcome. Please take note that non-fixing pull requests that change base
packages or another coin code will not be accepted. If you will have to change some of existing code, please file
Bug fixes and support for new coins are welcome. **Please take note that non-fixing pull requests that change base
packages or another coin code will not be accepted.** If you have a need to change some of the existing code, please file
an issue and discuss your request with Blockbook maintainers.
## Development environment
Instructions to set up your development environment and build Blockbook are described in separated
Instructions to set up your development environment and build Blockbook are described in a separate
[document](/docs/build.md).
## How can I contribute?
@ -25,8 +25,7 @@ updates. Do not leave random "+1" or "I have this too" comments, as they only cl
resolving it. However, if you have ways to reproduce the issue or have additional information that may help resolving
the issue, please leave a comment.
Include information about Blockbook instance which is exposed at internal HTTP port. Ports are listed in
[port registry](/docs/ports.md). For example execute `curl -k https://localhost:9030` for Bitcoin.
Include information about the Blockbook instance, which is shown at the Blockbook status page or returned by API call. For example execute `curl -k https://<server name>:<public port>/api` to get JSON containing details about Blockbook and Backend installation. Ports are listed in the [port registry](/docs/ports.md).
Also include the steps required to reproduce the problem if possible and applicable. This information will help us
review and fix your issue faster. When sending lengthy log-files, consider posting them as a gist
@ -34,35 +33,35 @@ review and fix your issue faster. When sending lengthy log-files, consider posti
### Adding coin support
> **Important notice**: Although we are happy for support of new coins, we do not have enough capacity to run them all
> on our infrastructure. We run Blockbook instances only for selected number of coins. If you want to have Blockbook
> instance for your coin, you will have to deploy it to your own server.
Trezor harware wallet supports over 500 coins, see https://trezor.io/coins/. You are free to add support for any of
them to Blockbook. Actually implemented coins are listed [here](/docs/ports.md).
them to Blockbook. Currently implemented coins are listed [here](/docs/ports.md).
You should follow few steps bellow to get smooth merge of your PR.
> Although we are happy for support of new coins we have not enough capacity to run them all on our infrastructure.
> Actually we can run Blockbook instances only for coins supported by Trezor wallet. If you want to have Blockbook
> instance for your coin, you will have to deploy your own server.
You should follow the steps below to get smooth merge of your PR.
#### Add coin definition
Coin definitions are stored in JSON files in *configs/coins* directory. They are single source of Blockbook
Coin definitions are stored in JSON files in *configs/coins* directory. They are the single source of Blockbook
configuration, Blockbook and back-end package definition and build metadata. Since Blockbook supports only single
coin index per running instance, every coin (including testnet) must have single definition file.
All options of coin definition are described in [config.md](/docs/config.md).
Because most of coins are fork of Bitcoin and they have similar way to install and configure their daemon, we use
templates to generate package definition and configuration files during build process. Similarly, there templates for Blockbook
templates to generate package definition and configuration files during build process. Similarly, there are templates for Blockbook
package. Templates are filled with data from coin definition. Although normally all package definitions are generated automatically
during the build process, sometimes there is a reason to see them. You can create them by calling
during the build process, sometimes there is a reason to check what was generated. You can create them by calling
`go run build/templates/generate.go coin`, where *coin* is name of definition file without .json extension. Files are
generated to *build/pkg-defs* directory.
Good examples of coin configuration are
[*configs/coins/bitcoin.json*](configs/coins/bitcoin.json) and
[*configs/coins/ethereum.json*](configs/coins/ethereum.json) for Bitcoin-like coins and different coins, respectively.
[*configs/coins/ethereum.json*](configs/coins/ethereum.json) for Bitcoin type coins and Ethereum type coins, respectively.
Usually you have to update only few options that differ from Bitcoin definition. At first there is base information
Usually you have to update only a few options that differ from the Bitcoin definition. At first there is base information
about coin in section *coin* name, alias etc. Then update port information in *port* section. We keep port series as
listed in [the port registry](/docs/ports.md). Select next port numbers in the series. Port numbers must be unique across all
port definitions.
@ -109,15 +108,15 @@ different concept than Bitcoin.
##### BlockChain interface
Type that implements *bchain.BlockChain* interface ensures communication with block chain network. Because
it calls node RPCs it usually has suffix RPC.
Type that implements *bchain.BlockChain* interface ensures communication with the block chain network. Because
it calls node RPCs, it usually has suffix RPC.
Initialization of object is separated into two stages. At first there is called factory method (details described
in next section) and then *bchain.BlockChain.Initialize()* method. Separated initialization method allows you call
in the next section) and then *bchain.BlockChain.Initialize()* method. Separated initialization method allows you call
inherited methods during initialization. However it is common practice override fields of embedded structure in factory
method.
During initialization, there is usually loaded chain information, registered message queue callback and created mempool
Initialization routine usually loads chain information, registers message queue callback and creates mempool
and parser objects.
BitcoinRPC uses *btc.RPCMarshaller* ([btc/codec.go](/bchain/coins/btc/codec.go)) in order to distinguish API version of
@ -136,7 +135,7 @@ must correspond to *coin.name* in coin definition file (see above).
Configuration passed to factory method is coin specific. For types that embed *btc.BitcoinRPC,* configuration must
contain at least fields referred in *btc.Configuration* ([btc/bitcoinrpc.go](/bchain/coins/btc/bitcoinrpc.go)).
For types that embed base struct it is common practise call factory method of embedded type in order to
For types that embed base struct it is common practice to call factory method of the embedded type in order to
create & initialize it. It is much more robust than simple struct composition.
For example see [zec/zcashrpc.go](/bchain/coins/zec/zcashrpc.go).
@ -146,7 +145,7 @@ For example see [zec/zcashrpc.go](/bchain/coins/zec/zcashrpc.go).
Type that implements *bchain.BlockChainParser* interface ensures parsing and conversions of block chain data. It is
initialized by *bchain.BlockChain* during initialization.
There are several groups of methods defined in *bchian.BlockChainParser*:
There are several groups of methods defined in *bchain.BlockChainParser*:
* *GetAddrDescFromVout* and *GetAddrDescFromAddress* Convert transaction addresses to *Address Descriptor* that is used as database ID.
Most of coins use output script as *Address Descriptor*.
@ -168,9 +167,9 @@ different approach for address representation than Bitcoin.
#### Add tests
Add unit tests and integration tests. PR without passing tests won't be accepted.
Tests are described [here](/docs/testing.md).
Add unit tests and integration tests. **Pull requests without passing tests will not be accepted**.
How to implement tests is described [here](/docs/testing.md).
#### Deploy public server
Deploy Blockbook server on public IP address. Blockbook maintainers will check implementation before merging.
Deploy Blockbook server on public IP address. Blockbook maintainers will check your implementation before merging.

219
Gopkg.lock generated
View File

@ -1,219 +0,0 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
branch = "master"
name = "github.com/Groestlcoin/go-groestl-hash"
packages = ["groestl","hash"]
revision = "790653ac190c4029ee200e82a8f21b5d1afaf7d6"
[[projects]]
branch = "master"
name = "github.com/beorn7/perks"
packages = ["quantile"]
revision = "3a771d992973f24aa725d07868b467d1ddfceafb"
[[projects]]
branch = "master"
name = "github.com/bsm/go-vlq"
packages = ["."]
revision = "ec6e8d4f5f4ec0f6e808ffc7f4dcc7516d4d7d49"
[[projects]]
branch = "master"
name = "github.com/btcsuite/btcd"
packages = ["blockchain","btcec","chaincfg","chaincfg/chainhash","database","txscript","wire"]
revision = "2be2f12b358dc57d70b8f501b00be450192efbc3"
[[projects]]
branch = "master"
name = "github.com/btcsuite/btclog"
packages = ["."]
revision = "84c8d2346e9fc8c7b947e243b9c24e6df9fd206a"
[[projects]]
branch = "master"
name = "github.com/btcsuite/btcutil"
packages = [".","base58","bech32"]
revision = "501929d3d046174c3d39f0ea54ece471aa17238c"
[[projects]]
name = "github.com/deckarep/golang-set"
packages = ["."]
revision = "1d4478f51bed434f1dadf96dcd9b43aabac66795"
version = "v1.7"
[[projects]]
name = "github.com/ethereum/go-ethereum"
packages = [".","common","common/hexutil","common/math","core/types","crypto","crypto/secp256k1","crypto/sha3","ethclient","ethdb","log","metrics","p2p/netutil","params","rlp","rpc","trie"]
revision = "89451f7c382ad2185987ee369f16416f89c28a7d"
version = "v1.8.15"
[[projects]]
name = "github.com/go-stack/stack"
packages = ["."]
revision = "259ab82a6cad3992b4e21ff5cac294ccb06474bc"
version = "v1.7.0"
[[projects]]
name = "github.com/gobuffalo/packr"
packages = ["."]
revision = "5a2cbb54c4e7d482e3f518c56f1f86f133d5204f"
version = "v1.13.7"
[[projects]]
name = "github.com/gogo/protobuf"
packages = ["proto"]
revision = "1adfc126b41513cc696b209667c8656ea7aac67c"
version = "v1.0.0"
[[projects]]
branch = "master"
name = "github.com/golang/glog"
packages = ["."]
revision = "23def4e6c14b4da8ac2ed8007337bc5eb5007998"
[[projects]]
name = "github.com/golang/protobuf"
packages = ["proto"]
revision = "925541529c1fa6821df4e44ce2723319eb2be768"
version = "v1.0.0"
[[projects]]
branch = "master"
name = "github.com/golang/snappy"
packages = ["."]
revision = "553a641470496b2327abcac10b36396bd98e45c9"
[[projects]]
name = "github.com/gorilla/websocket"
packages = ["."]
revision = "ea4d1f681babbce9545c9c5f3d5194a789c89f5b"
version = "v1.2.0"
[[projects]]
branch = "master"
name = "github.com/jakm/bchutil"
packages = ["."]
revision = "5a273ca8a96628732c07ff5cfb9f3e7d965241e8"
[[projects]]
branch = "master"
name = "github.com/jakm/btcutil"
packages = [".","base58","bech32","chaincfg","txscript"]
revision = "224b76333062172edefdeb502123fdda12205f76"
[[projects]]
branch = "master"
name = "github.com/juju/errors"
packages = ["."]
revision = "c7d06af17c68cd34c835053720b21f6549d9b0ee"
[[projects]]
branch = "master"
name = "github.com/martinboehm/golang-socketio"
packages = [".","protocol","transport"]
revision = "f60b0a8befde091474a624a8ffd81ee9912957b3"
[[projects]]
name = "github.com/matttproud/golang_protobuf_extensions"
packages = ["pbutil"]
revision = "3247c84500bff8d9fb6d579d800f20b3e091582c"
version = "v1.0.0"
[[projects]]
branch = "master"
name = "github.com/mr-tron/base58"
packages = ["base58"]
revision = "c1bdf7c52f59d6685ca597b9955a443ff95eeee6"
[[projects]]
branch = "master"
name = "github.com/pebbe/zmq4"
packages = ["."]
revision = "5b443b6471cea4b4f9f85025530c04c93233f76a"
[[projects]]
name = "github.com/pkg/errors"
packages = ["."]
revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
version = "v0.8.0"
[[projects]]
name = "github.com/prometheus/client_golang"
packages = ["prometheus","prometheus/promhttp"]
revision = "c5b7fccd204277076155f10851dad72b76a49317"
version = "v0.8.0"
[[projects]]
branch = "master"
name = "github.com/prometheus/client_model"
packages = ["go"]
revision = "99fa1f4be8e564e8a6b613da7fa6f46c9edafc6c"
[[projects]]
branch = "master"
name = "github.com/prometheus/common"
packages = ["expfmt","internal/bitbucket.org/ww/goautoneg","model"]
revision = "d0f7cd64bda49e08b22ae8a730aa57aa0db125d6"
[[projects]]
branch = "master"
name = "github.com/prometheus/procfs"
packages = [".","internal/util","nfs","xfs"]
revision = "8b1c2da0d56deffdbb9e48d4414b4e674bd8083e"
[[projects]]
name = "github.com/rs/cors"
packages = ["."]
revision = "feef513b9575b32f84bafa580aad89b011259019"
version = "v1.3.0"
[[projects]]
name = "github.com/schancel/cashaddr-converter"
packages = ["address","baseconv","cashaddress","legacy"]
revision = "0a38f5822f795dc3727b4caacc298e02938d9eb1"
version = "v9"
[[projects]]
branch = "master"
name = "github.com/syndtr/goleveldb"
packages = ["leveldb","leveldb/cache","leveldb/comparer","leveldb/errors","leveldb/filter","leveldb/iterator","leveldb/journal","leveldb/memdb","leveldb/opt","leveldb/storage","leveldb/table","leveldb/util"]
revision = "714f901b98fdb3aa954b4193d8cbd64a28d80cad"
[[projects]]
branch = "master"
name = "github.com/tecbot/gorocksdb"
packages = ["."]
revision = "214b6b7bc0f06812ab5602fdc502a3e619916f38"
[[projects]]
branch = "master"
name = "golang.org/x/crypto"
packages = ["ripemd160"]
revision = "d6449816ce06963d9d136eee5a56fca5b0616e7e"
[[projects]]
branch = "master"
name = "golang.org/x/net"
packages = ["websocket"]
revision = "61147c48b25b599e5b561d2e9c4f3e1ef489ca41"
[[projects]]
branch = "v2"
name = "gopkg.in/karalabe/cookiejar.v2"
packages = ["collections/prque"]
revision = "8dcd6a7f4951f6ff3ee9cbb919a06d8925822e57"
[[projects]]
branch = "v2"
name = "gopkg.in/natefinch/npipe.v2"
packages = ["."]
revision = "c1b8fa8bdccecb0b8db834ee0b92fdbcfa606dd6"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "3a7b863bec3806ba9460f3961e2d6e8ff4b77e5f66d196b1f79bd37fa6bf8d82"
solver-name = "gps-cdcl"
solver-version = 1

View File

@ -1,74 +0,0 @@
# Gopkg.toml example
#
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
# version = "1.0.0"
#
# [[constraint]]
# name = "github.com/user/project2"
# branch = "dev"
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"
[[constraint]]
branch = "master"
name = "github.com/bsm/go-vlq"
[[constraint]]
branch = "master"
name = "github.com/btcsuite/btcd"
[[constraint]]
branch = "master"
name = "github.com/btcsuite/btcutil"
[[constraint]]
branch = "master"
name = "github.com/golang/glog"
[[constraint]]
name = "github.com/gorilla/mux"
version = "1.6.1"
[[constraint]]
branch = "master"
name = "github.com/juju/errors"
[[constraint]]
branch = "master"
name = "github.com/martinboehm/golang-socketio"
[[constraint]]
branch = "master"
name = "github.com/pebbe/zmq4"
[[constraint]]
name = "github.com/prometheus/client_golang"
version = "0.8.0"
[[constraint]]
branch = "master"
name = "github.com/tecbot/gorocksdb"
[[constraint]]
name = "github.com/ethereum/go-ethereum"
version = "1.8.2"
[[constraint]]
name = "github.com/golang/protobuf"
version = "1.0.0"
[[constraint]]
branch = "master"
name = "github.com/cpacia/bchutil"

View File

@ -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

74
README-upstream.md 100644
View File

@ -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).

View File

@ -1,46 +1,57 @@
[![Go Report Card](https://goreportcard.com/badge/trezor/blockbook)](https://goreportcard.com/report/trezor/blockbook)
# Fork
Fork of Trezor Blockbook.
# Blockbook
> **WARNING: Blockbook is currently in the state of heavy development. We may implement at any time backwards incompatible changes that require full reindexation of the database. Also, do not expect this documentation to be always up to date.**
The differences:
**Blockbook** is back-end service for Trezor wallet. Main features of **Blockbook** are:
* Just for Ethereum.
- create missing indexes in the blockchain - index of addresses and address balances
- allow fast searches in the index of addresses
- implement parts Insight socket.io interface as required by Trezor wallet
- support of multiple coins
- simple blockchain explorer for implemented coins
- scripts for easy creation of debian packages for backend and blockbook
* Use existing `geth --full` server.
## Build and installation instructions
* Don't require `backend-*` package.
Officially supported platform is **Debian Linux** and **AMD64** architecture.
* Minimal UI, dark theme.
Memory and disk requirements for initial synchronization of **Bitcoin mainnet** are around 32 GB RAM and over 150 GB of disk size. After initial synchronization, fully synchronized instance takes around 10 GB RAM.
Other coins should have lower requirements depending on size of their block chain. Note that fast SSD disks are highly
recommended.
* Listen only on localhost, no SSL.
User installation guide is [here](https://wiki.trezor.io/User_manual:Running_a_local_instance_of_Trezor_Wallet_backend_(Blockbook)).
* Use spacecruft repos, not github.
Developer build guide is [here](/docs/build.md).
* Don't use CDN.
Contribution guide is [here](CONTRIBUTING.md).
# Install
# Implemented coins
```
# Install docker, etc.
git clone https://spacecruft.org/spacecruft/blockbook
cd blockbook
make deb-blockbook-ethereum
dpkg -i build/blockbook-ethereum_0.3.4_amd64.deb
```
The most significant coins implemented by Blockbook are:
- Bitcoin, Bcash, Bgold, ZCash, Dash, Litecoin
Edit config:
```
vim /opt/coins/blockbook/ethereum/config/blockchaincfg.json
```
Incomplete, experimental support is for:
- Ethereum, Ethereum Classic
XXX Hardcoded into systemd script, set `geth` node:
Testnets for some coins are also supported, for example:
- Bitcoin Testnet, Bcash Testnet, ZCash Testnet, Ethereum Testnet Ropsten
```
vim /lib/systemd/system/blockbook-ethereum.service
systemctl daemon-reload
```
List of all implemented coins is in [the registry of ports](/docs/ports.md).
Start:
```
systemctl start blockbook-ethereum.service
```
# Data storage in RocksDB
Logs:
```
tail -f /opt/coins/blockbook/ethereum/logs/blockbook.INFO
```
Blockbook stores data the key-value store RocksDB. Database format is described [here](/docs/rocksdb.md).
# Upstream
Fork of Trezor Blockbook. See `README-upstream.md`.
* https://github.com/trezor/blockbook

View File

@ -21,8 +21,9 @@ func init() {
panic(err)
}
if tosLink, err := box.MustString("tos_link"); err == nil {
tosLink = strings.TrimSpace(tosLink)
if _, err := url.ParseRequestURI(tosLink); err == nil {
Text.TOSLink = strings.TrimSpace(tosLink)
Text.TOSLink = tosLink
} else {
panic(fmt.Sprint("tos_link is not valid URL:", err.Error()))
}

View File

@ -1,13 +1,43 @@
package api
import (
"blockbook/bchain"
"blockbook/common"
"blockbook/db"
"encoding/json"
"errors"
"math/big"
"sort"
"time"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/bchain/coins/eth"
"spacecruft.org/spacecruft/blockbook/common"
"spacecruft.org/spacecruft/blockbook/db"
)
const maxUint32 = ^uint32(0)
const maxInt = int(^uint(0) >> 1)
const maxInt64 = int64(^uint64(0) >> 1)
// AccountDetails specifies what data returns GetAddress and GetXpub calls
type AccountDetails int
const (
// AccountDetailsBasic - only that address is indexed and some basic info
AccountDetailsBasic AccountDetails = iota
// AccountDetailsTokens - basic info + tokens
AccountDetailsTokens
// AccountDetailsTokenBalances - basic info + token with balance
AccountDetailsTokenBalances
// AccountDetailsTxidHistory - basic + token balances + txids, subject to paging
AccountDetailsTxidHistory
// AccountDetailsTxHistoryLight - basic + tokens + easily obtained tx data (not requiring requests to backend), subject to paging
AccountDetailsTxHistoryLight
// AccountDetailsTxHistory - basic + tokens + full tx data, subject to paging
AccountDetailsTxHistory
)
// ErrUnsupportedXpub is returned when coin type does not support xpub address derivation or provided string is not an xpub
var ErrUnsupportedXpub = errors.New("XPUB not supported")
// APIError extends error by information if the error details should be returned to the end user
type APIError struct {
Text string
@ -26,99 +56,316 @@ func NewAPIError(s string, public bool) error {
}
}
// ScriptSig contains input script
type ScriptSig struct {
Hex string `json:"hex"`
Asm string `json:"asm,omitempty"`
// Amount is datatype holding amounts
type Amount big.Int
// IsZeroBigInt if big int has zero value
func IsZeroBigInt(b *big.Int) bool {
return len(b.Bits()) == 0
}
// MarshalJSON Amount serialization
func (a *Amount) MarshalJSON() (out []byte, err error) {
if a == nil {
return []byte(`"0"`), nil
}
return []byte(`"` + (*big.Int)(a).String() + `"`), nil
}
func (a *Amount) String() string {
if a == nil {
return ""
}
return (*big.Int)(a).String()
}
// DecimalString returns amount with decimal point placed according to parameter d
func (a *Amount) DecimalString(d int) string {
return bchain.AmountToDecimalString((*big.Int)(a), d)
}
// AsBigInt returns big.Int type for the Amount (empty if Amount is nil)
func (a *Amount) AsBigInt() big.Int {
if a == nil {
return *new(big.Int)
}
return big.Int(*a)
}
// AsInt64 returns Amount as int64 (0 if Amount is nil).
// It is used only for legacy interfaces (socket.io)
// and generally not recommended to use for possible loss of precision.
func (a *Amount) AsInt64() int64 {
if a == nil {
return 0
}
return (*big.Int)(a).Int64()
}
// Vin contains information about single transaction input
type Vin struct {
Txid string `json:"txid"`
Vout uint32 `json:"vout"`
Sequence int64 `json:"sequence,omitempty"`
N int `json:"n"`
ScriptSig ScriptSig `json:"scriptSig"`
AddrDesc bchain.AddressDescriptor `json:"-"`
Addresses []string `json:"addresses"`
Searchable bool `json:"-"`
Value string `json:"value"`
ValueSat big.Int `json:"-"`
}
// ScriptPubKey contains output script and addresses derived from it
type ScriptPubKey struct {
Hex string `json:"hex"`
Asm string `json:"asm,omitempty"`
AddrDesc bchain.AddressDescriptor `json:"-"`
Addresses []string `json:"addresses"`
Searchable bool `json:"-"`
Type string `json:"type,omitempty"`
Txid string `json:"txid,omitempty"`
Vout uint32 `json:"vout,omitempty"`
Sequence int64 `json:"sequence,omitempty"`
N int `json:"n"`
AddrDesc bchain.AddressDescriptor `json:"-"`
Addresses []string `json:"addresses,omitempty"`
IsAddress bool `json:"isAddress"`
ValueSat *Amount `json:"value,omitempty"`
Hex string `json:"hex,omitempty"`
Asm string `json:"asm,omitempty"`
Coinbase string `json:"coinbase,omitempty"`
}
// Vout contains information about single transaction output
type Vout struct {
Value string `json:"value"`
ValueSat big.Int `json:"-"`
N int `json:"n"`
ScriptPubKey ScriptPubKey `json:"scriptPubKey"`
Spent bool `json:"spent"`
SpentTxID string `json:"spentTxId,omitempty"`
SpentIndex int `json:"spentIndex,omitempty"`
SpentHeight int `json:"spentHeight,omitempty"`
ValueSat *Amount `json:"value,omitempty"`
N int `json:"n"`
Spent bool `json:"spent,omitempty"`
SpentTxID string `json:"spentTxId,omitempty"`
SpentIndex int `json:"spentIndex,omitempty"`
SpentHeight int `json:"spentHeight,omitempty"`
Hex string `json:"hex,omitempty"`
Asm string `json:"asm,omitempty"`
AddrDesc bchain.AddressDescriptor `json:"-"`
Addresses []string `json:"addresses"`
IsAddress bool `json:"isAddress"`
Type string `json:"type,omitempty"`
}
// TokenType specifies type of token
type TokenType string
// ERC20TokenType is Ethereum ERC20 token
const ERC20TokenType TokenType = "ERC20"
// XPUBAddressTokenType is address derived from xpub
const XPUBAddressTokenType TokenType = "XPUBAddress"
// Token contains info about tokens held by an address
type Token struct {
Type TokenType `json:"type"`
Name string `json:"name"`
Path string `json:"path,omitempty"`
Contract string `json:"contract,omitempty"`
Transfers int `json:"transfers"`
Symbol string `json:"symbol,omitempty"`
Decimals int `json:"decimals,omitempty"`
BalanceSat *Amount `json:"balance,omitempty"`
TotalReceivedSat *Amount `json:"totalReceived,omitempty"`
TotalSentSat *Amount `json:"totalSent,omitempty"`
ContractIndex string `json:"-"`
}
// TokenTransfer contains info about a token transfer done in a transaction
type TokenTransfer struct {
Type TokenType `json:"type"`
From string `json:"from"`
To string `json:"to"`
Token string `json:"token"`
Name string `json:"name"`
Symbol string `json:"symbol"`
Decimals int `json:"decimals"`
Value *Amount `json:"value"`
}
// EthereumSpecific contains ethereum specific transaction data
type EthereumSpecific struct {
Status eth.TxStatus `json:"status"` // 1 OK, 0 Fail, -1 pending
Nonce uint64 `json:"nonce"`
GasLimit *big.Int `json:"gasLimit"`
GasUsed *big.Int `json:"gasUsed"`
GasPrice *Amount `json:"gasPrice"`
Data string `json:"data,omitempty"`
}
// Tx holds information about a transaction
type Tx struct {
Txid string `json:"txid"`
Version int32 `json:"version,omitempty"`
Locktime uint32 `json:"locktime,omitempty"`
Vin []Vin `json:"vin"`
Vout []Vout `json:"vout"`
Blockhash string `json:"blockhash,omitempty"`
Blockheight int `json:"blockheight"`
Confirmations uint32 `json:"confirmations"`
Time int64 `json:"time,omitempty"`
Blocktime int64 `json:"blocktime"`
ValueOut string `json:"valueOut"`
ValueOutSat big.Int `json:"-"`
Size int `json:"size,omitempty"`
ValueIn string `json:"valueIn"`
ValueInSat big.Int `json:"-"`
Fees string `json:"fees"`
FeesSat big.Int `json:"-"`
Hex string `json:"hex"`
Txid string `json:"txid"`
Version int32 `json:"version,omitempty"`
Locktime uint32 `json:"lockTime,omitempty"`
Vin []Vin `json:"vin"`
Vout []Vout `json:"vout"`
Blockhash string `json:"blockHash,omitempty"`
Blockheight int `json:"blockHeight"`
Confirmations uint32 `json:"confirmations"`
Blocktime int64 `json:"blockTime"`
Size int `json:"size,omitempty"`
ValueOutSat *Amount `json:"value"`
ValueInSat *Amount `json:"valueIn,omitempty"`
FeesSat *Amount `json:"fees,omitempty"`
Hex string `json:"hex,omitempty"`
Rbf bool `json:"rbf,omitempty"`
CoinSpecificData json.RawMessage `json:"coinSpecificData,omitempty"`
TokenTransfers []TokenTransfer `json:"tokenTransfers,omitempty"`
EthereumSpecific *EthereumSpecific `json:"ethereumSpecific,omitempty"`
}
// FeeStats contains detailed block fee statistics
type FeeStats struct {
TxCount int `json:"txCount"`
TotalFeesSat *Amount `json:"totalFeesSat"`
AverageFeePerKb int64 `json:"averageFeePerKb"`
DecilesFeePerKb [11]int64 `json:"decilesFeePerKb"`
}
// Paging contains information about paging for address, blocks and block
type Paging struct {
Page int `json:"page"`
TotalPages int `json:"totalPages"`
ItemsOnPage int `json:"itemsOnPage"`
Page int `json:"page,omitempty"`
TotalPages int `json:"totalPages,omitempty"`
ItemsOnPage int `json:"itemsOnPage,omitempty"`
}
// TokensToReturn specifies what tokens are returned by GetAddress and GetXpubAddress
type TokensToReturn int
const (
// AddressFilterVoutOff disables filtering of transactions by vout
AddressFilterVoutOff = -1
// AddressFilterVoutInputs specifies that only txs where the address is as input are returned
AddressFilterVoutInputs = -2
// AddressFilterVoutOutputs specifies that only txs where the address is as output are returned
AddressFilterVoutOutputs = -3
// AddressFilterVoutQueryNotNecessary signals that query for transactions is not necessary as there are no transactions for specified contract filter
AddressFilterVoutQueryNotNecessary = -4
// TokensToReturnNonzeroBalance - return only tokens with nonzero balance
TokensToReturnNonzeroBalance TokensToReturn = 0
// TokensToReturnUsed - return tokens with some transfers (even if they have zero balance now)
TokensToReturnUsed TokensToReturn = 1
// TokensToReturnDerived - return all derived tokens
TokensToReturnDerived TokensToReturn = 2
)
// AddressFilter is used to filter data returned from GetAddress api method
type AddressFilter struct {
Vout int
Contract string
FromHeight uint32
ToHeight uint32
TokensToReturn TokensToReturn
// OnlyConfirmed set to true will ignore mempool transactions; mempool is also ignored if FromHeight/ToHeight filter is specified
OnlyConfirmed bool
}
// Address holds information about address and its transactions
type Address struct {
Paging
AddrStr string `json:"addrStr"`
Balance string `json:"balance"`
TotalReceived string `json:"totalReceived"`
TotalSent string `json:"totalSent"`
UnconfirmedBalance string `json:"unconfirmedBalance"`
UnconfirmedTxApperances int `json:"unconfirmedTxApperances"`
TxApperances int `json:"txApperances"`
Transactions []*Tx `json:"txs,omitempty"`
Txids []string `json:"transactions,omitempty"`
AddrStr string `json:"address"`
BalanceSat *Amount `json:"balance"`
TotalReceivedSat *Amount `json:"totalReceived,omitempty"`
TotalSentSat *Amount `json:"totalSent,omitempty"`
UnconfirmedBalanceSat *Amount `json:"unconfirmedBalance"`
UnconfirmedTxs int `json:"unconfirmedTxs"`
Txs int `json:"txs"`
NonTokenTxs int `json:"nonTokenTxs,omitempty"`
Transactions []*Tx `json:"transactions,omitempty"`
Txids []string `json:"txids,omitempty"`
Nonce string `json:"nonce,omitempty"`
UsedTokens int `json:"usedTokens,omitempty"`
Tokens []Token `json:"tokens,omitempty"`
Erc20Contract *bchain.Erc20Contract `json:"erc20Contract,omitempty"`
// helpers for explorer
Filter string `json:"-"`
XPubAddresses map[string]struct{} `json:"-"`
}
// AddressUtxo holds information about address and its transactions
type AddressUtxo struct {
// Utxo is one unspent transaction output
type Utxo struct {
Txid string `json:"txid"`
Vout uint32 `json:"vout"`
Amount string `json:"amount"`
AmountSat big.Int `json:"satoshis"`
Vout int32 `json:"vout"`
AmountSat *Amount `json:"value"`
Height int `json:"height,omitempty"`
Confirmations int `json:"confirmations"`
Address string `json:"address,omitempty"`
Path string `json:"path,omitempty"`
Locktime uint32 `json:"lockTime,omitempty"`
Coinbase bool `json:"coinbase,omitempty"`
}
// Utxos is array of Utxo
type Utxos []Utxo
func (a Utxos) Len() int { return len(a) }
func (a Utxos) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a Utxos) Less(i, j int) bool {
// sort in reverse order, unconfirmed (height==0) utxos on top
hi := a[i].Height
hj := a[j].Height
if hi == 0 {
hi = maxInt
}
if hj == 0 {
hj = maxInt
}
return hi >= hj
}
// BalanceHistory contains info about one point in time of balance history
type BalanceHistory struct {
Time uint32 `json:"time"`
Txs uint32 `json:"txs"`
ReceivedSat *Amount `json:"received"`
SentSat *Amount `json:"sent"`
SentToSelfSat *Amount `json:"sentToSelf"`
FiatRates map[string]float64 `json:"rates,omitempty"`
Txid string `json:"txid,omitempty"`
}
// BalanceHistories is array of BalanceHistory
type BalanceHistories []BalanceHistory
func (a BalanceHistories) Len() int { return len(a) }
func (a BalanceHistories) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a BalanceHistories) Less(i, j int) bool {
ti := a[i].Time
tj := a[j].Time
if ti == tj {
return a[i].Txid < a[j].Txid
}
return ti < tj
}
// SortAndAggregate sums BalanceHistories to groups defined by parameter groupByTime
func (a BalanceHistories) SortAndAggregate(groupByTime uint32) BalanceHistories {
bhs := make(BalanceHistories, 0)
if len(a) > 0 {
bha := BalanceHistory{
ReceivedSat: &Amount{},
SentSat: &Amount{},
SentToSelfSat: &Amount{},
}
sort.Sort(a)
for i := range a {
bh := &a[i]
time := bh.Time - bh.Time%groupByTime
if bha.Time != time {
if bha.Time != 0 {
// in aggregate, do not return txid as it could multiple of them
bha.Txid = ""
bhs = append(bhs, bha)
}
bha = BalanceHistory{
Time: time,
ReceivedSat: &Amount{},
SentSat: &Amount{},
SentToSelfSat: &Amount{},
}
}
if bha.Txid != bh.Txid {
bha.Txs += bh.Txs
bha.Txid = bh.Txid
}
(*big.Int)(bha.ReceivedSat).Add((*big.Int)(bha.ReceivedSat), (*big.Int)(bh.ReceivedSat))
(*big.Int)(bha.SentSat).Add((*big.Int)(bha.SentSat), (*big.Int)(bh.SentSat))
(*big.Int)(bha.SentToSelfSat).Add((*big.Int)(bha.SentToSelfSat), (*big.Int)(bh.SentToSelfSat))
}
if bha.Txs > 0 {
bha.Txid = ""
bhs = append(bhs, bha)
}
}
return bhs
}
// Blocks is list of blocks with paging information
@ -127,11 +374,28 @@ type Blocks struct {
Blocks []db.BlockInfo `json:"blocks"`
}
// BlockInfo contains extended block header data and a list of block txids
type BlockInfo struct {
Hash string `json:"hash"`
Prev string `json:"previousBlockHash,omitempty"`
Next string `json:"nextBlockHash,omitempty"`
Height uint32 `json:"height"`
Confirmations int `json:"confirmations"`
Size int `json:"size"`
Time int64 `json:"time,omitempty"`
Version common.JSONNumber `json:"version"`
MerkleRoot string `json:"merkleRoot"`
Nonce string `json:"nonce"`
Bits string `json:"bits"`
Difficulty string `json:"difficulty"`
Txids []string `json:"tx,omitempty"`
}
// Block contains information about block
type Block struct {
Paging
bchain.BlockInfo
TxCount int `json:"TxCount"`
BlockInfo
TxCount int `json:"txCount"`
Transactions []*Tx `json:"txs,omitempty"`
}
@ -140,16 +404,17 @@ type BlockbookInfo struct {
Coin string `json:"coin"`
Host string `json:"host"`
Version string `json:"version"`
GitCommit string `json:"gitcommit"`
BuildTime string `json:"buildtime"`
GitCommit string `json:"gitCommit"`
BuildTime string `json:"buildTime"`
SyncMode bool `json:"syncMode"`
InitialSync bool `json:"initialsync"`
InitialSync bool `json:"initialSync"`
InSync bool `json:"inSync"`
BestHeight uint32 `json:"bestHeight"`
LastBlockTime time.Time `json:"lastBlockTime"`
InSyncMempool bool `json:"inSyncMempool"`
LastMempoolTime time.Time `json:"lastMempoolTime"`
MempoolSize int `json:"mempoolSize"`
Decimals int `json:"decimals"`
DbSize int64 `json:"dbSize"`
DbSizeFromColumns int64 `json:"dbSizeFromColumns,omitempty"`
DbColumns []common.InternalStateColumn `json:"dbColumns,omitempty"`
@ -158,6 +423,19 @@ type BlockbookInfo struct {
// SystemInfo contains information about the running blockbook and backend instance
type SystemInfo struct {
Blockbook *BlockbookInfo `json:"blockbook"`
Backend *bchain.ChainInfo `json:"backend"`
Blockbook *BlockbookInfo `json:"blockbook"`
Backend *common.BackendInfo `json:"backend"`
}
// MempoolTxid contains information about a transaction in mempool
type MempoolTxid struct {
Time int64 `json:"time"`
Txid string `json:"txid"`
}
// MempoolTxids contains a list of mempool txids with paging information
type MempoolTxids struct {
Paging
Mempool []MempoolTxid `json:"mempool"`
MempoolSize int `json:"mempoolSize"`
}

174
api/types_test.go 100644
View File

@ -0,0 +1,174 @@
// +build unittest
package api
import (
"encoding/json"
"math/big"
"reflect"
"testing"
)
func TestAmount_MarshalJSON(t *testing.T) {
type amounts struct {
A1 Amount `json:"a1"`
A2 Amount `json:"a2,omitempty"`
PA1 *Amount `json:"pa1"`
PA2 *Amount `json:"pa2,omitempty"`
}
tests := []struct {
name string
a amounts
want string
}{
{
name: "empty",
want: `{"a1":"0","a2":"0","pa1":null}`,
},
{
name: "1",
a: amounts{
A1: (Amount)(*big.NewInt(123456)),
A2: (Amount)(*big.NewInt(787901)),
PA1: (*Amount)(big.NewInt(234567)),
PA2: (*Amount)(big.NewInt(890123)),
},
want: `{"a1":"123456","a2":"787901","pa1":"234567","pa2":"890123"}`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
b, err := json.Marshal(&tt.a)
if err != nil {
t.Errorf("json.Marshal() error = %v", err)
return
}
if !reflect.DeepEqual(string(b), tt.want) {
t.Errorf("json.Marshal() = %v, want %v", string(b), tt.want)
}
})
}
}
func TestBalanceHistories_SortAndAggregate(t *testing.T) {
tests := []struct {
name string
a BalanceHistories
groupByTime uint32
want BalanceHistories
}{
{
name: "empty",
a: []BalanceHistory{},
groupByTime: 3600,
want: []BalanceHistory{},
},
{
name: "one",
a: []BalanceHistory{
{
ReceivedSat: (*Amount)(big.NewInt(1)),
SentSat: (*Amount)(big.NewInt(2)),
SentToSelfSat: (*Amount)(big.NewInt(1)),
Time: 1521514812,
Txid: "00b2c06055e5e90e9c82bd4181fde310104391a7fa4f289b1704e5d90caa3840",
Txs: 1,
},
},
groupByTime: 3600,
want: []BalanceHistory{
{
ReceivedSat: (*Amount)(big.NewInt(1)),
SentSat: (*Amount)(big.NewInt(2)),
SentToSelfSat: (*Amount)(big.NewInt(1)),
Time: 1521514800,
Txs: 1,
},
},
},
{
name: "aggregate",
a: []BalanceHistory{
{
ReceivedSat: (*Amount)(big.NewInt(1)),
SentSat: (*Amount)(big.NewInt(2)),
SentToSelfSat: (*Amount)(big.NewInt(0)),
Time: 1521504812,
Txid: "0011223344556677889900112233445566778899001122334455667788990011",
Txs: 1,
},
{
ReceivedSat: (*Amount)(big.NewInt(3)),
SentSat: (*Amount)(big.NewInt(4)),
SentToSelfSat: (*Amount)(big.NewInt(2)),
Time: 1521504812,
Txid: "00b2c06055e5e90e9c82bd4181fde310104391a7fa4f289b1704e5d90caa3840",
Txs: 1,
},
{
ReceivedSat: (*Amount)(big.NewInt(5)),
SentSat: (*Amount)(big.NewInt(6)),
SentToSelfSat: (*Amount)(big.NewInt(3)),
Time: 1521514812,
Txid: "00b2c06055e5e90e9c82bd4181fde310104391a7fa4f289b1704e5d90caa3840",
Txs: 1,
},
{
ReceivedSat: (*Amount)(big.NewInt(7)),
SentSat: (*Amount)(big.NewInt(8)),
SentToSelfSat: (*Amount)(big.NewInt(3)),
Time: 1521504812,
Txid: "00b2c06055e5e90e9c82bd4181fde310104391a7fa4f289b1704e5d90caa3840",
Txs: 1,
},
{
ReceivedSat: (*Amount)(big.NewInt(9)),
SentSat: (*Amount)(big.NewInt(10)),
SentToSelfSat: (*Amount)(big.NewInt(5)),
Time: 1521534812,
Txid: "0011223344556677889900112233445566778899001122334455667788990011",
Txs: 1,
},
{
ReceivedSat: (*Amount)(big.NewInt(11)),
SentSat: (*Amount)(big.NewInt(12)),
SentToSelfSat: (*Amount)(big.NewInt(6)),
Time: 1521534812,
Txid: "1122334455667788990011223344556677889900112233445566778899001100",
Txs: 1,
},
},
groupByTime: 3600,
want: []BalanceHistory{
{
ReceivedSat: (*Amount)(big.NewInt(11)),
SentSat: (*Amount)(big.NewInt(14)),
SentToSelfSat: (*Amount)(big.NewInt(5)),
Time: 1521504000,
Txs: 2,
},
{
ReceivedSat: (*Amount)(big.NewInt(5)),
SentSat: (*Amount)(big.NewInt(6)),
SentToSelfSat: (*Amount)(big.NewInt(3)),
Time: 1521514800,
Txs: 1,
},
{
ReceivedSat: (*Amount)(big.NewInt(20)),
SentSat: (*Amount)(big.NewInt(22)),
SentToSelfSat: (*Amount)(big.NewInt(11)),
Time: 1521532800,
Txs: 2,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.a.SortAndAggregate(tt.groupByTime); !reflect.DeepEqual(got, tt.want) {
t.Errorf("BalanceHistories.SortAndAggregate() = %+v, want %+v", got, tt.want)
}
})
}
}

221
api/typesv1.go 100644
View File

@ -0,0 +1,221 @@
package api
import (
"math/big"
"spacecruft.org/spacecruft/blockbook/bchain"
)
// ScriptSigV1 is used for legacy api v1
type ScriptSigV1 struct {
Hex string `json:"hex,omitempty"`
Asm string `json:"asm,omitempty"`
}
// VinV1 is used for legacy api v1
type VinV1 struct {
Txid string `json:"txid"`
Vout uint32 `json:"vout"`
Sequence int64 `json:"sequence,omitempty"`
N int `json:"n"`
ScriptSig ScriptSigV1 `json:"scriptSig"`
AddrDesc bchain.AddressDescriptor `json:"-"`
Addresses []string `json:"addresses"`
IsAddress bool `json:"-"`
Value string `json:"value"`
ValueSat big.Int `json:"-"`
}
// ScriptPubKeyV1 is used for legacy api v1
type ScriptPubKeyV1 struct {
Hex string `json:"hex,omitempty"`
Asm string `json:"asm,omitempty"`
AddrDesc bchain.AddressDescriptor `json:"-"`
Addresses []string `json:"addresses"`
IsAddress bool `json:"-"`
Type string `json:"type,omitempty"`
}
// VoutV1 is used for legacy api v1
type VoutV1 struct {
Value string `json:"value"`
ValueSat big.Int `json:"-"`
N int `json:"n"`
ScriptPubKey ScriptPubKeyV1 `json:"scriptPubKey"`
Spent bool `json:"spent"`
SpentTxID string `json:"spentTxId,omitempty"`
SpentIndex int `json:"spentIndex,omitempty"`
SpentHeight int `json:"spentHeight,omitempty"`
}
// TxV1 is used for legacy api v1
type TxV1 struct {
Txid string `json:"txid"`
Version int32 `json:"version,omitempty"`
Locktime uint32 `json:"locktime,omitempty"`
Vin []VinV1 `json:"vin"`
Vout []VoutV1 `json:"vout"`
Blockhash string `json:"blockhash,omitempty"`
Blockheight int `json:"blockheight"`
Confirmations uint32 `json:"confirmations"`
Time int64 `json:"time,omitempty"`
Blocktime int64 `json:"blocktime"`
ValueOut string `json:"valueOut"`
ValueOutSat big.Int `json:"-"`
Size int `json:"size,omitempty"`
ValueIn string `json:"valueIn"`
ValueInSat big.Int `json:"-"`
Fees string `json:"fees"`
FeesSat big.Int `json:"-"`
Hex string `json:"hex"`
}
// AddressV1 is used for legacy api v1
type AddressV1 struct {
Paging
AddrStr string `json:"addrStr"`
Balance string `json:"balance"`
TotalReceived string `json:"totalReceived"`
TotalSent string `json:"totalSent"`
UnconfirmedBalance string `json:"unconfirmedBalance"`
UnconfirmedTxApperances int `json:"unconfirmedTxApperances"`
TxApperances int `json:"txApperances"`
Transactions []*TxV1 `json:"txs,omitempty"`
Txids []string `json:"transactions,omitempty"`
}
// AddressUtxoV1 is used for legacy api v1
type AddressUtxoV1 struct {
Txid string `json:"txid"`
Vout uint32 `json:"vout"`
Amount string `json:"amount"`
AmountSat big.Int `json:"satoshis"`
Height int `json:"height,omitempty"`
Confirmations int `json:"confirmations"`
}
// BlockV1 contains information about block
type BlockV1 struct {
Paging
BlockInfo
TxCount int `json:"txCount"`
Transactions []*TxV1 `json:"txs,omitempty"`
}
// TxToV1 converts Tx to TxV1
func (w *Worker) TxToV1(tx *Tx) *TxV1 {
d := w.chainParser.AmountDecimals()
vinV1 := make([]VinV1, len(tx.Vin))
for i := range tx.Vin {
v := &tx.Vin[i]
vinV1[i] = VinV1{
AddrDesc: v.AddrDesc,
Addresses: v.Addresses,
N: v.N,
ScriptSig: ScriptSigV1{
Asm: v.Asm,
Hex: v.Hex,
},
IsAddress: v.IsAddress,
Sequence: v.Sequence,
Txid: v.Txid,
Value: v.ValueSat.DecimalString(d),
ValueSat: v.ValueSat.AsBigInt(),
Vout: v.Vout,
}
}
voutV1 := make([]VoutV1, len(tx.Vout))
for i := range tx.Vout {
v := &tx.Vout[i]
voutV1[i] = VoutV1{
N: v.N,
ScriptPubKey: ScriptPubKeyV1{
AddrDesc: v.AddrDesc,
Addresses: v.Addresses,
Asm: v.Asm,
Hex: v.Hex,
IsAddress: v.IsAddress,
Type: v.Type,
},
Spent: v.Spent,
SpentHeight: v.SpentHeight,
SpentIndex: v.SpentIndex,
SpentTxID: v.SpentTxID,
Value: v.ValueSat.DecimalString(d),
ValueSat: v.ValueSat.AsBigInt(),
}
}
return &TxV1{
Blockhash: tx.Blockhash,
Blockheight: tx.Blockheight,
Blocktime: tx.Blocktime,
Confirmations: tx.Confirmations,
Fees: tx.FeesSat.DecimalString(d),
FeesSat: tx.FeesSat.AsBigInt(),
Hex: tx.Hex,
Locktime: tx.Locktime,
Size: tx.Size,
Time: tx.Blocktime,
Txid: tx.Txid,
ValueIn: tx.ValueInSat.DecimalString(d),
ValueInSat: tx.ValueInSat.AsBigInt(),
ValueOut: tx.ValueOutSat.DecimalString(d),
ValueOutSat: tx.ValueOutSat.AsBigInt(),
Version: tx.Version,
Vin: vinV1,
Vout: voutV1,
}
}
func (w *Worker) transactionsToV1(txs []*Tx) []*TxV1 {
v1 := make([]*TxV1, len(txs))
for i := range txs {
v1[i] = w.TxToV1(txs[i])
}
return v1
}
// AddressToV1 converts Address to AddressV1
func (w *Worker) AddressToV1(a *Address) *AddressV1 {
d := w.chainParser.AmountDecimals()
return &AddressV1{
AddrStr: a.AddrStr,
Balance: a.BalanceSat.DecimalString(d),
Paging: a.Paging,
TotalReceived: a.TotalReceivedSat.DecimalString(d),
TotalSent: a.TotalSentSat.DecimalString(d),
Transactions: w.transactionsToV1(a.Transactions),
TxApperances: a.Txs,
Txids: a.Txids,
UnconfirmedBalance: a.UnconfirmedBalanceSat.DecimalString(d),
UnconfirmedTxApperances: a.UnconfirmedTxs,
}
}
// AddressUtxoToV1 converts []AddressUtxo to []AddressUtxoV1
func (w *Worker) AddressUtxoToV1(au Utxos) []AddressUtxoV1 {
d := w.chainParser.AmountDecimals()
v1 := make([]AddressUtxoV1, len(au))
for i := range au {
utxo := &au[i]
v1[i] = AddressUtxoV1{
AmountSat: utxo.AmountSat.AsBigInt(),
Amount: utxo.AmountSat.DecimalString(d),
Confirmations: utxo.Confirmations,
Height: utxo.Height,
Txid: utxo.Txid,
Vout: uint32(utxo.Vout),
}
}
return v1
}
// BlockToV1 converts Address to Address1
func (w *Worker) BlockToV1(b *Block) *BlockV1 {
return &BlockV1{
BlockInfo: b.BlockInfo,
Paging: b.Paging,
Transactions: w.transactionsToV1(b.Transactions),
TxCount: b.TxCount,
}
}

File diff suppressed because it is too large Load Diff

637
api/xpub.go 100644
View File

@ -0,0 +1,637 @@
package api
import (
"fmt"
"math/big"
"sort"
"sync"
"time"
"github.com/golang/glog"
"github.com/juju/errors"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/db"
)
const defaultAddressesGap = 20
const maxAddressesGap = 10000
const txInput = 1
const txOutput = 2
const xpubCacheSize = 512
const xpubCacheExpirationSeconds = 7200
var cachedXpubs = make(map[string]xpubData)
var cachedXpubsMux sync.Mutex
type xpubTxid struct {
txid string
height uint32
inputOutput byte
}
type xpubTxids []xpubTxid
func (a xpubTxids) Len() int { return len(a) }
func (a xpubTxids) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a xpubTxids) Less(i, j int) bool {
// if the heights are equal, make inputs less than outputs
hi := a[i].height
hj := a[j].height
if hi == hj {
return (a[i].inputOutput & txInput) >= (a[j].inputOutput & txInput)
}
return hi > hj
}
type xpubAddress struct {
addrDesc bchain.AddressDescriptor
balance *db.AddrBalance
txs uint32
maxHeight uint32
complete bool
txids xpubTxids
}
type xpubData struct {
gap int
accessed int64
basePath string
dataHeight uint32
dataHash string
txCountEstimate uint32
sentSat big.Int
balanceSat big.Int
addresses []xpubAddress
changeAddresses []xpubAddress
}
func (w *Worker) xpubGetAddressTxids(addrDesc bchain.AddressDescriptor, mempool bool, fromHeight, toHeight uint32, maxResults int) ([]xpubTxid, bool, error) {
var err error
complete := true
txs := make([]xpubTxid, 0, 4)
var callback db.GetTransactionsCallback
callback = func(txid string, height uint32, indexes []int32) error {
// take all txs in the last found block even if it exceeds maxResults
if len(txs) >= maxResults && txs[len(txs)-1].height != height {
complete = false
return &db.StopIteration{}
}
inputOutput := byte(0)
for _, index := range indexes {
if index < 0 {
inputOutput |= txInput
} else {
inputOutput |= txOutput
}
}
txs = append(txs, xpubTxid{txid, height, inputOutput})
return nil
}
if mempool {
uniqueTxs := make(map[string]int)
o, err := w.mempool.GetAddrDescTransactions(addrDesc)
if err != nil {
return nil, false, err
}
for _, m := range o {
if l, found := uniqueTxs[m.Txid]; !found {
l = len(txs)
callback(m.Txid, 0, []int32{m.Vout})
if len(txs) > l {
uniqueTxs[m.Txid] = l
}
} else {
if m.Vout < 0 {
txs[l].inputOutput |= txInput
} else {
txs[l].inputOutput |= txOutput
}
}
}
} else {
err = w.db.GetAddrDescTransactions(addrDesc, fromHeight, toHeight, callback)
if err != nil {
return nil, false, err
}
}
return txs, complete, nil
}
func (w *Worker) xpubCheckAndLoadTxids(ad *xpubAddress, filter *AddressFilter, maxHeight uint32, maxResults int) error {
// skip if not used
if ad.balance == nil {
return nil
}
// if completely loaded, check if there are not some new txs and load if necessary
if ad.complete {
if ad.balance.Txs != ad.txs {
newTxids, _, err := w.xpubGetAddressTxids(ad.addrDesc, false, ad.maxHeight+1, maxHeight, maxInt)
if err == nil {
ad.txids = append(newTxids, ad.txids...)
ad.maxHeight = maxHeight
ad.txs = uint32(len(ad.txids))
if ad.txs != ad.balance.Txs {
glog.Warning("xpubCheckAndLoadTxids inconsistency ", ad.addrDesc, ", ad.txs=", ad.txs, ", ad.balance.Txs=", ad.balance.Txs)
}
}
return err
}
return nil
}
// load all txids to get paging correctly
newTxids, complete, err := w.xpubGetAddressTxids(ad.addrDesc, false, 0, maxHeight, maxInt)
if err != nil {
return err
}
ad.txids = newTxids
ad.complete = complete
ad.maxHeight = maxHeight
if complete {
ad.txs = uint32(len(ad.txids))
if ad.txs != ad.balance.Txs {
glog.Warning("xpubCheckAndLoadTxids inconsistency ", ad.addrDesc, ", ad.txs=", ad.txs, ", ad.balance.Txs=", ad.balance.Txs)
}
}
return nil
}
func (w *Worker) xpubDerivedAddressBalance(data *xpubData, ad *xpubAddress) (bool, error) {
var err error
if ad.balance, err = w.db.GetAddrDescBalance(ad.addrDesc, db.AddressBalanceDetailUTXO); err != nil {
return false, err
}
if ad.balance != nil {
data.txCountEstimate += ad.balance.Txs
data.sentSat.Add(&data.sentSat, &ad.balance.SentSat)
data.balanceSat.Add(&data.balanceSat, &ad.balance.BalanceSat)
return true, nil
}
return false, nil
}
func (w *Worker) xpubScanAddresses(xpub string, data *xpubData, addresses []xpubAddress, gap int, change int, minDerivedIndex int, fork bool) (int, []xpubAddress, error) {
// rescan known addresses
lastUsed := 0
for i := range addresses {
ad := &addresses[i]
if fork {
// reset the cached data
ad.txs = 0
ad.maxHeight = 0
ad.complete = false
ad.txids = nil
}
used, err := w.xpubDerivedAddressBalance(data, ad)
if err != nil {
return 0, nil, err
}
if used {
lastUsed = i
}
}
// derive new addresses as necessary
missing := len(addresses) - lastUsed
for missing < gap {
from := len(addresses)
to := from + gap - missing
if to < minDerivedIndex {
to = minDerivedIndex
}
descriptors, err := w.chainParser.DeriveAddressDescriptorsFromTo(xpub, uint32(change), uint32(from), uint32(to))
if err != nil {
return 0, nil, err
}
for i, a := range descriptors {
ad := xpubAddress{addrDesc: a}
used, err := w.xpubDerivedAddressBalance(data, &ad)
if err != nil {
return 0, nil, err
}
if used {
lastUsed = i + from
}
addresses = append(addresses, ad)
}
missing = len(addresses) - lastUsed
}
return lastUsed, addresses, nil
}
func (w *Worker) tokenFromXpubAddress(data *xpubData, ad *xpubAddress, changeIndex int, index int, option AccountDetails) Token {
a, _, _ := w.chainParser.GetAddressesFromAddrDesc(ad.addrDesc)
var address string
if len(a) > 0 {
address = a[0]
}
var balance, totalReceived, totalSent *big.Int
var transfers int
if ad.balance != nil {
transfers = int(ad.balance.Txs)
if option >= AccountDetailsTokenBalances {
balance = &ad.balance.BalanceSat
totalSent = &ad.balance.SentSat
totalReceived = ad.balance.ReceivedSat()
}
}
return Token{
Type: XPUBAddressTokenType,
Name: address,
Decimals: w.chainParser.AmountDecimals(),
BalanceSat: (*Amount)(balance),
TotalReceivedSat: (*Amount)(totalReceived),
TotalSentSat: (*Amount)(totalSent),
Transfers: transfers,
Path: fmt.Sprintf("%s/%d/%d", data.basePath, changeIndex, index),
}
}
func evictXpubCacheItems() {
var oldestKey string
oldest := maxInt64
now := time.Now().Unix()
count := 0
for k, v := range cachedXpubs {
if v.accessed+xpubCacheExpirationSeconds < now {
delete(cachedXpubs, k)
count++
}
if v.accessed < oldest {
oldestKey = k
oldest = v.accessed
}
}
if oldestKey != "" && oldest+xpubCacheExpirationSeconds >= now {
delete(cachedXpubs, oldestKey)
count++
}
glog.Info("Evicted ", count, " items from xpub cache, oldest item accessed at ", time.Unix(oldest, 0), ", cache size ", len(cachedXpubs))
}
func (w *Worker) getXpubData(xpub string, page int, txsOnPage int, option AccountDetails, filter *AddressFilter, gap int) (*xpubData, uint32, error) {
if w.chainType != bchain.ChainBitcoinType {
return nil, 0, ErrUnsupportedXpub
}
var (
err error
bestheight uint32
besthash string
)
if gap <= 0 {
gap = defaultAddressesGap
} else if gap > maxAddressesGap {
// limit the maximum gap to protect against unreasonably big values that could cause high load of the server
gap = maxAddressesGap
}
// gap is increased one as there must be gap of empty addresses before the derivation is stopped
gap++
var processedHash string
cachedXpubsMux.Lock()
data, found := cachedXpubs[xpub]
cachedXpubsMux.Unlock()
// to load all data for xpub may take some time, do it in a loop to process a possible new block
for {
bestheight, besthash, err = w.db.GetBestBlock()
if err != nil {
return nil, 0, errors.Annotatef(err, "GetBestBlock")
}
if besthash == processedHash {
break
}
fork := false
if !found || data.gap != gap {
data = xpubData{gap: gap}
data.basePath, err = w.chainParser.DerivationBasePath(xpub)
if err != nil {
return nil, 0, err
}
} else {
hash, err := w.db.GetBlockHash(data.dataHeight)
if err != nil {
return nil, 0, err
}
if hash != data.dataHash {
// in case of for reset all cached data
fork = true
}
}
processedHash = besthash
if data.dataHeight < bestheight || fork {
data.dataHeight = bestheight
data.dataHash = besthash
data.balanceSat = *new(big.Int)
data.sentSat = *new(big.Int)
data.txCountEstimate = 0
var lastUsedIndex int
lastUsedIndex, data.addresses, err = w.xpubScanAddresses(xpub, &data, data.addresses, gap, 0, 0, fork)
if err != nil {
return nil, 0, err
}
_, data.changeAddresses, err = w.xpubScanAddresses(xpub, &data, data.changeAddresses, gap, 1, lastUsedIndex, fork)
if err != nil {
return nil, 0, err
}
}
if option >= AccountDetailsTxidHistory {
for _, da := range [][]xpubAddress{data.addresses, data.changeAddresses} {
for i := range da {
if err = w.xpubCheckAndLoadTxids(&da[i], filter, bestheight, (page+1)*txsOnPage); err != nil {
return nil, 0, err
}
}
}
}
}
data.accessed = time.Now().Unix()
cachedXpubsMux.Lock()
if len(cachedXpubs) >= xpubCacheSize {
evictXpubCacheItems()
}
cachedXpubs[xpub] = data
cachedXpubsMux.Unlock()
return &data, bestheight, nil
}
// GetXpubAddress computes address value and gets transactions for given address
func (w *Worker) GetXpubAddress(xpub string, page int, txsOnPage int, option AccountDetails, filter *AddressFilter, gap int) (*Address, error) {
start := time.Now()
page--
if page < 0 {
page = 0
}
type mempoolMap struct {
tx *Tx
inputOutput byte
}
var (
txc xpubTxids
txmMap map[string]*Tx
txCount int
txs []*Tx
txids []string
pg Paging
filtered bool
err error
uBalSat big.Int
unconfirmedTxs int
)
data, bestheight, err := w.getXpubData(xpub, page, txsOnPage, option, filter, gap)
if err != nil {
return nil, err
}
// setup filtering of txids
var txidFilter func(txid *xpubTxid, ad *xpubAddress) bool
if !(filter.FromHeight == 0 && filter.ToHeight == 0 && filter.Vout == AddressFilterVoutOff) {
toHeight := maxUint32
if filter.ToHeight != 0 {
toHeight = filter.ToHeight
}
txidFilter = func(txid *xpubTxid, ad *xpubAddress) bool {
if txid.height < filter.FromHeight || txid.height > toHeight {
return false
}
if filter.Vout != AddressFilterVoutOff {
if filter.Vout == AddressFilterVoutInputs && txid.inputOutput&txInput == 0 ||
filter.Vout == AddressFilterVoutOutputs && txid.inputOutput&txOutput == 0 {
return false
}
}
return true
}
filtered = true
}
// process mempool, only if ToHeight is not specified
if filter.ToHeight == 0 && !filter.OnlyConfirmed {
txmMap = make(map[string]*Tx)
mempoolEntries := make(bchain.MempoolTxidEntries, 0)
for _, da := range [][]xpubAddress{data.addresses, data.changeAddresses} {
for i := range da {
ad := &da[i]
newTxids, _, err := w.xpubGetAddressTxids(ad.addrDesc, true, 0, 0, maxInt)
if err != nil {
return nil, err
}
for _, txid := range newTxids {
// the same tx can have multiple addresses from the same xpub, get it from backend it only once
tx, foundTx := txmMap[txid.txid]
if !foundTx {
tx, err = w.GetTransaction(txid.txid, false, true)
// mempool transaction may fail
if err != nil || tx == nil {
glog.Warning("GetTransaction in mempool: ", err)
continue
}
txmMap[txid.txid] = tx
}
// skip already confirmed txs, mempool may be out of sync
if tx.Confirmations == 0 {
if !foundTx {
unconfirmedTxs++
}
uBalSat.Add(&uBalSat, tx.getAddrVoutValue(ad.addrDesc))
uBalSat.Sub(&uBalSat, tx.getAddrVinValue(ad.addrDesc))
// mempool txs are returned only on the first page, uniquely and filtered
if page == 0 && !foundTx && (txidFilter == nil || txidFilter(&txid, ad)) {
mempoolEntries = append(mempoolEntries, bchain.MempoolTxidEntry{Txid: txid.txid, Time: uint32(tx.Blocktime)})
}
}
}
}
}
// sort the entries by time descending
sort.Sort(mempoolEntries)
for _, entry := range mempoolEntries {
if option == AccountDetailsTxidHistory {
txids = append(txids, entry.Txid)
} else if option >= AccountDetailsTxHistoryLight {
txs = append(txs, txmMap[entry.Txid])
}
}
}
if option >= AccountDetailsTxidHistory {
txcMap := make(map[string]bool)
txc = make(xpubTxids, 0, 32)
for _, da := range [][]xpubAddress{data.addresses, data.changeAddresses} {
for i := range da {
ad := &da[i]
for _, txid := range ad.txids {
added, foundTx := txcMap[txid.txid]
// count txs regardless of filter but only once
if !foundTx {
txCount++
}
// add tx only once
if !added {
add := txidFilter == nil || txidFilter(&txid, ad)
txcMap[txid.txid] = add
if add {
txc = append(txc, txid)
}
}
}
}
}
sort.Stable(txc)
txCount = len(txcMap)
totalResults := txCount
if filtered {
totalResults = -1
}
var from, to int
pg, from, to, page = computePaging(len(txc), page, txsOnPage)
if len(txc) >= txsOnPage {
if totalResults < 0 {
pg.TotalPages = -1
} else {
pg, _, _, _ = computePaging(totalResults, page, txsOnPage)
}
}
// get confirmed transactions
for i := from; i < to; i++ {
xpubTxid := &txc[i]
if option == AccountDetailsTxidHistory {
txids = append(txids, xpubTxid.txid)
} else {
tx, err := w.txFromTxid(xpubTxid.txid, bestheight, option, nil)
if err != nil {
return nil, err
}
txs = append(txs, tx)
}
}
} else {
txCount = int(data.txCountEstimate)
}
usedTokens := 0
var tokens []Token
var xpubAddresses map[string]struct{}
if option > AccountDetailsBasic {
tokens = make([]Token, 0, 4)
xpubAddresses = make(map[string]struct{})
}
for ci, da := range [][]xpubAddress{data.addresses, data.changeAddresses} {
for i := range da {
ad := &da[i]
if ad.balance != nil {
usedTokens++
}
if option > AccountDetailsBasic {
token := w.tokenFromXpubAddress(data, ad, ci, i, option)
if filter.TokensToReturn == TokensToReturnDerived ||
filter.TokensToReturn == TokensToReturnUsed && ad.balance != nil ||
filter.TokensToReturn == TokensToReturnNonzeroBalance && ad.balance != nil && !IsZeroBigInt(&ad.balance.BalanceSat) {
tokens = append(tokens, token)
}
xpubAddresses[token.Name] = struct{}{}
}
}
}
var totalReceived big.Int
totalReceived.Add(&data.balanceSat, &data.sentSat)
addr := Address{
Paging: pg,
AddrStr: xpub,
BalanceSat: (*Amount)(&data.balanceSat),
TotalReceivedSat: (*Amount)(&totalReceived),
TotalSentSat: (*Amount)(&data.sentSat),
Txs: txCount,
UnconfirmedBalanceSat: (*Amount)(&uBalSat),
UnconfirmedTxs: unconfirmedTxs,
Transactions: txs,
Txids: txids,
UsedTokens: usedTokens,
Tokens: tokens,
XPubAddresses: xpubAddresses,
}
glog.Info("GetXpubAddress ", xpub[:16], ", ", len(data.addresses)+len(data.changeAddresses), " derived addresses, ", txCount, " confirmed txs, finished in ", time.Since(start))
return &addr, nil
}
// GetXpubUtxo returns unspent outputs for given xpub
func (w *Worker) GetXpubUtxo(xpub string, onlyConfirmed bool, gap int) (Utxos, error) {
start := time.Now()
data, _, err := w.getXpubData(xpub, 0, 1, AccountDetailsBasic, &AddressFilter{
Vout: AddressFilterVoutOff,
OnlyConfirmed: onlyConfirmed,
}, gap)
if err != nil {
return nil, err
}
r := make(Utxos, 0, 8)
for ci, da := range [][]xpubAddress{data.addresses, data.changeAddresses} {
for i := range da {
ad := &da[i]
onlyMempool := false
if ad.balance == nil {
if onlyConfirmed {
continue
}
onlyMempool = true
}
utxos, err := w.getAddrDescUtxo(ad.addrDesc, ad.balance, onlyConfirmed, onlyMempool)
if err != nil {
return nil, err
}
if len(utxos) > 0 {
t := w.tokenFromXpubAddress(data, ad, ci, i, AccountDetailsTokens)
for j := range utxos {
a := &utxos[j]
a.Address = t.Name
a.Path = t.Path
}
r = append(r, utxos...)
}
}
}
sort.Stable(r)
glog.Info("GetXpubUtxo ", xpub[:16], ", ", len(r), " utxos, finished in ", time.Since(start))
return r, nil
}
// GetXpubBalanceHistory returns history of balance for given xpub
func (w *Worker) GetXpubBalanceHistory(xpub string, fromTimestamp, toTimestamp int64, currencies []string, gap int, groupBy uint32) (BalanceHistories, error) {
bhs := make(BalanceHistories, 0)
start := time.Now()
fromUnix, fromHeight, toUnix, toHeight := w.balanceHistoryHeightsFromTo(fromTimestamp, toTimestamp)
if fromHeight >= toHeight {
return bhs, nil
}
data, _, err := w.getXpubData(xpub, 0, 1, AccountDetailsTxidHistory, &AddressFilter{
Vout: AddressFilterVoutOff,
OnlyConfirmed: true,
FromHeight: fromHeight,
ToHeight: toHeight,
}, gap)
if err != nil {
return nil, err
}
selfAddrDesc := make(map[string]struct{})
for _, da := range [][]xpubAddress{data.addresses, data.changeAddresses} {
for i := range da {
selfAddrDesc[string(da[i].addrDesc)] = struct{}{}
}
}
for _, da := range [][]xpubAddress{data.addresses, data.changeAddresses} {
for i := range da {
ad := &da[i]
txids := ad.txids
for txi := len(txids) - 1; txi >= 0; txi-- {
bh, err := w.balanceHistoryForTxid(ad.addrDesc, txids[txi].txid, fromUnix, toUnix, selfAddrDesc)
if err != nil {
return nil, err
}
if bh != nil {
bhs = append(bhs, *bh)
}
}
}
}
bha := bhs.SortAndAggregate(groupBy)
err = w.setFiatRateToBalanceHistories(bha, currencies)
if err != nil {
return nil, err
}
glog.Info("GetUtxoBalanceHistory ", xpub[:16], ", blocks ", fromHeight, "-", toHeight, ", count ", len(bha), ", finished in ", time.Since(start))
return bha, nil
}

View File

@ -0,0 +1,60 @@
package bchain
import (
"errors"
"math/big"
)
// BaseChain is base type for bchain.BlockChain
type BaseChain struct {
Parser BlockChainParser
Testnet bool
Network string
}
// TODO more bchain.BlockChain methods
// GetChainParser returns BlockChainParser
func (b *BaseChain) GetChainParser() BlockChainParser {
return b.Parser
}
// IsTestnet returns true if the blockchain is testnet
func (b *BaseChain) IsTestnet() bool {
return b.Testnet
}
// GetNetworkName returns network name
func (b *BaseChain) GetNetworkName() string {
return b.Network
}
// GetMempoolEntry is not supported by default
func (b *BaseChain) GetMempoolEntry(txid string) (*MempoolEntry, error) {
return nil, errors.New("GetMempoolEntry: not supported")
}
// EthereumTypeGetBalance is not supported
func (b *BaseChain) EthereumTypeGetBalance(addrDesc AddressDescriptor) (*big.Int, error) {
return nil, errors.New("Not supported")
}
// EthereumTypeGetNonce is not supported
func (b *BaseChain) EthereumTypeGetNonce(addrDesc AddressDescriptor) (uint64, error) {
return 0, errors.New("Not supported")
}
// EthereumTypeEstimateGas is not supported
func (b *BaseChain) EthereumTypeEstimateGas(params map[string]interface{}) (uint64, error) {
return 0, errors.New("Not supported")
}
// EthereumTypeGetErc20ContractInfo is not supported
func (b *BaseChain) EthereumTypeGetErc20ContractInfo(contractDesc AddressDescriptor) (*Erc20Contract, error) {
return nil, errors.New("Not supported")
}
// EthereumTypeGetErc20ContractBalance is not supported
func (b *BaseChain) EthereumTypeGetErc20ContractBalance(addrDesc, contractDesc AddressDescriptor) (*big.Int, error) {
return nil, errors.New("Not supported")
}

View File

@ -0,0 +1,136 @@
package bchain
import (
"sort"
"sync"
"time"
)
type addrIndex struct {
addrDesc string
n int32
}
type txEntry struct {
addrIndexes []addrIndex
time uint32
}
type txidio struct {
txid string
io []addrIndex
}
// BaseMempool is mempool base handle
type BaseMempool struct {
chain BlockChain
mux sync.Mutex
txEntries map[string]txEntry
addrDescToTx map[string][]Outpoint
OnNewTxAddr OnNewTxAddrFunc
OnNewTx OnNewTxFunc
}
// GetTransactions returns slice of mempool transactions for given address
func (m *BaseMempool) GetTransactions(address string) ([]Outpoint, error) {
parser := m.chain.GetChainParser()
addrDesc, err := parser.GetAddrDescFromAddress(address)
if err != nil {
return nil, err
}
return m.GetAddrDescTransactions(addrDesc)
}
// GetAddrDescTransactions returns slice of mempool transactions for given address descriptor, in reverse order
func (m *BaseMempool) GetAddrDescTransactions(addrDesc AddressDescriptor) ([]Outpoint, error) {
m.mux.Lock()
defer m.mux.Unlock()
outpoints := m.addrDescToTx[string(addrDesc)]
rv := make([]Outpoint, len(outpoints))
for i, j := len(outpoints)-1, 0; i >= 0; i-- {
rv[j] = outpoints[i]
j++
}
return rv, nil
}
func (a MempoolTxidEntries) Len() int { return len(a) }
func (a MempoolTxidEntries) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a MempoolTxidEntries) Less(i, j int) bool {
// if the Time is equal, sort by txid to make the order defined
hi := a[i].Time
hj := a[j].Time
if hi == hj {
return a[i].Txid > a[j].Txid
}
// order in reverse
return hi > hj
}
// removeEntryFromMempool removes entry from mempool structs. The caller is responsible for locking!
func (m *BaseMempool) removeEntryFromMempool(txid string, entry txEntry) {
delete(m.txEntries, txid)
for _, si := range entry.addrIndexes {
outpoints, found := m.addrDescToTx[si.addrDesc]
if found {
newOutpoints := make([]Outpoint, 0, len(outpoints)-1)
for _, o := range outpoints {
if o.Txid != txid {
newOutpoints = append(newOutpoints, o)
}
}
if len(newOutpoints) > 0 {
m.addrDescToTx[si.addrDesc] = newOutpoints
} else {
delete(m.addrDescToTx, si.addrDesc)
}
}
}
}
// GetAllEntries returns all mempool entries sorted by fist seen time in descending order
func (m *BaseMempool) GetAllEntries() MempoolTxidEntries {
i := 0
m.mux.Lock()
entries := make(MempoolTxidEntries, len(m.txEntries))
for txid, entry := range m.txEntries {
entries[i] = MempoolTxidEntry{
Txid: txid,
Time: entry.time,
}
i++
}
m.mux.Unlock()
sort.Sort(entries)
return entries
}
// GetTransactionTime returns first seen time of a transaction
func (m *BaseMempool) GetTransactionTime(txid string) uint32 {
m.mux.Lock()
e, found := m.txEntries[txid]
m.mux.Unlock()
if !found {
return 0
}
return e.time
}
func (m *BaseMempool) txToMempoolTx(tx *Tx) *MempoolTx {
mtx := MempoolTx{
Hex: tx.Hex,
Blocktime: time.Now().Unix(),
LockTime: tx.LockTime,
Txid: tx.Txid,
Version: tx.Version,
Vout: tx.Vout,
CoinSpecificData: tx.CoinSpecificData,
}
mtx.Vin = make([]MempoolVin, len(tx.Vin))
for i, vin := range tx.Vin {
mtx.Vin[i] = MempoolVin{
Vin: vin,
}
}
return &mtx
}

View File

@ -7,7 +7,9 @@ import (
"strings"
"github.com/gogo/protobuf/proto"
"github.com/golang/glog"
"github.com/juju/errors"
"spacecruft.org/spacecruft/blockbook/common"
)
// BaseParser implements data parsing/handling functionality base for all other parsers
@ -26,18 +28,32 @@ func (p *BaseParser) ParseTx(b []byte) (*Tx, error) {
return nil, errors.New("ParseTx: not implemented")
}
// GetAddrDescForUnknownInput returns nil AddressDescriptor
func (p *BaseParser) GetAddrDescForUnknownInput(tx *Tx, input int) AddressDescriptor {
var iTxid string
if len(tx.Vin) > input {
iTxid = tx.Vin[input].Txid
}
glog.Warningf("tx %v, input tx %v not found in txAddresses", tx.Txid, iTxid)
return nil
}
const zeros = "0000000000000000000000000000000000000000"
// AmountToBigInt converts amount in json.Number (string) to big.Int
// AmountToBigInt converts amount in common.JSONNumber (string) to big.Int
// it uses string operations to avoid problems with rounding
func (p *BaseParser) AmountToBigInt(n json.Number) (big.Int, error) {
func (p *BaseParser) AmountToBigInt(n common.JSONNumber) (big.Int, error) {
var r big.Int
s := string(n)
i := strings.IndexByte(s, '.')
d := p.AmountDecimalPoint
if d > len(zeros) {
d = len(zeros)
}
if i == -1 {
s = s + zeros[:p.AmountDecimalPoint]
s = s + zeros[:d]
} else {
z := p.AmountDecimalPoint - len(s) + i + 1
z := d - len(s) + i + 1
if z > 0 {
s = s[:i] + s[i+1:] + zeros[:z]
} else {
@ -50,18 +66,24 @@ func (p *BaseParser) AmountToBigInt(n json.Number) (big.Int, error) {
return r, nil
}
// AmountToDecimalString converts amount in big.Int to string with decimal point in the correct place
func (p *BaseParser) AmountToDecimalString(a *big.Int) string {
// AmountToDecimalString converts amount in big.Int to string with decimal point in the place defined by the parameter d
func AmountToDecimalString(a *big.Int, d int) string {
if a == nil {
return ""
}
n := a.String()
var s string
if n[0] == '-' {
n = n[1:]
s = "-"
}
if len(n) <= p.AmountDecimalPoint {
n = zeros[:p.AmountDecimalPoint-len(n)+1] + n
if d > len(zeros) {
d = len(zeros)
}
i := len(n) - p.AmountDecimalPoint
if len(n) <= d {
n = zeros[:d-len(n)+1] + n
}
i := len(n) - d
ad := strings.TrimRight(n[i:], "0")
if len(ad) > 0 {
n = n[:i] + "." + ad
@ -71,6 +93,16 @@ func (p *BaseParser) AmountToDecimalString(a *big.Int) string {
return s + n
}
// AmountToDecimalString converts amount in big.Int to string with decimal point in the correct place
func (p *BaseParser) AmountToDecimalString(a *big.Int) string {
return AmountToDecimalString(a, p.AmountDecimalPoint)
}
// AmountDecimals returns number of decimal places in amounts
func (p *BaseParser) AmountDecimals() int {
return p.AmountDecimalPoint
}
// ParseTxFromJson parses JSON message containing transaction and returns Tx struct
func (p *BaseParser) ParseTxFromJson(msg json.RawMessage) (*Tx, error) {
var tx Tx
@ -125,9 +157,14 @@ func (p *BaseParser) UnpackBlockHash(buf []byte) (string, error) {
return hex.EncodeToString(buf), nil
}
// IsUTXOChain returns true if the block chain is UTXO type, otherwise false
func (p *BaseParser) IsUTXOChain() bool {
return true
// GetChainType is type of the blockchain, default is ChainBitcoinType
func (p *BaseParser) GetChainType() ChainType {
return ChainBitcoinType
}
// MinimumCoinbaseConfirmations returns minimum number of confirmations a coinbase transaction must have before it can be spent
func (p *BaseParser) MinimumCoinbaseConfirmations() int {
return 0
}
// PackTx packs transaction to byte array using protobuf
@ -139,8 +176,9 @@ func (p *BaseParser) PackTx(tx *Tx, height uint32, blockTime int64) ([]byte, err
if err != nil {
return nil, errors.Annotatef(err, "Vin %v Hex %v", i, vi.ScriptSig.Hex)
}
// coinbase txs do not have Vin.txid
itxid, err := p.PackTxid(vi.Txid)
if err != nil {
if err != nil && err != ErrTxidMissing {
return nil, errors.Annotatef(err, "Vin %v Txid %v", i, vi.Txid)
}
pti[i] = &ProtoTransaction_VinType{
@ -171,6 +209,7 @@ func (p *BaseParser) PackTx(tx *Tx, height uint32, blockTime int64) ([]byte, err
Locktime: tx.LockTime,
Vin: pti,
Vout: pto,
Version: tx.Version,
}
if pt.Hex, err = hex.DecodeString(tx.Hex); err != nil {
return nil, errors.Annotatef(err, "Hex %v", tx.Hex)
@ -230,6 +269,33 @@ func (p *BaseParser) UnpackTx(buf []byte) (*Tx, uint32, error) {
Txid: txid,
Vin: vin,
Vout: vout,
Version: pt.Version,
}
return &tx, pt.Height, nil
}
// IsAddrDescIndexable returns true if AddressDescriptor should be added to index
// by default all AddressDescriptors are indexable
func (p *BaseParser) IsAddrDescIndexable(addrDesc AddressDescriptor) bool {
return true
}
// DerivationBasePath is unsupported
func (p *BaseParser) DerivationBasePath(xpub string) (string, error) {
return "", errors.New("Not supported")
}
// DeriveAddressDescriptors is unsupported
func (p *BaseParser) DeriveAddressDescriptors(xpub string, change uint32, indexes []uint32) ([]AddressDescriptor, error) {
return nil, errors.New("Not supported")
}
// DeriveAddressDescriptorsFromTo is unsupported
func (p *BaseParser) DeriveAddressDescriptorsFromTo(xpub string, change uint32, fromIndex uint32, toIndex uint32) ([]AddressDescriptor, error) {
return nil, errors.New("Not supported")
}
// EthereumTypeGetErc20FromTx is unsupported
func (p *BaseParser) EthereumTypeGetErc20FromTx(tx *Tx) ([]Erc20Transfer, error) {
return nil, errors.New("Not supported")
}

View File

@ -1,9 +1,12 @@
// +build unittest
package bchain
import (
"encoding/json"
"math/big"
"testing"
"spacecruft.org/spacecruft/blockbook/common"
)
func NewBaseParser(adp int) *BaseParser {
@ -27,7 +30,8 @@ var amounts = []struct {
{big.NewInt(-8), "-0.00000008", 8, "!"},
{big.NewInt(-89012345678), "-890.12345678", 8, "!"},
{big.NewInt(-12345), "-0.00012345", 8, "!"},
{big.NewInt(12345678), "0.123456789012", 8, "0.12345678"}, // test of truncation of too many decimal places
{big.NewInt(12345678), "0.123456789012", 8, "0.12345678"}, // test of truncation of too many decimal places
{big.NewInt(12345678), "0.0000000000000000000000000000000012345678", 1234, "!"}, // test of too big number decimal places
}
func TestBaseParser_AmountToDecimalString(t *testing.T) {
@ -43,7 +47,7 @@ func TestBaseParser_AmountToDecimalString(t *testing.T) {
func TestBaseParser_AmountToBigInt(t *testing.T) {
for _, tt := range amounts {
t.Run(tt.s, func(t *testing.T) {
got, err := NewBaseParser(tt.adp).AmountToBigInt(json.Number(tt.s))
got, err := NewBaseParser(tt.adp).AmountToBigInt(common.JSONNumber(tt.s))
if err != nil {
t.Errorf("BaseParser.AmountToBigInt() error = %v", err)
return

View File

@ -1,33 +1,42 @@
package bch
import (
"blockbook/bchain"
"blockbook/bchain/coins/btc"
"fmt"
"github.com/jakm/bchutil"
"github.com/jakm/btcutil"
"github.com/jakm/btcutil/chaincfg"
"github.com/jakm/btcutil/txscript"
"github.com/martinboehm/bchutil"
"github.com/martinboehm/btcutil"
"github.com/martinboehm/btcutil/chaincfg"
"github.com/martinboehm/btcutil/txscript"
"github.com/schancel/cashaddr-converter/address"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
)
// AddressFormat type is used to specify different formats of address
type AddressFormat = uint8
const (
// Legacy AddressFormat is the same as Bitcoin
Legacy AddressFormat = iota
// CashAddr AddressFormat is new Bitcoin Cash standard
CashAddr
)
const (
// MainNetPrefix is CashAddr prefix for mainnet
MainNetPrefix = "bitcoincash:"
// TestNetPrefix is CashAddr prefix for testnet
TestNetPrefix = "bchtest:"
// RegTestPrefix is CashAddr prefix for regtest
RegTestPrefix = "bchreg:"
)
var (
// MainNetParams are parser parameters for mainnet
MainNetParams chaincfg.Params
// TestNetParams are parser parameters for testnet
TestNetParams chaincfg.Params
// RegtestParams are parser parameters for regtest
RegtestParams chaincfg.Params
)
@ -62,13 +71,7 @@ func NewBCashParser(params *chaincfg.Params, c *btc.Configuration) (*BCashParser
return nil, fmt.Errorf("Unknown address format: %s", c.AddressFormat)
}
p := &BCashParser{
BitcoinParser: &btc.BitcoinParser{
BaseParser: &bchain.BaseParser{
BlockAddressesToKeep: c.BlockAddressesToKeep,
AmountDecimalPoint: 8,
},
Params: params,
},
BitcoinParser: btc.NewBitcoinParser(params, c),
AddressFormat: format,
}
p.OutputScriptToAddressesFunc = p.outputScriptToAddresses
@ -118,17 +121,16 @@ func (p *BCashParser) addressToOutputScript(address string) ([]byte, error) {
return nil, err
}
return script, nil
} else {
da, err := btcutil.DecodeAddress(address, p.Params)
if err != nil {
return nil, err
}
script, err := txscript.PayToAddrScript(da)
if err != nil {
return nil, err
}
return script, nil
}
da, err := btcutil.DecodeAddress(address, p.Params)
if err != nil {
return nil, err
}
script, err := txscript.PayToAddrScript(da)
if err != nil {
return nil, err
}
return script, nil
}
func isCashAddr(addr string) bool {
@ -149,7 +151,7 @@ func isCashAddr(addr string) bool {
func (p *BCashParser) outputScriptToAddresses(script []byte) ([]string, bool, error) {
// convert possible P2PK script to P2PK, which bchutil can process
var err error
script, err = txscript.ConvertP2PKtoP2PKH(script)
script, err = txscript.ConvertP2PKtoP2PKH(p.Params.Base58CksumHasher, script)
if err != nil {
return nil, false, err
}
@ -158,14 +160,13 @@ func (p *BCashParser) outputScriptToAddresses(script []byte) ([]string, bool, er
// do not return unknown script type error as error
if err.Error() == "unknown script type" {
// try OP_RETURN script
or := btc.TryParseOPReturn(script)
or := p.TryParseOPReturn(script)
if or != "" {
return []string{or}, false, nil
}
return []string{}, false, nil
} else {
return nil, false, err
}
return nil, false, err
}
// EncodeAddress returns CashAddr address
addr := a.EncodeAddress()

View File

@ -3,15 +3,15 @@
package bch
import (
"blockbook/bchain"
"blockbook/bchain/coins/btc"
"encoding/hex"
"math/big"
"os"
"reflect"
"testing"
"github.com/jakm/btcutil/chaincfg"
"github.com/martinboehm/btcutil/chaincfg"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
)
func TestMain(m *testing.M) {

View File

@ -1,15 +1,15 @@
package bch
import (
"blockbook/bchain"
"blockbook/bchain/coins/btc"
"encoding/hex"
"encoding/json"
"math/big"
"github.com/golang/glog"
"github.com/jakm/bchutil"
"github.com/juju/errors"
"github.com/martinboehm/bchutil"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
)
// BCashRPC is an interface to JSON-RPC bitcoind service.
@ -27,16 +27,18 @@ func NewBCashRPC(config json.RawMessage, pushHandler func(bchain.NotificationTyp
s := &BCashRPC{
b.(*btc.BitcoinRPC),
}
s.ChainConfig.SupportsEstimateSmartFee = false
return s, nil
}
// Initialize initializes BCashRPC instance.
func (b *BCashRPC) Initialize() error {
chainName, err := b.GetChainInfoAndInitializeMempool(b)
ci, err := b.GetChainInfo()
if err != nil {
return err
}
chainName := ci.Chain
params := GetChainParams(chainName)
@ -157,14 +159,27 @@ func (b *BCashRPC) GetBlockFull(hash string) (*bchain.Block, error) {
return nil, errors.New("Not implemented")
}
// EstimateSmartFee returns fee estimation.
func (b *BCashRPC) EstimateSmartFee(blocks int, conservative bool) (big.Int, error) {
glog.V(1).Info("rpc: estimatesmartfee ", blocks)
func isErrBlockNotFound(err *bchain.RPCError) bool {
return err.Message == "Block not found" ||
err.Message == "Block height out of range"
}
// EstimateFee returns fee estimation
func (b *BCashRPC) EstimateFee(blocks int) (big.Int, error) {
// from version BitcoinABC version 0.19.1 EstimateFee does not support parameter Blocks
if b.ChainConfig.CoinShortcut == "BCHSV" {
return b.BitcoinRPC.EstimateFee(blocks)
}
glog.V(1).Info("rpc: estimatefee ", blocks)
res := btc.ResEstimateFee{}
req := struct {
Method string `json:"method"`
}{
Method: "estimatefee",
}
res := btc.ResEstimateSmartFee{}
req := cmdEstimateSmartFee{Method: "estimatesmartfee"}
req.Params.Blocks = blocks
// conservative param is omitted
err := b.Call(&req, &res)
var r big.Int
@ -174,14 +189,15 @@ func (b *BCashRPC) EstimateSmartFee(blocks int, conservative bool) (big.Int, err
if res.Error != nil {
return r, res.Error
}
r, err = b.Parser.AmountToBigInt(res.Result.Feerate)
r, err = b.Parser.AmountToBigInt(res.Result)
if err != nil {
return r, err
}
return r, nil
}
func isErrBlockNotFound(err *bchain.RPCError) bool {
return err.Message == "Block not found" ||
err.Message == "Block height out of range"
// EstimateSmartFee returns fee estimation
func (b *BCashRPC) EstimateSmartFee(blocks int, conservative bool) (big.Int, error) {
// EstimateSmartFee is not supported by bcash
return b.EstimateFee(blocks)
}

View File

@ -0,0 +1,63 @@
package bellcoin
import (
"github.com/martinboehm/btcd/wire"
"github.com/martinboehm/btcutil/chaincfg"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
)
// magic numbers
const (
MainnetMagic wire.BitcoinNet = 0xbebacefa
TestnetMagic wire.BitcoinNet = 0x0709110b
)
// chain parameters
var (
MainNetParams chaincfg.Params
TestNetParams chaincfg.Params
)
func init() {
MainNetParams = chaincfg.MainNetParams
MainNetParams.Net = MainnetMagic
MainNetParams.PubKeyHashAddrID = []byte{25}
MainNetParams.ScriptHashAddrID = []byte{85}
MainNetParams.Bech32HRPSegwit = "bm"
TestNetParams = chaincfg.TestNet3Params
TestNetParams.Net = TestnetMagic
TestNetParams.PubKeyHashAddrID = []byte{111}
TestNetParams.ScriptHashAddrID = []byte{196}
TestNetParams.Bech32HRPSegwit = "bt"
}
// BellcoinParser handle
type BellcoinParser struct {
*btc.BitcoinParser
}
// NewBellcoinParser returns new BellcoinParser instance
func NewBellcoinParser(params *chaincfg.Params, c *btc.Configuration) *BellcoinParser {
return &BellcoinParser{BitcoinParser: btc.NewBitcoinParser(params, c)}
}
// GetChainParams contains network parameters for the main Bellcoin network,
// and the test Bellcoin network
func GetChainParams(chain string) *chaincfg.Params {
if !chaincfg.IsRegistered(&MainNetParams) {
err := chaincfg.Register(&MainNetParams)
if err == nil {
err = chaincfg.Register(&TestNetParams)
}
if err != nil {
panic(err)
}
}
switch chain {
case "test":
return &TestNetParams
default:
return &MainNetParams
}
}

View File

@ -0,0 +1,327 @@
// +build unittest
package bellcoin
import (
"encoding/hex"
"math/big"
"os"
"reflect"
"testing"
"github.com/martinboehm/btcutil/chaincfg"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
)
func TestMain(m *testing.M) {
c := m.Run()
chaincfg.ResetParams()
os.Exit(c)
}
func Test_GetAddrDescFromAddress_Mainnet(t *testing.T) {
type args struct {
address string
}
tests := []struct {
name string
args args
want string
wantErr bool
}{
{
name: "P2PKH1",
args: args{address: "BSQmMGWpjwP5Lu8feSypSaPFiTTrC3EdEx"},
want: "76a914f0e2aff6730b53b9986a5db8ca17c59426134a0988ac",
wantErr: false,
},
{
name: "P2PKH2",
args: args{address: "BLpETLYW1vs8csYSoYeCvPwsiCSTJUjx6T"},
want: "76a914b3822026c7f758b43a0882d7f2cbfa954702e45688ac",
wantErr: false,
},
{
name: "P2SH1",
args: args{address: "bNn9Y9YfgNUHXopXJEesS9M8noJzzrWTmP"},
want: "a9146e3c881d51d62a668362a5bba28be438f9c548e287",
wantErr: false,
},
{
name: "P2SH2",
args: args{address: "bXCT73hNPSwCeVbpvo3wJJU3erRjawUGSu"},
want: "a914ca962f788569443b33ec673208ccdcfaac6873b487",
wantErr: false,
},
{
name: "witness_v0_keyhash",
args: args{address: "bm1q3msdh3npg5ufvwm2sxltxcet6hll9tjzkzxzqt"},
want: "00148ee0dbc6614538963b6a81beb3632bd5fff2ae42",
wantErr: false,
},
{
name: "witness_v0_scripthashx",
args: args{address: "bm1q0uvgqxwqt0u4esawwcff7652jreqye30kmp4sj5dtydee50pze0sjx6wn5"},
want: "00207f188019c05bf95cc3ae76129f6a8a90f202662fb6c3584a8d591b9cd1e1165f",
wantErr: false,
},
}
parser := NewBellcoinParser(GetChainParams("main"), &btc.Configuration{})
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := parser.GetAddrDescFromAddress(tt.args.address)
if (err != nil) != tt.wantErr {
t.Errorf("GetAddrDescFromAddress() error = %v, wantErr %v", err, tt.wantErr)
return
}
h := hex.EncodeToString(got)
if !reflect.DeepEqual(h, tt.want) {
t.Errorf("GetAddrDescFromAddress() = %v, want %v", h, tt.want)
}
})
}
}
func Test_GetAddressesFromAddrDesc(t *testing.T) {
type args struct {
script string
}
tests := []struct {
name string
args args
want []string
want2 bool
wantErr bool
}{
{
name: "P2PKH",
args: args{script: "76a914f0e2aff6730b53b9986a5db8ca17c59426134a0988ac"},
want: []string{"BSQmMGWpjwP5Lu8feSypSaPFiTTrC3EdEx"},
want2: true,
wantErr: false,
},
{
name: "P2SH",
args: args{script: "a9146e3c881d51d62a668362a5bba28be438f9c548e287"},
want: []string{"bNn9Y9YfgNUHXopXJEesS9M8noJzzrWTmP"},
want2: true,
wantErr: false,
},
{
name: "P2WPKH",
args: args{script: "00148ee0dbc6614538963b6a81beb3632bd5fff2ae42"},
want: []string{"bm1q3msdh3npg5ufvwm2sxltxcet6hll9tjzkzxzqt"},
want2: true,
wantErr: false,
},
{
name: "P2WSH",
args: args{script: "00207f188019c05bf95cc3ae76129f6a8a90f202662fb6c3584a8d591b9cd1e1165f"},
want: []string{"bm1q0uvgqxwqt0u4esawwcff7652jreqye30kmp4sj5dtydee50pze0sjx6wn5"},
want2: true,
wantErr: false,
},
{
name: "OP_RETURN ascii",
args: args{script: "6a0461686f6a"},
want: []string{"OP_RETURN (ahoj)"},
want2: false,
wantErr: false,
},
{
name: "OP_RETURN hex",
args: args{script: "6a072020f1686f6a20"},
want: []string{"OP_RETURN 2020f1686f6a20"},
want2: false,
wantErr: false,
},
}
parser := NewBellcoinParser(GetChainParams("main"), &btc.Configuration{})
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
b, _ := hex.DecodeString(tt.args.script)
got, got2, err := parser.GetAddressesFromAddrDesc(b)
if (err != nil) != tt.wantErr {
t.Errorf("outputScriptToAddresses() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("GetAddressesFromAddrDesc() = %v, want %v", got, tt.want)
}
if !reflect.DeepEqual(got2, tt.want2) {
t.Errorf("GetAddressesFromAddrDesc() = %v, want %v", got2, tt.want2)
}
})
}
}
var (
testTx1 bchain.Tx
testTxPacked1 = "0003238c8bc5aac144020000000001026f3631c4db09d48354d12cd2e780b2aa0f92198fca7d044a6b073e9a06b4135d0200000017160014dfc67c36c26ea8036042c56b8ce5d5027d1fb81ffeffffffac13cfbe663797aced3134006adec863e5b3f9480ab65a4dda112de875f8dd6e010000006a473044022066e4d5f99fec12f076d7d912d1e530e4b950d23cc3de2b8127f98109f510c9d502207a74f2e7a753262fdd29756193493d47ead7880871cbc6c55cc6d20e229c0c1201210294fc5b3928335caddc7f4c536e0db85c736cbc7c164de0319976da90b65d288ffeffffff05f4c16020000000001976a91416e20046e69c396aed18c6663ddcbbf1dde9082f88ac0aa47113000000001976a91426fa5e6c4e579058d3f4d1dd83d99e291d4dc0c588acbcb11a00000000001976a9145a53bd436b5c19a42b1518ef18443e8403bcaeed88ac0c04693b0000000017a9140576053f982117afcff024013ed61270189c4fbc87358bad07000000001976a914e00ec357dfee124ac68b3c10dcf82e2ed0993ccc88ac02483045022100ed4b0e9b140850951ffbc10349e3ac56a18b80c600a77b95cfac274e10228eb602207c876b9b134e63b8a01ba28720dc4c1c2c67bb7e547fb71d31440cd365c674260121027aa4243e82c73c9c15d544a0b61a828eed1128464952bfbdc9235d4380f2767d008a230300"
)
func init() {
testTx1 = bchain.Tx{
Hex: "020000000001026f3631c4db09d48354d12cd2e780b2aa0f92198fca7d044a6b073e9a06b4135d0200000017160014dfc67c36c26ea8036042c56b8ce5d5027d1fb81ffeffffffac13cfbe663797aced3134006adec863e5b3f9480ab65a4dda112de875f8dd6e010000006a473044022066e4d5f99fec12f076d7d912d1e530e4b950d23cc3de2b8127f98109f510c9d502207a74f2e7a753262fdd29756193493d47ead7880871cbc6c55cc6d20e229c0c1201210294fc5b3928335caddc7f4c536e0db85c736cbc7c164de0319976da90b65d288ffeffffff05f4c16020000000001976a91416e20046e69c396aed18c6663ddcbbf1dde9082f88ac0aa47113000000001976a91426fa5e6c4e579058d3f4d1dd83d99e291d4dc0c588acbcb11a00000000001976a9145a53bd436b5c19a42b1518ef18443e8403bcaeed88ac0c04693b0000000017a9140576053f982117afcff024013ed61270189c4fbc87358bad07000000001976a914e00ec357dfee124ac68b3c10dcf82e2ed0993ccc88ac02483045022100ed4b0e9b140850951ffbc10349e3ac56a18b80c600a77b95cfac274e10228eb602207c876b9b134e63b8a01ba28720dc4c1c2c67bb7e547fb71d31440cd365c674260121027aa4243e82c73c9c15d544a0b61a828eed1128464952bfbdc9235d4380f2767d008a230300",
Blocktime: 1549095010,
Txid: "e7ef52bbf3d9cb1ca5dfdb02eabf108e2b0b7757b009d1cfb24a06e4126e67f2",
LockTime: 205706,
Version: 2,
Vin: []bchain.Vin{
{
ScriptSig: bchain.ScriptSig{
Hex: "160014dfc67c36c26ea8036042c56b8ce5d5027d1fb81f",
},
Txid: "5d13b4069a3e076b4a047dca8f19920faab280e7d22cd15483d409dbc431366f",
Vout: 2,
Sequence: 4294967294,
},
{
ScriptSig: bchain.ScriptSig{
Hex: "473044022066e4d5f99fec12f076d7d912d1e530e4b950d23cc3de2b8127f98109f510c9d502207a74f2e7a753262fdd29756193493d47ead7880871cbc6c55cc6d20e229c0c1201210294fc5b3928335caddc7f4c536e0db85c736cbc7c164de0319976da90b65d288f",
},
Txid: "6eddf875e82d11da4d5ab60a48f9b3e563c8de6a003431edac973766becf13ac",
Vout: 1,
Sequence: 4294967294,
},
},
Vout: []bchain.Vout{
{
ValueSat: *big.NewInt(543212020),
N: 0,
ScriptPubKey: bchain.ScriptPubKey{
Hex: "76a91416e20046e69c396aed18c6663ddcbbf1dde9082f88ac",
Addresses: []string{
"B6Y5DmPr1LPUP95YDEnX6FbJHERVipkRcg",
},
},
},
{
ValueSat: *big.NewInt(326214666),
N: 1,
ScriptPubKey: bchain.ScriptPubKey{
Hex: "76a91426fa5e6c4e579058d3f4d1dd83d99e291d4dc0c588ac",
Addresses: []string{
"B81BDwJTasemPgnHBxQ67wX2WV48b2XmEc",
},
},
},
{
ValueSat: *big.NewInt(1749436),
N: 2,
ScriptPubKey: bchain.ScriptPubKey{
Hex: "76a9145a53bd436b5c19a42b1518ef18443e8403bcaeed88ac",
Addresses: []string{
"BCggkHk8ZTVd9T4yseNmURj6w56XY47EUG",
},
},
},
{
ValueSat: *big.NewInt(996738060),
N: 3,
ScriptPubKey: bchain.ScriptPubKey{
Hex: "a9140576053f982117afcff024013ed61270189c4fbc87",
Addresses: []string{
"bDE9TQ3W5zF4ej8hLwYVdK5w8n5zhd9Qxj",
},
},
},
{
ValueSat: *big.NewInt(128813877),
N: 4,
ScriptPubKey: bchain.ScriptPubKey{
Hex: "76a914e00ec357dfee124ac68b3c10dcf82e2ed0993ccc88ac",
Addresses: []string{
"BQsnfSXNonZZMs1G6ndrVLbUJAK5tFG2bN",
},
},
},
},
}
}
func Test_PackTx(t *testing.T) {
type args struct {
tx bchain.Tx
height uint32
blockTime int64
parser *BellcoinParser
}
tests := []struct {
name string
args args
want string
wantErr bool
}{
{
name: "Bellcoin-1",
args: args{
tx: testTx1,
height: 205708,
blockTime: 1549095010,
parser: NewBellcoinParser(GetChainParams("main"), &btc.Configuration{}),
},
want: testTxPacked1,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := tt.args.parser.PackTx(&tt.args.tx, tt.args.height, tt.args.blockTime)
if (err != nil) != tt.wantErr {
t.Errorf("packTx() error = %v, wantErr %v", err, tt.wantErr)
return
}
h := hex.EncodeToString(got)
if !reflect.DeepEqual(h, tt.want) {
t.Errorf("packTx() = %v, want %v", h, tt.want)
}
})
}
}
func Test_UnpackTx(t *testing.T) {
type args struct {
packedTx string
parser *BellcoinParser
}
tests := []struct {
name string
args args
want *bchain.Tx
want1 uint32
wantErr bool
}{
{
name: "Bellcoin-1",
args: args{
packedTx: testTxPacked1,
parser: NewBellcoinParser(GetChainParams("main"), &btc.Configuration{}),
},
want: &testTx1,
want1: 205708,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
b, _ := hex.DecodeString(tt.args.packedTx)
got, got1, err := tt.args.parser.UnpackTx(b)
if (err != nil) != tt.wantErr {
t.Errorf("unpackTx() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("unpackTx() got = %v, want %v", got, tt.want)
}
if got1 != tt.want1 {
t.Errorf("unpackTx() got1 = %v, want %v", got1, tt.want1)
}
})
}
}

View File

@ -0,0 +1,58 @@
package bellcoin
import (
"encoding/json"
"github.com/golang/glog"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
)
// BellcoinRPC is an interface to JSON-RPC bitcoind service.
type BellcoinRPC struct {
*btc.BitcoinRPC
}
// NewBellcoinRPC returns new BellcoinRPC instance.
func NewBellcoinRPC(config json.RawMessage, pushHandler func(bchain.NotificationType)) (bchain.BlockChain, error) {
b, err := btc.NewBitcoinRPC(config, pushHandler)
if err != nil {
return nil, err
}
s := &BellcoinRPC{
b.(*btc.BitcoinRPC),
}
s.RPCMarshaler = btc.JSONMarshalerV2{}
s.ChainConfig.SupportsEstimateFee = false
return s, nil
}
// Initialize initializes BellcoinRPC instance.
func (b *BellcoinRPC) Initialize() error {
ci, err := b.GetChainInfo()
if err != nil {
return err
}
chainName := ci.Chain
glog.Info("Chain name ", chainName)
params := GetChainParams(chainName)
// always create parser
b.Parser = NewBellcoinParser(params, b.ChainConfig)
// parameters for getInfo request
if params.Net == MainnetMagic {
b.Testnet = false
b.Network = "livenet"
} else {
b.Testnet = true
b.Network = "testnet"
}
glog.Info("rpc: block chain ", params.Name)
return nil
}

View File

@ -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)
}

View File

@ -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)
}
})
}
}

View File

@ -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)
}

View File

@ -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
}
}

View File

@ -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)
}
})
}
}

View File

@ -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
}

View File

@ -1,24 +1,6 @@
package coins
import (
"blockbook/bchain"
"blockbook/bchain/coins/bch"
"blockbook/bchain/coins/btc"
"blockbook/bchain/coins/btg"
"blockbook/bchain/coins/dash"
"blockbook/bchain/coins/digibyte"
"blockbook/bchain/coins/dogecoin"
"blockbook/bchain/coins/eth"
"blockbook/bchain/coins/fujicoin"
"blockbook/bchain/coins/gamecredits"
"blockbook/bchain/coins/grs"
"blockbook/bchain/coins/litecoin"
"blockbook/bchain/coins/monacoin"
"blockbook/bchain/coins/myriad"
"blockbook/bchain/coins/namecoin"
"blockbook/bchain/coins/vertcoin"
"blockbook/bchain/coins/zec"
"blockbook/common"
"context"
"encoding/json"
"fmt"
@ -28,26 +10,76 @@ import (
"time"
"github.com/juju/errors"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/bchain/coins/bch"
"spacecruft.org/spacecruft/blockbook/bchain/coins/bellcoin"
"spacecruft.org/spacecruft/blockbook/bchain/coins/bitcore"
"spacecruft.org/spacecruft/blockbook/bchain/coins/bitzeny"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btg"
"spacecruft.org/spacecruft/blockbook/bchain/coins/cpuchain"
"spacecruft.org/spacecruft/blockbook/bchain/coins/dash"
"spacecruft.org/spacecruft/blockbook/bchain/coins/dcr"
"spacecruft.org/spacecruft/blockbook/bchain/coins/deeponion"
"spacecruft.org/spacecruft/blockbook/bchain/coins/digibyte"
"spacecruft.org/spacecruft/blockbook/bchain/coins/divi"
"spacecruft.org/spacecruft/blockbook/bchain/coins/dogecoin"
"spacecruft.org/spacecruft/blockbook/bchain/coins/eth"
"spacecruft.org/spacecruft/blockbook/bchain/coins/firo"
"spacecruft.org/spacecruft/blockbook/bchain/coins/flo"
"spacecruft.org/spacecruft/blockbook/bchain/coins/fujicoin"
"spacecruft.org/spacecruft/blockbook/bchain/coins/gamecredits"
"spacecruft.org/spacecruft/blockbook/bchain/coins/grs"
"spacecruft.org/spacecruft/blockbook/bchain/coins/koto"
"spacecruft.org/spacecruft/blockbook/bchain/coins/liquid"
"spacecruft.org/spacecruft/blockbook/bchain/coins/litecoin"
"spacecruft.org/spacecruft/blockbook/bchain/coins/monacoin"
"spacecruft.org/spacecruft/blockbook/bchain/coins/monetaryunit"
"spacecruft.org/spacecruft/blockbook/bchain/coins/myriad"
"spacecruft.org/spacecruft/blockbook/bchain/coins/namecoin"
"spacecruft.org/spacecruft/blockbook/bchain/coins/nuls"
"spacecruft.org/spacecruft/blockbook/bchain/coins/omotenashicoin"
"spacecruft.org/spacecruft/blockbook/bchain/coins/pivx"
"spacecruft.org/spacecruft/blockbook/bchain/coins/polis"
"spacecruft.org/spacecruft/blockbook/bchain/coins/qtum"
"spacecruft.org/spacecruft/blockbook/bchain/coins/ravencoin"
"spacecruft.org/spacecruft/blockbook/bchain/coins/ritocoin"
"spacecruft.org/spacecruft/blockbook/bchain/coins/snowgem"
"spacecruft.org/spacecruft/blockbook/bchain/coins/trezarcoin"
"spacecruft.org/spacecruft/blockbook/bchain/coins/unobtanium"
"spacecruft.org/spacecruft/blockbook/bchain/coins/vertcoin"
"spacecruft.org/spacecruft/blockbook/bchain/coins/viacoin"
"spacecruft.org/spacecruft/blockbook/bchain/coins/vipstarcoin"
"spacecruft.org/spacecruft/blockbook/bchain/coins/zec"
"spacecruft.org/spacecruft/blockbook/common"
)
type blockChainFactory func(config json.RawMessage, pushHandler func(bchain.NotificationType)) (bchain.BlockChain, error)
// BlockChainFactories is a map of constructors of coin RPC interfaces
var BlockChainFactories = make(map[string]blockChainFactory)
func init() {
BlockChainFactories["Bitcoin"] = btc.NewBitcoinRPC
BlockChainFactories["Testnet"] = btc.NewBitcoinRPC
BlockChainFactories["Signet"] = btc.NewBitcoinRPC
BlockChainFactories["Zcash"] = zec.NewZCashRPC
BlockChainFactories["Zcash Testnet"] = zec.NewZCashRPC
BlockChainFactories["Ethereum"] = eth.NewEthereumRPC
BlockChainFactories["Ethereum Classic"] = eth.NewEthereumRPC
BlockChainFactories["Ethereum Testnet Ropsten"] = eth.NewEthereumRPC
BlockChainFactories["Ethereum Testnet Goerli"] = eth.NewEthereumRPC
BlockChainFactories["Bcash"] = bch.NewBCashRPC
BlockChainFactories["Bcash Testnet"] = bch.NewBCashRPC
BlockChainFactories["Bgold"] = btg.NewBGoldRPC
BlockChainFactories["Bgold Testnet"] = btg.NewBGoldRPC
BlockChainFactories["Dash"] = dash.NewDashRPC
BlockChainFactories["Dash Testnet"] = dash.NewDashRPC
BlockChainFactories["Decred"] = dcr.NewDecredRPC
BlockChainFactories["Decred Testnet"] = dcr.NewDecredRPC
BlockChainFactories["GameCredits"] = gamecredits.NewGameCreditsRPC
BlockChainFactories["Koto"] = koto.NewKotoRPC
BlockChainFactories["Koto Testnet"] = koto.NewKotoRPC
BlockChainFactories["Litecoin"] = litecoin.NewLitecoinRPC
BlockChainFactories["Litecoin Testnet"] = litecoin.NewLitecoinRPC
BlockChainFactories["Dogecoin"] = dogecoin.NewDogecoinRPC
@ -56,11 +88,38 @@ func init() {
BlockChainFactories["Namecoin"] = namecoin.NewNamecoinRPC
BlockChainFactories["Monacoin"] = monacoin.NewMonacoinRPC
BlockChainFactories["Monacoin Testnet"] = monacoin.NewMonacoinRPC
BlockChainFactories["MonetaryUnit"] = monetaryunit.NewMonetaryUnitRPC
BlockChainFactories["DigiByte"] = digibyte.NewDigiByteRPC
BlockChainFactories["DigiByte Testnet"] = digibyte.NewDigiByteRPC
BlockChainFactories["Myriad"] = myriad.NewMyriadRPC
BlockChainFactories["Liquid"] = liquid.NewLiquidRPC
BlockChainFactories["Groestlcoin"] = grs.NewGroestlcoinRPC
BlockChainFactories["Groestlcoin Testnet"] = grs.NewGroestlcoinRPC
BlockChainFactories["PIVX"] = pivx.NewPivXRPC
BlockChainFactories["PIVX Testnet"] = pivx.NewPivXRPC
BlockChainFactories["Polis"] = polis.NewPolisRPC
BlockChainFactories["Firo"] = firo.NewFiroRPC
BlockChainFactories["Fujicoin"] = fujicoin.NewFujicoinRPC
BlockChainFactories["Flo"] = flo.NewFloRPC
BlockChainFactories["Bellcoin"] = bellcoin.NewBellcoinRPC
BlockChainFactories["Qtum"] = qtum.NewQtumRPC
BlockChainFactories["Viacoin"] = viacoin.NewViacoinRPC
BlockChainFactories["Qtum Testnet"] = qtum.NewQtumRPC
BlockChainFactories["NULS"] = nuls.NewNulsRPC
BlockChainFactories["VIPSTARCOIN"] = vipstarcoin.NewVIPSTARCOINRPC
BlockChainFactories["ZelCash"] = zec.NewZCashRPC
BlockChainFactories["Ravencoin"] = ravencoin.NewRavencoinRPC
BlockChainFactories["Ritocoin"] = ritocoin.NewRitocoinRPC
BlockChainFactories["Divi"] = divi.NewDiviRPC
BlockChainFactories["CPUchain"] = cpuchain.NewCPUchainRPC
BlockChainFactories["Unobtanium"] = unobtanium.NewUnobtaniumRPC
BlockChainFactories["DeepOnion"] = deeponion.NewDeepOnionRPC
BlockChainFactories["SnowGem"] = snowgem.NewSnowGemRPC
BlockChainFactories["Bitcore"] = bitcore.NewBitcoreRPC
BlockChainFactories["Omotenashicoin"] = omotenashicoin.NewOmotenashiCoinRPC
BlockChainFactories["Omotenashicoin Testnet"] = omotenashicoin.NewOmotenashiCoinRPC
BlockChainFactories["BitZeny"] = bitzeny.NewBitZenyRPC
BlockChainFactories["Trezarcoin"] = trezarcoin.NewTrezarcoinRPC
}
// GetCoinNameFromConfig gets coin name and coin shortcut from config file
@ -81,30 +140,34 @@ func GetCoinNameFromConfig(configfile string) (string, string, string, error) {
return cn.CoinName, cn.CoinShortcut, cn.CoinLabel, nil
}
// NewBlockChain creates bchain.BlockChain of type defined by parameter coin
func NewBlockChain(coin string, configfile string, pushHandler func(bchain.NotificationType), metrics *common.Metrics) (bchain.BlockChain, error) {
// NewBlockChain creates bchain.BlockChain and bchain.Mempool for the coin passed by the parameter coin
func NewBlockChain(coin string, configfile string, pushHandler func(bchain.NotificationType), metrics *common.Metrics) (bchain.BlockChain, bchain.Mempool, error) {
data, err := ioutil.ReadFile(configfile)
if err != nil {
return nil, errors.Annotatef(err, "Error reading file %v", configfile)
return nil, nil, errors.Annotatef(err, "Error reading file %v", configfile)
}
var config json.RawMessage
err = json.Unmarshal(data, &config)
if err != nil {
return nil, errors.Annotatef(err, "Error parsing file %v", configfile)
return nil, nil, errors.Annotatef(err, "Error parsing file %v", configfile)
}
bcf, ok := BlockChainFactories[coin]
if !ok {
return nil, errors.New(fmt.Sprint("Unsupported coin '", coin, "'. Must be one of ", reflect.ValueOf(BlockChainFactories).MapKeys()))
return nil, nil, errors.New(fmt.Sprint("Unsupported coin '", coin, "'. Must be one of ", reflect.ValueOf(BlockChainFactories).MapKeys()))
}
bc, err := bcf(config, pushHandler)
if err != nil {
return nil, err
return nil, nil, err
}
err = bc.Initialize()
if err != nil {
return nil, err
return nil, nil, err
}
return &blockChainWithMetrics{b: bc, m: metrics}, nil
mempool, err := bc.CreateMempool(bc)
if err != nil {
return nil, nil, err
}
return &blockChainWithMetrics{b: bc, m: metrics}, &mempoolWithMetrics{mempool: mempool, m: metrics}, nil
}
type blockChainWithMetrics struct {
@ -115,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
}
@ -124,6 +187,14 @@ func (c *blockChainWithMetrics) Initialize() error {
return c.b.Initialize()
}
func (c *blockChainWithMetrics) CreateMempool(chain bchain.BlockChain) (bchain.Mempool, error) {
return c.b.CreateMempool(chain)
}
func (c *blockChainWithMetrics) InitializeMempool(addrDescForOutpoint bchain.AddrDescForOutpointFunc, onNewTxAddr bchain.OnNewTxAddrFunc, onNewTx bchain.OnNewTxFunc) error {
return c.b.InitializeMempool(addrDescForOutpoint, onNewTxAddr, onNewTx)
}
func (c *blockChainWithMetrics) Shutdown(ctx context.Context) error {
return c.b.Shutdown(ctx)
}
@ -179,9 +250,9 @@ func (c *blockChainWithMetrics) GetBlockInfo(hash string) (v *bchain.BlockInfo,
return c.b.GetBlockInfo(hash)
}
func (c *blockChainWithMetrics) GetMempool() (v []string, err error) {
defer func(s time.Time) { c.observeRPCLatency("GetMempool", s, err) }(time.Now())
return c.b.GetMempool()
func (c *blockChainWithMetrics) GetMempoolTransactions() (v []string, err error) {
defer func(s time.Time) { c.observeRPCLatency("GetMempoolTransactions", s, err) }(time.Now())
return c.b.GetMempoolTransactions()
}
func (c *blockChainWithMetrics) GetTransaction(txid string) (v *bchain.Tx, err error) {
@ -189,9 +260,9 @@ func (c *blockChainWithMetrics) GetTransaction(txid string) (v *bchain.Tx, err e
return c.b.GetTransaction(txid)
}
func (c *blockChainWithMetrics) GetTransactionSpecific(txid string) (v json.RawMessage, err error) {
func (c *blockChainWithMetrics) GetTransactionSpecific(tx *bchain.Tx) (v json.RawMessage, err error) {
defer func(s time.Time) { c.observeRPCLatency("GetTransactionSpecific", s, err) }(time.Now())
return c.b.GetTransactionSpecific(txid)
return c.b.GetTransactionSpecific(tx)
}
func (c *blockChainWithMetrics) GetTransactionForMempool(txid string) (v *bchain.Tx, err error) {
@ -214,25 +285,6 @@ func (c *blockChainWithMetrics) SendRawTransaction(tx string) (v string, err err
return c.b.SendRawTransaction(tx)
}
func (c *blockChainWithMetrics) ResyncMempool(onNewTxAddr bchain.OnNewTxAddrFunc) (count int, err error) {
defer func(s time.Time) { c.observeRPCLatency("ResyncMempool", s, err) }(time.Now())
count, err = c.b.ResyncMempool(onNewTxAddr)
if err == nil {
c.m.MempoolSize.Set(float64(count))
}
return count, err
}
func (c *blockChainWithMetrics) GetMempoolTransactions(address string) (v []string, err error) {
defer func(s time.Time) { c.observeRPCLatency("GetMempoolTransactions", s, err) }(time.Now())
return c.b.GetMempoolTransactions(address)
}
func (c *blockChainWithMetrics) GetMempoolTransactionsForAddrDesc(addrDesc bchain.AddressDescriptor) (v []string, err error) {
defer func(s time.Time) { c.observeRPCLatency("GetMempoolTransactionsForAddrDesc", s, err) }(time.Now())
return c.b.GetMempoolTransactionsForAddrDesc(addrDesc)
}
func (c *blockChainWithMetrics) GetMempoolEntry(txid string) (v *bchain.MempoolEntry, err error) {
defer func(s time.Time) { c.observeRPCLatency("GetMempoolEntry", s, err) }(time.Now())
return c.b.GetMempoolEntry(txid)
@ -241,3 +293,69 @@ func (c *blockChainWithMetrics) GetMempoolEntry(txid string) (v *bchain.MempoolE
func (c *blockChainWithMetrics) GetChainParser() bchain.BlockChainParser {
return c.b.GetChainParser()
}
func (c *blockChainWithMetrics) EthereumTypeGetBalance(addrDesc bchain.AddressDescriptor) (v *big.Int, err error) {
defer func(s time.Time) { c.observeRPCLatency("EthereumTypeGetBalance", s, err) }(time.Now())
return c.b.EthereumTypeGetBalance(addrDesc)
}
func (c *blockChainWithMetrics) EthereumTypeGetNonce(addrDesc bchain.AddressDescriptor) (v uint64, err error) {
defer func(s time.Time) { c.observeRPCLatency("EthereumTypeGetNonce", s, err) }(time.Now())
return c.b.EthereumTypeGetNonce(addrDesc)
}
func (c *blockChainWithMetrics) EthereumTypeEstimateGas(params map[string]interface{}) (v uint64, err error) {
defer func(s time.Time) { c.observeRPCLatency("EthereumTypeEstimateGas", s, err) }(time.Now())
return c.b.EthereumTypeEstimateGas(params)
}
func (c *blockChainWithMetrics) EthereumTypeGetErc20ContractInfo(contractDesc bchain.AddressDescriptor) (v *bchain.Erc20Contract, err error) {
defer func(s time.Time) { c.observeRPCLatency("EthereumTypeGetErc20ContractInfo", s, err) }(time.Now())
return c.b.EthereumTypeGetErc20ContractInfo(contractDesc)
}
func (c *blockChainWithMetrics) EthereumTypeGetErc20ContractBalance(addrDesc, contractDesc bchain.AddressDescriptor) (v *big.Int, err error) {
defer func(s time.Time) { c.observeRPCLatency("EthereumTypeGetErc20ContractInfo", s, err) }(time.Now())
return c.b.EthereumTypeGetErc20ContractBalance(addrDesc, contractDesc)
}
type mempoolWithMetrics struct {
mempool bchain.Mempool
m *common.Metrics
}
func (c *mempoolWithMetrics) observeRPCLatency(method string, start time.Time, err error) {
var e string
if err != nil {
e = "failure"
}
c.m.RPCLatency.With(common.Labels{"method": method, "error": e}).Observe(float64(time.Since(start)) / 1e6) // in milliseconds
}
func (c *mempoolWithMetrics) Resync() (count int, err error) {
defer func(s time.Time) { c.observeRPCLatency("ResyncMempool", s, err) }(time.Now())
count, err = c.mempool.Resync()
if err == nil {
c.m.MempoolSize.Set(float64(count))
}
return count, err
}
func (c *mempoolWithMetrics) GetTransactions(address string) (v []bchain.Outpoint, err error) {
defer func(s time.Time) { c.observeRPCLatency("GetMempoolTransactions", s, err) }(time.Now())
return c.mempool.GetTransactions(address)
}
func (c *mempoolWithMetrics) GetAddrDescTransactions(addrDesc bchain.AddressDescriptor) (v []bchain.Outpoint, err error) {
defer func(s time.Time) { c.observeRPCLatency("GetMempoolTransactionsForAddrDesc", s, err) }(time.Now())
return c.mempool.GetAddrDescTransactions(addrDesc)
}
func (c *mempoolWithMetrics) GetAllEntries() (v bchain.MempoolTxidEntries) {
defer func(s time.Time) { c.observeRPCLatency("GetAllEntries", s, nil) }(time.Now())
return c.mempool.GetAllEntries()
}
func (c *mempoolWithMetrics) GetTransactionTime(txid string) uint32 {
return c.mempool.GetTransactionTime(txid)
}

View File

@ -1,28 +1,52 @@
package btc
import (
"blockbook/bchain"
"bytes"
"encoding/binary"
"encoding/hex"
"math/big"
"strconv"
vlq "github.com/bsm/go-vlq"
"github.com/btcsuite/btcd/blockchain"
"github.com/btcsuite/btcd/wire"
"github.com/jakm/btcutil"
"github.com/jakm/btcutil/chaincfg"
"github.com/jakm/btcutil/txscript"
"github.com/juju/errors"
"github.com/martinboehm/btcd/blockchain"
"github.com/martinboehm/btcd/wire"
"github.com/martinboehm/btcutil"
"github.com/martinboehm/btcutil/chaincfg"
"github.com/martinboehm/btcutil/hdkeychain"
"github.com/martinboehm/btcutil/txscript"
"spacecruft.org/spacecruft/blockbook/bchain"
)
// temp params for signet(wait btcd commit)
// magic numbers
const (
SignetMagic wire.BitcoinNet = 0x6a70c7f0
)
// chain parameters
var (
SigNetParams chaincfg.Params
)
func init() {
SigNetParams = chaincfg.TestNet3Params
SigNetParams.Net = SignetMagic
}
// OutputScriptToAddressesFunc converts ScriptPubKey to bitcoin addresses
type OutputScriptToAddressesFunc func(script []byte) ([]string, bool, error)
// BitcoinParser handle
type BitcoinParser struct {
*bchain.BaseParser
Params *chaincfg.Params
OutputScriptToAddressesFunc OutputScriptToAddressesFunc
Params *chaincfg.Params
OutputScriptToAddressesFunc OutputScriptToAddressesFunc
XPubMagic uint32
XPubMagicSegwitP2sh uint32
XPubMagicSegwitNative uint32
Slip44 uint32
minimumCoinbaseConfirmations int
}
// NewBitcoinParser returns new BitcoinParser instance
@ -32,7 +56,12 @@ func NewBitcoinParser(params *chaincfg.Params, c *Configuration) *BitcoinParser
BlockAddressesToKeep: c.BlockAddressesToKeep,
AmountDecimalPoint: 8,
},
Params: params,
Params: params,
XPubMagic: c.XPubMagic,
XPubMagicSegwitP2sh: c.XPubMagicSegwitP2sh,
XPubMagicSegwitNative: c.XPubMagicSegwitNative,
Slip44: c.Slip44,
minimumCoinbaseConfirmations: c.MinimumCoinbaseConfirmations,
}
p.OutputScriptToAddressesFunc = p.outputScriptToAddresses
return p
@ -50,6 +79,8 @@ func GetChainParams(chain string) *chaincfg.Params {
return &chaincfg.TestNet3Params
case "regtest":
return &chaincfg.RegressionNetParams
case "signet":
return &SigNetParams
}
return &chaincfg.MainNetParams
}
@ -62,7 +93,7 @@ func (p *BitcoinParser) GetAddrDescFromVout(output *bchain.Vout) (bchain.Address
}
// convert possible P2PK script to P2PKH
// so that all transactions by given public key are indexed together
return txscript.ConvertP2PKtoP2PKH(ad)
return txscript.ConvertP2PKtoP2PKH(p.Params.Base58CksumHasher, ad)
}
// GetAddrDescFromAddress returns internal address representation (descriptor) of given address
@ -80,6 +111,15 @@ func (p *BitcoinParser) GetScriptFromAddrDesc(addrDesc bchain.AddressDescriptor)
return addrDesc, nil
}
// IsAddrDescIndexable returns true if AddressDescriptor should be added to index
// empty or OP_RETURN scripts are not indexed
func (p *BitcoinParser) IsAddrDescIndexable(addrDesc bchain.AddressDescriptor) bool {
if len(addrDesc) == 0 || addrDesc[0] == txscript.OP_RETURN {
return false
}
return true
}
// addressToOutputScript converts bitcoin address to ScriptPubKey
func (p *BitcoinParser) addressToOutputScript(address string) ([]byte, error) {
da, err := btcutil.DecodeAddress(address, p.Params)
@ -94,7 +134,7 @@ func (p *BitcoinParser) addressToOutputScript(address string) ([]byte, error) {
}
// TryParseOPReturn tries to process OP_RETURN script and return its string representation
func TryParseOPReturn(script []byte) string {
func (p *BitcoinParser) TryParseOPReturn(script []byte) string {
if len(script) > 1 && script[0] == txscript.OP_RETURN {
// trying 2 variants of OP_RETURN data
// 1) OP_RETURN OP_PUSHDATA1 <datalen> <data>
@ -113,6 +153,13 @@ func TryParseOPReturn(script []byte) string {
data = script[2:]
}
if l == len(data) {
var ed string
ed = p.tryParseOmni(data)
if ed != "" {
return ed
}
isASCII := true
for _, c := range data {
if c < 32 || c > 127 {
@ -120,7 +167,6 @@ func TryParseOPReturn(script []byte) string {
break
}
}
var ed string
if isASCII {
ed = "(" + string(data) + ")"
} else {
@ -132,6 +178,39 @@ func TryParseOPReturn(script []byte) string {
return ""
}
var omniCurrencyMap = map[uint32]string{
1: "Omni",
2: "Test Omni",
31: "TetherUS",
}
// tryParseOmni tries to extract Omni simple send transaction from script
func (p *BitcoinParser) tryParseOmni(data []byte) string {
// currently only simple send transaction version 0 is supported, see
// https://github.com/OmniLayer/spec#transfer-coins-simple-send
if len(data) != 20 || data[0] != 'o' {
return ""
}
// omni (4) <tx_version> (2) <tx_type> (2)
omniHeader := []byte{'o', 'm', 'n', 'i', 0, 0, 0, 0}
if bytes.Compare(data[0:8], omniHeader) != 0 {
return ""
}
currencyID := binary.BigEndian.Uint32(data[8:12])
currency, ok := omniCurrencyMap[currencyID]
if !ok {
return ""
}
amount := new(big.Int)
amount.SetBytes(data[12:])
amountStr := p.AmountToDecimalString(amount)
ed := "OMNI Simple Send: " + amountStr + " " + currency + " (#" + strconv.Itoa(int(currencyID)) + ")"
return ed
}
// outputScriptToAddresses converts ScriptPubKey to addresses with a flag that the addresses are searchable
func (p *BitcoinParser) outputScriptToAddresses(script []byte) ([]string, bool, error) {
sc, addresses, _, err := txscript.ExtractPkScriptAddrs(script, p.Params)
@ -146,7 +225,7 @@ func (p *BitcoinParser) outputScriptToAddresses(script []byte) ([]string, bool,
if sc == txscript.PubKeyHashTy || sc == txscript.WitnessV0PubKeyHashTy || sc == txscript.ScriptHashTy || sc == txscript.WitnessV0ScriptHashTy {
s = true
} else if len(rv) == 0 {
or := TryParseOPReturn(script)
or := p.TryParseOPReturn(script)
if or != "" {
rv = []string{or}
}
@ -154,6 +233,7 @@ func (p *BitcoinParser) outputScriptToAddresses(script []byte) ([]string, bool,
return rv, s, nil
}
// TxFromMsgTx converts bitcoin wire Tx to bchain.Tx
func (p *BitcoinParser) TxFromMsgTx(t *wire.MsgTx, parseAddresses bool) bchain.Tx {
vin := make([]bchain.Vin, len(t.TxIn))
for i, in := range t.TxIn {
@ -265,3 +345,109 @@ func (p *BitcoinParser) UnpackTx(buf []byte) (*bchain.Tx, uint32, error) {
return tx, height, nil
}
// MinimumCoinbaseConfirmations returns minimum number of confirmations a coinbase transaction must have before it can be spent
func (p *BitcoinParser) MinimumCoinbaseConfirmations() int {
return p.minimumCoinbaseConfirmations
}
func (p *BitcoinParser) addrDescFromExtKey(extKey *hdkeychain.ExtendedKey) (bchain.AddressDescriptor, error) {
var a btcutil.Address
var err error
if extKey.Version() == p.XPubMagicSegwitP2sh {
// redeemScript <witness version: OP_0><len pubKeyHash: 20><20-byte-pubKeyHash>
pubKeyHash := btcutil.Hash160(extKey.PubKeyBytes())
redeemScript := make([]byte, len(pubKeyHash)+2)
redeemScript[0] = 0
redeemScript[1] = byte(len(pubKeyHash))
copy(redeemScript[2:], pubKeyHash)
hash := btcutil.Hash160(redeemScript)
a, err = btcutil.NewAddressScriptHashFromHash(hash, p.Params)
} else if extKey.Version() == p.XPubMagicSegwitNative {
a, err = btcutil.NewAddressWitnessPubKeyHash(btcutil.Hash160(extKey.PubKeyBytes()), p.Params)
} else {
// default to P2PKH address
a, err = extKey.Address(p.Params)
}
if err != nil {
return nil, err
}
return txscript.PayToAddrScript(a)
}
// DeriveAddressDescriptors derives address descriptors from given xpub for listed indexes
func (p *BitcoinParser) DeriveAddressDescriptors(xpub string, change uint32, indexes []uint32) ([]bchain.AddressDescriptor, error) {
extKey, err := hdkeychain.NewKeyFromString(xpub, p.Params.Base58CksumHasher)
if err != nil {
return nil, err
}
changeExtKey, err := extKey.Child(change)
if err != nil {
return nil, err
}
ad := make([]bchain.AddressDescriptor, len(indexes))
for i, index := range indexes {
indexExtKey, err := changeExtKey.Child(index)
if err != nil {
return nil, err
}
ad[i], err = p.addrDescFromExtKey(indexExtKey)
if err != nil {
return nil, err
}
}
return ad, nil
}
// DeriveAddressDescriptorsFromTo derives address descriptors from given xpub for addresses in index range
func (p *BitcoinParser) DeriveAddressDescriptorsFromTo(xpub string, change uint32, fromIndex uint32, toIndex uint32) ([]bchain.AddressDescriptor, error) {
if toIndex <= fromIndex {
return nil, errors.New("toIndex<=fromIndex")
}
extKey, err := hdkeychain.NewKeyFromString(xpub, p.Params.Base58CksumHasher)
if err != nil {
return nil, err
}
changeExtKey, err := extKey.Child(change)
if err != nil {
return nil, err
}
ad := make([]bchain.AddressDescriptor, toIndex-fromIndex)
for index := fromIndex; index < toIndex; index++ {
indexExtKey, err := changeExtKey.Child(index)
if err != nil {
return nil, err
}
ad[index-fromIndex], err = p.addrDescFromExtKey(indexExtKey)
if err != nil {
return nil, err
}
}
return ad, nil
}
// DerivationBasePath returns base path of xpub
func (p *BitcoinParser) DerivationBasePath(xpub string) (string, error) {
extKey, err := hdkeychain.NewKeyFromString(xpub, p.Params.Base58CksumHasher)
if err != nil {
return "", err
}
var c, bip string
cn := extKey.ChildNum()
if cn >= 0x80000000 {
cn -= 0x80000000
c = "'"
}
c = strconv.Itoa(int(cn)) + c
if extKey.Depth() != 3 {
return "unknown/" + c, nil
}
if extKey.Version() == p.XPubMagicSegwitP2sh {
bip = "49"
} else if extKey.Version() == p.XPubMagicSegwitNative {
bip = "84"
} else {
bip = "44"
}
return "m/" + bip + "'/" + strconv.Itoa(int(p.Slip44)) + "'/" + c, nil
}

View File

@ -3,14 +3,14 @@
package btc
import (
"blockbook/bchain"
"encoding/hex"
"math/big"
"os"
"reflect"
"testing"
"github.com/jakm/btcutil/chaincfg"
"github.com/martinboehm/btcutil/chaincfg"
"spacecruft.org/spacecruft/blockbook/bchain"
)
func TestMain(m *testing.M) {
@ -19,7 +19,7 @@ func TestMain(m *testing.M) {
os.Exit(c)
}
func Test_GetAddrDescFromAddress(t *testing.T) {
func TestGetAddrDescFromAddress(t *testing.T) {
type args struct {
address string
}
@ -77,7 +77,7 @@ func Test_GetAddrDescFromAddress(t *testing.T) {
}
}
func Test_GetAddrDescFromVout(t *testing.T) {
func TestGetAddrDescFromVout(t *testing.T) {
type args struct {
vout bchain.Vout
}
@ -141,7 +141,7 @@ func Test_GetAddrDescFromVout(t *testing.T) {
}
}
func Test_GetAddressesFromAddrDesc(t *testing.T) {
func TestGetAddressesFromAddrDesc(t *testing.T) {
type args struct {
script string
}
@ -215,6 +215,27 @@ func Test_GetAddressesFromAddrDesc(t *testing.T) {
want2: false,
wantErr: false,
},
{
name: "OP_RETURN omni simple send tether",
args: args{script: "6a146f6d6e69000000000000001f00000709bb647351"},
want: []string{"OMNI Simple Send: 77383.80022609 TetherUS (#31)"},
want2: false,
wantErr: false,
},
{
name: "OP_RETURN omni simple send not supported coin",
args: args{script: "6a146f6d6e69000000000000000300000709bb647351"},
want: []string{"OP_RETURN 6f6d6e69000000000000000300000709bb647351"},
want2: false,
wantErr: false,
},
{
name: "OP_RETURN omni not supported version",
args: args{script: "6a146f6d6e69010000000000000300000709bb647351"},
want: []string{"OP_RETURN 6f6d6e69010000000000000300000709bb647351"},
want2: false,
wantErr: false,
},
}
parser := NewBitcoinParser(GetChainParams("main"), &Configuration{})
@ -238,10 +259,11 @@ func Test_GetAddressesFromAddrDesc(t *testing.T) {
}
var (
testTx1, testTx2 bchain.Tx
testTx1, testTx2, testTx3 bchain.Tx
testTxPacked1 = "0001e2408ba8d7af5401000000017f9a22c9cbf54bd902400df746f138f37bcf5b4d93eb755820e974ba43ed5f42040000006a4730440220037f4ed5427cde81d55b9b6a2fd08c8a25090c2c2fff3a75c1a57625ca8a7118022076c702fe55969fa08137f71afd4851c48e31082dd3c40c919c92cdbc826758d30121029f6da5623c9f9b68a9baf9c1bc7511df88fa34c6c2f71f7c62f2f03ff48dca80feffffff019c9700000000000017a9146144d57c8aff48492c9dfb914e120b20bad72d6f8773d00700"
testTxPacked2 = "0007c91a899ab7da6a010000000001019d64f0c72a0d206001decbffaa722eb1044534c74eee7a5df8318e42a4323ec10000000017160014550da1f5d25a9dae2eafd6902b4194c4c6500af6ffffffff02809698000000000017a914cd668d781ece600efa4b2404dc91fd26b8b8aed8870553d7360000000017a914246655bdbd54c7e477d0ea2375e86e0db2b8f80a8702473044022076aba4ad559616905fa51d4ddd357fc1fdb428d40cb388e042cdd1da4a1b7357022011916f90c712ead9a66d5f058252efd280439ad8956a967e95d437d246710bc9012102a80a5964c5612bb769ef73147b2cf3c149bc0fd4ecb02f8097629c94ab013ffd00000000"
testTxPacked3 = "00003d818bfda9aa3e02000000000102deb1999a857ab0a13d6b12fbd95ea75b409edde5f2ff747507ce42d9986a8b9d0000000000fdffffff9fd2d3361e203b2375eba6438efbef5b3075531e7e583c7cc76b7294fe7f22980000000000fdffffff02a0860100000000001600148091746745464e7555c31e9a5afceac14a02978ae7fc1c0000000000160014565ea9ff4589d3e05ba149ae6e257752bfdc2a1e0247304402207d67d320a8e813f986b35e9791935fcb736754812b7038686f5de6cfdcda99cd02201c3bb2c178e0056016437ecfe365a7eef84aa9d293ebdc566177af82e22fcdd3012103abb30c1bbe878b07b58dc169b1d061d48c60be8107f632a59778b38bf7ceea5a02473044022044f54a478cfe086e870cb026c9dcd4e14e63778bef569a4d55a6332725cd9a9802202f0e94c04e6f328fc64ad9efe552888c299750d1b8d033324825a3ff29920e030121036fcd433428aa7dc65c4f5408fa31f208c54fe4b4c6c1ae9c39a825ed4f1ac039813d0000"
)
func init() {
@ -314,9 +336,57 @@ func init() {
},
},
}
testTx3 = bchain.Tx{
Hex: "02000000000102deb1999a857ab0a13d6b12fbd95ea75b409edde5f2ff747507ce42d9986a8b9d0000000000fdffffff9fd2d3361e203b2375eba6438efbef5b3075531e7e583c7cc76b7294fe7f22980000000000fdffffff02a0860100000000001600148091746745464e7555c31e9a5afceac14a02978ae7fc1c0000000000160014565ea9ff4589d3e05ba149ae6e257752bfdc2a1e0247304402207d67d320a8e813f986b35e9791935fcb736754812b7038686f5de6cfdcda99cd02201c3bb2c178e0056016437ecfe365a7eef84aa9d293ebdc566177af82e22fcdd3012103abb30c1bbe878b07b58dc169b1d061d48c60be8107f632a59778b38bf7ceea5a02473044022044f54a478cfe086e870cb026c9dcd4e14e63778bef569a4d55a6332725cd9a9802202f0e94c04e6f328fc64ad9efe552888c299750d1b8d033324825a3ff29920e030121036fcd433428aa7dc65c4f5408fa31f208c54fe4b4c6c1ae9c39a825ed4f1ac039813d0000",
Blocktime: 1607805599,
Txid: "24551a58a1d1fb89d7052e2bbac7cb69a7825ee1e39439befbec8c32148cf735",
LockTime: 15745,
Version: 2,
Vin: []bchain.Vin{
{
ScriptSig: bchain.ScriptSig{
Hex: "",
},
Txid: "9d8b6a98d942ce077574fff2e5dd9e405ba75ed9fb126b3da1b07a859a99b1de",
Vout: 0,
Sequence: 4294967293,
},
{
ScriptSig: bchain.ScriptSig{
Hex: "",
},
Txid: "98227ffe94726bc77c3c587e1e5375305beffb8e43a6eb75233b201e36d3d29f",
Vout: 0,
Sequence: 4294967293,
},
},
Vout: []bchain.Vout{
{
ValueSat: *big.NewInt(100000),
N: 0,
ScriptPubKey: bchain.ScriptPubKey{
Hex: "00148091746745464e7555c31e9a5afceac14a02978a",
Addresses: []string{
"tb1qszghge69ge8824wrr6d94l82c99q99u2ccgv5w",
},
},
},
{
ValueSat: *big.NewInt(1899751),
N: 1,
ScriptPubKey: bchain.ScriptPubKey{
Hex: "0014565ea9ff4589d3e05ba149ae6e257752bfdc2a1e",
Addresses: []string{
"tb1q2e02nl6938f7qkapfxhxufth22lac2s792vsxp",
},
},
},
},
}
}
func Test_PackTx(t *testing.T) {
func TestPackTx(t *testing.T) {
type args struct {
tx bchain.Tx
height uint32
@ -351,6 +421,17 @@ func Test_PackTx(t *testing.T) {
want: testTxPacked2,
wantErr: false,
},
{
name: "signet-1",
args: args{
tx: testTx3,
height: 15745,
blockTime: 1607805599,
parser: NewBitcoinParser(GetChainParams("signet"), &Configuration{}),
},
want: testTxPacked3,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@ -367,7 +448,7 @@ func Test_PackTx(t *testing.T) {
}
}
func Test_UnpackTx(t *testing.T) {
func TestUnpackTx(t *testing.T) {
type args struct {
packedTx string
parser *BitcoinParser
@ -399,6 +480,16 @@ func Test_UnpackTx(t *testing.T) {
want1: 510234,
wantErr: false,
},
{
name: "signet-1",
args: args{
packedTx: testTxPacked3,
parser: NewBitcoinParser(GetChainParams("signet"), &Configuration{}),
},
want: &testTx3,
want1: 15745,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@ -417,3 +508,245 @@ func Test_UnpackTx(t *testing.T) {
})
}
}
func TestDeriveAddressDescriptors(t *testing.T) {
btcMainParser := NewBitcoinParser(GetChainParams("main"), &Configuration{XPubMagic: 76067358, XPubMagicSegwitP2sh: 77429938, XPubMagicSegwitNative: 78792518})
type args struct {
xpub string
change uint32
indexes []uint32
parser *BitcoinParser
}
tests := []struct {
name string
args args
want []string
wantErr bool
}{
{
name: "m/44'/0'/0'",
args: args{
xpub: "xpub6BosfCnifzxcFwrSzQiqu2DBVTshkCXacvNsWGYJVVhhawA7d4R5WSWGFNbi8Aw6ZRc1brxMyWMzG3DSSSSoekkudhUd9yLb6qx39T9nMdj",
change: 0,
indexes: []uint32{0, 1234},
parser: btcMainParser,
},
want: []string{"1LqBGSKuX5yYUonjxT5qGfpUsXKYYWeabA", "1P9w11dXAmG3QBjKLAvCsek8izs1iR2iFi"},
},
{
name: "m/49'/0'/0'",
args: args{
xpub: "ypub6Ww3ibxVfGzLrAH1PNcjyAWenMTbbAosGNB6VvmSEgytSER9azLDWCxoJwW7Ke7icmizBMXrzBx9979FfaHxHcrArf3zbeJJJUZPf663zsP",
change: 0,
indexes: []uint32{0, 1234},
parser: btcMainParser,
},
want: []string{"37VucYSaXLCAsxYyAPfbSi9eh4iEcbShgf", "367meFzJ9KqDLm9PX6U8Z8RdmkSNBuxX8T"},
},
{
name: "m/84'/0'/0'",
args: args{
xpub: "zpub6rFR7y4Q2AijBEqTUquhVz398htDFrtymD9xYYfG1m4wAcvPhXNfE3EfH1r1ADqtfSdVCToUG868RvUUkgDKf31mGDtKsAYz2oz2AGutZYs",
change: 0,
indexes: []uint32{0, 1234},
parser: btcMainParser,
},
want: []string{"bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu", "bc1q4nm6g46ujzyjaeusralaz2nfv2rf04jjfyamkw"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := tt.args.parser.DeriveAddressDescriptors(tt.args.xpub, tt.args.change, tt.args.indexes)
if (err != nil) != tt.wantErr {
t.Errorf("DeriveAddressDescriptorsFromTo() error = %v, wantErr %v", err, tt.wantErr)
return
}
gotAddresses := make([]string, len(got))
for i, ad := range got {
aa, _, err := tt.args.parser.GetAddressesFromAddrDesc(ad)
if err != nil || len(aa) != 1 {
t.Errorf("DeriveAddressDescriptorsFromTo() got incorrect address descriptor %v, error %v", ad, err)
return
}
gotAddresses[i] = aa[0]
}
if !reflect.DeepEqual(gotAddresses, tt.want) {
t.Errorf("DeriveAddressDescriptorsFromTo() = %v, want %v", gotAddresses, tt.want)
}
})
}
}
func TestDeriveAddressDescriptorsFromTo(t *testing.T) {
btcMainParser := NewBitcoinParser(GetChainParams("main"), &Configuration{XPubMagic: 76067358, XPubMagicSegwitP2sh: 77429938, XPubMagicSegwitNative: 78792518})
btcTestnetsParser := NewBitcoinParser(GetChainParams("test"), &Configuration{XPubMagic: 70617039, XPubMagicSegwitP2sh: 71979618, XPubMagicSegwitNative: 73342198})
type args struct {
xpub string
change uint32
fromIndex uint32
toIndex uint32
parser *BitcoinParser
}
tests := []struct {
name string
args args
want []string
wantErr bool
}{
{
name: "m/44'/0'/0'",
args: args{
xpub: "xpub6BosfCnifzxcFwrSzQiqu2DBVTshkCXacvNsWGYJVVhhawA7d4R5WSWGFNbi8Aw6ZRc1brxMyWMzG3DSSSSoekkudhUd9yLb6qx39T9nMdj",
change: 0,
fromIndex: 0,
toIndex: 1,
parser: btcMainParser,
},
want: []string{"1LqBGSKuX5yYUonjxT5qGfpUsXKYYWeabA"},
},
{
name: "m/49'/0'/0'",
args: args{
xpub: "ypub6Ww3ibxVfGzLrAH1PNcjyAWenMTbbAosGNB6VvmSEgytSER9azLDWCxoJwW7Ke7icmizBMXrzBx9979FfaHxHcrArf3zbeJJJUZPf663zsP",
change: 0,
fromIndex: 0,
toIndex: 1,
parser: btcMainParser,
},
want: []string{"37VucYSaXLCAsxYyAPfbSi9eh4iEcbShgf"},
},
{
name: "m/84'/0'/0'",
args: args{
xpub: "zpub6rFR7y4Q2AijBEqTUquhVz398htDFrtymD9xYYfG1m4wAcvPhXNfE3EfH1r1ADqtfSdVCToUG868RvUUkgDKf31mGDtKsAYz2oz2AGutZYs",
change: 0,
fromIndex: 0,
toIndex: 1,
parser: btcMainParser,
},
want: []string{"bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu"},
},
{
name: "m/49'/1'/0'",
args: args{
xpub: "upub5DR1Mg5nykixzYjFXWW5GghAU7dDqoPVJ2jrqFbL8sJ7Hs7jn69MP7KBnnmxn88GeZtnH8PRKV9w5MMSFX8AdEAoXY8Qd8BJPoXtpMeHMxJ",
change: 0,
fromIndex: 0,
toIndex: 10,
parser: btcTestnetsParser,
},
want: []string{"2N4Q5FhU2497BryFfUgbqkAJE87aKHUhXMp", "2Mt7P2BAfE922zmfXrdcYTLyR7GUvbwSEns", "2N6aUMgQk8y1zvoq6FeWFyotyj75WY9BGsu", "2NA7tbZWM9BcRwBuebKSQe2xbhhF1paJwBM", "2N8RZMzvrUUnpLmvACX9ysmJ2MX3GK5jcQM", "2MvUUSiQZDSqyeSdofKX9KrSCio1nANPDTe", "2NBXaWu1HazjoUVgrXgcKNoBLhtkkD9Gmet", "2N791Ttf89tMVw2maj86E1Y3VgxD9Mc7PU7", "2NCJmwEq8GJm8t8GWWyBXAfpw7F2qZEVP5Y", "2NEgW71hWKer2XCSA8ZCC2VnWpB77L6bk68"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := tt.args.parser.DeriveAddressDescriptorsFromTo(tt.args.xpub, tt.args.change, tt.args.fromIndex, tt.args.toIndex)
if (err != nil) != tt.wantErr {
t.Errorf("DeriveAddressDescriptorsFromTo() error = %v, wantErr %v", err, tt.wantErr)
return
}
gotAddresses := make([]string, len(got))
for i, ad := range got {
aa, _, err := tt.args.parser.GetAddressesFromAddrDesc(ad)
if err != nil || len(aa) != 1 {
t.Errorf("DeriveAddressDescriptorsFromTo() got incorrect address descriptor %v, error %v", ad, err)
return
}
gotAddresses[i] = aa[0]
}
if !reflect.DeepEqual(gotAddresses, tt.want) {
t.Errorf("DeriveAddressDescriptorsFromTo() = %v, want %v", gotAddresses, tt.want)
}
})
}
}
func BenchmarkDeriveAddressDescriptorsFromToXpub(b *testing.B) {
btcMainParser := NewBitcoinParser(GetChainParams("main"), &Configuration{XPubMagic: 76067358, XPubMagicSegwitP2sh: 77429938, XPubMagicSegwitNative: 78792518})
for i := 0; i < b.N; i++ {
btcMainParser.DeriveAddressDescriptorsFromTo("xpub6BosfCnifzxcFwrSzQiqu2DBVTshkCXacvNsWGYJVVhhawA7d4R5WSWGFNbi8Aw6ZRc1brxMyWMzG3DSSSSoekkudhUd9yLb6qx39T9nMdj", 1, 0, 100)
}
}
func BenchmarkDeriveAddressDescriptorsFromToYpub(b *testing.B) {
btcMainParser := NewBitcoinParser(GetChainParams("main"), &Configuration{XPubMagic: 76067358, XPubMagicSegwitP2sh: 77429938, XPubMagicSegwitNative: 78792518})
for i := 0; i < b.N; i++ {
btcMainParser.DeriveAddressDescriptorsFromTo("ypub6Ww3ibxVfGzLrAH1PNcjyAWenMTbbAosGNB6VvmSEgytSER9azLDWCxoJwW7Ke7icmizBMXrzBx9979FfaHxHcrArf3zbeJJJUZPf663zsP", 1, 0, 100)
}
}
func BenchmarkDeriveAddressDescriptorsFromToZpub(b *testing.B) {
btcMainParser := NewBitcoinParser(GetChainParams("main"), &Configuration{XPubMagic: 76067358, XPubMagicSegwitP2sh: 77429938, XPubMagicSegwitNative: 78792518})
for i := 0; i < b.N; i++ {
btcMainParser.DeriveAddressDescriptorsFromTo("zpub6rFR7y4Q2AijBEqTUquhVz398htDFrtymD9xYYfG1m4wAcvPhXNfE3EfH1r1ADqtfSdVCToUG868RvUUkgDKf31mGDtKsAYz2oz2AGutZYs", 1, 0, 100)
}
}
func TestBitcoinParser_DerivationBasePath(t *testing.T) {
btcMainParser := NewBitcoinParser(GetChainParams("main"), &Configuration{XPubMagic: 76067358, XPubMagicSegwitP2sh: 77429938, XPubMagicSegwitNative: 78792518, Slip44: 0})
btcTestnetsParser := NewBitcoinParser(GetChainParams("test"), &Configuration{XPubMagic: 70617039, XPubMagicSegwitP2sh: 71979618, XPubMagicSegwitNative: 73342198, Slip44: 1})
zecMainParser := NewBitcoinParser(GetChainParams("main"), &Configuration{XPubMagic: 76067358, Slip44: 133})
type args struct {
xpub string
parser *BitcoinParser
}
tests := []struct {
name string
args args
want string
wantErr bool
}{
{
name: "m/84'/0'/0'",
args: args{
xpub: "zpub6rFR7y4Q2AijBEqTUquhVz398htDFrtymD9xYYfG1m4wAcvPhXNfE3EfH1r1ADqtfSdVCToUG868RvUUkgDKf31mGDtKsAYz2oz2AGutZYs",
parser: btcMainParser,
},
want: "m/84'/0'/0'",
},
{
name: "m/49'/0'/55 - not hardened account",
args: args{
xpub: "ypub6XKbB5DJRAbW4TRJLp4uXQXG3ob5BtByXsNZFBjq9qcbzrczjVXfCz5cEo1SFDexmeWRnbCMDaRgaW4m9d2nBaa8FvUQCu3n9G1UBR8WhbT",
parser: btcMainParser,
},
want: "m/49'/0'/55",
},
{
name: "m/49'/0' - incomplete path, without account",
args: args{
xpub: "ypub6UzM8PUqxcSoqC9gumfoiFhE8Qt84HbGpCD4eVJfJAojXTVtBxeddvTWJGJhGoaVBNJLmEgMdLXHgaLVJa4xEvk2tcokkdZhFdkxMLUE9sB",
parser: btcMainParser,
},
want: "unknown/0'",
},
{
name: "m/49'/1'/0'",
args: args{
xpub: "upub5DR1Mg5nykixzYjFXWW5GghAU7dDqoPVJ2jrqFbL8sJ7Hs7jn69MP7KBnnmxn88GeZtnH8PRKV9w5MMSFX8AdEAoXY8Qd8BJPoXtpMeHMxJ",
parser: btcTestnetsParser,
},
want: "m/49'/1'/0'",
},
{
name: "m/44'/133'/12'",
args: args{
xpub: "xpub6CQdEahwhKRTLYpP6cyb7ZaGb3r4tVdyPX6dC1PfrNuByrCkWDgUkmpD28UdV9QccKgY1ZiAbGv1Fakcg2LxdFVSTNKHcjdRjqhjPK8Trkb",
parser: zecMainParser,
},
want: "m/44'/133'/12'",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := tt.args.parser.DerivationBasePath(tt.args.xpub)
if (err != nil) != tt.wantErr {
t.Errorf("BitcoinParser.DerivationBasePath() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("BitcoinParser.DerivationBasePath() = %v, want %v", got, tt.want)
}
})
}
}

View File

@ -1,7 +1,6 @@
package btc
import (
"blockbook/bchain"
"bytes"
"context"
"encoding/hex"
@ -11,24 +10,24 @@ import (
"math/big"
"net"
"net/http"
"runtime/debug"
"time"
"github.com/btcsuite/btcd/wire"
"github.com/golang/glog"
"github.com/juju/errors"
"github.com/martinboehm/btcd/wire"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/common"
)
// BitcoinRPC is an interface to JSON-RPC bitcoind service.
type BitcoinRPC struct {
*bchain.BaseChain
client http.Client
rpcURL string
user string
password string
Parser bchain.BlockChainParser
Testnet bool
Network string
Mempool *bchain.UTXOMempool
Mempool *bchain.MempoolBitcoinType
ParseBlocks bool
pushHandler func(bchain.NotificationType)
mq *bchain.MQ
@ -36,22 +35,30 @@ type BitcoinRPC struct {
RPCMarshaler RPCMarshaler
}
// Configuration represents json config file
type Configuration struct {
CoinName string `json:"coin_name"`
CoinShortcut string `json:"coin_shortcut"`
RPCURL string `json:"rpc_url"`
RPCUser string `json:"rpc_user"`
RPCPass string `json:"rpc_pass"`
RPCTimeout int `json:"rpc_timeout"`
Parse bool `json:"parse"`
MessageQueueBinding string `json:"message_queue_binding"`
Subversion string `json:"subversion"`
BlockAddressesToKeep int `json:"block_addresses_to_keep"`
MempoolWorkers int `json:"mempool_workers"`
MempoolSubWorkers int `json:"mempool_sub_workers"`
AddressFormat string `json:"address_format"`
SupportsEstimateFee bool `json:"supports_estimate_fee"`
SupportsEstimateSmartFee bool `json:"supports_estimate_smart_fee"`
CoinName string `json:"coin_name"`
CoinShortcut string `json:"coin_shortcut"`
RPCURL string `json:"rpc_url"`
RPCUser string `json:"rpc_user"`
RPCPass string `json:"rpc_pass"`
RPCTimeout int `json:"rpc_timeout"`
Parse bool `json:"parse"`
MessageQueueBinding string `json:"message_queue_binding"`
Subversion string `json:"subversion"`
BlockAddressesToKeep int `json:"block_addresses_to_keep"`
MempoolWorkers int `json:"mempool_workers"`
MempoolSubWorkers int `json:"mempool_sub_workers"`
AddressFormat string `json:"address_format"`
SupportsEstimateFee bool `json:"supports_estimate_fee"`
SupportsEstimateSmartFee bool `json:"supports_estimate_smart_fee"`
XPubMagic uint32 `json:"xpub_magic,omitempty"`
XPubMagicSegwitP2sh uint32 `json:"xpub_magic_segwit_p2sh,omitempty"`
XPubMagicSegwitNative uint32 `json:"xpub_magic_segwit_native,omitempty"`
Slip44 uint32 `json:"slip44,omitempty"`
AlternativeEstimateFee string `json:"alternative_estimate_fee,omitempty"`
AlternativeEstimateFeeParams string `json:"alternative_estimate_fee_params,omitempty"`
MinimumCoinbaseConfirmations int `json:"minimumCoinbaseConfirmations,omitempty"`
}
// NewBitcoinRPC returns new BitcoinRPC instance.
@ -66,6 +73,10 @@ func NewBitcoinRPC(config json.RawMessage, pushHandler func(bchain.NotificationT
if c.BlockAddressesToKeep < 100 {
c.BlockAddressesToKeep = 100
}
// default MinimumCoinbaseConfirmations is 100
if c.MinimumCoinbaseConfirmations == 0 {
c.MinimumCoinbaseConfirmations = 100
}
// at least 1 mempool worker/subworker for synchronous mempool synchronization
if c.MempoolWorkers < 1 {
c.MempoolWorkers = 1
@ -84,6 +95,7 @@ func NewBitcoinRPC(config json.RawMessage, pushHandler func(bchain.NotificationT
}
s := &BitcoinRPC{
BaseChain: &bchain.BaseChain{},
client: http.Client{Timeout: time.Duration(c.RPCTimeout) * time.Second, Transport: transport},
rpcURL: c.RPCURL,
user: c.RPCUser,
@ -97,37 +109,15 @@ func NewBitcoinRPC(config json.RawMessage, pushHandler func(bchain.NotificationT
return s, nil
}
// GetChainInfoAndInitializeMempool is called by Initialize and reused by other coins
// it contacts the blockchain rpc interface for the first time
// and if successful it connects to ZeroMQ and creates mempool handler
func (b *BitcoinRPC) GetChainInfoAndInitializeMempool(bc bchain.BlockChain) (string, error) {
// try to connect to block chain and get some info
ci, err := bc.GetChainInfo()
if err != nil {
return "", err
}
chainName := ci.Chain
mq, err := bchain.NewMQ(b.ChainConfig.MessageQueueBinding, b.pushHandler)
if err != nil {
glog.Error("mq: ", err)
return "", err
}
b.mq = mq
b.Mempool = bchain.NewUTXOMempool(bc, b.ChainConfig.MempoolWorkers, b.ChainConfig.MempoolSubWorkers)
return chainName, nil
}
// Initialize initializes BitcoinRPC instance.
func (b *BitcoinRPC) Initialize() error {
b.ChainConfig.SupportsEstimateFee = false
chainName, err := b.GetChainInfoAndInitializeMempool(b)
ci, err := b.GetChainInfo()
if err != nil {
return err
}
chainName := ci.Chain
params := GetChainParams(chainName)
@ -145,9 +135,45 @@ func (b *BitcoinRPC) Initialize() error {
glog.Info("rpc: block chain ", params.Name)
if b.ChainConfig.AlternativeEstimateFee == "whatthefee" {
if err = InitWhatTheFee(b, b.ChainConfig.AlternativeEstimateFeeParams); err != nil {
glog.Error("InitWhatTheFee error ", err, " Reverting to default estimateFee functionality")
// disable AlternativeEstimateFee logic
b.ChainConfig.AlternativeEstimateFee = ""
}
}
return nil
}
// CreateMempool creates mempool if not already created, however does not initialize it
func (b *BitcoinRPC) CreateMempool(chain bchain.BlockChain) (bchain.Mempool, error) {
if b.Mempool == nil {
b.Mempool = bchain.NewMempoolBitcoinType(chain, b.ChainConfig.MempoolWorkers, b.ChainConfig.MempoolSubWorkers)
}
return b.Mempool, nil
}
// InitializeMempool creates ZeroMQ subscription and sets AddrDescForOutpointFunc to the Mempool
func (b *BitcoinRPC) InitializeMempool(addrDescForOutpoint bchain.AddrDescForOutpointFunc, onNewTxAddr bchain.OnNewTxAddrFunc, onNewTx bchain.OnNewTxFunc) error {
if b.Mempool == nil {
return errors.New("Mempool not created")
}
b.Mempool.AddrDescForOutpoint = addrDescForOutpoint
b.Mempool.OnNewTxAddr = onNewTxAddr
b.Mempool.OnNewTx = onNewTx
if b.mq == nil {
mq, err := bchain.NewMQ(b.ChainConfig.MessageQueueBinding, b.pushHandler)
if err != nil {
glog.Error("mq: ", err)
return err
}
b.mq = mq
}
return nil
}
// Shutdown ZeroMQ and other resources
func (b *BitcoinRPC) Shutdown(ctx context.Context) error {
if b.mq != nil {
if err := b.mq.Shutdown(ctx); err != nil {
@ -158,18 +184,12 @@ func (b *BitcoinRPC) Shutdown(ctx context.Context) error {
return nil
}
func (b *BitcoinRPC) IsTestnet() bool {
return b.Testnet
}
func (b *BitcoinRPC) GetNetworkName() string {
return b.Network
}
// GetCoinName returns the coin name
func (b *BitcoinRPC) GetCoinName() string {
return b.ChainConfig.CoinName
}
// GetSubversion returns the backend subversion
func (b *BitcoinRPC) GetSubversion() string {
return b.ChainConfig.Subversion
}
@ -219,13 +239,13 @@ type CmdGetBlockChainInfo struct {
type ResGetBlockChainInfo struct {
Error *bchain.RPCError `json:"error"`
Result struct {
Chain string `json:"chain"`
Blocks int `json:"blocks"`
Headers int `json:"headers"`
Bestblockhash string `json:"bestblockhash"`
Difficulty json.Number `json:"difficulty"`
SizeOnDisk int64 `json:"size_on_disk"`
Warnings string `json:"warnings"`
Chain string `json:"chain"`
Blocks int `json:"blocks"`
Headers int `json:"headers"`
Bestblockhash string `json:"bestblockhash"`
Difficulty common.JSONNumber `json:"difficulty"`
SizeOnDisk int64 `json:"size_on_disk"`
Warnings string `json:"warnings"`
} `json:"result"`
}
@ -238,11 +258,11 @@ type CmdGetNetworkInfo struct {
type ResGetNetworkInfo struct {
Error *bchain.RPCError `json:"error"`
Result struct {
Version json.Number `json:"version"`
Subversion json.Number `json:"subversion"`
ProtocolVersion json.Number `json:"protocolversion"`
Timeoffset float64 `json:"timeoffset"`
Warnings string `json:"warnings"`
Version common.JSONNumber `json:"version"`
Subversion common.JSONNumber `json:"subversion"`
ProtocolVersion common.JSONNumber `json:"protocolversion"`
Timeoffset float64 `json:"timeoffset"`
Warnings string `json:"warnings"`
} `json:"result"`
}
@ -340,8 +360,8 @@ type CmdEstimateSmartFee struct {
type ResEstimateSmartFee struct {
Error *bchain.RPCError `json:"error"`
Result struct {
Feerate json.Number `json:"feerate"`
Blocks int `json:"blocks"`
Feerate common.JSONNumber `json:"feerate"`
Blocks int `json:"blocks"`
} `json:"result"`
}
@ -355,8 +375,8 @@ type CmdEstimateFee struct {
}
type ResEstimateFee struct {
Error *bchain.RPCError `json:"error"`
Result json.Number `json:"result"`
Error *bchain.RPCError `json:"error"`
Result common.JSONNumber `json:"result"`
}
// sendrawtransaction
@ -462,7 +482,8 @@ func (b *BitcoinRPC) GetChainInfo() (*bchain.ChainInfo, error) {
return rv, nil
}
func isErrBlockNotFound(err *bchain.RPCError) bool {
// IsErrBlockNotFound returns true if error means block was not found
func IsErrBlockNotFound(err *bchain.RPCError) bool {
return err.Message == "Block not found" ||
err.Message == "Block height out of range"
}
@ -480,7 +501,7 @@ func (b *BitcoinRPC) GetBlockHash(height uint32) (string, error) {
return "", errors.Annotatef(err, "height %v", height)
}
if res.Error != nil {
if isErrBlockNotFound(res.Error) {
if IsErrBlockNotFound(res.Error) {
return "", bchain.ErrBlockNotFound
}
return "", errors.Annotatef(res.Error, "height %v", height)
@ -502,7 +523,7 @@ func (b *BitcoinRPC) GetBlockHeader(hash string) (*bchain.BlockHeader, error) {
return nil, errors.Annotatef(err, "hash %v", hash)
}
if res.Error != nil {
if isErrBlockNotFound(res.Error) {
if IsErrBlockNotFound(res.Error) {
return nil, bchain.ErrBlockNotFound
}
return nil, errors.Annotatef(res.Error, "hash %v", hash)
@ -556,7 +577,7 @@ func (b *BitcoinRPC) GetBlockInfo(hash string) (*bchain.BlockInfo, error) {
return nil, errors.Annotatef(err, "hash %v", hash)
}
if res.Error != nil {
if isErrBlockNotFound(res.Error) {
if IsErrBlockNotFound(res.Error) {
return nil, bchain.ErrBlockNotFound
}
return nil, errors.Annotatef(res.Error, "hash %v", hash)
@ -580,7 +601,7 @@ func (b *BitcoinRPC) GetBlockWithoutHeader(hash string, height uint32) (*bchain.
return block, nil
}
// GetBlockRaw returns block with given hash as bytes.
// GetBlockRaw returns block with given hash as bytes
func (b *BitcoinRPC) GetBlockRaw(hash string) ([]byte, error) {
glog.V(1).Info("rpc: getblock (verbosity=0) ", hash)
@ -594,7 +615,7 @@ func (b *BitcoinRPC) GetBlockRaw(hash string) ([]byte, error) {
return nil, errors.Annotatef(err, "hash %v", hash)
}
if res.Error != nil {
if isErrBlockNotFound(res.Error) {
if IsErrBlockNotFound(res.Error) {
return nil, bchain.ErrBlockNotFound
}
return nil, errors.Annotatef(res.Error, "hash %v", hash)
@ -602,7 +623,7 @@ func (b *BitcoinRPC) GetBlockRaw(hash string) ([]byte, error) {
return hex.DecodeString(res.Result)
}
// GetBlockFull returns block with given hash.
// GetBlockFull returns block with given hash
func (b *BitcoinRPC) GetBlockFull(hash string) (*bchain.Block, error) {
glog.V(1).Info("rpc: getblock (verbosity=2) ", hash)
@ -616,16 +637,30 @@ func (b *BitcoinRPC) GetBlockFull(hash string) (*bchain.Block, error) {
return nil, errors.Annotatef(err, "hash %v", hash)
}
if res.Error != nil {
if isErrBlockNotFound(res.Error) {
if IsErrBlockNotFound(res.Error) {
return nil, bchain.ErrBlockNotFound
}
return nil, errors.Annotatef(res.Error, "hash %v", hash)
}
for i := range res.Result.Txs {
tx := &res.Result.Txs[i]
for j := range tx.Vout {
vout := &tx.Vout[j]
// convert vout.JsonValue to big.Int and clear it, it is only temporary value used for unmarshal
vout.ValueSat, err = b.Parser.AmountToBigInt(vout.JsonValue)
if err != nil {
return nil, err
}
vout.JsonValue = ""
}
}
return &res.Result, nil
}
// GetMempool returns transactions in mempool.
func (b *BitcoinRPC) GetMempool() ([]string, error) {
// GetMempoolTransactions returns transactions in mempool
func (b *BitcoinRPC) GetMempoolTransactions() ([]string, error) {
glog.V(1).Info("rpc: getrawmempool")
res := ResGetMempool{}
@ -641,7 +676,15 @@ func (b *BitcoinRPC) GetMempool() ([]string, error) {
return res.Result, nil
}
// GetTransactionForMempool returns a transaction by the transaction ID.
// IsMissingTx return true if error means missing tx
func IsMissingTx(err *bchain.RPCError) bool {
if err.Code == -5 { // "No such mempool or blockchain transaction"
return true
}
return false
}
// GetTransactionForMempool returns a transaction by the transaction ID
// It could be optimized for mempool, i.e. without block time and confirmations
func (b *BitcoinRPC) GetTransactionForMempool(txid string) (*bchain.Tx, error) {
glog.V(1).Info("rpc: getrawtransaction nonverbose ", txid)
@ -655,6 +698,9 @@ func (b *BitcoinRPC) GetTransactionForMempool(txid string) (*bchain.Tx, error) {
return nil, errors.Annotatef(err, "txid %v", txid)
}
if res.Error != nil {
if IsMissingTx(res.Error) {
return nil, bchain.ErrTxNotFound
}
return nil, errors.Annotatef(res.Error, "txid %v", txid)
}
data, err := hex.DecodeString(res.Result)
@ -668,13 +714,14 @@ func (b *BitcoinRPC) GetTransactionForMempool(txid string) (*bchain.Tx, error) {
return tx, nil
}
// GetTransaction returns a transaction by the transaction ID.
// GetTransaction returns a transaction by the transaction ID
func (b *BitcoinRPC) GetTransaction(txid string) (*bchain.Tx, error) {
r, err := b.GetTransactionSpecific(txid)
r, err := b.getRawTransaction(txid)
if err != nil {
return nil, err
}
tx, err := b.Parser.ParseTxFromJson(r)
tx.CoinSpecificData = r
if err != nil {
return nil, errors.Annotatef(err, "txid %v", txid)
}
@ -682,7 +729,15 @@ func (b *BitcoinRPC) GetTransaction(txid string) (*bchain.Tx, error) {
}
// GetTransactionSpecific returns json as returned by backend, with all coin specific data
func (b *BitcoinRPC) GetTransactionSpecific(txid string) (json.RawMessage, error) {
func (b *BitcoinRPC) GetTransactionSpecific(tx *bchain.Tx) (json.RawMessage, error) {
if csd, ok := tx.CoinSpecificData.(json.RawMessage); ok {
return csd, nil
}
return b.getRawTransaction(tx.Txid)
}
// getRawTransaction returns json as returned by backend, with all coin specific data
func (b *BitcoinRPC) getRawTransaction(txid string) (json.RawMessage, error) {
glog.V(1).Info("rpc: getrawtransaction ", txid)
res := ResGetRawTransaction{}
@ -695,28 +750,14 @@ func (b *BitcoinRPC) GetTransactionSpecific(txid string) (json.RawMessage, error
return nil, errors.Annotatef(err, "txid %v", txid)
}
if res.Error != nil {
if IsMissingTx(res.Error) {
return nil, bchain.ErrTxNotFound
}
return nil, errors.Annotatef(res.Error, "txid %v", txid)
}
return res.Result, nil
}
// ResyncMempool gets mempool transactions and maps output scripts to transactions.
// ResyncMempool is not reentrant, it should be called from a single thread.
// It returns number of transactions in mempool
func (b *BitcoinRPC) ResyncMempool(onNewTxAddr bchain.OnNewTxAddrFunc) (int, error) {
return b.Mempool.Resync(onNewTxAddr)
}
// GetMempoolTransactions returns slice of mempool transactions for given address
func (b *BitcoinRPC) GetMempoolTransactions(address string) ([]string, error) {
return b.Mempool.GetTransactions(address)
}
// GetMempoolTransactionsForAddrDesc returns slice of mempool transactions for given address descriptor
func (b *BitcoinRPC) GetMempoolTransactionsForAddrDesc(addrDesc bchain.AddressDescriptor) ([]string, error) {
return b.Mempool.GetAddrDescTransactions(addrDesc)
}
// EstimateSmartFee returns fee estimation
func (b *BitcoinRPC) EstimateSmartFee(blocks int, conservative bool) (big.Int, error) {
// use EstimateFee if EstimateSmartFee is not supported
@ -778,7 +819,7 @@ func (b *BitcoinRPC) EstimateFee(blocks int) (big.Int, error) {
return r, nil
}
// SendRawTransaction sends raw transaction.
// SendRawTransaction sends raw transaction
func (b *BitcoinRPC) SendRawTransaction(tx string) (string, error) {
glog.V(1).Info("rpc: sendrawtransaction")
@ -828,6 +869,7 @@ func safeDecodeResponse(body io.ReadCloser, res interface{}) (err error) {
defer func() {
if r := recover(); r != nil {
glog.Error("unmarshal json recovered from panic: ", r, "; data: ", string(data))
debug.PrintStack()
if len(data) > 0 && len(data) < 2048 {
err = errors.Errorf("Error: %v", string(data))
} else {
@ -842,6 +884,7 @@ func safeDecodeResponse(body io.ReadCloser, res interface{}) (err error) {
return json.Unmarshal(data, &res)
}
// Call calls Backend RPC interface, using RPCMarshaler interface to marshall the request
func (b *BitcoinRPC) Call(req interface{}, res interface{}) error {
httpData, err := b.RPCMarshaler.Marshal(req)
if err != nil {
@ -872,8 +915,3 @@ func (b *BitcoinRPC) Call(req interface{}, res interface{}) error {
}
return safeDecodeResponse(httpRes.Body, &res)
}
// GetChainParser returns BlockChainParser
func (b *BitcoinRPC) GetChainParser() bchain.BlockChainParser {
return b.Parser
}

View File

@ -6,12 +6,15 @@ import (
"reflect"
)
// RPCMarshaler is used for marshalling requests to Bitcoin Type RPC interfaces
type RPCMarshaler interface {
Marshal(v interface{}) ([]byte, error)
}
// JSONMarshalerV2 is used for marshalling requests to newer Bitcoin Type RPC interfaces
type JSONMarshalerV2 struct{}
// Marshal converts struct passed by parameter to JSON
func (JSONMarshalerV2) Marshal(v interface{}) ([]byte, error) {
d, err := json.Marshal(v)
if err != nil {
@ -20,10 +23,13 @@ func (JSONMarshalerV2) Marshal(v interface{}) ([]byte, error) {
return d, nil
}
var InvalidValue = errors.New("Invalid value to marshal")
// ErrInvalidValue is error returned by JSONMarshalerV1.Marshal if the passed structure contains invalid value
var ErrInvalidValue = errors.New("Invalid value to marshal")
// JSONMarshalerV1 is used for marshalling requests to legacy Bitcoin Type RPC interfaces
type JSONMarshalerV1 struct{}
// Marshal converts struct passed by parameter to JSON
func (JSONMarshalerV1) Marshal(v interface{}) ([]byte, error) {
u := cmdUntypedParams{}
@ -50,7 +56,7 @@ func (JSONMarshalerV1) Marshal(v interface{}) ([]byte, error) {
f := v.FieldByName("Method")
if !f.IsValid() || f.Kind() != reflect.String {
return nil, InvalidValue
return nil, ErrInvalidValue
}
u.Method = f.String()
@ -69,7 +75,7 @@ func (JSONMarshalerV1) Marshal(v interface{}) ([]byte, error) {
arr[i] = f.Field(i).Interface()
}
default:
return nil, InvalidValue
return nil, ErrInvalidValue
}
u.Params = arr
}

View File

@ -0,0 +1,156 @@
package btc
import (
"bytes"
"encoding/json"
"fmt"
"math"
"net/http"
"strconv"
"sync"
"time"
"github.com/golang/glog"
"github.com/juju/errors"
"spacecruft.org/spacecruft/blockbook/bchain"
)
// https://whatthefee.io returns
// {"index": [3, 6, 9, 12, 18, 24, 36, 48, 72, 96, 144],
// "columns": ["0.0500", "0.2000", "0.5000", "0.8000", "0.9500"],
// "data": [[60, 180, 280, 400, 440], [20, 120, 180, 380, 440],
// [0, 120, 160, 360, 420], [0, 80, 160, 300, 380], [0, 20, 120, 220, 360],
// [0, 20, 100, 180, 300], [0, 0, 80, 140, 240], [0, 0, 60, 100, 180],
// [0, 0, 40, 60, 140], [0, 0, 20, 20, 60], [0, 0, 0, 0, 20]]}
type whatTheFeeServiceResult struct {
Index []int `json:"index"`
Columns []string `json:"columns"`
Data [][]int `json:"data"`
}
type whatTheFeeParams struct {
URL string `json:"url"`
PeriodSeconds int `periodSeconds:"url"`
}
type whatTheFeeFee struct {
blocks int
feesPerKB []int
}
type whatTheFeeData struct {
params whatTheFeeParams
probabilities []string
fees []whatTheFeeFee
lastSync time.Time
chain bchain.BlockChain
mux sync.Mutex
}
var whatTheFee whatTheFeeData
// InitWhatTheFee initializes https://whatthefee.io handler
func InitWhatTheFee(chain bchain.BlockChain, params string) error {
err := json.Unmarshal([]byte(params), &whatTheFee.params)
if err != nil {
return err
}
if whatTheFee.params.URL == "" || whatTheFee.params.PeriodSeconds == 0 {
return errors.New("Missing parameters")
}
whatTheFee.chain = chain
go whatTheFeeDownloader()
return nil
}
func whatTheFeeDownloader() {
period := time.Duration(whatTheFee.params.PeriodSeconds) * time.Second
timer := time.NewTimer(period)
counter := 0
for {
var data whatTheFeeServiceResult
err := whatTheFeeGetData(&data)
if err != nil {
glog.Error("whatTheFeeGetData ", err)
} else {
if whatTheFeeProcessData(&data) {
if counter%60 == 0 {
whatTheFeeCompareToDefault()
}
counter++
}
}
<-timer.C
timer.Reset(period)
}
}
func whatTheFeeProcessData(data *whatTheFeeServiceResult) bool {
if len(data.Index) == 0 || len(data.Index) != len(data.Data) || len(data.Columns) == 0 {
glog.Errorf("invalid data %+v", data)
return false
}
whatTheFee.mux.Lock()
defer whatTheFee.mux.Unlock()
whatTheFee.probabilities = data.Columns
whatTheFee.fees = make([]whatTheFeeFee, len(data.Index))
for i, blocks := range data.Index {
if len(data.Columns) != len(data.Data[i]) {
glog.Errorf("invalid data %+v", data)
return false
}
fees := make([]int, len(data.Columns))
for j, l := range data.Data[i] {
fees[j] = int(1000 * math.Exp(float64(l)/100))
}
whatTheFee.fees[i] = whatTheFeeFee{
blocks: blocks,
feesPerKB: fees,
}
}
whatTheFee.lastSync = time.Now()
glog.Infof("%+v", whatTheFee.fees)
return true
}
func whatTheFeeGetData(res interface{}) error {
var httpData []byte
httpReq, err := http.NewRequest("GET", whatTheFee.params.URL, bytes.NewBuffer(httpData))
if err != nil {
return err
}
httpRes, err := http.DefaultClient.Do(httpReq)
if httpRes != nil {
defer httpRes.Body.Close()
}
if err != nil {
return err
}
if httpRes.StatusCode != 200 {
return errors.New("whatthefee.io returned status " + strconv.Itoa(httpRes.StatusCode))
}
return safeDecodeResponse(httpRes.Body, &res)
}
func whatTheFeeCompareToDefault() {
output := ""
for _, fee := range whatTheFee.fees {
output += fmt.Sprint(fee.blocks, ",")
for _, wtf := range fee.feesPerKB {
output += fmt.Sprint(wtf, ",")
}
conservative, err := whatTheFee.chain.EstimateSmartFee(fee.blocks, true)
if err != nil {
glog.Error(err)
return
}
economical, err := whatTheFee.chain.EstimateSmartFee(fee.blocks, false)
if err != nil {
glog.Error(err)
return
}
output += fmt.Sprint(conservative.String(), ",", economical.String(), "\n")
}
glog.Info("whatTheFeeCompareToDefault\n", output)
}

View File

@ -1,25 +1,29 @@
package btg
import (
"blockbook/bchain"
"blockbook/bchain/coins/btc"
"blockbook/bchain/coins/utils"
"bytes"
"encoding/binary"
"io"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/jakm/btcutil/chaincfg"
"github.com/martinboehm/btcd/chaincfg/chainhash"
"github.com/martinboehm/btcd/wire"
"github.com/martinboehm/btcutil/chaincfg"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
"spacecruft.org/spacecruft/blockbook/bchain/coins/utils"
)
const (
// MainnetMagic is mainnet network constant
MainnetMagic wire.BitcoinNet = 0x446d47e1
// TestnetMagic is testnet network constant
TestnetMagic wire.BitcoinNet = 0x456e48e2
)
var (
// MainNetParams are parser parameters for mainnet
MainNetParams chaincfg.Params
// TestNetParams are parser parameters for testnet
TestNetParams chaincfg.Params
)

View File

@ -3,7 +3,6 @@
package btg
import (
"blockbook/bchain/coins/btc"
"bytes"
"encoding/hex"
"fmt"
@ -12,7 +11,8 @@ import (
"path/filepath"
"testing"
"github.com/jakm/btcutil/chaincfg"
"github.com/martinboehm/btcutil/chaincfg"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
)
func TestMain(m *testing.M) {
@ -28,7 +28,7 @@ type testBlock struct {
}
var testParseBlockTxs = map[int]testBlock{
104000: testBlock{
104000: {
size: 15776,
time: 1295705889,
txs: []string{
@ -81,7 +81,7 @@ var testParseBlockTxs = map[int]testBlock{
"33ad36d79d63b575c7532c516f16b19541f5c637caf7073beb7ddf604c3f39cc",
},
},
532144: testBlock{
532144: {
size: 12198,
time: 1528372417,
txs: []string{

View File

@ -1,11 +1,11 @@
package btg
import (
"blockbook/bchain"
"blockbook/bchain/coins/btc"
"encoding/json"
"github.com/golang/glog"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
)
// BGoldRPC is an interface to JSON-RPC bitcoind service.
@ -13,7 +13,7 @@ type BGoldRPC struct {
*btc.BitcoinRPC
}
// NewBCashRPC returns new BGoldRPC instance.
// NewBGoldRPC returns new BGoldRPC instance.
func NewBGoldRPC(config json.RawMessage, pushHandler func(bchain.NotificationType)) (bchain.BlockChain, error) {
b, err := btc.NewBitcoinRPC(config, pushHandler)
if err != nil {
@ -29,10 +29,11 @@ func NewBGoldRPC(config json.RawMessage, pushHandler func(bchain.NotificationTyp
// Initialize initializes BGoldRPC instance.
func (b *BGoldRPC) Initialize() error {
chainName, err := b.GetChainInfoAndInitializeMempool(b)
ci, err := b.GetChainInfo()
if err != nil {
return err
}
chainName := ci.Chain
params := GetChainParams(chainName)

View File

@ -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
}
}

View File

@ -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
}

View File

@ -0,0 +1 @@
00000020d5add6362e89ca36f34c908e030bb2467ab919cd7a961b2616aa756606000000dd88d513ccaf848da5a42d5b7af930f8ead36ed3b42b8c4b8c6b6b94ec512c95bf946a5d27980a1d10001de501010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff200362380104bf946a5d086000232f000000000d2f6e6f64655374726174756d2f00000000030000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf980010b27010000001976a914697ab38c1db7d672f5dbedff8936c290f906742c88ac80f0fa02000000001976a914fdcd08e6cb3451c95057130f94ef88b78a70b88e88ac0120000000000000000000000000000000000000000000000000000000000000000000000000

View File

@ -0,0 +1 @@
0000002010588a0622de0f5b07b03f65fc661efbcbc262c46218efe9924eb8b1040000000beb93211c662280d24e8b18edc2d5d63b13e91be4c905db2d997d7293ee6f30f3966a5d9ceb0a1deeeef9e702010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff200369380104f3966a5d0860007697000000000d2f6e6f64655374726174756d2f00000000030000000000000000266a24aa21a9ede177006e83279e577b138ece75a323e0fa913da994727d1166dbee18c5bf20b9cb751027010000001976a914697ab38c1db7d672f5dbedff8936c290f906742c88ac9afefa02000000001976a914fdcd08e6cb3451c95057130f94ef88b78a70b88e88ac01200000000000000000000000000000000000000000000000000000000000000000000000000200000003837669f694a699355c53550cb151a939e9626cabefaae45c98ef1a493986ee2e000000006a473044022035f1d62eb19f6b444170b1bc0db9d5133cd514403f289d7a1505a34be872882f02200420fc3a6b3b127b6ac12e516d32c6bb8e08733c1932c3b29e36538e2713d337012103eda5ae5990c8bf9155a14df837f42398f895d5a3c94d8ee7b51034d177b044f2feffffffc73fb3c2a22b1f4f960971c40502cdf37d54648ad43381324dd1ee2d5f065831020000006a473044022043ce48c511bbe514b44cd3c531259c382fe535b6ea405dcb42720cf3ddcbbadb02203693f81b0de34b7a6479babd0c74a5c907858d876b617cc031cdcae585a9c63e0121025e6cd086754c0264cecbab789fae6ab7e2b551030fb88d2bef5b32c1fd99ca02feffffffce4d953b655a1978c1b39653602514b436f6478f1eac9b816462ac4a448435e5000000006b483045022100e0f55692f19b2498fd4116f7e25cb80be29cf6f5198a2f7161cb3cf920984d370220524b0f63aff32f1f7364bc7393b6bbca956ed954eb9089d1279afc66d86108ca012103a8986d67d7c01ca1c02d0e86cc7b1b0c7a172c9cffd7729bc308d6fa8110b6aefeffffff046636021b000000001976a914add8a0089511e14059d632c7bd980e8db1be62fd88ac8efafe3c000000001976a914317ea248669f7ef786ef867ce90214cba9edef8a88ac303bae3b000000001976a9141abe5eab04d3dc3b7deeb16fe3ba3674205a907588ac9e192e72000000001976a914cd6d6c32dee3d00c9c1afcc3f93ce9a4f30333a188ac67380100

View File

@ -1,21 +1,27 @@
package dash
import (
"blockbook/bchain/coins/btc"
"github.com/btcsuite/btcd/wire"
"github.com/jakm/btcutil/chaincfg"
"github.com/martinboehm/btcd/wire"
"github.com/martinboehm/btcutil/chaincfg"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
)
const (
// MainnetMagic is mainnet network constant
MainnetMagic wire.BitcoinNet = 0xbd6b0cbf
// TestnetMagic is testnet network constant
TestnetMagic wire.BitcoinNet = 0xffcae2ce
// RegtestMagic is regtest network constant
RegtestMagic wire.BitcoinNet = 0xdcb7c1fc
)
var (
// MainNetParams are parser parameters for mainnet
MainNetParams chaincfg.Params
// TestNetParams are parser parameters for testnet
TestNetParams chaincfg.Params
// RegtestParams are parser parameters for regtest
RegtestParams chaincfg.Params
)
@ -45,11 +51,15 @@ func init() {
// DashParser handle
type DashParser struct {
*btc.BitcoinParser
baseparser *bchain.BaseParser
}
// NewDashParser returns new DashParser instance
func NewDashParser(params *chaincfg.Params, c *btc.Configuration) *DashParser {
return &DashParser{BitcoinParser: btc.NewBitcoinParser(params, c)}
return &DashParser{
BitcoinParser: btc.NewBitcoinParser(params, c),
baseparser: &bchain.BaseParser{},
}
}
// GetChainParams contains network parameters for the main Dash network,
@ -77,3 +87,13 @@ func GetChainParams(chain string) *chaincfg.Params {
return &MainNetParams
}
}
// PackTx packs transaction to byte array using protobuf
func (p *DashParser) PackTx(tx *bchain.Tx, height uint32, blockTime int64) ([]byte, error) {
return p.baseparser.PackTx(tx, height, blockTime)
}
// UnpackTx unpacks transaction from protobuf byte array
func (p *DashParser) UnpackTx(buf []byte) (*bchain.Tx, uint32, error) {
return p.baseparser.UnpackTx(buf)
}

View File

@ -0,0 +1,334 @@
// +build unittest
package dash
import (
"bytes"
"encoding/hex"
"fmt"
"io/ioutil"
"math/big"
"path/filepath"
"reflect"
"testing"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
)
type testBlock struct {
size int
time int64
txs []string
}
var testParseBlockTxs = map[int]testBlock{
500001: {
size: 6521,
time: 1468043164,
txs: []string{
"6d45c761892eb07b7651140aa42901b03cb501a82585fb360ff8f155d46727b0",
"5f6cbefca8a48cccd40805013e5f6c602e0d35c3511ccb7a2ae25e51dd95d38f",
"0eb37faa8b2f24c68c8e4a3009a051caded005d5f13f0dc216ff6422256f6b7b",
"2565cfacb7f8cbc73dc1053b06ca527d9798bf6bf29a778cb5924a17dd167a39",
"85f93911e8a4d8d9bbf3de009a666ed594d62aa41a34a9e3763058067e64f084",
"46712f0f32a392c71df443798e120d0f40eb93063631a992b3dedd4d4afc04e4",
"1790efaa05caad7ab546ef479041c3cb1cb9bca7b9cc7992566d2e2344701167",
},
},
// last block without special transactions, valid for bitcoin parser
1028159: {
size: 8608,
time: 1551246608,
txs: []string{
"a800f5b2dde5d48bda08d9d6fc5647c41cec902ce690a5a2be0665e6acf77c35",
"981d6668e65b70fcd97ddd68319f3c5e5163e510cc0ed479be5667bf1782f036",
"b9fd19d37ec97d038da2ccad9414ef311275d5fd3762bdec3e76f535e2295f4c",
"1b4051d02c9919ef8d482cadf6ca2002442d9436b444923cd295fe56009ec52b",
"6f1ec8472624b8e7481024ee8b228086b9b32606790e94f161589d3fe2b3a826",
"b12c512803d733f3f7afce846e18c6a46c713533cdb18a13392cbda88866523c",
"69e6d67946ed660b440c8e457933ae594ce60acdbe17ded091ce0ac6f41ed186",
"755c3b7cad9b569def3f69f897da2ee7732ee2e0a165965512680b4fe9086e12",
"63de772ff400789c2c3ad9be653817bebf92551e139d80c8e735bb0610865500",
"1566bd9bb2413c63412a13d16c7017814363f668d8b3bfe66a5478734e73f010",
"d7f441b0abca7df6530a0620661c839244bb6f26e1b4a53b783fb3acc1f5f42a",
"ec7762d0e02a87e311b128662db8ef4161dcc9d9f2831250c7366eed98fc744b",
"b977669bfc0ac3b9ca9de7512fda564a69fe49dde8a286fcb7ea99147db54b5f",
},
},
// first block with special transactions, invalid for bitcoin parser
// 1028160: {
// size: 2347,
// time: 1551246710,
// txs: []string{
// "71d6975e3b79b52baf26c3269896a34f3bedfb04561c692ffa31f64dada1f9c4",
// "ed732a404cdfd4e0475a7a016200b7eef191f2c9de0ffdef8a20091c0499299c",
// "99d0613f82ea1f928bbc98318665adbdf5b40d206bd487fe77d542e86c903f55",
// "05cbf334a563468d0e378c56b43fb5254ee9c0e35ca8fab5cb242ebd825ae97b",
// "ee91fae7be36b3b81bc60992e904e1ae91e7dffdd5751ccaef557ba62ea80a4f",
// },
// },
}
func helperLoadBlock(t *testing.T, height int) []byte {
name := fmt.Sprintf("block_dump.%d", height)
path := filepath.Join("testdata", name)
d, err := ioutil.ReadFile(path)
if err != nil {
t.Fatal(err)
}
d = bytes.TrimSpace(d)
b := make([]byte, hex.DecodedLen(len(d)))
_, err = hex.Decode(b, d)
if err != nil {
t.Fatal(err)
}
return b
}
func TestParseBlock(t *testing.T) {
p := NewDashParser(GetChainParams("main"), &btc.Configuration{})
for height, tb := range testParseBlockTxs {
b := helperLoadBlock(t, height)
blk, err := p.ParseBlock(b)
if err != nil {
t.Errorf("ParseBlock() error %v", err)
}
if blk.Size != tb.size {
t.Errorf("ParseBlock() block size: got %d, want %d", blk.Size, tb.size)
}
if blk.Time != tb.time {
t.Errorf("ParseBlock() block time: got %d, want %d", blk.Time, tb.time)
}
if len(blk.Txs) != len(tb.txs) {
t.Errorf("ParseBlock() number of transactions: got %d, want %d", len(blk.Txs), len(tb.txs))
}
for ti, tx := range tb.txs {
if blk.Txs[ti].Txid != tx {
t.Errorf("ParseBlock() transaction %d: got %s, want %s", ti, blk.Txs[ti].Txid, tx)
}
}
}
}
var (
testTx1 = bchain.Tx{
Blocktime: 1551246710,
Confirmations: 0,
Hex: "0100000001f85264d11a747bdba77d411e5e4a3d35e3aeb5843b34a95234a2121ac65496bd000000006b483045022100dfa158fbd9773fab4f6f329c807e040af0c3a40967cbe01667169b914ed5ad960220061c5876364caa3e3c9c990ad2b4cc8b1a53d4f954dbda8434b0e67cc8348ff6012103093865e1e132b33a2a5ed01c79d2edba3473826a66cb26b8311bfa42749c2190ffffffff02ec3f8a2a010000001976a91470dcef2a22575d7a8f0779fb1d6cdd48135bd22788ac3116491d000000001976a91471348f7780e955a2a60eba17ecc4c826ebc23a9888ac00000000",
LockTime: 0,
Time: 1551246710,
Txid: "ed732a404cdfd4e0475a7a016200b7eef191f2c9de0ffdef8a20091c0499299c",
Version: 1,
Vin: []bchain.Vin{
{
Txid: "bd9654c61a12a23452a9343b84b5aee3353d4a5e1e417da7db7b741ad16452f8",
Vout: 0,
ScriptSig: bchain.ScriptSig{
Hex: "483045022100dfa158fbd9773fab4f6f329c807e040af0c3a40967cbe01667169b914ed5ad960220061c5876364caa3e3c9c990ad2b4cc8b1a53d4f954dbda8434b0e67cc8348ff6012103093865e1e132b33a2a5ed01c79d2edba3473826a66cb26b8311bfa42749c2190",
},
Sequence: 4294967295,
},
},
Vout: []bchain.Vout{
{
N: 0,
ScriptPubKey: bchain.ScriptPubKey{
Addresses: []string{"XkycBX1ykVXXs92pAi6ZQwZPEre9kSHHKH"},
Hex: "76a91470dcef2a22575d7a8f0779fb1d6cdd48135bd22788ac",
},
ValueSat: *big.NewInt(5008670700),
},
{
N: 1,
ScriptPubKey: bchain.ScriptPubKey{
Addresses: []string{"Xm1R9thKBm2EZKZevXsmMX4DVwQQuTohZu"},
Hex: "76a91471348f7780e955a2a60eba17ecc4c826ebc23a9888ac",
},
ValueSat: *big.NewInt(491329073),
},
},
}
testTxPacked1 = "0a20ed732a404cdfd4e0475a7a016200b7eef191f2c9de0ffdef8a20091c0499299c12e2010100000001f85264d11a747bdba77d411e5e4a3d35e3aeb5843b34a95234a2121ac65496bd000000006b483045022100dfa158fbd9773fab4f6f329c807e040af0c3a40967cbe01667169b914ed5ad960220061c5876364caa3e3c9c990ad2b4cc8b1a53d4f954dbda8434b0e67cc8348ff6012103093865e1e132b33a2a5ed01c79d2edba3473826a66cb26b8311bfa42749c2190ffffffff02ec3f8a2a010000001976a91470dcef2a22575d7a8f0779fb1d6cdd48135bd22788ac3116491d000000001976a91471348f7780e955a2a60eba17ecc4c826ebc23a9888ac0000000018f6cad8e305200028c0e03e3299010a001220bd9654c61a12a23452a9343b84b5aee3353d4a5e1e417da7db7b741ad16452f81800226b483045022100dfa158fbd9773fab4f6f329c807e040af0c3a40967cbe01667169b914ed5ad960220061c5876364caa3e3c9c990ad2b4cc8b1a53d4f954dbda8434b0e67cc8348ff6012103093865e1e132b33a2a5ed01c79d2edba3473826a66cb26b8311bfa42749c219028ffffffff0f3a480a05012a8a3fec10001a1976a91470dcef2a22575d7a8f0779fb1d6cdd48135bd22788ac2222586b7963425831796b565858733932704169365a51775a50457265396b5348484b483a470a041d49163110011a1976a91471348f7780e955a2a60eba17ecc4c826ebc23a9888ac2222586d31523974684b426d32455a4b5a657658736d4d5834445677515175546f685a754001"
testTx2 = bchain.Tx{
Blocktime: 1551246710,
Confirmations: 0,
Hex: "03000500010000000000000000000000000000000000000000000000000000000000000000ffffffff170340b00f1291af3c09542bc8349901000000002f4e614effffffff024181f809000000001976a9146a341485a9444b35dc9cb90d24e7483de7d37e0088ac3581f809000000001976a9140d1156f6026bf975ea3553b03fb534d0959c294c88ac0000000026010040b00f000000000000000000000000000000000000000000000000000000000000000000",
LockTime: 0,
Time: 1551246710,
Txid: "71d6975e3b79b52baf26c3269896a34f3bedfb04561c692ffa31f64dada1f9c4",
Version: 3,
Vin: []bchain.Vin{
{
Coinbase: "0340b00f1291af3c09542bc8349901000000002f4e614e",
Sequence: 4294967295,
},
},
Vout: []bchain.Vout{
{
N: 0,
ScriptPubKey: bchain.ScriptPubKey{
Addresses: []string{"XkNPrBSJtrHZUvUqb3JF4g5rMB3uzaJfEL"},
Hex: "76a9146a341485a9444b35dc9cb90d24e7483de7d37e0088ac",
},
ValueSat: *big.NewInt(167280961),
},
{
N: 1,
ScriptPubKey: bchain.ScriptPubKey{
Addresses: []string{"XbswPXhcLqm5AN5gwcTTyiUGSP2YndWwk9"},
Hex: "76a9140d1156f6026bf975ea3553b03fb534d0959c294c88ac",
},
ValueSat: *big.NewInt(167280949),
},
},
}
testTxPacked2 = "0a2071d6975e3b79b52baf26c3269896a34f3bedfb04561c692ffa31f64dada1f9c412b50103000500010000000000000000000000000000000000000000000000000000000000000000ffffffff170340b00f1291af3c09542bc8349901000000002f4e614effffffff024181f809000000001976a9146a341485a9444b35dc9cb90d24e7483de7d37e0088ac3581f809000000001976a9140d1156f6026bf975ea3553b03fb534d0959c294c88ac0000000026010040b00f00000000000000000000000000000000000000000000000000000000000000000018f6cad8e305200028c0e03e32380a2e30333430623030663132393161663363303935343262633833343939303130303030303030303266346536313465180028ffffffff0f3a470a0409f8814110001a1976a9146a341485a9444b35dc9cb90d24e7483de7d37e0088ac2222586b4e507242534a7472485a5576557162334a46346735724d4233757a614a66454c3a470a0409f8813510011a1976a9140d1156f6026bf975ea3553b03fb534d0959c294c88ac222258627377505868634c716d35414e35677763545479695547535032596e6457776b394003"
)
func TestBaseParser_ParseTxFromJson(t *testing.T) {
p := NewDashParser(GetChainParams("main"), &btc.Configuration{})
tests := []struct {
name string
msg string
want *bchain.Tx
wantErr bool
}{
{
name: "normal tx",
msg: `{"hex":"0100000001f85264d11a747bdba77d411e5e4a3d35e3aeb5843b34a95234a2121ac65496bd000000006b483045022100dfa158fbd9773fab4f6f329c807e040af0c3a40967cbe01667169b914ed5ad960220061c5876364caa3e3c9c990ad2b4cc8b1a53d4f954dbda8434b0e67cc8348ff6012103093865e1e132b33a2a5ed01c79d2edba3473826a66cb26b8311bfa42749c2190ffffffff02ec3f8a2a010000001976a91470dcef2a22575d7a8f0779fb1d6cdd48135bd22788ac3116491d000000001976a91471348f7780e955a2a60eba17ecc4c826ebc23a9888ac00000000","txid":"ed732a404cdfd4e0475a7a016200b7eef191f2c9de0ffdef8a20091c0499299c","size":226,"version":1,"type":0,"locktime":0,"vin":[{"txid":"bd9654c61a12a23452a9343b84b5aee3353d4a5e1e417da7db7b741ad16452f8","vout":0,"scriptSig":{"asm":"3045022100dfa158fbd9773fab4f6f329c807e040af0c3a40967cbe01667169b914ed5ad960220061c5876364caa3e3c9c990ad2b4cc8b1a53d4f954dbda8434b0e67cc8348ff6[ALL]03093865e1e132b33a2a5ed01c79d2edba3473826a66cb26b8311bfa42749c2190","hex":"483045022100dfa158fbd9773fab4f6f329c807e040af0c3a40967cbe01667169b914ed5ad960220061c5876364caa3e3c9c990ad2b4cc8b1a53d4f954dbda8434b0e67cc8348ff6012103093865e1e132b33a2a5ed01c79d2edba3473826a66cb26b8311bfa42749c2190"},"value":55.00000000,"valueSat":5500000000,"address":"Xgcv4bKAXaWf5sjX9KR49L98jeMwNgeXWh","sequence":4294967295}],"vout":[{"value":50.08670700,"valueSat":5008670700,"n":0,"scriptPubKey":{"asm":"OP_DUPOP_HASH16070dcef2a22575d7a8f0779fb1d6cdd48135bd227OP_EQUALVERIFYOP_CHECKSIG","hex":"76a91470dcef2a22575d7a8f0779fb1d6cdd48135bd22788ac","reqSigs":1,"type":"pubkeyhash","addresses":["XkycBX1ykVXXs92pAi6ZQwZPEre9kSHHKH"]}},{"value":4.91329073,"valueSat":491329073,"n":1,"scriptPubKey":{"asm":"OP_DUPOP_HASH16071348f7780e955a2a60eba17ecc4c826ebc23a98OP_EQUALVERIFYOP_CHECKSIG","hex":"76a91471348f7780e955a2a60eba17ecc4c826ebc23a9888ac","reqSigs":1,"type":"pubkeyhash","addresses":["Xm1R9thKBm2EZKZevXsmMX4DVwQQuTohZu"]}}],"blockhash":"000000000000002099caaf1a877911d99a5980ede9b981280eecb291afedf87b","height":1028160,"confirmations":0,"time":1551246710,"blocktime":1551246710,"instantlock":false}`,
want: &testTx1,
},
{
name: "special tx - DIP2",
msg: `{"hex":"03000500010000000000000000000000000000000000000000000000000000000000000000ffffffff170340b00f1291af3c09542bc8349901000000002f4e614effffffff024181f809000000001976a9146a341485a9444b35dc9cb90d24e7483de7d37e0088ac3581f809000000001976a9140d1156f6026bf975ea3553b03fb534d0959c294c88ac0000000026010040b00f000000000000000000000000000000000000000000000000000000000000000000","txid":"71d6975e3b79b52baf26c3269896a34f3bedfb04561c692ffa31f64dada1f9c4","size":181,"version":3,"type":5,"locktime":0,"vin":[{"coinbase":"0340b00f1291af3c09542bc8349901000000002f4e614e","sequence":4294967295}],"vout":[{"value":1.67280961,"valueSat":167280961,"n":0,"scriptPubKey":{"asm":"OP_DUPOP_HASH1606a341485a9444b35dc9cb90d24e7483de7d37e00OP_EQUALVERIFYOP_CHECKSIG","hex":"76a9146a341485a9444b35dc9cb90d24e7483de7d37e0088ac","reqSigs":1,"type":"pubkeyhash","addresses":["XkNPrBSJtrHZUvUqb3JF4g5rMB3uzaJfEL"]}},{"value":1.67280949,"valueSat":167280949,"n":1,"scriptPubKey":{"asm":"OP_DUPOP_HASH1600d1156f6026bf975ea3553b03fb534d0959c294cOP_EQUALVERIFYOP_CHECKSIG","hex":"76a9140d1156f6026bf975ea3553b03fb534d0959c294c88ac","reqSigs":1,"type":"pubkeyhash","addresses":["XbswPXhcLqm5AN5gwcTTyiUGSP2YndWwk9"]}}],"extraPayloadSize":38,"extraPayload":"010040b00f000000000000000000000000000000000000000000000000000000000000000000","cbTx":{"version":1,"height":1028160,"merkleRootMNList":"0000000000000000000000000000000000000000000000000000000000000000"},"blockhash":"000000000000002099caaf1a877911d99a5980ede9b981280eecb291afedf87b","height":1028160,"confirmations":0,"time":1551246710,"blocktime":1551246710,"instantlock":false}`,
want: &testTx2,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := p.ParseTxFromJson([]byte(tt.msg))
if (err != nil) != tt.wantErr {
t.Errorf("DashParser.ParseTxFromJson() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("DashParser.ParseTxFromJson() = %+v, want %+v", got, tt.want)
}
})
}
}
func Test_PackTx(t *testing.T) {
type args struct {
tx bchain.Tx
height uint32
blockTime int64
parser *DashParser
}
tests := []struct {
name string
args args
want string
wantErr bool
}{
{
name: "dash-1",
args: args{
tx: testTx1,
height: 1028160,
blockTime: 1551246710,
parser: NewDashParser(GetChainParams("main"), &btc.Configuration{}),
},
want: testTxPacked1,
wantErr: false,
},
{
name: "dash-2",
args: args{
tx: testTx2,
height: 1028160,
blockTime: 1551246710,
parser: NewDashParser(GetChainParams("main"), &btc.Configuration{}),
},
want: testTxPacked2,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := tt.args.parser.PackTx(&tt.args.tx, tt.args.height, tt.args.blockTime)
if (err != nil) != tt.wantErr {
t.Errorf("packTx() error = %v, wantErr %v", err, tt.wantErr)
return
}
h := hex.EncodeToString(got)
if !reflect.DeepEqual(h, tt.want) {
t.Errorf("packTx() = %v, want %v", h, tt.want)
}
})
}
}
func Test_UnpackTx(t *testing.T) {
type args struct {
packedTx string
parser *DashParser
}
tests := []struct {
name string
args args
want *bchain.Tx
want1 uint32
wantErr bool
}{
{
name: "dash-1",
args: args{
packedTx: testTxPacked1,
parser: NewDashParser(GetChainParams("main"), &btc.Configuration{}),
},
want: &testTx1,
want1: 1028160,
wantErr: false,
},
{
name: "dash-2",
args: args{
packedTx: testTxPacked2,
parser: NewDashParser(GetChainParams("main"), &btc.Configuration{}),
},
want: &testTx2,
want1: 1028160,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
b, _ := hex.DecodeString(tt.args.packedTx)
got, got1, err := tt.args.parser.UnpackTx(b)
if (err != nil) != tt.wantErr {
t.Errorf("unpackTx() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("unpackTx() got = %v, want %v", got, tt.want)
}
if got1 != tt.want1 {
t.Errorf("unpackTx() got1 = %v, want %v", got1, tt.want1)
}
})
}
}

View File

@ -1,19 +1,22 @@
package dash
import (
"blockbook/bchain"
"blockbook/bchain/coins/btc"
"encoding/json"
"github.com/golang/glog"
"github.com/juju/errors"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
)
// DashRPC is an interface to JSON-RPC bitcoind service.
const firstBlockWithSpecialTransactions = 1028160
// DashRPC is an interface to JSON-RPC bitcoind service
type DashRPC struct {
*btc.BitcoinRPC
}
// NewDashRPC returns new DashRPC instance.
// NewDashRPC returns new DashRPC instance
func NewDashRPC(config json.RawMessage, pushHandler func(bchain.NotificationType)) (bchain.BlockChain, error) {
b, err := btc.NewBitcoinRPC(config, pushHandler)
if err != nil {
@ -31,10 +34,11 @@ func NewDashRPC(config json.RawMessage, pushHandler func(bchain.NotificationType
// Initialize initializes DashRPC instance.
func (b *DashRPC) Initialize() error {
chainName, err := b.GetChainInfoAndInitializeMempool(b)
ci, err := b.GetChainInfo()
if err != nil {
return err
}
chainName := ci.Chain
params := GetChainParams(chainName)
@ -54,3 +58,57 @@ func (b *DashRPC) Initialize() error {
return nil
}
// GetBlock returns block with given hash
func (b *DashRPC) GetBlock(hash string, height uint32) (*bchain.Block, error) {
if hash == "" && height < firstBlockWithSpecialTransactions {
return b.BitcoinRPC.GetBlock(hash, height)
}
var err error
if hash == "" && height > 0 {
hash, err = b.GetBlockHash(height)
if err != nil {
return nil, err
}
}
glog.V(1).Info("rpc: getblock (verbosity=1) ", hash)
res := btc.ResGetBlockThin{}
req := btc.CmdGetBlock{Method: "getblock"}
req.Params.BlockHash = hash
req.Params.Verbosity = 1
err = b.Call(&req, &res)
if err != nil {
return nil, errors.Annotatef(err, "hash %v", hash)
}
if res.Error != nil {
return nil, errors.Annotatef(res.Error, "hash %v", hash)
}
txs := make([]bchain.Tx, 0, len(res.Result.Txids))
for _, txid := range res.Result.Txids {
tx, err := b.GetTransaction(txid)
if err != nil {
if err == bchain.ErrTxNotFound {
glog.Errorf("rpc: getblock: skipping transanction in block %s due error: %s", hash, err)
continue
}
return nil, err
}
txs = append(txs, *tx)
}
block := &bchain.Block{
BlockHeader: res.Result.BlockHeader,
Txs: txs,
}
return block, nil
}
// GetTransactionForMempool returns a transaction by the transaction ID.
// It could be optimized for mempool, i.e. without block time and confirmations
func (b *DashRPC) GetTransactionForMempool(txid string) (*bchain.Tx, error) {
return b.GetTransaction(txid)
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
00000020b652661d4cc8a262f9561a313b8cb15fcb4f69e6f88e908b3e0000000000000048a11a95e5a224637dc3639bf6c20c0ee0ca16b66a5dba5a9823ad9515a772c07625765cb8f34119a74e94e10503000500010000000000000000000000000000000000000000000000000000000000000000ffffffff170340b00f1291af3c09542bc8349901000000002f4e614effffffff024181f809000000001976a9146a341485a9444b35dc9cb90d24e7483de7d37e0088ac3581f809000000001976a9140d1156f6026bf975ea3553b03fb534d0959c294c88ac0000000026010040b00f0000000000000000000000000000000000000000000000000000000000000000000100000001f85264d11a747bdba77d411e5e4a3d35e3aeb5843b34a95234a2121ac65496bd000000006b483045022100dfa158fbd9773fab4f6f329c807e040af0c3a40967cbe01667169b914ed5ad960220061c5876364caa3e3c9c990ad2b4cc8b1a53d4f954dbda8434b0e67cc8348ff6012103093865e1e132b33a2a5ed01c79d2edba3473826a66cb26b8311bfa42749c2190ffffffff02ec3f8a2a010000001976a91470dcef2a22575d7a8f0779fb1d6cdd48135bd22788ac3116491d000000001976a91471348f7780e955a2a60eba17ecc4c826ebc23a9888ac000000000200000002ce167f4b13fdd25facf0a92aba23f5220f8366ac5ed29b83a3a6f2c9f76f5726000000006b483045022100c39871831ee92830da00fe1ba69925f8476cf39bcd1629857bf59b908b53649402201d168fb69ea995ed49059b7d532475891d3b482d19778f339bb95f3a6be3df6701210387bfcd2d4f7e7e68549ecb21a14e6c56eb7c5c3c5c5fddf975106fa4891268b5feffffff01fc4e7dd7446a800cf382e1f668fb155faa6c0f31f9871cdc54d801b64edbcc000000006b4830450221008b55df7a57529004a0cdf6b4ff43be097f9451c86a8457f073dd5cbab3a60f3002205c5b68ed70901e235402c850bec7ca3fac7448faf5a6989de78c2215734a7de60121034e448205a98486cb2fcc0294fa88579b6a6ba0213cbcf7e68cb9527a63a5c826feffffff028cb45e00000000001976a914cf50f0c1cecf279dac4207cd5e77caf2e1b4193f88ac2989f809000000001976a914c646b9ce369333f0b174bf70ffe169464c81776a88ac3eb00f000200000001ff6f6384099ff092fcc1257ebd2ba723fc33a1b3c858c8e4390180a071ed15aa010000006b483045022100d6e867d1c5d815552d1efa4ec06b0111d8b180d708f3497bcd53e8d945aebdbb02205fa734c5a3bb18da893da3dcd9540a2ea7535418323e8c0c1b0a88cc932a1fe5012102e03200f8de25214eaf675554a2f28d2b60c7c4c545691729dec1c2fe0d3b630bfeffffff023d346901000000001976a914953ba93fae9ab552793533769a7de6f7630781e688ac56870603000000001976a914887933e453428b99b3b2dfdd8359f6d8016873e688ac39b00f0002000000082e51598a94eea6059479fb3b31605bbc44ce50c25f014e91bd8bdd5b14891442000000006b483045022100dd04fefab5ad17929d1ed8561335a7c493761e0cbf6a23b8445d21da7b09bd4b0220398f2df4d47bc0b924ce71754d452268485b85ab3f1a8e21529559b4302003510121036b56e0f8996c289842752bc71eb60f2f9964b28653d3857725efe1bdc3193386feffffffacd131246ada5f7ad177bdc6d236766ff78559506a5da90e81fcb26366ac377b010000006a47304402202932864432b51a9894128b74fc109951d182fc91bac3a67a61b3b7a83e3bea71022021dd28448b54bf560dca69c198cdd9a0d1184adb231c72141db19d5114559ffd012102102efafa2ca31499090aaea1bf1dfd91f16bf29c96202faf48cdf14a791f757cfeffffffecba1b7c9071c42ff79e5db479369d5adb8995ec1ea28ba75676d58c09c10089000000006b48304502210096b33a9a65cbeca58cdd0ca01d4792dd075ac832aecb2d113c522bc635f535bd0220736d726b6126c4845335ad61af1e7e193d7d8661ca69c749ca257191bd24447f01210357386ba93caf20c1fee3bccf00cd4ad96535e42bda22d22c605cf3cf7532dde4feffffff0e7e7a7cfffa2c6917dc5578eac7153b334d7f813d256ccea9e60d1eb9f62da3000000006a4730440220362935ea1a705d4c275d733e350f53798f05e8a39e6cdd03f3ba5434c6d78c370220031729d52d9f8d1a301ce59335af7a5b7fa70f0bcae42dd95254e22a725bd9d4012102f7164234b08f8a6f47d92fa01eac375d0c1ebd4300ce40412302a5ea0218c19afeffffffa0f6f00c4b99b7b25ef20e64ce4a4bf5e2ca13e3cdbe8e1d7ec7dfab1e0292ab000000006a473044022077a9137654cafd4a583ae5b04faf1d549ab5d1d0655e6b48b4e1242a596efca4022008372fc1bec3490c4376408d4c9eed32b4182563ba43943b4bb959c29c91058a0121022bd6d141f0c455c7f54cb690db6b1349223ddd27fb65d64bb9ac35735ef48c5bfeffffffab6cf7419933410d25538a971b13d099e155c0339c2257f350ab2ef24944c1b9000000006b483045022100dd7b537e334e3dac76c57eb0683ea4cd74fd9a59f55f928f8c7f94a19064d8ea022045f4452f3742bb6ee2a7080507433904f5fcb94ef9ae8aa6bac0778a9889f4ef012102d00a705fcd458bc3e6dc0eab9f449162f73fff0773a4e09e2a3164b39bc26983feffffffc817f12e090dfe54e5c0bb1546fb01a6eac23f49aafa367bbbb0dc857bc98dc9000000006b483045022100959e19c75158f235abd196643dff70762f6263a058ed4b7d19c00f20ba18fef00220402189f2c8dc56139812d65c76b2b1a90b5148d7ecda6326053afc26471f9261012103330db7800ba95f92006909aba0e270985cccb34c7787dbf4776b6762f97132abfeffffff540f639ab8f986bc0832181c9efd315a1af81d07e61b367cdb10cab58f9518ff000000006b48304502210083db93b6724fe929e984f3e5df3ece7c43277a926abc841cd4cd75d15d787de0022027a299e82e420d418da973f82afcd80cf986c022042d9c758f51af64aa08fb2e01210369eccadf5758ca5eac2f3ce63001294819c119d1411a294135280cfd423ac59dfeffffff028f650f00000000001976a914535960e621cc458918703e33a7644501299a9d2188ace5a2ea01000000001976a914dcdc4137f2e7a347fcb14c85a6d1730d3e212c4b88ac00000000

File diff suppressed because one or more lines are too long

View File

@ -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
}

View File

@ -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)
}
})
}
}

View File

@ -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)
}
}

View File

@ -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)
}

View File

@ -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)
}
})
}
}

View File

@ -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)
}

View File

@ -1,18 +1,21 @@
package digibyte
import (
"blockbook/bchain/coins/btc"
"github.com/btcsuite/btcd/wire"
"github.com/jakm/btcutil/chaincfg"
"github.com/martinboehm/btcd/wire"
"github.com/martinboehm/btcutil/chaincfg"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
)
const (
// MainnetMagic is mainnet network constant
MainnetMagic wire.BitcoinNet = 0xdab6c3fa
TestnetMagic wire.BitcoinNet = 0xddbdc8fd
)
var (
// MainNetParams are parser parameters for mainnet
MainNetParams chaincfg.Params
TestNetParams chaincfg.Params
)
func init() {
@ -21,6 +24,12 @@ func init() {
MainNetParams.PubKeyHashAddrID = []byte{30}
MainNetParams.ScriptHashAddrID = []byte{63}
MainNetParams.Bech32HRPSegwit = "dgb"
TestNetParams = chaincfg.TestNet3Params
TestNetParams.Net = TestnetMagic
TestNetParams.PubKeyHashAddrID = []byte{126}
TestNetParams.ScriptHashAddrID = []byte{140}
TestNetParams.Bech32HRPSegwit = "dgbt"
}
// DigiByteParser handle
@ -28,18 +37,27 @@ type DigiByteParser struct {
*btc.BitcoinParser
}
// NewDigiByteParser returns new VertcoinParser instance
// NewDigiByteParser returns new DigiByteParser instance
func NewDigiByteParser(params *chaincfg.Params, c *btc.Configuration) *DigiByteParser {
return &DigiByteParser{BitcoinParser: btc.NewBitcoinParser(params, c)}
}
// GetChainParams contains network parameters for the main DigiByte network
// and the DigiByte Testnet network
func GetChainParams(chain string) *chaincfg.Params {
if !chaincfg.IsRegistered(&MainNetParams) {
err := chaincfg.Register(&MainNetParams)
if err == nil {
err = chaincfg.Register(&TestNetParams)
}
if err != nil {
panic(err)
}
}
return &MainNetParams
switch chain {
case "test":
return &TestNetParams
default:
return &MainNetParams
}
}

View File

@ -3,15 +3,15 @@
package digibyte
import (
"blockbook/bchain"
"blockbook/bchain/coins/btc"
"encoding/hex"
"math/big"
"os"
"reflect"
"testing"
"github.com/jakm/btcutil/chaincfg"
"github.com/martinboehm/btcutil/chaincfg"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
)
func TestMain(m *testing.M) {

View File

@ -1,11 +1,11 @@
package digibyte
import (
"blockbook/bchain"
"blockbook/bchain/coins/btc"
"encoding/json"
"github.com/golang/glog"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
)
// DigiByteRPC is an interface to JSON-RPC bitcoind service.
@ -31,10 +31,11 @@ func NewDigiByteRPC(config json.RawMessage, pushHandler func(bchain.Notification
// Initialize initializes DigiByteRPC instance.
func (b *DigiByteRPC) Initialize() error {
chainName, err := b.GetChainInfoAndInitializeMempool(b)
ci, err := b.GetChainInfo()
if err != nil {
return err
}
chainName := ci.Chain
glog.Info("Chain name ", chainName)
params := GetChainParams(chainName)

View File

@ -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
}

View File

@ -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)
}
}
}
}

View File

@ -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
}

View File

@ -0,0 +1 @@
04000000881f4e2a409a1b75c62ed6fdc6fa278b00f304bbe0729853bc336cbc30c4931da66efc4bd6260f95886061747185eace5294209dbe390fa3408534e42a0f5d525dba255dfe78101b0000000000000000000000000000000000000000000000000000000000000000000000000201000000010000000000000000000000000000000000000000000000000000000000000000ffffffff06036f37060101ffffffff01000000000000000000000000000100000001652779264ccc941129adb833b8a42a6d278a98f295d25a0a53868ab1dc13bed4020000006a473044022046c6ab760d25c7d94f20aa2c81cc281a57bb3fa174023dcaac385727ab05f214022056841e947398a1d86bf7d0b99191f7d3421d0abf6ad1357955d38f5fe6c3a8c40121023c85ccefc390984ba8f71a0f0b999b7b6c6e9a30ccd63087d67d34d5c1fefc7dffffffff0300000000000000000080170a4e830000001976a914771f45c78657c739f0cc26dfc0b5a87ddd6e431c88ac009ca6920c0000001976a914e7c53cbb09860c3713b44958dd644e4d9154345688ac00000000412066000b291e3e3fd8f1fce0812ba4aaad2502a702fd3466a3d8a7dc1a78c816be01209f1179d41f7d38da229f1843a9c19cf67a9d610c2a767979ea31b264f3eb

View File

@ -0,0 +1 @@
040000000ff149a32a715106e7023cd41193d7ea64ca0092edb4659f035a881fc452b845e9249f2c4463db2074e307ba62e93619fd3d17a3b6b7437a326a79869f35c00c5e58265db760101b0000000000000000000000000000000000000000000000000000000000000000000000000501000000010000000000000000000000000000000000000000000000000000000000000000ffffffff06030a3a060101ffffffff01000000000000000000000000000100000001fdb6b4d9d9aa8afbdaac2d9bc4c29371147a6a3672529ad7ae45a7944558ec93020000006a473044022061bc3ffe2b069a39acdba6a169ae4fbaa34b32360ff9a45fce9c92eb0434ca6602206fa6c22110f4a14c8cee58e458e3522f41993e461278ef8ccb94322595981ec1012103b2e3a485f7a7da6eb16ea025ef45efa613d18350b2538899fab0b5a2e86ffda3ffffffff0300000000000000000000a882a22d0000001976a914899ef2e7669830485476f9c910a8330959d245e188ac009ca6920c0000001976a914a9147e5ce1507c2f49d9ceb8bbd2d280fb410b8188ac000000000100000002a0a25308b7afb81327a4a8cb1d13aa54fa53edf292bdf7fe2739f297f4027bb4000000006b4830450221009396b072158c158e7e90cdd797a0a58caf15c7e1589030d542a32e3568324a22022063d90727f3a71a9c01f75ece872ee0240f46eeac14531a7f2bfdb3fe5d0e793f01210263e67be5854349c3c8e23fea5020aa2b7bfd46916ce4e805676e4cd3b375cda5ffffffff8c7745969fbcd3bfb698d51cc8b110fe540d6a0ed9816bd2c1e375f0e7c2ec94000000006a4730440220748c808b1107ecf01559159c76163a2206af771604643aefea438f3c99c699a0022006c067c603fb0b4bc7a5cd129750fc010613957ec0666c41185d035b6f5fcbb1012102a535454382f62235104836bbbe1c894f1d6705c828b8be11f623068159882eb0ffffffff022018e505000000001976a914bba57e3d30f892d804d588ee633dcbb6a96b0cbb88ac0010a5d4e80000001976a914dd40b08afb0dc8496892bfe5e3ae1aceda57135b88ac000000000100000001583f174f32c563eb47555fbacdb0937881b4a25f35fe5f13d8c49cbf92654fdd000000006b483045022100cbacf5f775ab4fabdfb147c095c0865dd56bc461115b03a7a29ab96037fc4b3f02206b824c334b950d856550fff524717e07095fa2d9bbe8d5d8096813721d98715c012103f4e09f09921ad224e9e590df477cea01fc8114e816a2112ed59518bf99cdaa3effffffff0200f2052a010000001976a9148226210ccf912fb3969c0996a5582dcfb99a2e1788ac60b66daee00000001976a914d649408521dfef8449203417d77e44eadfc329f588ac00000000010000000199bd849243dea120d7d7df9037caa11327ed9df671e90c06442d12859ef4c052000000006a4730440220101b4e2022b3ffa023b83197d8ceb45b3ef31ff2da5eba7a6f7f914448397c31022023cb7908eafab080da4ff37460feb3c72e1ac6ecdb052d2df0c69a3954ec44550121026fd3594eb77ded5de88f54a528950659822859472fcee072a59657032aaf6fe2ffffffff02f071dee22c0000001976a914a316f474589a2f2013bd60b7d0ffb735e33915d188ac00e1823e0c0100001976a914ee628c1d309de0e0d3033d047b19921563f97ee288ac00000000411f3b6a2aabf7c3ccdd2e4d30a581a53670b6bc623e324eb3416100dfe64b3c54a84567bace9246f935b310e026c137eb7ec9836dfb46854aa6695af50a1ac5dd64

View File

@ -0,0 +1 @@
04000000c7fed5984ba7d11c7e4865c1d41ad1755a1082782d81a27fc08888bc749881e9e709a5bb1daf5a76582def89c13eba5e7c4cf8104407ecf81f74be6ba18e89c6ae3e275da61b0d1b0000000000000000000000000000000000000000000000000000000000000000000000000201000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0603de3d060101ffffffff01000000000000000000000000000100000001084b029489e1cddf726080c447c8a2b1d4bbe43024db31b8b19bc07585db9555010000006a473044022017422b9e3414d6233fa75f9eb7778469bebbb40686b0f7eb77d90a04c80149610220411f1063086fe205ea821ceb0de89e8158e202aba00f5ebb92b51f97381311fd012102ccb10a2f0603a0624b8708abefb5f4700631fc131c5de38b51e0359e2ffa7d1cffffffff03000000000000000000f260de1a580100001976a9145b1d583a4c270f2f14be77b298f0a9c6df97471388ac009ca6920c0000001976a914cb1196fb1b98d04b0cb8d2ffde3c2de3eb83d9fe88ac00000000411f90bba5b16a087a5b7e0b8fc2aba041d81d74d270b502b24ea5801b5f4f975bba382260a6fd247facbb49acb8f304324deb3c1e00060d10a4958c047e4530c9fa

View File

@ -1,19 +1,21 @@
package dogecoin
import (
"blockbook/bchain"
"blockbook/bchain/coins/btc"
"blockbook/bchain/coins/utils"
"bytes"
"github.com/btcsuite/btcd/wire"
"github.com/jakm/btcutil/chaincfg"
"github.com/martinboehm/btcd/wire"
"github.com/martinboehm/btcutil/chaincfg"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
"spacecruft.org/spacecruft/blockbook/bchain/coins/utils"
)
// magic numbers
const (
MainnetMagic wire.BitcoinNet = 0xc0c0c0c0
)
// chain parameters
var (
MainNetParams chaincfg.Params
)

View File

@ -3,8 +3,6 @@
package dogecoin
import (
"blockbook/bchain"
"blockbook/bchain/coins/btc"
"bytes"
"encoding/hex"
"fmt"
@ -15,7 +13,9 @@ import (
"reflect"
"testing"
"github.com/jakm/btcutil/chaincfg"
"github.com/martinboehm/btcutil/chaincfg"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
)
func TestMain(m *testing.M) {
@ -46,6 +46,12 @@ func Test_GetAddrDescFromAddress_Mainnet(t *testing.T) {
want: "76a914efb6158f75743c611858fdfd0f4aaec6cc6196bc88ac",
wantErr: false,
},
{
name: "P2PKH3",
args: args{address: "DHobAps6DjZ5n4xMV75n7kJv299Zi85FCG"},
want: "76a9148ae937291e72f7368421dbaa966c44950eb14db788ac",
wantErr: false,
},
{
name: "P2SH1",
args: args{address: "9tg1kVUk339Tk58ewu5T8QT82Z6cE4UvSU"},
@ -76,6 +82,81 @@ func Test_GetAddrDescFromAddress_Mainnet(t *testing.T) {
}
}
func Test_GetAddressesFromAddrDesc_Mainnet(t *testing.T) {
type args struct {
script string
}
tests := []struct {
name string
args args
want []string
want2 bool
wantErr bool
}{
{
name: "P2PKH1",
args: args{script: "76a9148841590909747c0f97af158f22fadacb1652522088ac"},
want: []string{"DHZYinsaM9nW5piCMN639ELRKbZomThPnZ"},
want2: true,
wantErr: false,
},
{
name: "P2PKH2",
args: args{script: "76a914efb6158f75743c611858fdfd0f4aaec6cc6196bc88ac"},
want: []string{"DSzaAYEYyy9ngjoJ294r7jzFM3xhD6bKHK"},
want2: true,
wantErr: false,
},
{
name: "P2PKH3",
args: args{script: "76a91450e86eeac599ad023b8981296d01b50bdabcdd9788ac"},
want: []string{"DCWu3MLz9xBGFuuLyNDf6QjuGp49f5tfc9"},
want2: true,
wantErr: false,
},
{
name: "P2SH1",
args: args{script: "a9141889a089400ea25d28694fd98aa7702b21eeeab187"},
want: []string{"9tg1kVUk339Tk58ewu5T8QT82Z6cE4UvSU"},
want2: true,
wantErr: false,
},
{
name: "OP_RETURN ascii",
args: args{script: "6a0461686f6a"},
want: []string{"OP_RETURN (ahoj)"},
want2: false,
wantErr: false,
},
{
name: "OP_RETURN hex",
args: args{script: "6a072020f1686f6a20"},
want: []string{"OP_RETURN 2020f1686f6a20"},
want2: false,
wantErr: false,
},
}
parser := NewDogecoinParser(GetChainParams("main"), &btc.Configuration{})
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
b, _ := hex.DecodeString(tt.args.script)
got, got2, err := parser.GetAddressesFromAddrDesc(b)
if (err != nil) != tt.wantErr {
t.Errorf("GetAddressesFromAddrDesc() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("GetAddressesFromAddrDesc() = %v, want %v", got, tt.want)
}
if !reflect.DeepEqual(got2, tt.want2) {
t.Errorf("GetAddressesFromAddrDesc() = %v, want %v", got2, tt.want2)
}
})
}
}
var (
testTx1 bchain.Tx
testTxPacked1 = "00030e6d8ba8d7aa2001000000016b3c0c53267964120acf7f7e72217e3f463e52ce622f89659f6a6bb8e69a4d91000000006c493046022100a96454237e3a020994534583e28c04757881374bceac89f933ea9ff00b4db259022100fbb757ff7ea4f02c4e42556b2834c61eba1f1af605db089d836a0614d90a3b46012103cebdde6d1046e285df4f48497bc50dc20a4a258ca5b7308cb0a929c9fdadcd9dffffffff0217e823ca7f0200001976a914eef21768a546590993e313c7f3dfadf6a6efa1e888acaddf4cba010000001976a914e0fee2ea29dd9c6c759d8341bd0da4c4f738cced88ac00000000"
@ -276,7 +357,7 @@ type testBlock struct {
var testParseBlockTxs = map[int]testBlock{
// block without auxpow
12345: testBlock{
12345: {
size: 8582,
time: 1387104223,
txs: []string{
@ -314,7 +395,7 @@ var testParseBlockTxs = map[int]testBlock{
},
},
// 1st block with auxpow
371337: testBlock{
371337: {
size: 1704,
time: 1410464577,
txs: []string{
@ -327,7 +408,7 @@ var testParseBlockTxs = map[int]testBlock{
},
},
// block with auxpow
567890: testBlock{
567890: {
size: 3833,
time: 1422855443,
txs: []string{
@ -343,7 +424,7 @@ var testParseBlockTxs = map[int]testBlock{
},
},
// recent block
2264125: testBlock{
2264125: {
size: 8531,
time: 1529099968,
txs: []string{

View File

@ -1,11 +1,11 @@
package dogecoin
import (
"blockbook/bchain"
"blockbook/bchain/coins/btc"
"encoding/json"
"github.com/golang/glog"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
)
// DogecoinRPC is an interface to JSON-RPC dogecoind service.
@ -31,10 +31,11 @@ func NewDogecoinRPC(config json.RawMessage, pushHandler func(bchain.Notification
// Initialize initializes DogecoinRPC instance.
func (b *DogecoinRPC) Initialize() error {
chainName, err := b.GetChainInfoAndInitializeMempool(b)
ci, err := b.GetChainInfo()
if err != nil {
return err
}
chainName := ci.Chain
glog.Info("Chain name ", chainName)
params := GetChainParams(chainName)

View File

@ -0,0 +1,242 @@
package eth
import (
"bytes"
"context"
"encoding/hex"
"math/big"
"strings"
"sync"
"unicode/utf8"
ethcommon "github.com/ethereum/go-ethereum/common"
"github.com/golang/glog"
"github.com/juju/errors"
"spacecruft.org/spacecruft/blockbook/bchain"
)
var erc20abi = `[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function","signature":"0x06fdde03"},
{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function","signature":"0x95d89b41"},
{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"type":"function","signature":"0x313ce567"},
{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function","signature":"0x18160ddd"},
{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"type":"function","signature":"0x70a08231"},
{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function","signature":"0xa9059cbb"},
{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function","signature":"0x23b872dd"},
{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"}],"name":"approve","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function","signature":"0x095ea7b3"},
{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"remaining","type":"uint256"}],"payable":false,"type":"function","signature":"0xdd62ed3e"},
{"anonymous":false,"inputs":[{"indexed":true,"name":"_from","type":"address"},{"indexed":true,"name":"_to","type":"address"},{"indexed":false,"name":"_value","type":"uint256"}],"name":"Transfer","type":"event","signature":"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"},
{"anonymous":false,"inputs":[{"indexed":true,"name":"_owner","type":"address"},{"indexed":true,"name":"_spender","type":"address"},{"indexed":false,"name":"_value","type":"uint256"}],"name":"Approval","type":"event","signature":"0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925"},
{"inputs":[{"name":"_initialAmount","type":"uint256"},{"name":"_tokenName","type":"string"},{"name":"_decimalUnits","type":"uint8"},{"name":"_tokenSymbol","type":"string"}],"payable":false,"type":"constructor"},
{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"},{"name":"_extraData","type":"bytes"}],"name":"approveAndCall","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function","signature":"0xcae9ca51"},
{"constant":true,"inputs":[],"name":"version","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function","signature":"0x54fd4d50"}]`
// doing the parsing/processing without using go-ethereum/accounts/abi library, it is simple to get data from Transfer event
const erc20TransferMethodSignature = "0xa9059cbb"
const erc20TransferEventSignature = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
const erc20NameSignature = "0x06fdde03"
const erc20SymbolSignature = "0x95d89b41"
const erc20DecimalsSignature = "0x313ce567"
const erc20BalanceOf = "0x70a08231"
var cachedContracts = make(map[string]*bchain.Erc20Contract)
var cachedContractsMux sync.Mutex
func addressFromPaddedHex(s string) (string, error) {
var t big.Int
var ok bool
if has0xPrefix(s) {
_, ok = t.SetString(s[2:], 16)
} else {
_, ok = t.SetString(s, 16)
}
if !ok {
return "", errors.New("Data is not a number")
}
a := ethcommon.BigToAddress(&t)
return a.String(), nil
}
func erc20GetTransfersFromLog(logs []*rpcLog) ([]bchain.Erc20Transfer, error) {
var r []bchain.Erc20Transfer
for _, l := range logs {
if len(l.Topics) == 3 && l.Topics[0] == erc20TransferEventSignature {
var t big.Int
_, ok := t.SetString(l.Data, 0)
if !ok {
return nil, errors.New("Data is not a number")
}
from, err := addressFromPaddedHex(l.Topics[1])
if err != nil {
return nil, err
}
to, err := addressFromPaddedHex(l.Topics[2])
if err != nil {
return nil, err
}
r = append(r, bchain.Erc20Transfer{
Contract: EIP55AddressFromAddress(l.Address),
From: EIP55AddressFromAddress(from),
To: EIP55AddressFromAddress(to),
Tokens: t,
})
}
}
return r, nil
}
func erc20GetTransfersFromTx(tx *rpcTransaction) ([]bchain.Erc20Transfer, error) {
var r []bchain.Erc20Transfer
if len(tx.Payload) == 128+len(erc20TransferMethodSignature) && strings.HasPrefix(tx.Payload, erc20TransferMethodSignature) {
to, err := addressFromPaddedHex(tx.Payload[len(erc20TransferMethodSignature) : 64+len(erc20TransferMethodSignature)])
if err != nil {
return nil, err
}
var t big.Int
_, ok := t.SetString(tx.Payload[len(erc20TransferMethodSignature)+64:], 16)
if !ok {
return nil, errors.New("Data is not a number")
}
r = append(r, bchain.Erc20Transfer{
Contract: EIP55AddressFromAddress(tx.To),
From: EIP55AddressFromAddress(tx.From),
To: EIP55AddressFromAddress(to),
Tokens: t,
})
}
return r, nil
}
func (b *EthereumRPC) ethCall(data, to string) (string, error) {
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
defer cancel()
var r string
err := b.rpc.CallContext(ctx, &r, "eth_call", map[string]interface{}{
"data": data,
"to": to,
}, "latest")
if err != nil {
return "", err
}
return r, nil
}
func parseErc20NumericProperty(contractDesc bchain.AddressDescriptor, data string) *big.Int {
if has0xPrefix(data) {
data = data[2:]
}
if len(data) == 64 {
var n big.Int
_, ok := n.SetString(data, 16)
if ok {
return &n
}
}
if glog.V(1) {
glog.Warning("Cannot parse '", data, "' for contract ", contractDesc)
}
return nil
}
func parseErc20StringProperty(contractDesc bchain.AddressDescriptor, data string) string {
if has0xPrefix(data) {
data = data[2:]
}
if len(data) > 128 {
n := parseErc20NumericProperty(contractDesc, data[64:128])
if n != nil {
l := n.Uint64()
if l > 0 && 2*int(l) <= len(data)-128 {
b, err := hex.DecodeString(data[128 : 128+2*l])
if err == nil {
return string(b)
}
}
}
}
// allow string properties as UTF-8 data
b, err := hex.DecodeString(data)
if err == nil {
i := bytes.Index(b, []byte{0})
if i > 32 {
i = 32
}
if i > 0 {
b = b[:i]
}
if utf8.Valid(b) {
return string(b)
}
}
if glog.V(1) {
glog.Warning("Cannot parse '", data, "' for contract ", contractDesc)
}
return ""
}
// EthereumTypeGetErc20ContractInfo returns information about ERC20 contract
func (b *EthereumRPC) EthereumTypeGetErc20ContractInfo(contractDesc bchain.AddressDescriptor) (*bchain.Erc20Contract, error) {
cds := string(contractDesc)
cachedContractsMux.Lock()
contract, found := cachedContracts[cds]
cachedContractsMux.Unlock()
if !found {
address := EIP55Address(contractDesc)
data, err := b.ethCall(erc20NameSignature, address)
if err != nil {
// ignore the error from the eth_call - since geth v1.9.15 they changed the behavior
// and returning error "execution reverted" for some non contract addresses
// https://github.com/ethereum/go-ethereum/issues/21249#issuecomment-648647672
glog.Warning(errors.Annotatef(err, "erc20NameSignature %v", address))
return nil, nil
// return nil, errors.Annotatef(err, "erc20NameSignature %v", address)
}
name := parseErc20StringProperty(contractDesc, data)
if name != "" {
data, err = b.ethCall(erc20SymbolSignature, address)
if err != nil {
glog.Warning(errors.Annotatef(err, "erc20SymbolSignature %v", address))
return nil, nil
// return nil, errors.Annotatef(err, "erc20SymbolSignature %v", address)
}
symbol := parseErc20StringProperty(contractDesc, data)
data, err = b.ethCall(erc20DecimalsSignature, address)
if err != nil {
glog.Warning(errors.Annotatef(err, "erc20DecimalsSignature %v", address))
// return nil, errors.Annotatef(err, "erc20DecimalsSignature %v", address)
}
contract = &bchain.Erc20Contract{
Contract: address,
Name: name,
Symbol: symbol,
}
d := parseErc20NumericProperty(contractDesc, data)
if d != nil {
contract.Decimals = int(uint8(d.Uint64()))
} else {
contract.Decimals = EtherAmountDecimalPoint
}
} else {
contract = nil
}
cachedContractsMux.Lock()
cachedContracts[cds] = contract
cachedContractsMux.Unlock()
}
return contract, nil
}
// EthereumTypeGetErc20ContractBalance returns balance of ERC20 contract for given address
func (b *EthereumRPC) EthereumTypeGetErc20ContractBalance(addrDesc, contractDesc bchain.AddressDescriptor) (*big.Int, error) {
addr := EIP55Address(addrDesc)
contract := EIP55Address(contractDesc)
req := erc20BalanceOf + "0000000000000000000000000000000000000000000000000000000000000000"[len(addr)-2:] + addr[2:]
data, err := b.ethCall(req, contract)
if err != nil {
return nil, err
}
r := parseErc20NumericProperty(contractDesc, data)
if r == nil {
return nil, errors.New("Invalid balance")
}
return r, nil
}

View File

@ -0,0 +1,204 @@
// +build unittest
package eth
import (
"fmt"
"math/big"
"strings"
"testing"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/tests/dbtestdata"
)
func TestErc20_erc20GetTransfersFromLog(t *testing.T) {
tests := []struct {
name string
args []*rpcLog
want []bchain.Erc20Transfer
wantErr bool
}{
{
name: "1",
args: []*rpcLog{
{
Address: "0x76a45e8976499ab9ae223cc584019341d5a84e96",
Topics: []string{
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
"0x0000000000000000000000002aacf811ac1a60081ea39f7783c0d26c500871a8",
"0x000000000000000000000000e9a5216ff992cfa01594d43501a56e12769eb9d2",
},
Data: "0x0000000000000000000000000000000000000000000000000000000000000123",
},
},
want: []bchain.Erc20Transfer{
{
Contract: "0x76a45e8976499ab9ae223cc584019341d5a84e96",
From: "0x2aacf811ac1a60081ea39f7783c0d26c500871a8",
To: "0xe9a5216ff992cfa01594d43501a56e12769eb9d2",
Tokens: *big.NewInt(0x123),
},
},
},
{
name: "2",
args: []*rpcLog{
{ // Transfer
Address: "0x0d0f936ee4c93e25944694d6c121de94d9760f11",
Topics: []string{
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
"0x0000000000000000000000006f44cceb49b4a5812d54b6f494fc2febf25511ed",
"0x0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d",
},
Data: "0x0000000000000000000000000000000000000000000000006a8313d60b1f606b",
},
{ // Transfer
Address: "0xc778417e063141139fce010982780140aa0cd5ab",
Topics: []string{
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
"0x0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d",
"0x0000000000000000000000006f44cceb49b4a5812d54b6f494fc2febf25511ed",
},
Data: "0x000000000000000000000000000000000000000000000000000308fd0e798ac0",
},
{ // not Transfer
Address: "0x479cc461fecd078f766ecc58533d6f69580cf3ac",
Topics: []string{
"0x0d0b9391970d9a25552f37d436d2aae2925e2bfe1b2a923754bada030c498cb3",
"0x0000000000000000000000006f44cceb49b4a5812d54b6f494fc2febf25511ed",
"0x0000000000000000000000000000000000000000000000000000000000000000",
"0x5af266c0a89a07c1917deaa024414577e6c3c31c8907d079e13eb448c082594f",
},
Data: "0x0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d0000000000000",
},
{ // not Transfer
Address: "0x0d0f936ee4c93e25944694d6c121de94d9760f11",
Topics: []string{
"0x0d0b9391970d9a25552f37d436d2aae2925e2bfe1b2a923754bada030c498cb3",
"0x0000000000000000000000007b62eb7fe80350dc7ec945c0b73242cb9877fb1b",
"0xb0b69dad58df6032c3b266e19b1045b19c87acd2c06fb0c598090f44b8e263aa",
},
Data: "0x0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d000000000000000000000000c778417e063141139fce010982780140aa0cd5ab0000000000000000000000000d0f936ee4c93e25944694d6c121de94d9760f1100000000000000000000000000000000000000000000000000031855667df7a80000000000000000000000000000000000000000000000006a8313d60b1f800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
},
},
want: []bchain.Erc20Transfer{
{
Contract: "0x0d0f936ee4c93e25944694d6c121de94d9760f11",
From: "0x6f44cceb49b4a5812d54b6f494fc2febf25511ed",
To: "0x4bda106325c335df99eab7fe363cac8a0ba2a24d",
Tokens: *big.NewInt(0x6a8313d60b1f606b),
},
{
Contract: "0xc778417e063141139fce010982780140aa0cd5ab",
From: "0x4bda106325c335df99eab7fe363cac8a0ba2a24d",
To: "0x6f44cceb49b4a5812d54b6f494fc2febf25511ed",
Tokens: *big.NewInt(0x308fd0e798ac0),
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := erc20GetTransfersFromLog(tt.args)
if (err != nil) != tt.wantErr {
t.Errorf("erc20GetTransfersFromLog error = %v, wantErr %v", err, tt.wantErr)
return
}
// the addresses could have different case
if strings.ToLower(fmt.Sprint(got)) != strings.ToLower(fmt.Sprint(tt.want)) {
t.Errorf("erc20GetTransfersFromLog = %+v, want %+v", got, tt.want)
}
})
}
}
func TestErc20_parseErc20StringProperty(t *testing.T) {
tests := []struct {
name string
args string
want string
}{
{
name: "1",
args: "0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000758504c4f44444500000000000000000000000000000000000000000000000000",
want: "XPLODDE",
},
{
name: "2",
args: "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000022426974436c617665202d20436f6e73756d657220416374697669747920546f6b656e00000000000000",
want: "BitClave - Consumer Activity Token",
},
{
name: "short",
args: "0x44616920537461626c65636f696e2076312e3000000000000000000000000000",
want: "Dai Stablecoin v1.0",
},
{
name: "short2",
args: "0x44616920537461626c65636f696e2076312e3020444444444444444444444444",
want: "Dai Stablecoin v1.0 DDDDDDDDDDDD",
},
{
name: "long",
args: "0x556e6973776170205631000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
want: "Uniswap V1",
},
{
name: "garbage",
args: "0x2234880850896048596206002535425366538144616734015984380565810000",
want: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := parseErc20StringProperty(nil, tt.args)
// the addresses could have different case
if got != tt.want {
t.Errorf("parseErc20StringProperty = %v, want %v", got, tt.want)
}
})
}
}
func TestErc20_erc20GetTransfersFromTx(t *testing.T) {
p := NewEthereumParser(1)
b := dbtestdata.GetTestEthereumTypeBlock1(p)
bn, _ := new(big.Int).SetString("21e19e0c9bab2400000", 16)
tests := []struct {
name string
args *rpcTransaction
want []bchain.Erc20Transfer
}{
{
name: "0",
args: (b.Txs[0].CoinSpecificData.(completeTransaction)).Tx,
want: []bchain.Erc20Transfer{},
},
{
name: "1",
args: (b.Txs[1].CoinSpecificData.(completeTransaction)).Tx,
want: []bchain.Erc20Transfer{
{
Contract: "0x4af4114f73d1c1c903ac9e0361b379d1291808a2",
From: "0x20cd153de35d469ba46127a0c8f18626b59a256a",
To: "0x555ee11fbddc0e49a9bab358a8941ad95ffdb48f",
Tokens: *bn,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := erc20GetTransfersFromTx(tt.args)
if err != nil {
t.Errorf("erc20GetTransfersFromTx error = %v", err)
return
}
// the addresses could have different case
if strings.ToLower(fmt.Sprint(got)) != strings.ToLower(fmt.Sprint(tt.want)) {
t.Errorf("erc20GetTransfersFromTx = %+v, want %+v", got, tt.want)
}
})
}
}

View File

@ -1,59 +1,92 @@
package eth
import (
"blockbook/bchain"
"encoding/hex"
"encoding/json"
"math/big"
"strconv"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/golang/protobuf/proto"
"github.com/juju/errors"
ethcommon "github.com/ethereum/go-ethereum/common"
"spacecruft.org/spacecruft/blockbook/bchain"
"golang.org/x/crypto/sha3"
)
// EthereumTypeAddressDescriptorLen - in case of EthereumType, the AddressDescriptor has fixed length
const EthereumTypeAddressDescriptorLen = 20
// EtherAmountDecimalPoint defines number of decimal points in Ether amounts
const EtherAmountDecimalPoint = 18
// EthereumParser handle
type EthereumParser struct {
*bchain.BaseParser
}
// NewEthereumParser returns new EthereumParser instance
func NewEthereumParser() *EthereumParser {
func NewEthereumParser(b int) *EthereumParser {
return &EthereumParser{&bchain.BaseParser{
BlockAddressesToKeep: 0,
AmountDecimalPoint: 18,
BlockAddressesToKeep: b,
AmountDecimalPoint: EtherAmountDecimalPoint,
}}
}
type rpcHeader struct {
Hash string `json:"hash"`
ParentHash string `json:"parentHash"`
Difficulty string `json:"difficulty"`
Number string `json:"number"`
Time string `json:"timestamp"`
Size string `json:"size"`
Nonce string `json:"nonce"`
}
type rpcTransaction struct {
AccountNonce string `json:"nonce" gencodec:"required"`
Price string `json:"gasPrice" gencodec:"required"`
GasLimit string `json:"gas" gencodec:"required"`
To string `json:"to" rlp:"nil"` // nil means contract creation
Value string `json:"value" gencodec:"required"`
Payload string `json:"input" gencodec:"required"`
Hash ethcommon.Hash `json:"hash" rlp:"-"`
BlockNumber string `json:"blockNumber"`
BlockHash *ethcommon.Hash `json:"blockHash,omitempty"`
From string `json:"from"`
TransactionIndex string `json:"transactionIndex"`
// Signature values
V string `json:"v" gencodec:"required"`
R string `json:"r" gencodec:"required"`
S string `json:"s" gencodec:"required"`
AccountNonce string `json:"nonce"`
GasPrice string `json:"gasPrice"`
GasLimit string `json:"gas"`
To string `json:"to"` // nil means contract creation
Value string `json:"value"`
Payload string `json:"input"`
Hash string `json:"hash"`
BlockNumber string `json:"blockNumber"`
BlockHash string `json:"blockHash,omitempty"`
From string `json:"from"`
TransactionIndex string `json:"transactionIndex"`
// Signature values - ignored
// V string `json:"v"`
// R string `json:"r"`
// S string `json:"s"`
}
type rpcBlock struct {
Hash ethcommon.Hash `json:"hash"`
type rpcLog struct {
Address string `json:"address"`
Topics []string `json:"topics"`
Data string `json:"data"`
}
type rpcLogWithTxHash struct {
rpcLog
Hash string `json:"transactionHash"`
}
type rpcReceipt struct {
GasUsed string `json:"gasUsed"`
Status string `json:"status"`
Logs []*rpcLog `json:"logs"`
}
type completeTransaction struct {
Tx *rpcTransaction `json:"tx"`
Receipt *rpcReceipt `json:"receipt,omitempty"`
}
type rpcBlockTransactions struct {
Transactions []rpcTransaction `json:"transactions"`
UncleHashes []ethcommon.Hash `json:"uncles"`
}
func ethHashToHash(h ethcommon.Hash) string {
return h.Hex()
type rpcBlockTxids struct {
Transactions []string `json:"transactions"`
}
func ethNumber(n string) (int64, error) {
@ -63,27 +96,35 @@ func ethNumber(n string) (int64, error) {
return 0, errors.Errorf("Not a number: '%v'", n)
}
func (p *EthereumParser) ethTxToTx(tx *rpcTransaction, blocktime int64, confirmations uint32) (*bchain.Tx, error) {
txid := ethHashToHash(tx.Hash)
func (p *EthereumParser) ethTxToTx(tx *rpcTransaction, receipt *rpcReceipt, blocktime int64, confirmations uint32, fixEIP55 bool) (*bchain.Tx, error) {
txid := tx.Hash
var (
fa, ta []string
err error
)
if len(tx.From) > 2 {
if fixEIP55 {
tx.From = EIP55AddressFromAddress(tx.From)
}
fa = []string{tx.From}
}
if len(tx.To) > 2 {
if fixEIP55 {
tx.To = EIP55AddressFromAddress(tx.To)
}
ta = []string{tx.To}
}
// temporarily, the complete rpcTransaction without BlockHash is marshalled and hex encoded to bchain.Tx.Hex
bh := tx.BlockHash
tx.BlockHash = nil
b, err := json.Marshal(tx)
if err != nil {
return nil, err
if fixEIP55 && receipt != nil && receipt.Logs != nil {
for _, l := range receipt.Logs {
if len(l.Address) > 2 {
l.Address = EIP55AddressFromAddress(l.Address)
}
}
}
ct := completeTransaction{
Tx: tx,
Receipt: receipt,
}
tx.BlockHash = bh
h := hex.EncodeToString(b)
vs, err := hexutil.DecodeBig(tx.Value)
if err != nil {
return nil, err
@ -91,7 +132,7 @@ func (p *EthereumParser) ethTxToTx(tx *rpcTransaction, blocktime int64, confirma
return &bchain.Tx{
Blocktime: blocktime,
Confirmations: confirmations,
Hex: h,
// Hex
// LockTime
Time: blocktime,
Txid: txid,
@ -115,6 +156,7 @@ func (p *EthereumParser) ethTxToTx(tx *rpcTransaction, blocktime int64, confirma
},
},
},
CoinSpecificData: ct,
}, nil
}
@ -136,18 +178,52 @@ func (p *EthereumParser) GetAddrDescFromAddress(address string) (bchain.AddressD
if has0xPrefix(address) {
address = address[2:]
}
if len(address) == 0 {
if len(address) != EthereumTypeAddressDescriptorLen*2 {
return nil, bchain.ErrAddressMissing
}
if len(address)&1 == 1 {
address = "0" + address
}
return hex.DecodeString(address)
}
// EIP55Address returns an EIP55-compliant hex string representation of the address
func EIP55Address(addrDesc bchain.AddressDescriptor) string {
raw := hexutil.Encode(addrDesc)
if len(raw) != 42 {
return raw
}
sha := sha3.NewLegacyKeccak256()
result := []byte(raw)
sha.Write(result[2:])
hash := sha.Sum(nil)
for i := 2; i < len(result); i++ {
hashByte := hash[(i-2)>>1]
if i%2 == 0 {
hashByte = hashByte >> 4
} else {
hashByte &= 0xf
}
if result[i] > '9' && hashByte > 7 {
result[i] -= 32
}
}
return string(result)
}
// EIP55AddressFromAddress returns an EIP55-compliant hex string representation of the address
func EIP55AddressFromAddress(address string) string {
if has0xPrefix(address) {
address = address[2:]
}
b, err := hex.DecodeString(address)
if err != nil {
return address
}
return EIP55Address(b)
}
// GetAddressesFromAddrDesc returns addresses for given address descriptor with flag if the addresses are searchable
func (p *EthereumParser) GetAddressesFromAddrDesc(addrDesc bchain.AddressDescriptor) ([]string, bool, error) {
return []string{hexutil.Encode(addrDesc)}, true, nil
return []string{EIP55Address(addrDesc)}, true, nil
}
// GetScriptFromAddrDesc returns output script for given address descriptor
@ -179,83 +255,148 @@ func hexEncodeBig(b []byte) string {
// PackTx packs transaction to byte array
func (p *EthereumParser) PackTx(tx *bchain.Tx, height uint32, blockTime int64) ([]byte, error) {
b, err := hex.DecodeString(tx.Hex)
if err != nil {
return nil, err
}
var r rpcTransaction
var err error
var n uint64
err = json.Unmarshal(b, &r)
if err != nil {
return nil, err
r, ok := tx.CoinSpecificData.(completeTransaction)
if !ok {
return nil, errors.New("Missing CoinSpecificData")
}
pt := &ProtoTransaction{}
if pt.AccountNonce, err = hexutil.DecodeUint64(r.AccountNonce); err != nil {
return nil, errors.Annotatef(err, "AccountNonce %v", r.AccountNonce)
pt := &ProtoCompleteTransaction{}
pt.Tx = &ProtoCompleteTransaction_TxType{}
if pt.Tx.AccountNonce, err = hexutil.DecodeUint64(r.Tx.AccountNonce); err != nil {
return nil, errors.Annotatef(err, "AccountNonce %v", r.Tx.AccountNonce)
}
if n, err = hexutil.DecodeUint64(r.BlockNumber); err != nil {
return nil, errors.Annotatef(err, "BlockNumber %v", r.BlockNumber)
// pt.BlockNumber = height
if n, err = hexutil.DecodeUint64(r.Tx.BlockNumber); err != nil {
return nil, errors.Annotatef(err, "BlockNumber %v", r.Tx.BlockNumber)
}
pt.BlockNumber = uint32(n)
pt.BlockTime = uint64(blockTime)
if pt.From, err = hexDecode(r.From); err != nil {
return nil, errors.Annotatef(err, "From %v", r.From)
if pt.Tx.From, err = hexDecode(r.Tx.From); err != nil {
return nil, errors.Annotatef(err, "From %v", r.Tx.From)
}
if pt.GasLimit, err = hexutil.DecodeUint64(r.GasLimit); err != nil {
return nil, errors.Annotatef(err, "GasLimit %v", r.GasLimit)
if pt.Tx.GasLimit, err = hexutil.DecodeUint64(r.Tx.GasLimit); err != nil {
return nil, errors.Annotatef(err, "GasLimit %v", r.Tx.GasLimit)
}
pt.Hash = r.Hash.Bytes()
if pt.Payload, err = hexDecode(r.Payload); err != nil {
return nil, errors.Annotatef(err, "Payload %v", r.Payload)
if pt.Tx.Hash, err = hexDecode(r.Tx.Hash); err != nil {
return nil, errors.Annotatef(err, "Hash %v", r.Tx.Hash)
}
if pt.Price, err = hexDecodeBig(r.Price); err != nil {
return nil, errors.Annotatef(err, "Price %v", r.Price)
if pt.Tx.Payload, err = hexDecode(r.Tx.Payload); err != nil {
return nil, errors.Annotatef(err, "Payload %v", r.Tx.Payload)
}
if pt.R, err = hexDecodeBig(r.R); err != nil {
return nil, errors.Annotatef(err, "R %v", r.R)
if pt.Tx.GasPrice, err = hexDecodeBig(r.Tx.GasPrice); err != nil {
return nil, errors.Annotatef(err, "Price %v", r.Tx.GasPrice)
}
if pt.S, err = hexDecodeBig(r.S); err != nil {
return nil, errors.Annotatef(err, "S %v", r.S)
// if pt.R, err = hexDecodeBig(r.R); err != nil {
// return nil, errors.Annotatef(err, "R %v", r.R)
// }
// if pt.S, err = hexDecodeBig(r.S); err != nil {
// return nil, errors.Annotatef(err, "S %v", r.S)
// }
// if pt.V, err = hexDecodeBig(r.V); err != nil {
// return nil, errors.Annotatef(err, "V %v", r.V)
// }
if pt.Tx.To, err = hexDecode(r.Tx.To); err != nil {
return nil, errors.Annotatef(err, "To %v", r.Tx.To)
}
if pt.V, err = hexDecodeBig(r.V); err != nil {
return nil, errors.Annotatef(err, "V %v", r.V)
if n, err = hexutil.DecodeUint64(r.Tx.TransactionIndex); err != nil {
return nil, errors.Annotatef(err, "TransactionIndex %v", r.Tx.TransactionIndex)
}
if pt.To, err = hexDecode(r.To); err != nil {
return nil, errors.Annotatef(err, "To %v", r.To)
pt.Tx.TransactionIndex = uint32(n)
if pt.Tx.Value, err = hexDecodeBig(r.Tx.Value); err != nil {
return nil, errors.Annotatef(err, "Value %v", r.Tx.Value)
}
if n, err = hexutil.DecodeUint64(r.TransactionIndex); err != nil {
return nil, errors.Annotatef(err, "TransactionIndex %v", r.TransactionIndex)
}
pt.TransactionIndex = uint32(n)
if pt.Value, err = hexDecodeBig(r.Value); err != nil {
return nil, errors.Annotatef(err, "Value %v", r.Value)
if r.Receipt != nil {
pt.Receipt = &ProtoCompleteTransaction_ReceiptType{}
if pt.Receipt.GasUsed, err = hexDecodeBig(r.Receipt.GasUsed); err != nil {
return nil, errors.Annotatef(err, "GasUsed %v", r.Receipt.GasUsed)
}
if r.Receipt.Status != "" {
if pt.Receipt.Status, err = hexDecodeBig(r.Receipt.Status); err != nil {
return nil, errors.Annotatef(err, "Status %v", r.Receipt.Status)
}
} else {
// unknown status, use 'U' as status bytes
// there is a potential for conflict with value 0x55 but this is not used by any chain at this moment
pt.Receipt.Status = []byte{'U'}
}
ptLogs := make([]*ProtoCompleteTransaction_ReceiptType_LogType, len(r.Receipt.Logs))
for i, l := range r.Receipt.Logs {
a, err := hexutil.Decode(l.Address)
if err != nil {
return nil, errors.Annotatef(err, "Address cannot be decoded %v", l)
}
d, err := hexutil.Decode(l.Data)
if err != nil {
return nil, errors.Annotatef(err, "Data cannot be decoded %v", l)
}
t := make([][]byte, len(l.Topics))
for j, s := range l.Topics {
t[j], err = hexutil.Decode(s)
if err != nil {
return nil, errors.Annotatef(err, "Topic cannot be decoded %v", l)
}
}
ptLogs[i] = &ProtoCompleteTransaction_ReceiptType_LogType{
Address: a,
Data: d,
Topics: t,
}
}
pt.Receipt.Log = ptLogs
}
return proto.Marshal(pt)
}
// UnpackTx unpacks transaction from byte array
func (p *EthereumParser) UnpackTx(buf []byte) (*bchain.Tx, uint32, error) {
var pt ProtoTransaction
var pt ProtoCompleteTransaction
err := proto.Unmarshal(buf, &pt)
if err != nil {
return nil, 0, err
}
r := rpcTransaction{
AccountNonce: hexutil.EncodeUint64(pt.AccountNonce),
BlockNumber: hexutil.EncodeUint64(uint64(pt.BlockNumber)),
From: hexutil.Encode(pt.From),
GasLimit: hexutil.EncodeUint64(pt.GasLimit),
Hash: ethcommon.BytesToHash(pt.Hash),
Payload: hexutil.Encode(pt.Payload),
Price: hexEncodeBig(pt.Price),
R: hexEncodeBig(pt.R),
S: hexEncodeBig(pt.S),
V: hexEncodeBig(pt.V),
To: hexutil.Encode(pt.To),
TransactionIndex: hexutil.EncodeUint64(uint64(pt.TransactionIndex)),
Value: hexEncodeBig(pt.Value),
rt := rpcTransaction{
AccountNonce: hexutil.EncodeUint64(pt.Tx.AccountNonce),
BlockNumber: hexutil.EncodeUint64(uint64(pt.BlockNumber)),
From: EIP55Address(pt.Tx.From),
GasLimit: hexutil.EncodeUint64(pt.Tx.GasLimit),
Hash: hexutil.Encode(pt.Tx.Hash),
Payload: hexutil.Encode(pt.Tx.Payload),
GasPrice: hexEncodeBig(pt.Tx.GasPrice),
// R: hexEncodeBig(pt.R),
// S: hexEncodeBig(pt.S),
// V: hexEncodeBig(pt.V),
To: EIP55Address(pt.Tx.To),
TransactionIndex: hexutil.EncodeUint64(uint64(pt.Tx.TransactionIndex)),
Value: hexEncodeBig(pt.Tx.Value),
}
tx, err := p.ethTxToTx(&r, int64(pt.BlockTime), 0)
var rr *rpcReceipt
if pt.Receipt != nil {
logs := make([]*rpcLog, len(pt.Receipt.Log))
for i, l := range pt.Receipt.Log {
topics := make([]string, len(l.Topics))
for j, t := range l.Topics {
topics[j] = hexutil.Encode(t)
}
logs[i] = &rpcLog{
Address: EIP55Address(l.Address),
Data: hexutil.Encode(l.Data),
Topics: topics,
}
}
status := ""
// handle a special value []byte{'U'} as unknown state
if len(pt.Receipt.Status) != 1 || pt.Receipt.Status[0] != 'U' {
status = hexEncodeBig(pt.Receipt.Status)
}
rr = &rpcReceipt{
GasUsed: hexEncodeBig(pt.Receipt.GasUsed),
Status: status,
Logs: logs,
}
}
tx, err := p.ethTxToTx(&rt, rr, int64(pt.BlockTime), 0, false)
if err != nil {
return nil, 0, err
}
@ -293,7 +434,92 @@ func (p *EthereumParser) UnpackBlockHash(buf []byte) (string, error) {
return hexutil.Encode(buf), nil
}
// IsUTXOChain returns true if the block chain is UTXO type, otherwise false
func (p *EthereumParser) IsUTXOChain() bool {
return false
// GetChainType returns EthereumType
func (p *EthereumParser) GetChainType() bchain.ChainType {
return bchain.ChainEthereumType
}
// GetHeightFromTx returns ethereum specific data from bchain.Tx
func GetHeightFromTx(tx *bchain.Tx) (uint32, error) {
var bn string
csd, ok := tx.CoinSpecificData.(completeTransaction)
if !ok {
return 0, errors.New("Missing CoinSpecificData")
}
bn = csd.Tx.BlockNumber
n, err := hexutil.DecodeUint64(bn)
if err != nil {
return 0, errors.Annotatef(err, "BlockNumber %v", bn)
}
return uint32(n), nil
}
// EthereumTypeGetErc20FromTx returns Erc20 data from bchain.Tx
func (p *EthereumParser) EthereumTypeGetErc20FromTx(tx *bchain.Tx) ([]bchain.Erc20Transfer, error) {
var r []bchain.Erc20Transfer
var err error
csd, ok := tx.CoinSpecificData.(completeTransaction)
if ok {
if csd.Receipt != nil {
r, err = erc20GetTransfersFromLog(csd.Receipt.Logs)
} else {
r, err = erc20GetTransfersFromTx(csd.Tx)
}
if err != nil {
return nil, err
}
}
return r, nil
}
// TxStatus is status of transaction
type TxStatus int
// statuses of transaction
const (
TxStatusUnknown = TxStatus(iota - 2)
TxStatusPending
TxStatusFailure
TxStatusOK
)
// EthereumTxData contains ethereum specific transaction data
type EthereumTxData struct {
Status TxStatus `json:"status"` // 1 OK, 0 Fail, -1 pending, -2 unknown
Nonce uint64 `json:"nonce"`
GasLimit *big.Int `json:"gaslimit"`
GasUsed *big.Int `json:"gasused"`
GasPrice *big.Int `json:"gasprice"`
Data string `json:"data"`
}
// GetEthereumTxData returns EthereumTxData from bchain.Tx
func GetEthereumTxData(tx *bchain.Tx) *EthereumTxData {
return GetEthereumTxDataFromSpecificData(tx.CoinSpecificData)
}
// GetEthereumTxDataFromSpecificData returns EthereumTxData from coinSpecificData
func GetEthereumTxDataFromSpecificData(coinSpecificData interface{}) *EthereumTxData {
etd := EthereumTxData{Status: TxStatusPending}
csd, ok := coinSpecificData.(completeTransaction)
if ok {
if csd.Tx != nil {
etd.Nonce, _ = hexutil.DecodeUint64(csd.Tx.AccountNonce)
etd.GasLimit, _ = hexutil.DecodeBig(csd.Tx.GasLimit)
etd.GasPrice, _ = hexutil.DecodeBig(csd.Tx.GasPrice)
etd.Data = csd.Tx.Payload
}
if csd.Receipt != nil {
switch csd.Receipt.Status {
case "0x1":
etd.Status = TxStatusOK
case "": // old transactions did not set status
etd.Status = TxStatusUnknown
default:
etd.Status = TxStatusFailure
}
etd.GasUsed, _ = hexutil.DecodeBig(csd.Receipt.GasUsed)
}
}
return &etd
}

View File

@ -3,11 +3,14 @@
package eth
import (
"blockbook/bchain"
"encoding/hex"
"fmt"
"math/big"
"reflect"
"testing"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/tests/dbtestdata"
)
func TestEthParser_GetAddrDescFromAddress(t *testing.T) {
@ -31,9 +34,10 @@ func TestEthParser_GetAddrDescFromAddress(t *testing.T) {
want: "47526228d673e9f079630d6cdaff5a2ed13e0e60",
},
{
name: "odd address",
args: args{address: "7526228d673e9f079630d6cdaff5a2ed13e0e60"},
want: "07526228d673e9f079630d6cdaff5a2ed13e0e60",
name: "address of wrong length",
args: args{address: "7526228d673e9f079630d6cdaff5a2ed13e0e60"},
want: "",
wantErr: true,
},
{
name: "ErrAddressMissing",
@ -50,7 +54,7 @@ func TestEthParser_GetAddrDescFromAddress(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
p := NewEthereumParser()
p := NewEthereumParser(1)
got, err := p.GetAddrDescFromAddress(tt.args.address)
if (err != nil) != tt.wantErr {
t.Errorf("EthParser.GetAddrDescFromAddress() error = %v, wantErr %v", err, tt.wantErr)
@ -64,53 +68,171 @@ func TestEthParser_GetAddrDescFromAddress(t *testing.T) {
}
}
var (
testTx1, testTx2 bchain.Tx
testTxPacked1 = "08aebf0a1205012a05f20018a0f73622081234567890abcdef2a24f025caaf00000000000000000000000000000000000000000000000000000000000002253220e6b168d6bb3d8ed78e03dbf828b6bfd1fb613f6e129cba624964984553724c5d38f095af014092f4c1d5054a14682b7903a11098cf770c7aef4aa02a85b3f3601a5214dacc9c61754a0c4616fc5323dc946e89eb272302580162011b6a201bd40a31122c03918df6d166d740a6a3a22f08a25934ceb1688c62977661c80c7220607fbc15c1f7995a4258f5a9bccc63b040362d1991d5efe1361c56222e4ca89f"
testTxPacked2 = "08ece40212050430e234001888a4012201213220cd647151552b5132b2aef7c9be00dc6f73afc5901dde157aab131335baaa853b38889eaf0140fa83c3d5054a14555ee11fbddc0e49a9bab358a8941ad95ffdb48f52143e3a3d69dc66ba10737f531ed088954a9ec89d97580a6201296a20f7161c170d43573ad9c8d701cdaf714ff2a548a562b0dc639230d17889fcd40572203c4977fc90385a27efa0032e17b49fd575b2826cb56e3d1ecf21524f2a94f915"
)
var testTx1, testTx2, testTx1Failed, testTx1NoStatus bchain.Tx
func init() {
testTx1 = bchain.Tx{
Blocktime: 1521515026,
Hex: "7b226e6f6e6365223a2230783239666165222c226761735072696365223a223078313261303566323030222c22676173223a2230786462626130222c22746f223a22307836383262373930336131313039386366373730633761656634616130326138356233663336303161222c2276616c7565223a22307831323334353637383930616263646566222c22696e707574223a223078663032356361616630303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030323235222c2268617368223a22307865366231363864366262336438656437386530336462663832386236626664316662363133663665313239636261363234393634393834353533373234633564222c22626c6f636b4e756d626572223a223078326263616630222c2266726f6d223a22307864616363396336313735346130633436313666633533323364633934366538396562323732333032222c227472616e73616374696f6e496e646578223a22307831222c2276223a2230783162222c2272223a22307831626434306133313132326330333931386466366431363664373430613661336132326630386132353933346365623136383863363239373736363163383063222c2273223a22307836303766626331356331663739393561343235386635613962636363363362303430333632643139393164356566653133363163353632323265346361383966227d",
Time: 1521515026,
Txid: "0xe6b168d6bb3d8ed78e03dbf828b6bfd1fb613f6e129cba624964984553724c5d",
Blocktime: 1534858022,
Time: 1534858022,
Txid: "0xcd647151552b5132b2aef7c9be00dc6f73afc5901dde157aab131335baaa853b",
Vin: []bchain.Vin{
{
Addresses: []string{"0xdacc9c61754a0c4616fc5323dc946e89eb272302"},
Addresses: []string{"0x3E3a3D69dc66bA10737F531ed088954a9EC89d97"},
},
},
Vout: []bchain.Vout{
{
ValueSat: *big.NewInt(1311768467294899695),
ValueSat: *big.NewInt(1999622000000000000),
ScriptPubKey: bchain.ScriptPubKey{
Addresses: []string{"0x682b7903a11098cf770c7aef4aa02a85b3f3601a"},
Addresses: []string{"0x555Ee11FBDDc0E49A9bAB358A8941AD95fFDB48f"},
},
},
},
CoinSpecificData: completeTransaction{
Tx: &rpcTransaction{
AccountNonce: "0xb26c",
GasPrice: "0x430e23400",
GasLimit: "0x5208",
To: "0x555Ee11FBDDc0E49A9bAB358A8941AD95fFDB48f",
Value: "0x1bc0159d530e6000",
Payload: "0x",
Hash: "0xcd647151552b5132b2aef7c9be00dc6f73afc5901dde157aab131335baaa853b",
BlockNumber: "0x41eee8",
From: "0x3E3a3D69dc66bA10737F531ed088954a9EC89d97",
TransactionIndex: "0xa",
},
Receipt: &rpcReceipt{
GasUsed: "0x5208",
Status: "0x1",
Logs: []*rpcLog{},
},
},
}
testTx2 = bchain.Tx{
Blocktime: 1521533434,
Hex: "7b226e6f6e6365223a22307862323663222c226761735072696365223a223078343330653233343030222c22676173223a22307835323038222c22746f223a22307835353565653131666264646330653439613962616233353861383934316164393566666462343866222c2276616c7565223a2230783231222c22696e707574223a223078222c2268617368223a22307863643634373135313535326235313332623261656637633962653030646336663733616663353930316464653135376161623133313333356261616138353362222c22626c6f636b4e756d626572223a223078326263663038222c2266726f6d223a22307833653361336436396463363662613130373337663533316564303838393534613965633839643937222c227472616e73616374696f6e496e646578223a22307861222c2276223a2230783239222c2272223a22307866373136316331373064343335373361643963386437303163646166373134666632613534386135363262306463363339323330643137383839666364343035222c2273223a22307833633439373766633930333835613237656661303033326531376234396664353735623238323663623536653364316563663231353234663261393466393135227d",
Time: 1521533434,
Txid: "0xcd647151552b5132b2aef7c9be00dc6f73afc5901dde157aab131335baaa853b",
Blocktime: 1534858022,
Time: 1534858022,
Txid: "0xa9cd088aba2131000da6f38a33c20169baee476218deea6b78720700b895b101",
Vin: []bchain.Vin{
{
Addresses: []string{"0x3e3a3d69dc66ba10737f531ed088954a9ec89d97"},
Addresses: []string{"0x20cD153de35D469BA46127A0C8F18626b59a256A"},
},
},
Vout: []bchain.Vout{
{
ValueSat: *big.NewInt(33),
ValueSat: *big.NewInt(0),
ScriptPubKey: bchain.ScriptPubKey{
Addresses: []string{"0x555ee11fbddc0e49a9bab358a8941ad95ffdb48f"},
Addresses: []string{"0x4af4114F73d1c1C903aC9E0361b379D1291808A2"},
},
},
},
CoinSpecificData: completeTransaction{
Tx: &rpcTransaction{
AccountNonce: "0xd0",
GasPrice: "0x9502f9000",
GasLimit: "0x130d5",
To: "0x4af4114F73d1c1C903aC9E0361b379D1291808A2",
Value: "0x0",
Payload: "0xa9059cbb000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f00000000000000000000000000000000000000000000021e19e0c9bab2400000",
Hash: "0xa9cd088aba2131000da6f38a33c20169baee476218deea6b78720700b895b101",
BlockNumber: "0x41eee8",
From: "0x20cD153de35D469BA46127A0C8F18626b59a256A",
TransactionIndex: "0x0"},
Receipt: &rpcReceipt{
GasUsed: "0xcb39",
Status: "0x1",
Logs: []*rpcLog{
{
Address: "0x4af4114F73d1c1C903aC9E0361b379D1291808A2",
Data: "0x00000000000000000000000000000000000000000000021e19e0c9bab2400000",
Topics: []string{
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
"0x00000000000000000000000020cd153de35d469ba46127a0c8f18626b59a256a",
"0x000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f",
},
},
},
},
},
}
testTx1Failed = bchain.Tx{
Blocktime: 1534858022,
Time: 1534858022,
Txid: "0xcd647151552b5132b2aef7c9be00dc6f73afc5901dde157aab131335baaa853b",
Vin: []bchain.Vin{
{
Addresses: []string{"0x3E3a3D69dc66bA10737F531ed088954a9EC89d97"},
},
},
Vout: []bchain.Vout{
{
ValueSat: *big.NewInt(1999622000000000000),
ScriptPubKey: bchain.ScriptPubKey{
Addresses: []string{"0x555Ee11FBDDc0E49A9bAB358A8941AD95fFDB48f"},
},
},
},
CoinSpecificData: completeTransaction{
Tx: &rpcTransaction{
AccountNonce: "0xb26c",
GasPrice: "0x430e23400",
GasLimit: "0x5208",
To: "0x555Ee11FBDDc0E49A9bAB358A8941AD95fFDB48f",
Value: "0x1bc0159d530e6000",
Payload: "0x",
Hash: "0xcd647151552b5132b2aef7c9be00dc6f73afc5901dde157aab131335baaa853b",
BlockNumber: "0x41eee8",
From: "0x3E3a3D69dc66bA10737F531ed088954a9EC89d97",
TransactionIndex: "0xa",
},
Receipt: &rpcReceipt{
GasUsed: "0x5208",
Status: "0x0",
Logs: []*rpcLog{},
},
},
}
testTx1NoStatus = bchain.Tx{
Blocktime: 1534858022,
Time: 1534858022,
Txid: "0xcd647151552b5132b2aef7c9be00dc6f73afc5901dde157aab131335baaa853b",
Vin: []bchain.Vin{
{
Addresses: []string{"0x3E3a3D69dc66bA10737F531ed088954a9EC89d97"},
},
},
Vout: []bchain.Vout{
{
ValueSat: *big.NewInt(1999622000000000000),
ScriptPubKey: bchain.ScriptPubKey{
Addresses: []string{"0x555Ee11FBDDc0E49A9bAB358A8941AD95fFDB48f"},
},
},
},
CoinSpecificData: completeTransaction{
Tx: &rpcTransaction{
AccountNonce: "0xb26c",
GasPrice: "0x430e23400",
GasLimit: "0x5208",
To: "0x555Ee11FBDDc0E49A9bAB358A8941AD95fFDB48f",
Value: "0x1bc0159d530e6000",
Payload: "0x",
Hash: "0xcd647151552b5132b2aef7c9be00dc6f73afc5901dde157aab131335baaa853b",
BlockNumber: "0x41eee8",
From: "0x3E3a3D69dc66bA10737F531ed088954a9EC89d97",
TransactionIndex: "0xa",
},
Receipt: &rpcReceipt{
GasUsed: "0x5208",
Status: "",
Logs: []*rpcLog{},
},
},
}
}
func TestEthereumParser_PackTx(t *testing.T) {
@ -130,22 +252,40 @@ func TestEthereumParser_PackTx(t *testing.T) {
name: "1",
args: args{
tx: &testTx1,
height: 2870000,
blockTime: 1521515026,
height: 4321000,
blockTime: 1534858022,
},
want: testTxPacked1,
want: dbtestdata.EthTx1Packed,
},
{
name: "2",
args: args{
tx: &testTx2,
height: 2871048,
blockTime: 1521533434,
height: 4321000,
blockTime: 1534858022,
},
want: testTxPacked2,
want: dbtestdata.EthTx2Packed,
},
{
name: "3",
args: args{
tx: &testTx1Failed,
height: 4321000,
blockTime: 1534858022,
},
want: dbtestdata.EthTx1FailedPacked,
},
{
name: "4",
args: args{
tx: &testTx1NoStatus,
height: 4321000,
blockTime: 1534858022,
},
want: dbtestdata.EthTx1NoStatusPacked,
},
}
p := NewEthereumParser()
p := NewEthereumParser(1)
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := p.PackTx(tt.args.tx, tt.args.height, tt.args.blockTime)
@ -175,18 +315,30 @@ func TestEthereumParser_UnpackTx(t *testing.T) {
}{
{
name: "1",
args: args{hex: testTxPacked1},
args: args{hex: dbtestdata.EthTx1Packed},
want: &testTx1,
want1: 2870000,
want1: 4321000,
},
{
name: "2",
args: args{hex: testTxPacked2},
args: args{hex: dbtestdata.EthTx2Packed},
want: &testTx2,
want1: 2871048,
want1: 4321000,
},
{
name: "3",
args: args{hex: dbtestdata.EthTx1FailedPacked},
want: &testTx1Failed,
want1: 4321000,
},
{
name: "4",
args: args{hex: dbtestdata.EthTx1NoStatusPacked},
want: &testTx1NoStatus,
want1: 4321000,
},
}
p := NewEthereumParser()
p := NewEthereumParser(1)
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
b, err := hex.DecodeString(tt.args.hex)
@ -198,8 +350,22 @@ func TestEthereumParser_UnpackTx(t *testing.T) {
t.Errorf("EthereumParser.UnpackTx() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("EthereumParser.UnpackTx() got = %v, want %v", got, tt.want)
// DeepEqual has problems with pointers in completeTransaction
gs := got.CoinSpecificData.(completeTransaction)
ws := tt.want.CoinSpecificData.(completeTransaction)
gc := *got
wc := *tt.want
gc.CoinSpecificData = nil
wc.CoinSpecificData = nil
if fmt.Sprint(gc) != fmt.Sprint(wc) {
// if !reflect.DeepEqual(gc, wc) {
t.Errorf("EthereumParser.UnpackTx() gc got = %+v, want %+v", gc, wc)
}
if !reflect.DeepEqual(gs.Tx, ws.Tx) {
t.Errorf("EthereumParser.UnpackTx() gs.Tx got = %+v, want %+v", gs.Tx, ws.Tx)
}
if !reflect.DeepEqual(gs.Receipt, ws.Receipt) {
t.Errorf("EthereumParser.UnpackTx() gs.Receipt got = %+v, want %+v", gs.Receipt, ws.Receipt)
}
if got1 != tt.want1 {
t.Errorf("EthereumParser.UnpackTx() got1 = %v, want %v", got1, tt.want1)
@ -207,3 +373,30 @@ func TestEthereumParser_UnpackTx(t *testing.T) {
})
}
}
func TestEthereumParser_GetEthereumTxData(t *testing.T) {
tests := []struct {
name string
tx *bchain.Tx
want string
}{
{
name: "Test empty data",
tx: &testTx1,
want: "0x",
},
{
name: "Test non empty data",
tx: &testTx2,
want: "0xa9059cbb000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f00000000000000000000000000000000000000000000021e19e0c9bab2400000",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := GetEthereumTxData(tt.tx)
if got.Data != tt.want {
t.Errorf("EthereumParser.GetEthereumTxData() = %v, want %v", got.Data, tt.want)
}
})
}
}

View File

@ -1,7 +1,6 @@
package eth
import (
"blockbook/bchain"
"context"
"encoding/json"
"fmt"
@ -10,14 +9,16 @@ import (
"sync"
"time"
"github.com/golang/glog"
"github.com/juju/errors"
ethereum "github.com/ethereum/go-ethereum"
ethcommon "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/rpc"
"github.com/golang/glog"
"github.com/juju/errors"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/common"
)
// EthereumNet type specifies the type of ethereum network
@ -28,25 +29,31 @@ const (
MainNet EthereumNet = 1
// TestNet is Ropsten test network
TestNet EthereumNet = 3
// TestNetGoerli is Goerli test network
TestNetGoerli EthereumNet = 5
)
// Configuration represents json config file
type Configuration struct {
CoinName string `json:"coin_name"`
CoinShortcut string `json:"coin_shortcut"`
RPCURL string `json:"rpc_url"`
RPCTimeout int `json:"rpc_timeout"`
CoinName string `json:"coin_name"`
CoinShortcut string `json:"coin_shortcut"`
RPCURL string `json:"rpc_url"`
RPCTimeout int `json:"rpc_timeout"`
BlockAddressesToKeep int `json:"block_addresses_to_keep"`
MempoolTxTimeoutHours int `json:"mempoolTxTimeoutHours"`
QueryBackendOnMempoolResync bool `json:"queryBackendOnMempoolResync"`
}
// EthereumRPC is an interface to JSON-RPC eth service.
type EthereumRPC struct {
*bchain.BaseChain
client *ethclient.Client
rpc *rpc.Client
timeout time.Duration
Parser *EthereumParser
Testnet bool
Network string
Mempool *bchain.NonUTXOMempool
bestHeaderMu sync.Mutex
Mempool *bchain.MempoolEthereumType
mempoolInitialized bool
bestHeaderLock sync.Mutex
bestHeader *ethtypes.Header
bestHeaderTime time.Time
chanNewBlock chan *ethtypes.Header
@ -54,7 +61,6 @@ type EthereumRPC struct {
chanNewTx chan ethcommon.Hash
newTxSubscription *rpc.ClientSubscription
ChainConfig *Configuration
isETC bool
}
// NewEthereumRPC returns new EthRPC instance.
@ -65,25 +71,27 @@ func NewEthereumRPC(config json.RawMessage, pushHandler func(bchain.Notification
if err != nil {
return nil, errors.Annotatef(err, "Invalid configuration file")
}
rc, err := rpc.Dial(c.RPCURL)
// keep at least 100 mappings block->addresses to allow rollback
if c.BlockAddressesToKeep < 100 {
c.BlockAddressesToKeep = 100
}
rc, ec, err := openRPC(c.RPCURL)
if err != nil {
return nil, err
}
ec := ethclient.NewClient(rc)
s := &EthereumRPC{
BaseChain: &bchain.BaseChain{},
client: ec,
rpc: rc,
ChainConfig: &c,
}
// always create parser
s.Parser = NewEthereumParser()
s.Parser = NewEthereumParser(c.BlockAddressesToKeep)
s.timeout = time.Duration(c.RPCTimeout) * time.Second
// detect ethereum classic
s.isETC = s.ChainConfig.CoinName == "Ethereum Classic"
// new blocks notifications handling
// the subscription is done in Initialize
s.chanNewBlock = make(chan *ethtypes.Header)
@ -95,10 +103,10 @@ func NewEthereumRPC(config json.RawMessage, pushHandler func(bchain.Notification
}
glog.V(2).Info("rpc: new block header ", h.Number)
// update best header to the new header
s.bestHeaderMu.Lock()
s.bestHeaderLock.Lock()
s.bestHeader = h
s.bestHeaderTime = time.Now()
s.bestHeaderMu.Unlock()
s.bestHeaderLock.Unlock()
// notify blockbook
pushHandler(bchain.NotificationNewBlock)
}
@ -113,9 +121,11 @@ func NewEthereumRPC(config json.RawMessage, pushHandler func(bchain.Notification
if !ok {
break
}
hex := t.Hex()
if glog.V(2) {
glog.Info("rpc: new tx ", t.Hex())
glog.Info("rpc: new tx ", hex)
}
s.Mempool.AddTransactionToMempool(hex)
pushHandler(bchain.NotificationNewTx)
}
}()
@ -123,6 +133,15 @@ func NewEthereumRPC(config json.RawMessage, pushHandler func(bchain.Notification
return s, nil
}
func openRPC(url string) (*rpc.Client, *ethclient.Client, error) {
rc, err := rpc.Dial(url)
if err != nil {
return nil, nil, err
}
ec := ethclient.NewClient(rc)
return rc, ec, nil
}
// Initialize initializes ethereum rpc interface
func (b *EthereumRPC) Initialize() error {
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
@ -143,32 +162,72 @@ func (b *EthereumRPC) Initialize() error {
b.Testnet = true
b.Network = "testnet"
break
case TestNetGoerli:
b.Testnet = true
b.Network = "goerli"
default:
return errors.Errorf("Unknown network id %v", id)
}
glog.Info("rpc: block chain ", b.Network)
if b.isETC {
glog.Info(b.ChainConfig.CoinName, " does not support subscription to newHeads")
} else {
// subscriptions
if err = b.subscribe(func() (*rpc.ClientSubscription, error) {
// invalidate the previous subscription - it is either the first one or there was an error
b.newBlockSubscription = nil
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
defer cancel()
sub, err := b.rpc.EthSubscribe(ctx, b.chanNewBlock, "newHeads")
if err != nil {
return nil, errors.Annotatef(err, "EthSubscribe newHeads")
}
b.newBlockSubscription = sub
glog.Info("Subscribed to newHeads")
return sub, nil
}); err != nil {
return err
}
return nil
}
// CreateMempool creates mempool if not already created, however does not initialize it
func (b *EthereumRPC) CreateMempool(chain bchain.BlockChain) (bchain.Mempool, error) {
if b.Mempool == nil {
b.Mempool = bchain.NewMempoolEthereumType(chain, b.ChainConfig.MempoolTxTimeoutHours, b.ChainConfig.QueryBackendOnMempoolResync)
glog.Info("mempool created, MempoolTxTimeoutHours=", b.ChainConfig.MempoolTxTimeoutHours, ", QueryBackendOnMempoolResync=", b.ChainConfig.QueryBackendOnMempoolResync)
}
if err = b.subscribe(func() (*rpc.ClientSubscription, error) {
return b.Mempool, nil
}
// InitializeMempool creates subscriptions to newHeads and newPendingTransactions
func (b *EthereumRPC) InitializeMempool(addrDescForOutpoint bchain.AddrDescForOutpointFunc, onNewTxAddr bchain.OnNewTxAddrFunc, onNewTx bchain.OnNewTxFunc) error {
if b.Mempool == nil {
return errors.New("Mempool not created")
}
// get initial mempool transactions
txs, err := b.GetMempoolTransactions()
if err != nil {
return err
}
for _, txid := range txs {
b.Mempool.AddTransactionToMempool(txid)
}
b.Mempool.OnNewTxAddr = onNewTxAddr
b.Mempool.OnNewTx = onNewTx
if err = b.subscribeEvents(); err != nil {
return err
}
b.mempoolInitialized = true
return nil
}
func (b *EthereumRPC) subscribeEvents() error {
// subscriptions
if err := b.subscribe(func() (*rpc.ClientSubscription, error) {
// invalidate the previous subscription - it is either the first one or there was an error
b.newBlockSubscription = nil
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
defer cancel()
sub, err := b.rpc.EthSubscribe(ctx, b.chanNewBlock, "newHeads")
if err != nil {
return nil, errors.Annotatef(err, "EthSubscribe newHeads")
}
b.newBlockSubscription = sub
glog.Info("Subscribed to newHeads")
return sub, nil
}); err != nil {
return err
}
if err := b.subscribe(func() (*rpc.ClientSubscription, error) {
// invalidate the previous subscription - it is either the first one or there was an error
b.newTxSubscription = nil
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
@ -184,9 +243,6 @@ func (b *EthereumRPC) Initialize() error {
return err
}
// create mempool
b.Mempool = bchain.NewNonUTXOMempool(b)
return nil
}
@ -206,8 +262,8 @@ func (b *EthereumRPC) subscribe(f func() (*rpc.ClientSubscription, error)) error
return
}
glog.Error("Subscription error ", e)
timer := time.NewTimer(time.Second)
// try in 1 second interval to resubscribe
timer := time.NewTimer(time.Second * 2)
// try in 2 second interval to resubscribe
for {
select {
case e = <-s.Err():
@ -221,7 +277,8 @@ func (b *EthereumRPC) subscribe(f func() (*rpc.ClientSubscription, error)) error
s = ns
continue Loop
}
timer.Reset(time.Second)
glog.Error("Resubscribe error ", err)
timer.Reset(time.Second * 2)
}
}
}
@ -229,8 +286,7 @@ func (b *EthereumRPC) subscribe(f func() (*rpc.ClientSubscription, error)) error
return nil
}
// Shutdown cleans up rpc interface to ethereum
func (b *EthereumRPC) Shutdown(ctx context.Context) error {
func (b *EthereumRPC) closeRPC() {
if b.newBlockSubscription != nil {
b.newBlockSubscription.Unsubscribe()
}
@ -240,54 +296,80 @@ func (b *EthereumRPC) Shutdown(ctx context.Context) error {
if b.rpc != nil {
b.rpc.Close()
}
}
func (b *EthereumRPC) reconnectRPC() error {
glog.Info("Reconnecting RPC")
b.closeRPC()
rc, ec, err := openRPC(b.ChainConfig.RPCURL)
if err != nil {
return err
}
b.rpc = rc
b.client = ec
return b.subscribeEvents()
}
// Shutdown cleans up rpc interface to ethereum
func (b *EthereumRPC) Shutdown(ctx context.Context) error {
b.closeRPC()
close(b.chanNewBlock)
glog.Info("rpc: shutdown")
return nil
}
func (b *EthereumRPC) IsTestnet() bool {
return b.Testnet
}
func (b *EthereumRPC) GetNetworkName() string {
return b.Network
}
// GetCoinName returns coin name
func (b *EthereumRPC) GetCoinName() string {
return b.ChainConfig.CoinName
}
// GetSubversion returns empty string, ethereum does not have subversion
func (b *EthereumRPC) GetSubversion() string {
return ""
}
// GetChainInfo returns information about the connected backend
func (b *EthereumRPC) GetChainInfo() (*bchain.ChainInfo, error) {
h, err := b.getBestHeader()
if err != nil {
return nil, err
}
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
defer cancel()
id, err := b.client.NetworkID(ctx)
if err != nil {
return nil, err
}
rv := &bchain.ChainInfo{}
var ver string
if err := b.rpc.CallContext(ctx, &ver, "web3_clientVersion"); err != nil {
return nil, err
}
rv := &bchain.ChainInfo{
Blocks: int(h.Number.Int64()),
Bestblockhash: h.Hash().Hex(),
Difficulty: h.Difficulty.String(),
Version: ver,
}
idi := int(id.Uint64())
if idi == 1 {
rv.Chain = "mainnet"
} else {
rv.Chain = "testnet " + strconv.Itoa(idi)
}
// TODO - return more information about the chain
return rv, nil
}
func (b *EthereumRPC) getBestHeader() (*ethtypes.Header, error) {
b.bestHeaderMu.Lock()
defer b.bestHeaderMu.Unlock()
// ETC does not have newBlocks subscription, bestHeader must be updated very often (each 1 second)
if b.isETC {
if b.bestHeaderTime.Add(1 * time.Second).Before(time.Now()) {
b.bestHeader = nil
b.bestHeaderLock.Lock()
defer b.bestHeaderLock.Unlock()
// if the best header was not updated for 15 minutes, there could be a subscription problem, reconnect RPC
// do it only in case of normal operation, not initial synchronization
if b.bestHeaderTime.Add(15*time.Minute).Before(time.Now()) && !b.bestHeaderTime.IsZero() && b.mempoolInitialized {
err := b.reconnectRPC()
if err != nil {
return nil, err
}
b.bestHeader = nil
}
if b.bestHeader == nil {
var err error
@ -295,6 +377,7 @@ func (b *EthereumRPC) getBestHeader() (*ethtypes.Header, error) {
defer cancel()
b.bestHeader, err = b.client.HeaderByNumber(ctx, nil)
if err != nil {
b.bestHeader = nil
return nil, err
}
b.bestHeaderTime = time.Now()
@ -302,23 +385,25 @@ func (b *EthereumRPC) getBestHeader() (*ethtypes.Header, error) {
return b.bestHeader, nil
}
// GetBestBlockHash returns hash of the tip of the best-block-chain
func (b *EthereumRPC) GetBestBlockHash() (string, error) {
h, err := b.getBestHeader()
if err != nil {
return "", err
}
return ethHashToHash(h.Hash()), nil
return h.Hash().Hex(), nil
}
// GetBestBlockHeight returns height of the tip of the best-block-chain
func (b *EthereumRPC) GetBestBlockHeight() (uint32, error) {
h, err := b.getBestHeader()
if err != nil {
return 0, err
}
// TODO - can it grow over 2^32 ?
return uint32(h.Number.Uint64()), nil
}
// GetBlockHash returns hash of block in best-block-chain at given height
func (b *EthereumRPC) GetBlockHash(height uint32) (string, error) {
var n big.Int
n.SetUint64(uint64(height))
@ -331,36 +416,47 @@ func (b *EthereumRPC) GetBlockHash(height uint32) (string, error) {
}
return "", errors.Annotatef(err, "height %v", height)
}
return ethHashToHash(h.Hash()), nil
return h.Hash().Hex(), nil
}
func (b *EthereumRPC) ethHeaderToBlockHeader(h *ethtypes.Header) (*bchain.BlockHeader, error) {
hn := h.Number.Uint64()
c, err := b.computeConfirmations(hn)
func (b *EthereumRPC) ethHeaderToBlockHeader(h *rpcHeader) (*bchain.BlockHeader, error) {
height, err := ethNumber(h.Number)
if err != nil {
return nil, err
}
c, err := b.computeConfirmations(uint64(height))
if err != nil {
return nil, err
}
time, err := ethNumber(h.Time)
if err != nil {
return nil, err
}
size, err := ethNumber(h.Size)
if err != nil {
return nil, err
}
return &bchain.BlockHeader{
Hash: ethHashToHash(h.Hash()),
Height: uint32(hn),
Hash: h.Hash,
Prev: h.ParentHash,
Height: uint32(height),
Confirmations: int(c),
Time: int64(h.Time.Uint64()),
// Next
// Prev
Time: time,
Size: int(size),
}, nil
}
// GetBlockHeader returns header of block with given hash
func (b *EthereumRPC) GetBlockHeader(hash string) (*bchain.BlockHeader, error) {
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
defer cancel()
h, err := b.client.HeaderByHash(ctx, ethcommon.HexToHash(hash))
raw, err := b.getBlockRaw(hash, 0, false)
if err != nil {
if err == ethereum.NotFound {
return nil, bchain.ErrBlockNotFound
}
return nil, err
}
var h rpcHeader
if err := json.Unmarshal(raw, &h); err != nil {
return nil, errors.Annotatef(err, "hash %v", hash)
}
return b.ethHeaderToBlockHeader(h)
return b.ethHeaderToBlockHeader(&h)
}
func (b *EthereumRPC) computeConfirmations(n uint64) (uint32, error) {
@ -373,61 +469,82 @@ func (b *EthereumRPC) computeConfirmations(n uint64) (uint32, error) {
return uint32(bn - n + 1), nil
}
// GetBlock returns block with given hash or height, hash has precedence if both passed
func (b *EthereumRPC) GetBlock(hash string, height uint32) (*bchain.Block, error) {
func (b *EthereumRPC) getBlockRaw(hash string, height uint32, fullTxs bool) (json.RawMessage, error) {
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
defer cancel()
var raw json.RawMessage
var err error
if hash != "" {
err = b.rpc.CallContext(ctx, &raw, "eth_getBlockByHash", ethcommon.HexToHash(hash), true)
if hash == "pending" {
err = b.rpc.CallContext(ctx, &raw, "eth_getBlockByNumber", hash, fullTxs)
} else {
err = b.rpc.CallContext(ctx, &raw, "eth_getBlockByHash", ethcommon.HexToHash(hash), fullTxs)
}
} else {
err = b.rpc.CallContext(ctx, &raw, "eth_getBlockByNumber", fmt.Sprintf("%#x", height), true)
err = b.rpc.CallContext(ctx, &raw, "eth_getBlockByNumber", fmt.Sprintf("%#x", height), fullTxs)
}
if err != nil {
return nil, errors.Annotatef(err, "hash %v, height %v", hash, height)
} else if len(raw) == 0 {
return nil, bchain.ErrBlockNotFound
}
// Decode header and transactions.
var head *ethtypes.Header
var body rpcBlock
return raw, nil
}
func (b *EthereumRPC) getERC20EventsForBlock(blockNumber string) (map[string][]*rpcLog, error) {
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
defer cancel()
var logs []rpcLogWithTxHash
err := b.rpc.CallContext(ctx, &logs, "eth_getLogs", map[string]interface{}{
"fromBlock": blockNumber,
"toBlock": blockNumber,
"topics": []string{erc20TransferEventSignature},
})
if err != nil {
return nil, errors.Annotatef(err, "blockNumber %v", blockNumber)
}
r := make(map[string][]*rpcLog)
for i := range logs {
l := &logs[i]
r[l.Hash] = append(r[l.Hash], &l.rpcLog)
}
return r, nil
}
// GetBlock returns block with given hash or height, hash has precedence if both passed
func (b *EthereumRPC) GetBlock(hash string, height uint32) (*bchain.Block, error) {
raw, err := b.getBlockRaw(hash, height, true)
if err != nil {
return nil, err
}
var head rpcHeader
if err := json.Unmarshal(raw, &head); err != nil {
return nil, errors.Annotatef(err, "hash %v, height %v", hash, height)
}
if head == nil {
return nil, bchain.ErrBlockNotFound
}
var body rpcBlockTransactions
if err := json.Unmarshal(raw, &body); err != nil {
return nil, errors.Annotatef(err, "hash %v, height %v", hash, height)
}
// Quick-verify transaction and uncle lists. This mostly helps with debugging the server.
if head.UncleHash == ethtypes.EmptyUncleHash && len(body.UncleHashes) > 0 {
return nil, errors.Annotatef(fmt.Errorf("server returned non-empty uncle list but block header indicates no uncles"), "hash %v, height %v", hash, height)
}
if head.UncleHash != ethtypes.EmptyUncleHash && len(body.UncleHashes) == 0 {
return nil, errors.Annotatef(fmt.Errorf("server returned empty uncle list but block header indicates uncles"), "hash %v, height %v", hash, height)
}
if head.TxHash == ethtypes.EmptyRootHash && len(body.Transactions) > 0 {
return nil, errors.Annotatef(fmt.Errorf("server returned non-empty transaction list but block header indicates no transactions"), "hash %v, height %v", hash, height)
}
if head.TxHash != ethtypes.EmptyRootHash && len(body.Transactions) == 0 {
return nil, errors.Annotatef(fmt.Errorf("server returned empty transaction list but block header indicates transactions"), "hash %v, height %v", hash, height)
}
bbh, err := b.ethHeaderToBlockHeader(head)
bbh, err := b.ethHeaderToBlockHeader(&head)
if err != nil {
return nil, errors.Annotatef(err, "hash %v, height %v", hash, height)
}
// TODO - this is probably not the correct size
bbh.Size = len(raw)
// get ERC20 events
logs, err := b.getERC20EventsForBlock(head.Number)
if err != nil {
return nil, err
}
btxs := make([]bchain.Tx, len(body.Transactions))
for i, tx := range body.Transactions {
btx, err := b.Parser.ethTxToTx(&tx, int64(head.Time.Uint64()), uint32(bbh.Confirmations))
for i := range body.Transactions {
tx := &body.Transactions[i]
btx, err := b.Parser.ethTxToTx(tx, &rpcReceipt{Logs: logs[tx.Hash]}, bbh.Time, uint32(bbh.Confirmations), true)
if err != nil {
return nil, errors.Annotatef(err, "hash %v, height %v, txid %v", hash, height, tx.Hash.String())
return nil, errors.Annotatef(err, "hash %v, height %v, txid %v", hash, height, tx.Hash)
}
btxs[i] = *btx
if b.mempoolInitialized {
b.Mempool.RemoveTransactionFromMempool(tx.Hash)
}
}
bbk := bchain.Block{
BlockHeader: *bbh,
@ -438,8 +555,28 @@ func (b *EthereumRPC) GetBlock(hash string, height uint32) (*bchain.Block, error
// GetBlockInfo returns extended header (more info than in bchain.BlockHeader) with a list of txids
func (b *EthereumRPC) GetBlockInfo(hash string) (*bchain.BlockInfo, error) {
// TODO - implement
return nil, errors.New("Not implemented yet")
raw, err := b.getBlockRaw(hash, 0, false)
if err != nil {
return nil, err
}
var head rpcHeader
var txs rpcBlockTxids
if err := json.Unmarshal(raw, &head); err != nil {
return nil, errors.Annotatef(err, "hash %v", hash)
}
if err = json.Unmarshal(raw, &txs); err != nil {
return nil, err
}
bch, err := b.ethHeaderToBlockHeader(&head)
if err != nil {
return nil, err
}
return &bchain.BlockInfo{
BlockHeader: *bch,
Difficulty: common.JSONNumber(head.Difficulty),
Nonce: common.JSONNumber(head.Nonce),
Txids: txs.Transactions,
}, nil
}
// GetTransactionForMempool returns a transaction by the transaction ID.
@ -453,32 +590,45 @@ func (b *EthereumRPC) GetTransaction(txid string) (*bchain.Tx, error) {
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
defer cancel()
var tx *rpcTransaction
err := b.rpc.CallContext(ctx, &tx, "eth_getTransactionByHash", ethcommon.HexToHash(txid))
hash := ethcommon.HexToHash(txid)
err := b.rpc.CallContext(ctx, &tx, "eth_getTransactionByHash", hash)
if err != nil {
return nil, err
} else if tx == nil {
return nil, ethereum.NotFound
} else if tx.R == "" {
if !b.isETC {
return nil, errors.Annotatef(fmt.Errorf("server returned transaction without signature"), "txid %v", txid)
} else {
glog.Warning("server returned transaction without signature, txid ", txid)
if b.mempoolInitialized {
b.Mempool.RemoveTransactionFromMempool(txid)
}
return nil, bchain.ErrTxNotFound
}
var btx *bchain.Tx
if tx.BlockNumber == "" {
// mempool tx
btx, err = b.Parser.ethTxToTx(tx, 0, 0)
btx, err = b.Parser.ethTxToTx(tx, nil, 0, 0, true)
if err != nil {
return nil, errors.Annotatef(err, "txid %v", txid)
}
} else {
// non mempool tx - we must read the block header to get the block time
n, err := ethNumber(tx.BlockNumber)
// non mempool tx - read the block header to get the block time
raw, err := b.getBlockRaw(tx.BlockHash, 0, false)
if err != nil {
return nil, err
}
var ht struct {
Time string `json:"timestamp"`
}
if err := json.Unmarshal(raw, &ht); err != nil {
return nil, errors.Annotatef(err, "hash %v", hash)
}
var time int64
if time, err = ethNumber(ht.Time); err != nil {
return nil, errors.Annotatef(err, "txid %v", txid)
}
var receipt rpcReceipt
err = b.rpc.CallContext(ctx, &receipt, "eth_getTransactionReceipt", hash)
if err != nil {
return nil, errors.Annotatef(err, "txid %v", txid)
}
h, err := b.client.HeaderByHash(ctx, *tx.BlockHash)
n, err := ethNumber(tx.BlockNumber)
if err != nil {
return nil, errors.Annotatef(err, "txid %v", txid)
}
@ -486,46 +636,46 @@ func (b *EthereumRPC) GetTransaction(txid string) (*bchain.Tx, error) {
if err != nil {
return nil, errors.Annotatef(err, "txid %v", txid)
}
btx, err = b.Parser.ethTxToTx(tx, h.Time.Int64(), confirmations)
btx, err = b.Parser.ethTxToTx(tx, &receipt, time, confirmations, true)
if err != nil {
return nil, errors.Annotatef(err, "txid %v", txid)
}
// remove tx from mempool if it is there
if b.mempoolInitialized {
b.Mempool.RemoveTransactionFromMempool(txid)
}
}
return btx, nil
}
// GetTransactionSpecific returns json as returned by backend, with all coin specific data
func (b *EthereumRPC) GetTransactionSpecific(txid string) (json.RawMessage, error) {
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
defer cancel()
var tx json.RawMessage
err := b.rpc.CallContext(ctx, &tx, "eth_getTransactionByHash", ethcommon.HexToHash(txid))
if err != nil {
return nil, err
} else if tx == nil {
return nil, ethereum.NotFound
func (b *EthereumRPC) GetTransactionSpecific(tx *bchain.Tx) (json.RawMessage, error) {
csd, ok := tx.CoinSpecificData.(completeTransaction)
if !ok {
ntx, err := b.GetTransaction(tx.Txid)
if err != nil {
return nil, err
}
csd, ok = ntx.CoinSpecificData.(completeTransaction)
if !ok {
return nil, errors.New("Cannot get CoinSpecificData")
}
}
return tx, nil
m, err := json.Marshal(&csd)
return json.RawMessage(m), err
}
type rpcMempoolBlock struct {
Transactions []string `json:"transactions"`
}
func (b *EthereumRPC) GetMempool() ([]string, error) {
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
defer cancel()
var raw json.RawMessage
var err error
err = b.rpc.CallContext(ctx, &raw, "eth_getBlockByNumber", "pending", false)
// GetMempoolTransactions returns transactions in mempool
func (b *EthereumRPC) GetMempoolTransactions() ([]string, error) {
raw, err := b.getBlockRaw("pending", 0, false)
if err != nil {
return nil, err
} else if len(raw) == 0 {
return nil, bchain.ErrBlockNotFound
}
var body rpcMempoolBlock
if err := json.Unmarshal(raw, &body); err != nil {
return nil, err
var body rpcBlockTxids
if len(raw) > 0 {
if err := json.Unmarshal(raw, &body); err != nil {
return nil, err
}
}
return body.Transactions, nil
}
@ -539,21 +689,57 @@ func (b *EthereumRPC) EstimateFee(blocks int) (big.Int, error) {
func (b *EthereumRPC) EstimateSmartFee(blocks int, conservative bool) (big.Int, error) {
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
defer cancel()
// TODO - what parameters of msg to use to get better estimate, maybe more data from the wallet are needed
a := ethcommon.HexToAddress("0x1234567890123456789012345678901234567890")
msg := ethereum.CallMsg{
To: &a,
}
g, err := b.client.EstimateGas(ctx, msg)
var r big.Int
if err != nil {
return r, err
gp, err := b.client.SuggestGasPrice(ctx)
if err == nil && b != nil {
r = *gp
}
r.SetUint64(g)
return r, nil
return r, err
}
// SendRawTransaction sends raw transaction.
func getStringFromMap(p string, params map[string]interface{}) (string, bool) {
v, ok := params[p]
if ok {
s, ok := v.(string)
return s, ok
}
return "", false
}
// EthereumTypeEstimateGas returns estimation of gas consumption for given transaction parameters
func (b *EthereumRPC) EthereumTypeEstimateGas(params map[string]interface{}) (uint64, error) {
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
defer cancel()
msg := ethereum.CallMsg{}
s, ok := getStringFromMap("from", params)
if ok && len(s) > 0 {
msg.From = ethcommon.HexToAddress(s)
}
s, ok = getStringFromMap("to", params)
if ok && len(s) > 0 {
a := ethcommon.HexToAddress(s)
msg.To = &a
}
s, ok = getStringFromMap("data", params)
if ok && len(s) > 0 {
msg.Data = ethcommon.FromHex(s)
}
s, ok = getStringFromMap("value", params)
if ok && len(s) > 0 {
msg.Value, _ = hexutil.DecodeBig(s)
}
s, ok = getStringFromMap("gas", params)
if ok && len(s) > 0 {
msg.Gas, _ = hexutil.DecodeUint64(s)
}
s, ok = getStringFromMap("gasPrice", params)
if ok && len(s) > 0 {
msg.GasPrice, _ = hexutil.DecodeBig(s)
}
return b.client.EstimateGas(ctx, msg)
}
// SendRawTransaction sends raw transaction
func (b *EthereumRPC) SendRawTransaction(hex string) (string, error) {
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
defer cancel()
@ -574,24 +760,21 @@ func (b *EthereumRPC) SendRawTransaction(hex string) (string, error) {
return result, nil
}
func (b *EthereumRPC) ResyncMempool(onNewTxAddr bchain.OnNewTxAddrFunc) (int, error) {
return b.Mempool.Resync(onNewTxAddr)
// EthereumTypeGetBalance returns current balance of an address
func (b *EthereumRPC) EthereumTypeGetBalance(addrDesc bchain.AddressDescriptor) (*big.Int, error) {
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
defer cancel()
return b.client.BalanceAt(ctx, ethcommon.BytesToAddress(addrDesc), nil)
}
// GetMempoolTransactions returns slice of mempool transactions for given address
func (b *EthereumRPC) GetMempoolTransactions(address string) ([]string, error) {
return b.Mempool.GetTransactions(address)
}
// GetMempoolTransactionsForAddrDesc returns slice of mempool transactions for given address descriptor
func (b *EthereumRPC) GetMempoolTransactionsForAddrDesc(addrDesc bchain.AddressDescriptor) ([]string, error) {
return b.Mempool.GetAddrDescTransactions(addrDesc)
}
func (b *EthereumRPC) GetMempoolEntry(txid string) (*bchain.MempoolEntry, error) {
return nil, errors.New("GetMempoolEntry: not implemented")
// EthereumTypeGetNonce returns current balance of an address
func (b *EthereumRPC) EthereumTypeGetNonce(addrDesc bchain.AddressDescriptor) (uint64, error) {
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
defer cancel()
return b.client.NonceAt(ctx, ethcommon.BytesToAddress(addrDesc), nil)
}
// GetChainParser returns ethereum BlockChainParser
func (b *EthereumRPC) GetChainParser() bchain.BlockChainParser {
return b.Parser
}

View File

@ -0,0 +1,261 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: bchain/coins/eth/ethtx.proto
/*
Package eth is a generated protocol buffer package.
It is generated from these files:
bchain/coins/eth/ethtx.proto
It has these top-level messages:
ProtoCompleteTransaction
*/
package eth
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
type ProtoCompleteTransaction struct {
BlockNumber uint32 `protobuf:"varint,1,opt,name=BlockNumber" json:"BlockNumber,omitempty"`
BlockTime uint64 `protobuf:"varint,2,opt,name=BlockTime" json:"BlockTime,omitempty"`
Tx *ProtoCompleteTransaction_TxType `protobuf:"bytes,3,opt,name=Tx" json:"Tx,omitempty"`
Receipt *ProtoCompleteTransaction_ReceiptType `protobuf:"bytes,4,opt,name=Receipt" json:"Receipt,omitempty"`
}
func (m *ProtoCompleteTransaction) Reset() { *m = ProtoCompleteTransaction{} }
func (m *ProtoCompleteTransaction) String() string { return proto.CompactTextString(m) }
func (*ProtoCompleteTransaction) ProtoMessage() {}
func (*ProtoCompleteTransaction) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
func (m *ProtoCompleteTransaction) GetBlockNumber() uint32 {
if m != nil {
return m.BlockNumber
}
return 0
}
func (m *ProtoCompleteTransaction) GetBlockTime() uint64 {
if m != nil {
return m.BlockTime
}
return 0
}
func (m *ProtoCompleteTransaction) GetTx() *ProtoCompleteTransaction_TxType {
if m != nil {
return m.Tx
}
return nil
}
func (m *ProtoCompleteTransaction) GetReceipt() *ProtoCompleteTransaction_ReceiptType {
if m != nil {
return m.Receipt
}
return nil
}
type ProtoCompleteTransaction_TxType struct {
AccountNonce uint64 `protobuf:"varint,1,opt,name=AccountNonce" json:"AccountNonce,omitempty"`
GasPrice []byte `protobuf:"bytes,2,opt,name=GasPrice,proto3" json:"GasPrice,omitempty"`
GasLimit uint64 `protobuf:"varint,3,opt,name=GasLimit" json:"GasLimit,omitempty"`
Value []byte `protobuf:"bytes,4,opt,name=Value,proto3" json:"Value,omitempty"`
Payload []byte `protobuf:"bytes,5,opt,name=Payload,proto3" json:"Payload,omitempty"`
Hash []byte `protobuf:"bytes,6,opt,name=Hash,proto3" json:"Hash,omitempty"`
To []byte `protobuf:"bytes,7,opt,name=To,proto3" json:"To,omitempty"`
From []byte `protobuf:"bytes,8,opt,name=From,proto3" json:"From,omitempty"`
TransactionIndex uint32 `protobuf:"varint,9,opt,name=TransactionIndex" json:"TransactionIndex,omitempty"`
}
func (m *ProtoCompleteTransaction_TxType) Reset() { *m = ProtoCompleteTransaction_TxType{} }
func (m *ProtoCompleteTransaction_TxType) String() string { return proto.CompactTextString(m) }
func (*ProtoCompleteTransaction_TxType) ProtoMessage() {}
func (*ProtoCompleteTransaction_TxType) Descriptor() ([]byte, []int) {
return fileDescriptor0, []int{0, 0}
}
func (m *ProtoCompleteTransaction_TxType) GetAccountNonce() uint64 {
if m != nil {
return m.AccountNonce
}
return 0
}
func (m *ProtoCompleteTransaction_TxType) GetGasPrice() []byte {
if m != nil {
return m.GasPrice
}
return nil
}
func (m *ProtoCompleteTransaction_TxType) GetGasLimit() uint64 {
if m != nil {
return m.GasLimit
}
return 0
}
func (m *ProtoCompleteTransaction_TxType) GetValue() []byte {
if m != nil {
return m.Value
}
return nil
}
func (m *ProtoCompleteTransaction_TxType) GetPayload() []byte {
if m != nil {
return m.Payload
}
return nil
}
func (m *ProtoCompleteTransaction_TxType) GetHash() []byte {
if m != nil {
return m.Hash
}
return nil
}
func (m *ProtoCompleteTransaction_TxType) GetTo() []byte {
if m != nil {
return m.To
}
return nil
}
func (m *ProtoCompleteTransaction_TxType) GetFrom() []byte {
if m != nil {
return m.From
}
return nil
}
func (m *ProtoCompleteTransaction_TxType) GetTransactionIndex() uint32 {
if m != nil {
return m.TransactionIndex
}
return 0
}
type ProtoCompleteTransaction_ReceiptType struct {
GasUsed []byte `protobuf:"bytes,1,opt,name=GasUsed,proto3" json:"GasUsed,omitempty"`
Status []byte `protobuf:"bytes,2,opt,name=Status,proto3" json:"Status,omitempty"`
Log []*ProtoCompleteTransaction_ReceiptType_LogType `protobuf:"bytes,3,rep,name=Log" json:"Log,omitempty"`
}
func (m *ProtoCompleteTransaction_ReceiptType) Reset() { *m = ProtoCompleteTransaction_ReceiptType{} }
func (m *ProtoCompleteTransaction_ReceiptType) String() string { return proto.CompactTextString(m) }
func (*ProtoCompleteTransaction_ReceiptType) ProtoMessage() {}
func (*ProtoCompleteTransaction_ReceiptType) Descriptor() ([]byte, []int) {
return fileDescriptor0, []int{0, 1}
}
func (m *ProtoCompleteTransaction_ReceiptType) GetGasUsed() []byte {
if m != nil {
return m.GasUsed
}
return nil
}
func (m *ProtoCompleteTransaction_ReceiptType) GetStatus() []byte {
if m != nil {
return m.Status
}
return nil
}
func (m *ProtoCompleteTransaction_ReceiptType) GetLog() []*ProtoCompleteTransaction_ReceiptType_LogType {
if m != nil {
return m.Log
}
return nil
}
type ProtoCompleteTransaction_ReceiptType_LogType struct {
Address []byte `protobuf:"bytes,1,opt,name=Address,proto3" json:"Address,omitempty"`
Data []byte `protobuf:"bytes,2,opt,name=Data,proto3" json:"Data,omitempty"`
Topics [][]byte `protobuf:"bytes,3,rep,name=Topics,proto3" json:"Topics,omitempty"`
}
func (m *ProtoCompleteTransaction_ReceiptType_LogType) Reset() {
*m = ProtoCompleteTransaction_ReceiptType_LogType{}
}
func (m *ProtoCompleteTransaction_ReceiptType_LogType) String() string {
return proto.CompactTextString(m)
}
func (*ProtoCompleteTransaction_ReceiptType_LogType) ProtoMessage() {}
func (*ProtoCompleteTransaction_ReceiptType_LogType) Descriptor() ([]byte, []int) {
return fileDescriptor0, []int{0, 1, 0}
}
func (m *ProtoCompleteTransaction_ReceiptType_LogType) GetAddress() []byte {
if m != nil {
return m.Address
}
return nil
}
func (m *ProtoCompleteTransaction_ReceiptType_LogType) GetData() []byte {
if m != nil {
return m.Data
}
return nil
}
func (m *ProtoCompleteTransaction_ReceiptType_LogType) GetTopics() [][]byte {
if m != nil {
return m.Topics
}
return nil
}
func init() {
proto.RegisterType((*ProtoCompleteTransaction)(nil), "eth.ProtoCompleteTransaction")
proto.RegisterType((*ProtoCompleteTransaction_TxType)(nil), "eth.ProtoCompleteTransaction.TxType")
proto.RegisterType((*ProtoCompleteTransaction_ReceiptType)(nil), "eth.ProtoCompleteTransaction.ReceiptType")
proto.RegisterType((*ProtoCompleteTransaction_ReceiptType_LogType)(nil), "eth.ProtoCompleteTransaction.ReceiptType.LogType")
}
func init() { proto.RegisterFile("bchain/coins/eth/ethtx.proto", fileDescriptor0) }
var fileDescriptor0 = []byte{
// 409 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x92, 0xdf, 0x8a, 0xd4, 0x30,
0x18, 0xc5, 0xe9, 0x9f, 0x99, 0xd9, 0xfd, 0xa6, 0x8a, 0x04, 0x91, 0x30, 0xec, 0x45, 0x59, 0xbc,
0x18, 0xbd, 0xe8, 0xe2, 0xea, 0x0b, 0xac, 0x23, 0xae, 0xc2, 0xb0, 0x0e, 0x31, 0x7a, 0x9f, 0x49,
0xc3, 0x36, 0x38, 0x6d, 0x4a, 0x93, 0x42, 0xf7, 0x8d, 0x7c, 0x21, 0xdf, 0xc5, 0x4b, 0xc9, 0xd7,
0x74, 0x1d, 0x11, 0x65, 0x2f, 0x0a, 0xf9, 0x9d, 0x7e, 0xa7, 0x39, 0x27, 0x29, 0x9c, 0xed, 0x65,
0x25, 0x74, 0x73, 0x21, 0x8d, 0x6e, 0xec, 0x85, 0x72, 0x95, 0x7f, 0xdc, 0x50, 0xb4, 0x9d, 0x71,
0x86, 0x24, 0xca, 0x55, 0xe7, 0xdf, 0x67, 0x40, 0x77, 0x1e, 0x37, 0xa6, 0x6e, 0x0f, 0xca, 0x29,
0xde, 0x89, 0xc6, 0x0a, 0xe9, 0xb4, 0x69, 0x48, 0x0e, 0xcb, 0xb7, 0x07, 0x23, 0xbf, 0xdd, 0xf4,
0xf5, 0x5e, 0x75, 0x34, 0xca, 0xa3, 0xf5, 0x23, 0x76, 0x2c, 0x91, 0x33, 0x38, 0x45, 0xe4, 0xba,
0x56, 0x34, 0xce, 0xa3, 0x75, 0xca, 0x7e, 0x0b, 0xe4, 0x0d, 0xc4, 0x7c, 0xa0, 0x49, 0x1e, 0xad,
0x97, 0x97, 0xcf, 0x0b, 0xe5, 0xaa, 0xe2, 0x5f, 0x5b, 0x15, 0x7c, 0xe0, 0x77, 0xad, 0x62, 0x31,
0x1f, 0xc8, 0x06, 0x16, 0x4c, 0x49, 0xa5, 0x5b, 0x47, 0x53, 0xb4, 0xbe, 0xf8, 0xbf, 0x35, 0x0c,
0xa3, 0x7f, 0x72, 0xae, 0x7e, 0x46, 0x30, 0x1f, 0xbf, 0x49, 0xce, 0x21, 0xbb, 0x92, 0xd2, 0xf4,
0x8d, 0xbb, 0x31, 0x8d, 0x54, 0x58, 0x23, 0x65, 0x7f, 0x68, 0x64, 0x05, 0x27, 0xd7, 0xc2, 0xee,
0x3a, 0x2d, 0xc7, 0x1a, 0x19, 0xbb, 0xe7, 0xf0, 0x6e, 0xab, 0x6b, 0xed, 0xb0, 0x4b, 0xca, 0xee,
0x99, 0x3c, 0x85, 0xd9, 0x57, 0x71, 0xe8, 0x15, 0x26, 0xcd, 0xd8, 0x08, 0x84, 0xc2, 0x62, 0x27,
0xee, 0x0e, 0x46, 0x94, 0x74, 0x86, 0xfa, 0x84, 0x84, 0x40, 0xfa, 0x41, 0xd8, 0x8a, 0xce, 0x51,
0xc6, 0x35, 0x79, 0x0c, 0x31, 0x37, 0x74, 0x81, 0x4a, 0xcc, 0x8d, 0x9f, 0x79, 0xdf, 0x99, 0x9a,
0x9e, 0x8c, 0x33, 0x7e, 0x4d, 0x5e, 0xc2, 0x93, 0xa3, 0xca, 0x1f, 0x9b, 0x52, 0x0d, 0xf4, 0x14,
0xaf, 0xe3, 0x2f, 0x7d, 0xf5, 0x23, 0x82, 0xe5, 0xd1, 0x99, 0xf8, 0x34, 0xd7, 0xc2, 0x7e, 0xb1,
0xaa, 0xc4, 0xea, 0x19, 0x9b, 0x90, 0x3c, 0x83, 0xf9, 0x67, 0x27, 0x5c, 0x6f, 0x43, 0xe7, 0x40,
0x64, 0x03, 0xc9, 0xd6, 0xdc, 0xd2, 0x24, 0x4f, 0xd6, 0xcb, 0xcb, 0x57, 0x0f, 0x3e, 0xfd, 0x62,
0x6b, 0x6e, 0xf1, 0x16, 0xbc, 0x7b, 0xf5, 0x09, 0x16, 0x81, 0x7d, 0x82, 0xab, 0xb2, 0xec, 0x94,
0xb5, 0x53, 0x82, 0x80, 0xbe, 0xeb, 0x3b, 0xe1, 0x44, 0xd8, 0x1f, 0xd7, 0x3e, 0x15, 0x37, 0xad,
0x96, 0x16, 0x03, 0x64, 0x2c, 0xd0, 0x7e, 0x8e, 0xbf, 0xed, 0xeb, 0x5f, 0x01, 0x00, 0x00, 0xff,
0xff, 0xc2, 0x69, 0x8d, 0xdf, 0xd6, 0x02, 0x00, 0x00,
}

View File

@ -0,0 +1,30 @@
syntax = "proto3";
package eth;
message ProtoCompleteTransaction {
message TxType {
uint64 AccountNonce = 1;
bytes GasPrice = 2;
uint64 GasLimit = 3;
bytes Value = 4;
bytes Payload = 5;
bytes Hash = 6;
bytes To = 7;
bytes From = 8;
uint32 TransactionIndex = 9;
}
message ReceiptType {
message LogType {
bytes Address = 1;
bytes Data = 2;
repeated bytes Topics = 3;
}
bytes GasUsed = 1;
bytes Status = 2;
repeated LogType Log = 3;
}
uint32 BlockNumber = 1;
uint64 BlockTime = 2;
TxType Tx = 3;
ReceiptType Receipt = 4;
}

View File

@ -1,175 +0,0 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: tx.proto
/*
Package eth is a generated protocol buffer package.
It is generated from these files:
tx.proto
It has these top-level messages:
ProtoTransaction
*/
package eth
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
type ProtoTransaction struct {
AccountNonce uint64 `protobuf:"varint,1,opt,name=AccountNonce" json:"AccountNonce,omitempty"`
Price []byte `protobuf:"bytes,2,opt,name=Price,proto3" json:"Price,omitempty"`
GasLimit uint64 `protobuf:"varint,3,opt,name=GasLimit" json:"GasLimit,omitempty"`
Value []byte `protobuf:"bytes,4,opt,name=Value,proto3" json:"Value,omitempty"`
Payload []byte `protobuf:"bytes,5,opt,name=Payload,proto3" json:"Payload,omitempty"`
Hash []byte `protobuf:"bytes,6,opt,name=Hash,proto3" json:"Hash,omitempty"`
BlockNumber uint32 `protobuf:"varint,7,opt,name=BlockNumber" json:"BlockNumber,omitempty"`
BlockTime uint64 `protobuf:"varint,8,opt,name=BlockTime" json:"BlockTime,omitempty"`
To []byte `protobuf:"bytes,9,opt,name=To,proto3" json:"To,omitempty"`
From []byte `protobuf:"bytes,10,opt,name=From,proto3" json:"From,omitempty"`
TransactionIndex uint32 `protobuf:"varint,11,opt,name=TransactionIndex" json:"TransactionIndex,omitempty"`
V []byte `protobuf:"bytes,12,opt,name=V,proto3" json:"V,omitempty"`
R []byte `protobuf:"bytes,13,opt,name=R,proto3" json:"R,omitempty"`
S []byte `protobuf:"bytes,14,opt,name=S,proto3" json:"S,omitempty"`
}
func (m *ProtoTransaction) Reset() { *m = ProtoTransaction{} }
func (m *ProtoTransaction) String() string { return proto.CompactTextString(m) }
func (*ProtoTransaction) ProtoMessage() {}
func (*ProtoTransaction) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
func (m *ProtoTransaction) GetAccountNonce() uint64 {
if m != nil {
return m.AccountNonce
}
return 0
}
func (m *ProtoTransaction) GetPrice() []byte {
if m != nil {
return m.Price
}
return nil
}
func (m *ProtoTransaction) GetGasLimit() uint64 {
if m != nil {
return m.GasLimit
}
return 0
}
func (m *ProtoTransaction) GetValue() []byte {
if m != nil {
return m.Value
}
return nil
}
func (m *ProtoTransaction) GetPayload() []byte {
if m != nil {
return m.Payload
}
return nil
}
func (m *ProtoTransaction) GetHash() []byte {
if m != nil {
return m.Hash
}
return nil
}
func (m *ProtoTransaction) GetBlockNumber() uint32 {
if m != nil {
return m.BlockNumber
}
return 0
}
func (m *ProtoTransaction) GetBlockTime() uint64 {
if m != nil {
return m.BlockTime
}
return 0
}
func (m *ProtoTransaction) GetTo() []byte {
if m != nil {
return m.To
}
return nil
}
func (m *ProtoTransaction) GetFrom() []byte {
if m != nil {
return m.From
}
return nil
}
func (m *ProtoTransaction) GetTransactionIndex() uint32 {
if m != nil {
return m.TransactionIndex
}
return 0
}
func (m *ProtoTransaction) GetV() []byte {
if m != nil {
return m.V
}
return nil
}
func (m *ProtoTransaction) GetR() []byte {
if m != nil {
return m.R
}
return nil
}
func (m *ProtoTransaction) GetS() []byte {
if m != nil {
return m.S
}
return nil
}
func init() {
proto.RegisterType((*ProtoTransaction)(nil), "eth.ProtoTransaction")
}
func init() { proto.RegisterFile("tx.proto", fileDescriptor0) }
var fileDescriptor0 = []byte{
// 262 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x64, 0x90, 0xbd, 0x6a, 0xeb, 0x40,
0x10, 0x85, 0x59, 0x59, 0xb6, 0xe5, 0xb1, 0x6c, 0xcc, 0x70, 0x8b, 0xe1, 0x92, 0x42, 0xb8, 0x12,
0x29, 0xd2, 0xe4, 0x09, 0x92, 0x22, 0x3f, 0x10, 0x8c, 0x90, 0x85, 0xfa, 0xf5, 0x7a, 0xc1, 0x22,
0x92, 0x26, 0x48, 0x2b, 0x70, 0x5e, 0x38, 0xcf, 0x11, 0x76, 0x44, 0x12, 0x87, 0x74, 0xf3, 0x7d,
0x70, 0xf6, 0x2c, 0x07, 0x22, 0x77, 0xbe, 0x79, 0xeb, 0xd8, 0x31, 0x4e, 0xac, 0x3b, 0x6d, 0x3f,
0x02, 0xd8, 0x64, 0x1e, 0x8b, 0x4e, 0xb7, 0xbd, 0x36, 0xae, 0xe2, 0x16, 0xb7, 0x10, 0xdf, 0x19,
0xc3, 0x43, 0xeb, 0x76, 0xdc, 0x1a, 0x4b, 0x2a, 0x51, 0x69, 0x98, 0xff, 0x72, 0xf8, 0x0f, 0xa6,
0x59, 0x57, 0x19, 0x4b, 0x41, 0xa2, 0xd2, 0x38, 0x1f, 0x01, 0xff, 0x43, 0xf4, 0xa8, 0xfb, 0x97,
0xaa, 0xa9, 0x1c, 0x4d, 0x24, 0xf5, 0xcd, 0x3e, 0x51, 0xea, 0x7a, 0xb0, 0x14, 0x8e, 0x09, 0x01,
0x24, 0x98, 0x67, 0xfa, 0xbd, 0x66, 0x7d, 0xa4, 0xa9, 0xf8, 0x2f, 0x44, 0x84, 0xf0, 0x49, 0xf7,
0x27, 0x9a, 0x89, 0x96, 0x1b, 0x13, 0x58, 0xde, 0xd7, 0x6c, 0x5e, 0x77, 0x43, 0x73, 0xb0, 0x1d,
0xcd, 0x13, 0x95, 0xae, 0xf2, 0x4b, 0x85, 0x57, 0xb0, 0x10, 0x2c, 0xaa, 0xc6, 0x52, 0x24, 0x5f,
0xf8, 0x11, 0xb8, 0x86, 0xa0, 0x60, 0x5a, 0xc8, 0x8b, 0x41, 0xc1, 0xbe, 0xe3, 0xa1, 0xe3, 0x86,
0x60, 0xec, 0xf0, 0x37, 0x5e, 0xc3, 0xe6, 0x62, 0x8c, 0xe7, 0xf6, 0x68, 0xcf, 0xb4, 0x94, 0xa2,
0x3f, 0x1e, 0x63, 0x50, 0x25, 0xc5, 0x12, 0x56, 0xa5, 0xa7, 0x9c, 0x56, 0x23, 0xe5, 0x9e, 0xf6,
0xb4, 0x1e, 0x69, 0x7f, 0x98, 0xc9, 0xe8, 0xb7, 0x9f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x15, 0xc8,
0xe4, 0x30, 0x80, 0x01, 0x00, 0x00,
}

View File

@ -1,19 +0,0 @@
syntax = "proto3";
package eth;
message ProtoTransaction {
uint64 AccountNonce = 1;
bytes Price = 2;
uint64 GasLimit = 3;
bytes Value = 4;
bytes Payload = 5;
bytes Hash = 6;
uint32 BlockNumber = 7;
uint64 BlockTime = 8;
bytes To = 9;
bytes From = 10;
uint32 TransactionIndex = 11;
bytes V = 12;
bytes R = 13;
bytes S = 14;
}

View File

@ -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
}

View File

@ -0,0 +1,312 @@
package firo
import (
"bytes"
"encoding/binary"
"encoding/json"
"io"
"github.com/martinboehm/btcd/chaincfg/chainhash"
"github.com/martinboehm/btcd/wire"
"github.com/martinboehm/btcutil/chaincfg"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
)
const (
OpZeroCoinMint = 0xc1
OpZeroCoinSpend = 0xc2
OpSigmaMint = 0xc3
OpSigmaSpend = 0xc4
OpLelantusMint = 0xc5
OpLelantusJMint = 0xc6
OpLelantusJoinSplit = 0xc7
MainnetMagic wire.BitcoinNet = 0xe3d9fef1
TestnetMagic wire.BitcoinNet = 0xcffcbeea
RegtestMagic wire.BitcoinNet = 0xfabfb5da
GenesisBlockTime = 1414776286
SwitchToMTPBlockHeader = 1544443200
MTPL = 64
SpendTxID = "0000000000000000000000000000000000000000000000000000000000000000"
TransactionQuorumCommitmentType = 6
)
var (
MainNetParams chaincfg.Params
TestNetParams chaincfg.Params
RegtestParams chaincfg.Params
)
func init() {
// mainnet
MainNetParams = chaincfg.MainNetParams
MainNetParams.Net = MainnetMagic
MainNetParams.AddressMagicLen = 1
MainNetParams.PubKeyHashAddrID = []byte{0x52}
MainNetParams.ScriptHashAddrID = []byte{0x07}
// testnet
TestNetParams = chaincfg.TestNet3Params
TestNetParams.Net = TestnetMagic
TestNetParams.AddressMagicLen = 1
TestNetParams.PubKeyHashAddrID = []byte{0x41}
TestNetParams.ScriptHashAddrID = []byte{0xb2}
// regtest
RegtestParams = chaincfg.RegressionNetParams
RegtestParams.Net = RegtestMagic
}
// FiroParser handle
type FiroParser struct {
*btc.BitcoinParser
}
// NewFiroParser returns new FiroParser instance
func NewFiroParser(params *chaincfg.Params, c *btc.Configuration) *FiroParser {
return &FiroParser{
BitcoinParser: btc.NewBitcoinParser(params, c),
}
}
// GetChainParams contains network parameters for the main Firo network,
// the regression test Firo network, the test Firo network and
// the simulation test Firo network, in this order
func GetChainParams(chain string) *chaincfg.Params {
if !chaincfg.IsRegistered(&MainNetParams) {
err := chaincfg.Register(&MainNetParams)
if err == nil {
err = chaincfg.Register(&TestNetParams)
}
if err == nil {
err = chaincfg.Register(&RegtestParams)
}
if err != nil {
panic(err)
}
}
switch chain {
case "test":
return &TestNetParams
case "regtest":
return &RegtestParams
default:
return &MainNetParams
}
}
// GetAddressesFromAddrDesc returns addresses for given address descriptor with flag if the addresses are searchable
func (p *FiroParser) GetAddressesFromAddrDesc(addrDesc bchain.AddressDescriptor) ([]string, bool, error) {
if len(addrDesc) > 0 {
switch addrDesc[0] {
case OpZeroCoinMint:
return []string{"Zeromint"}, false, nil
case OpZeroCoinSpend:
return []string{"Zerospend"}, false, nil
case OpSigmaMint:
return []string{"Sigmamint"}, false, nil
case OpSigmaSpend:
return []string{"Sigmaspend"}, false, nil
case OpLelantusMint:
return []string{"LelantusMint"}, false, nil
case OpLelantusJMint:
return []string{"LelantusJMint"}, false, nil
case OpLelantusJoinSplit:
return []string{"LelantusJoinSplit"}, false, nil
}
}
return p.OutputScriptToAddressesFunc(addrDesc)
}
// PackTx packs transaction to byte array using protobuf
func (p *FiroParser) PackTx(tx *bchain.Tx, height uint32, blockTime int64) ([]byte, error) {
return p.BaseParser.PackTx(tx, height, blockTime)
}
// UnpackTx unpacks transaction from protobuf byte array
func (p *FiroParser) UnpackTx(buf []byte) (*bchain.Tx, uint32, error) {
return p.BaseParser.UnpackTx(buf)
}
// TxFromFiroMsgTx converts bitcoin wire Tx to bchain.Tx
func (p *FiroParser) TxFromFiroMsgTx(t *FiroMsgTx, parseAddresses bool) bchain.Tx {
btx := p.TxFromMsgTx(&t.MsgTx, parseAddresses)
// NOTE: wire.MsgTx.TxHash() doesn't include extra
btx.Txid = t.TxHash().String()
return btx
}
// ParseBlock parses raw block to our Block struct
func (p *FiroParser) ParseBlock(b []byte) (*bchain.Block, error) {
reader := bytes.NewReader(b)
// parse standard block header first
header, err := parseBlockHeader(reader)
if err != nil {
return nil, err
}
// then MTP header
if isMTP(header) {
mtpHeader := MTPBlockHeader{}
mtpHashData := MTPHashData{}
// header
err = binary.Read(reader, binary.LittleEndian, &mtpHeader)
if err != nil {
return nil, err
}
// hash data
err = binary.Read(reader, binary.LittleEndian, &mtpHashData)
if err != nil {
return nil, err
}
// proof
for i := 0; i < MTPL*3; i++ {
var numberProofBlocks uint8
err = binary.Read(reader, binary.LittleEndian, &numberProofBlocks)
if err != nil {
return nil, err
}
for j := uint8(0); j < numberProofBlocks; j++ {
var mtpData [16]uint8
err = binary.Read(reader, binary.LittleEndian, mtpData[:])
if err != nil {
return nil, err
}
}
}
}
// parse txs
ntx, err := wire.ReadVarInt(reader, 0)
if err != nil {
return nil, err
}
txs := make([]bchain.Tx, ntx)
for i := uint64(0); i < ntx; i++ {
tx := FiroMsgTx{}
// read version and seek back
var version uint32 = 0
if err = binary.Read(reader, binary.LittleEndian, &version); err != nil {
return nil, err
}
if _, err = reader.Seek(-4, io.SeekCurrent); err != nil {
return nil, err
}
txVersion := version & 0xffff
txType := (version >> 16) & 0xffff
enc := wire.WitnessEncoding
// transaction quorum commitment could not be parsed with witness flag
if txVersion == 3 && txType == TransactionQuorumCommitmentType {
enc = wire.BaseEncoding
}
if err = tx.FiroDecode(reader, 0, enc); err != nil {
return nil, err
}
btx := p.TxFromFiroMsgTx(&tx, false)
if err = p.parseFiroTx(&btx); err != nil {
return nil, err
}
txs[i] = btx
}
return &bchain.Block{
BlockHeader: bchain.BlockHeader{
Size: len(b),
Time: header.Timestamp.Unix(),
},
Txs: txs,
}, nil
}
// ParseTxFromJson parses JSON message containing transaction and returns Tx struct
func (p *FiroParser) ParseTxFromJson(msg json.RawMessage) (*bchain.Tx, error) {
var tx bchain.Tx
err := json.Unmarshal(msg, &tx)
if err != nil {
return nil, err
}
for i := range tx.Vout {
vout := &tx.Vout[i]
// convert vout.JsonValue to big.Int and clear it, it is only temporary value used for unmarshal
vout.ValueSat, err = p.AmountToBigInt(vout.JsonValue)
if err != nil {
return nil, err
}
vout.JsonValue = ""
}
p.parseFiroTx(&tx)
return &tx, nil
}
func (p *FiroParser) parseFiroTx(tx *bchain.Tx) error {
for i := range tx.Vin {
vin := &tx.Vin[i]
// FIXME: right now we treat zerocoin spend vin as coinbase
// change this after blockbook support special type of vin
if vin.Txid == SpendTxID {
vin.Coinbase = vin.Txid
vin.Txid = ""
vin.Sequence = 0
vin.Vout = 0
}
}
return nil
}
func parseBlockHeader(r io.Reader) (*wire.BlockHeader, error) {
h := &wire.BlockHeader{}
err := h.Deserialize(r)
return h, err
}
func isMTP(h *wire.BlockHeader) bool {
epoch := h.Timestamp.Unix()
// the genesis block never be MTP block
return epoch > GenesisBlockTime && epoch >= SwitchToMTPBlockHeader
}
type MTPHashData struct {
HashRootMTP [16]uint8
BlockMTP [128][128]uint64
}
type MTPBlockHeader struct {
VersionMTP int32
MTPHashValue chainhash.Hash
Reserved1 chainhash.Hash
Reserved2 chainhash.Hash
}

View File

@ -0,0 +1,981 @@
// +build unittest
package firo
import (
"bytes"
"encoding/hex"
"encoding/json"
"io/ioutil"
"math/big"
"os"
"reflect"
"strings"
"testing"
"github.com/martinboehm/btcd/wire"
"github.com/martinboehm/btcutil/chaincfg"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
)
var (
testTx1, testTx2, testTx3, testTx4, testTx5, testTx6 bchain.Tx
rawTestTx1, rawTestTx2, rawTestTx3, rawTestTx4, rawTestTx5, rawTestTx6 string
testTxPacked1, testTxPacked2, testTxPacked3, testTxPacked4, testTxPacked5, testTxPacked6 string
rawBlock1, rawBlock2, rawBlock3 string
jsonTx json.RawMessage
)
func readHexs(path string) []string {
raw, err := ioutil.ReadFile(path)
if err != nil {
panic(err)
}
rawStr := string(raw)
raws := strings.Split(rawStr, "\n")
return raws
}
func init() {
rawBlocks := readHexs("./testdata/rawblock.hex")
rawBlock1 = rawBlocks[0]
rawBlock2 = rawBlocks[1]
rawBlock3 = rawBlocks[2]
hextxs := readHexs("./testdata/txs.hex")
rawTestTx1 = hextxs[0]
rawTestTx2 = hextxs[1]
rawTestTx3 = hextxs[2]
rawTestTx4 = hextxs[3]
rawTestTx5 = hextxs[4]
rawTestTx6 = hextxs[5]
rawSpendHex := readHexs("./testdata/rawspend.hex")[0]
rawSpendTx, err := ioutil.ReadFile("./testdata/spendtx.json")
if err != nil {
panic(err)
}
jsonTx = json.RawMessage(rawSpendTx)
testTxPackeds := readHexs("./testdata/packedtxs.hex")
testTxPacked1 = testTxPackeds[0]
testTxPacked2 = testTxPackeds[1]
testTxPacked3 = testTxPackeds[2]
testTxPacked4 = testTxPackeds[3]
testTxPacked5 = testTxPackeds[4]
testTxPacked6 = testTxPackeds[5]
testTx1 = bchain.Tx{
Hex: rawTestTx1,
Blocktime: 1533980594,
Time: 1533980594,
Txid: "9d9e759dd970d86df9e105a7d4f671543bc16a03b6c5d2b48895f2a00aa7dd23",
LockTime: 99688,
Vin: []bchain.Vin{
{
ScriptSig: bchain.ScriptSig{
Hex: "47304402205b7d9c9aae790b69017651e10134735928df3b4a4a2feacc9568eb4fa133ed5902203f21a399385ce29dd79831ea34aa535612aa4314c5bd0b002bbbc9bcd2de1436012102b8d462740c99032a00083ac7028879acec244849e54ad0a04ea87f632f54b1d2",
},
Txid: "463a2d66b04636a014da35724909425f3403d9d786dd4f79780de50d47b18716",
Vout: 1,
Sequence: 4294967294,
},
},
Vout: []bchain.Vout{
{
ValueSat: *big.NewInt(100000000),
N: 0,
ScriptPubKey: bchain.ScriptPubKey{
Hex: "c10280004c80f767f3ee79953c67a7ed386dcccf1243619eb4bbbe414a3982dd94a83c1b69ac52d6ab3b653a3e05c4e4516c8dfe1e58ada40461bc5835a4a0d0387a51c29ac11b72ae25bbcdef745f50ad08f08b3e9bc2c31a35444398a490e65ac090e9f341f1abdebe47e57e8237ac25d098e951b4164a35caea29f30acb50b12e4425df28",
},
},
{
ValueSat: *big.NewInt(871824000),
N: 1,
ScriptPubKey: bchain.ScriptPubKey{
Hex: "76a914c963f917c7f23cb4243e079db33107571b87690588ac",
Addresses: []string{
"aK5KKi8qqDbspcXFfDjx8UBGMouhYbYZVp",
},
},
},
},
}
testTx2 = bchain.Tx{
Hex: rawTestTx2,
Blocktime: 1481277009,
Time: 1481277009,
Txid: "3d721fdce2855e2b4a54b74a26edd58a7262e1f195b5acaaae7832be6e0b3d32",
LockTime: 0,
Vin: []bchain.Vin{
{
Coinbase: rawSpendHex,
Txid: "0000000000000000000000000000000000000000000000000000000000000000",
Vout: 4294967295,
Sequence: 2,
},
},
Vout: []bchain.Vout{
{
ValueSat: *big.NewInt(5000000000),
N: 0,
ScriptPubKey: bchain.ScriptPubKey{
Hex: "76a914b9e262e30df03e88ccea312652bc83ca7290c8fc88ac",
Addresses: []string{
"aHfKwzFZMiSxDuNL4jts819nh57t2yJG1h",
},
},
},
},
}
testTx3 = bchain.Tx{
Hex: rawTestTx3,
Blocktime: 1547091829,
Time: 1547091829,
Txid: "96ae951083651f141d1fb2719c76d47e5a3ad421b81905f679c0edb60f2de0ff",
LockTime: 126200,
Vin: []bchain.Vin{
{
ScriptSig: bchain.ScriptSig{
Hex: "483045022100bdc6b51c114617e29e28390dc9b3ad95b833ca3d1f0429ba667c58a667f9124702204ca2ed362dd9ef723ddbdcf4185b47c28b127a36f46bc4717662be863309b3e601210387e7ff08b953e3736955408fc6ebcd8aa84a04cc4b45758ea29cc2cfe1820535",
},
Txid: "448ccfd9c3f375be8701b86aff355a230dbe240334233f2ed476fcae6abd295d",
Vout: 1,
Sequence: 4294967294,
},
},
Vout: []bchain.Vout{
{
ValueSat: *big.NewInt(42000000000),
N: 0,
ScriptPubKey: bchain.ScriptPubKey{
Hex: "76a91429bef7962c5c65a2f0f4f7d9ec791866c54f851688ac",
Addresses: []string{
"a4XCDQ7AnRH9opZ4h6LcG3g7ocSV2SbBmS",
},
},
},
{
ValueSat: *big.NewInt(107300000000),
N: 1,
ScriptPubKey: bchain.ScriptPubKey{
Hex: "76a914e2cee7b71c3a4637dbdfe613f19f4b4f2d070d7f88ac",
Addresses: []string{
"aMPiKHB3E1AGPi8kKLknx6j1L4JnKCGkLw",
},
},
},
},
}
testTx4 = bchain.Tx{
Hex: rawTestTx4,
Blocktime: 1533977563,
Time: 1533977563,
Txid: "914ccbdb72f593e5def15978cf5891e1384a1b85e89374fc1c440c074c6dd286",
LockTime: 0,
Vin: []bchain.Vin{
{
Coinbase: "03a1860104dba36e5b082a00077c00000000052f6d70682f",
Sequence: 0,
},
},
Vout: []bchain.Vout{
{
ValueSat: *big.NewInt(2800200000),
N: 0,
ScriptPubKey: bchain.ScriptPubKey{
Hex: "76a91436e086acf6561a68ba64196e7b92b606d0b8516688ac",
Addresses: []string{
"a5idCcHN8WYxvFCeBXSXvMPrZHuBkZmqEJ",
},
},
},
{
ValueSat: *big.NewInt(1500000000),
N: 1,
ScriptPubKey: bchain.ScriptPubKey{
Hex: "76a914381a5dd1a279e8e63e67cde39ecfa61a99dd2ba288ac",
Addresses: []string{
"a5q7Ad4okSFFVh5adyqx5DT21RTxJykpUM",
},
},
},
{
ValueSat: *big.NewInt(100000000),
N: 2,
ScriptPubKey: bchain.ScriptPubKey{
Hex: "76a9147d9ed014fc4e603fca7c2e3f9097fb7d0fb487fc88ac",
Addresses: []string{
"aCAgTPgtYcA4EysU4UKC86EQd5cTtHtCcr",
},
},
},
{
ValueSat: *big.NewInt(100000000),
N: 3,
ScriptPubKey: bchain.ScriptPubKey{
Hex: "76a914bc7e5a5234db3ab82d74c396ad2b2af419b7517488ac",
Addresses: []string{
"aHu897ivzmeFuLNB6956X6gyGeVNHUBRgD",
},
},
},
{
ValueSat: *big.NewInt(100000000),
N: 4,
ScriptPubKey: bchain.ScriptPubKey{
Hex: "76a914ff71b0c9c2a90c6164a50a2fb523eb54a8a6b55088ac",
Addresses: []string{
"aQ18FBVFtnueucZKeVg4srhmzbpAeb1KoN",
},
},
},
{
ValueSat: *big.NewInt(300000000),
N: 5,
ScriptPubKey: bchain.ScriptPubKey{
Hex: "76a9140654dd9b856f2ece1d56cb4ee5043cd9398d962c88ac",
Addresses: []string{
"a1HwTdCmQV3NspP2QqCGpehoFpi8NY4Zg3",
},
},
},
{
ValueSat: *big.NewInt(100000000),
N: 6,
ScriptPubKey: bchain.ScriptPubKey{
Hex: "76a9140b4bfb256ef4bfa360e3b9e66e53a0bd84d196bc88ac",
Addresses: []string{
"a1kCCGddf5pMXSipLVD9hBG2MGGVNaJ15U",
},
},
},
// TODO: test segwit
},
}
testTx5 = bchain.Tx{
Hex: rawTestTx5,
Blocktime: 1591752749,
Time: 1591752749,
Txid: "8d1f32f35c32d2c127a7400dc1ec52049fbf0b8bcdf284cfaa3da59b6169a22d",
LockTime: 0,
Vin: []bchain.Vin{},
Vout: []bchain.Vout{},
}
testTx6 = bchain.Tx{
Hex: rawTestTx6,
Blocktime: 1591762049,
Time: 1591762049,
Txid: "e5767d3606230a65f150837a6f28b4f0e4c2702a683045df3883d57702739c61",
LockTime: 0,
Vin: []bchain.Vin{
{
Coinbase: "02b4140101",
Sequence: 4294967295,
},
},
Vout: []bchain.Vout{
{
ValueSat: *big.NewInt(1400000000),
N: 0,
ScriptPubKey: bchain.ScriptPubKey{
Hex: "2103fb09a216761d5e7f248294970c2370f7f84ce1ad564b8e7096b1e19116af1d52ac",
Addresses: []string{
"TAn9Ghkp31myXRgejCj11wWVHT14Lsj349",
},
},
},
{
ValueSat: *big.NewInt(50000000),
N: 1,
ScriptPubKey: bchain.ScriptPubKey{
Hex: "76a914296134d2415bf1f2b518b3f673816d7e603b160088ac",
Addresses: []string{
"TDk19wPKYq91i18qmY6U9FeTdTxwPeSveo",
},
},
},
{
ValueSat: *big.NewInt(50000000),
N: 2,
ScriptPubKey: bchain.ScriptPubKey{
Hex: "76a914e1e1dc06a889c1b6d3eb00eef7a96f6a7cfb884888ac",
Addresses: []string{
"TWZZcDGkNixTAMtRBqzZkkMHbq1G6vUTk5",
},
},
},
{
ValueSat: *big.NewInt(50000000),
N: 3,
ScriptPubKey: bchain.ScriptPubKey{
Hex: "76a914ab03ecfddee6330497be894d16c29ae341c123aa88ac",
Addresses: []string{
"TRZTFdNCKCKbLMQV8cZDkQN9Vwuuq4gDzT",
},
},
},
{
ValueSat: *big.NewInt(150000000),
N: 4,
ScriptPubKey: bchain.ScriptPubKey{
Hex: "76a9144281a58a1d5b2d3285e00cb45a8492debbdad4c588ac",
Addresses: []string{
"TG2ruj59E5b1u9G3F7HQVs6pCcVDBxrQve",
},
},
},
{
ValueSat: *big.NewInt(50000000),
N: 5,
ScriptPubKey: bchain.ScriptPubKey{
Hex: "76a9141fd264c0bb53bd9fef18e2248ddf1383d6e811ae88ac",
Addresses: []string{
"TCsTzQZKVn4fao8jDmB9zQBk9YQNEZ3XfS",
},
},
},
{
ValueSat: *big.NewInt(750000000),
N: 6,
ScriptPubKey: bchain.ScriptPubKey{
Hex: "76a91471a3892d164ffa3829078bf9ad5f114a3908ce5588ac",
Addresses: []string{
"TLL5GQULX4uBfz7yXL6VcZyvzdKVv1RGxm",
},
},
},
},
}
}
func TestMain(m *testing.M) {
c := m.Run()
chaincfg.ResetParams()
os.Exit(c)
}
func TestGetAddrDesc(t *testing.T) {
type args struct {
tx bchain.Tx
parser *FiroParser
}
tests := []struct {
name string
args args
}{
{
name: "firo-1",
args: args{
tx: testTx1,
parser: NewFiroParser(GetChainParams("main"), &btc.Configuration{}),
},
},
// FIXME: work around handle zerocoin spend as coinbase
// {
// name: "firo-2",
// args: args{
// tx: testTx2,
// parser: NewFiroParser(GetChainParams("main"), &btc.Configuration{}),
// },
// },
{
name: "firo-3",
args: args{
tx: testTx3,
parser: NewFiroParser(GetChainParams("main"), &btc.Configuration{}),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
for n, vout := range tt.args.tx.Vout {
got1, err := tt.args.parser.GetAddrDescFromVout(&vout)
if err != nil {
t.Errorf("getAddrDescFromVout() error = %v, vout = %d", err, n)
return
}
// normal vout
if len(vout.ScriptPubKey.Addresses) >= 1 {
got2, err := tt.args.parser.GetAddrDescFromAddress(vout.ScriptPubKey.Addresses[0])
if err != nil {
t.Errorf("getAddrDescFromAddress() error = %v, vout = %d", err, n)
return
}
if !bytes.Equal(got1, got2) {
t.Errorf("Address descriptors mismatch: got1 = %v, got2 = %v", got1, got2)
}
}
}
})
}
}
func TestGetAddrDescFromVoutForMint(t *testing.T) {
type args struct {
vout bchain.Vout
}
tests := []struct {
name string
args args
want string
wantErr bool
}{
{
name: "OP_RETURN",
args: args{vout: bchain.Vout{ScriptPubKey: bchain.ScriptPubKey{Hex: "6a072020f1686f6a20"}}},
want: "6a072020f1686f6a20",
wantErr: false,
},
{
name: "OP_ZEROCOINMINT",
args: args{vout: bchain.Vout{ScriptPubKey: bchain.ScriptPubKey{Hex: "c10280004c80f767f3ee79953c67a7ed386dcccf1243619eb4bbbe414a3982dd94a83c1b69ac52d6ab3b653a3e05c4e4516c8dfe1e58ada40461bc5835a4a0d0387a51c29ac11b72ae25bbcdef745f50ad08f08b3e9bc2c31a35444398a490e65ac090e9f341f1abdebe47e57e8237ac25d098e951b4164a35caea29f30acb50b12e4425df28"}}},
want: "c10280004c80f767f3ee79953c67a7ed386dcccf1243619eb4bbbe414a3982dd94a83c1b69ac52d6ab3b653a3e05c4e4516c8dfe1e58ada40461bc5835a4a0d0387a51c29ac11b72ae25bbcdef745f50ad08f08b3e9bc2c31a35444398a490e65ac090e9f341f1abdebe47e57e8237ac25d098e951b4164a35caea29f30acb50b12e4425df28",
wantErr: false,
},
{
name: "OP_SIGMAMINT",
args: args{vout: bchain.Vout{ScriptPubKey: bchain.ScriptPubKey{Hex: "c317dcee5b8b2c5b79728abc3a39abc54682b31a4e18f5abb6f34dc8089544763b0000"}}},
want: "c317dcee5b8b2c5b79728abc3a39abc54682b31a4e18f5abb6f34dc8089544763b0000",
wantErr: false,
},
}
parser := NewFiroParser(GetChainParams("main"), &btc.Configuration{})
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := parser.GetAddrDescFromVout(&tt.args.vout)
if (err != nil) != tt.wantErr {
t.Errorf("GetAddrDescFromVout() error = %v, wantErr %v", err, tt.wantErr)
return
}
h := hex.EncodeToString(got)
if !reflect.DeepEqual(h, tt.want) {
t.Errorf("GetAddrDescFromVout() = %v, want %v", h, tt.want)
}
})
}
}
func TestGetAddressesFromAddrDescForMint(t *testing.T) {
type args struct {
script string
}
tests := []struct {
name string
args args
want []string
want2 bool
wantErr bool
}{
{
name: "OP_RETURN hex",
args: args{script: "6a072020f1686f6a20"},
want: []string{"OP_RETURN 2020f1686f6a20"},
want2: false,
wantErr: false,
},
{
name: "OP_ZEROCOINMINT size hex",
args: args{script: "c10280004c80f767f3ee79953c67a7ed386dcccf1243619eb4bbbe414a3982dd94a83c1b69ac52d6ab3b653a3e05c4e4516c8dfe1e58ada40461bc5835a4a0d0387a51c29ac11b72ae25bbcdef745f50ad08f08b3e9bc2c31a35444398a490e65ac090e9f341f1abdebe47e57e8237ac25d098e951b4164a35caea29f30acb50b12e4425df28"},
want: []string{"Zeromint"},
want2: false,
wantErr: false,
},
{
name: "OP_SIGMAMINT size hex",
args: args{script: "c317dcee5b8b2c5b79728abc3a39abc54682b31a4e18f5abb6f34dc8089544763b0000"},
want: []string{"Sigmamint"},
want2: false,
wantErr: false,
},
}
parser := NewFiroParser(GetChainParams("main"), &btc.Configuration{})
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
b, _ := hex.DecodeString(tt.args.script)
got, got2, err := parser.GetAddressesFromAddrDesc(b)
if (err != nil) != tt.wantErr {
t.Errorf("GetAddressesFromAddrDesc() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("GetAddressesFromAddrDesc() = %v, want %v", got, tt.want)
}
if !reflect.DeepEqual(got2, tt.want2) {
t.Errorf("GetAddressesFromAddrDesc() = %v, want %v", got2, tt.want2)
}
})
}
}
func TestPackTx(t *testing.T) {
type args struct {
tx bchain.Tx
height uint32
blockTime int64
parser *FiroParser
}
tests := []struct {
name string
args args
want string
wantErr bool
}{
{
name: "firo-1",
args: args{
tx: testTx1,
height: 100002,
blockTime: 1533980594,
parser: NewFiroParser(GetChainParams("main"), &btc.Configuration{}),
},
want: testTxPacked1,
wantErr: false,
},
// FIXME: work around handle zerocoin spend as coinbase
// {
// name: "firo-2",
// args: args{
// tx: testTx2,
// height: 11002,
// blockTime: 1481277009,
// parser: NewFiroParser(GetChainParams("main"), &btc.Configuration{}),
// },
// want: testTxPacked2,
// wantErr: true,
// },
{
name: "firo-3",
args: args{
tx: testTx3,
height: 126202,
blockTime: 1547091829,
parser: NewFiroParser(GetChainParams("main"), &btc.Configuration{}),
},
want: testTxPacked3,
wantErr: false,
},
{
name: "firo-coinbase",
args: args{
tx: testTx4,
height: 100001,
blockTime: 1533977563,
parser: NewFiroParser(GetChainParams("main"), &btc.Configuration{}),
},
want: testTxPacked4,
wantErr: false,
},
{
name: "firo-quorum-commitment-tx",
args: args{
tx: testTx5,
height: 5268,
blockTime: 1591752749,
parser: NewFiroParser(GetChainParams("test"), &btc.Configuration{}),
},
want: testTxPacked5,
wantErr: false,
},
{
name: "firo-special-coinbase-tx",
args: args{
tx: testTx6,
height: 5300,
blockTime: 1591762049,
parser: NewFiroParser(GetChainParams("test"), &btc.Configuration{}),
},
want: testTxPacked6,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := tt.args.parser.PackTx(&tt.args.tx, tt.args.height, tt.args.blockTime)
if (err != nil) != tt.wantErr {
t.Errorf("packTx() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !tt.wantErr {
h := hex.EncodeToString(got)
if !reflect.DeepEqual(h, tt.want) {
t.Errorf("packTx() = %v, want %v", h, tt.want)
}
}
})
}
}
func TestUnpackTx(t *testing.T) {
type args struct {
packedTx string
parser *FiroParser
}
tests := []struct {
name string
args args
want *bchain.Tx
want1 uint32
wantErr bool
}{
{
name: "firo-1",
args: args{
packedTx: testTxPacked1,
parser: NewFiroParser(GetChainParams("main"), &btc.Configuration{}),
},
want: &testTx1,
want1: 100002,
wantErr: false,
},
// FIXME: work around handle zerocoin spend as coinbase
// {
// name: "firo-2",
// args: args{
// packedTx: testTxPacked2,
// parser: NewFiroParser(GetChainParams("main"), &btc.Configuration{}),
// },
// want: &testTx2,
// want1: 11002,
// wantErr: true,
// },
{
name: "firo-3",
args: args{
packedTx: testTxPacked3,
parser: NewFiroParser(GetChainParams("main"), &btc.Configuration{}),
},
want: &testTx3,
want1: 126202,
wantErr: false,
},
{
name: "firo-coinbase",
args: args{
packedTx: testTxPacked4,
parser: NewFiroParser(GetChainParams("main"), &btc.Configuration{}),
},
want: &testTx4,
want1: 100001,
wantErr: false,
},
{
name: "firo-special-tx",
args: args{
packedTx: testTxPacked5,
parser: NewFiroParser(GetChainParams("test"), &btc.Configuration{}),
},
want: &testTx5,
want1: 5268,
wantErr: false,
},
{
name: "firo-special-coinbase-tx",
args: args{
packedTx: testTxPacked6,
parser: NewFiroParser(GetChainParams("test"), &btc.Configuration{}),
},
want: &testTx6,
want1: 5300,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
b, _ := hex.DecodeString(tt.args.packedTx)
got, got1, err := tt.args.parser.UnpackTx(b)
if (err != nil) != tt.wantErr {
t.Errorf("unpackTx() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !tt.wantErr {
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("unpackTx() got = %v, want %v", got, tt.want)
}
if got1 != tt.want1 {
t.Errorf("unpackTx() got1 = %v, want %v", got1, tt.want1)
}
}
})
}
}
func TestParseBlock(t *testing.T) {
type args struct {
rawBlock string
parser *FiroParser
}
tests := []struct {
name string
args args
want *bchain.Block
wantTxs int
wantErr bool
}{
{
name: "normal-block",
args: args{
rawBlock: rawBlock1,
parser: NewFiroParser(GetChainParams("main"), &btc.Configuration{}),
},
want: &bchain.Block{
BlockHeader: bchain.BlockHeader{
Size: 200286,
Time: 1547120622,
},
},
wantTxs: 3,
wantErr: false,
},
{
name: "spend-block",
args: args{
rawBlock: rawBlock2,
parser: NewFiroParser(GetChainParams("main"), &btc.Configuration{}),
},
want: &bchain.Block{
BlockHeader: bchain.BlockHeader{
Size: 25298,
Time: 1482107572,
},
},
wantTxs: 4,
wantErr: false,
},
{
name: "special-tx-block",
args: args{
rawBlock: rawBlock3,
parser: NewFiroParser(GetChainParams("test"), &btc.Configuration{}),
},
want: &bchain.Block{
BlockHeader: bchain.BlockHeader{
Size: 200062,
Time: 1591752749,
},
},
wantTxs: 3,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
b, _ := hex.DecodeString(tt.args.rawBlock)
got, err := tt.args.parser.ParseBlock(b)
if (err != nil) != tt.wantErr {
t.Errorf("parseBlock() error = %+v", err)
}
if got != nil {
if !reflect.DeepEqual(got.BlockHeader, tt.want.BlockHeader) {
t.Errorf("parseBlock() got = %v, want %v", got.BlockHeader, tt.want.BlockHeader)
}
if len(got.Txs) != tt.wantTxs {
t.Errorf("parseBlock() txs length got = %d, want %d", len(got.Txs), tt.wantTxs)
}
}
})
}
}
func TestDecodeTransaction(t *testing.T) {
type args struct {
enc wire.MessageEncoding
rawTransaction string
parser *FiroParser
privacyType byte // 0 as non privacy
}
tests := []struct {
name string
args args
want bchain.Tx
wantErr bool
}{
{
name: "normal-transaction",
args: args{
enc: wire.WitnessEncoding,
rawTransaction: rawTestTx1,
parser: NewFiroParser(GetChainParams("main"), &btc.Configuration{}),
},
want: testTx1,
},
{
name: "coinbase-firospend",
args: args{
enc: wire.WitnessEncoding,
rawTransaction: rawTestTx2,
parser: NewFiroParser(GetChainParams("main"), &btc.Configuration{}),
privacyType: OpSigmaSpend,
},
want: testTx2,
},
{
name: "normal-transaction-2",
args: args{
enc: wire.WitnessEncoding,
rawTransaction: rawTestTx3,
parser: NewFiroParser(GetChainParams("main"), &btc.Configuration{}),
},
want: testTx3,
},
{
name: "coinbase-transaction",
args: args{
enc: wire.WitnessEncoding,
rawTransaction: rawTestTx4,
parser: NewFiroParser(GetChainParams("main"), &btc.Configuration{}),
},
want: testTx4,
},
{
name: "quorum-commitment-transaction",
args: args{
enc: wire.BaseEncoding,
rawTransaction: rawTestTx5,
parser: NewFiroParser(GetChainParams("main"), &btc.Configuration{}),
},
want: testTx5,
},
{
name: "quorum-commitment-transaction-witness",
args: args{
enc: wire.WitnessEncoding,
rawTransaction: rawTestTx5,
parser: NewFiroParser(GetChainParams("main"), &btc.Configuration{}),
},
wantErr: true,
},
{
name: "special-coinbase",
args: args{
enc: wire.WitnessEncoding,
rawTransaction: rawTestTx6,
parser: NewFiroParser(GetChainParams("test"), &btc.Configuration{}),
},
want: testTx6,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
b, _ := hex.DecodeString(tt.args.rawTransaction)
r := bytes.NewReader(b)
msg := FiroMsgTx{}
err := msg.FiroDecode(r, 0, tt.args.enc)
if tt.wantErr {
if err == nil {
t.Errorf("Want error")
}
return
}
if err != nil {
t.Fatal(err)
}
got := tt.args.parser.TxFromFiroMsgTx(&msg, true)
if pErr := tt.args.parser.parseFiroTx(&got); pErr != nil {
t.Fatal(pErr)
}
if r.Len() != 0 {
t.Errorf("Expected EOF but there are remaining %d bytes to read", r.Len())
}
if len(got.Vin) != len(tt.want.Vin) {
t.Errorf("Check vin size, got %v, want %v", len(got.Vin), len(tt.want.Vin))
}
for i := 0; i != len(got.Vin); i++ {
if !reflect.DeepEqual(got.Vin[i].Addresses, tt.want.Vin[i].Addresses) {
t.Errorf("Check Addresses at input %d, got %v, want %v",
i, got.Vin[i].Addresses, tt.want.Vin[i].Addresses)
}
if !reflect.DeepEqual(got.Vin[i].Coinbase, tt.want.Vin[i].Coinbase) {
t.Errorf("Check Coinbase at input %d, got %v, want %v",
i, got.Vin[i].Coinbase, tt.want.Vin[i].Coinbase)
}
if !reflect.DeepEqual(got.Vin[i].ScriptSig, tt.want.Vin[i].ScriptSig) {
t.Errorf("Check ScriptSig at input %d, got %v, want %v",
i, got.Vin[i].ScriptSig, tt.want.Vin[i].ScriptSig)
}
if !reflect.DeepEqual(got.Vin[i].Sequence, tt.want.Vin[i].Sequence) {
t.Errorf("Check Sequence at input %d, got %v, want %v",
i, got.Vin[i].Sequence, tt.want.Vin[i].Sequence)
}
if tt.args.privacyType == 0 && !reflect.DeepEqual(got.Vin[i].Txid, tt.want.Vin[i].Txid) {
t.Errorf("Check Txid at input %d, got %v, want %v",
i, got.Vin[i].Txid, tt.want.Vin[i].Txid)
}
if tt.args.privacyType == 0 && !reflect.DeepEqual(got.Vin[i].Vout, tt.want.Vin[i].Vout) {
t.Errorf("Check Vout at input %d, got %v, want %v",
i, got.Vin[i].Vout, tt.want.Vin[i].Vout)
}
}
if len(got.Vout) != len(tt.want.Vout) {
t.Errorf("Check vout size, got %v, want %v", len(got.Vout), len(tt.want.Vout))
}
for i := 0; i != len(got.Vout); i++ {
if !reflect.DeepEqual(got.Vout[i].JsonValue, tt.want.Vout[i].JsonValue) {
t.Errorf("Check JsonValue at output %d, got %v, want %v",
i, got.Vout[i].JsonValue, tt.want.Vout[i].JsonValue)
}
if !reflect.DeepEqual(got.Vout[i].N, tt.want.Vout[i].N) {
t.Errorf("Check N at output %d, got %v, want %v",
i, got.Vout[i].N, tt.want.Vout[i].N)
}
// empty addresses and null should be the same
if !((len(got.Vout[i].ScriptPubKey.Addresses) == 0 && len(got.Vout[i].ScriptPubKey.Addresses) == len(tt.want.Vout[i].ScriptPubKey.Addresses)) ||
reflect.DeepEqual(got.Vout[i].ScriptPubKey.Addresses, tt.want.Vout[i].ScriptPubKey.Addresses)) {
t.Errorf("Check ScriptPubKey.Addresses at output %d, got %v, want %v",
i, got.Vout[i].ScriptPubKey.Addresses, tt.want.Vout[i].ScriptPubKey.Addresses)
}
if !reflect.DeepEqual(got.Vout[i].ScriptPubKey.Hex, tt.want.Vout[i].ScriptPubKey.Hex) {
t.Errorf("Check ScriptPubKey.Hex at output %d, got %v, want %v",
i, got.Vout[i].ScriptPubKey.Hex, tt.want.Vout[i].ScriptPubKey.Hex)
}
if !reflect.DeepEqual(got.Vout[i].ValueSat, tt.want.Vout[i].ValueSat) {
t.Errorf("Check ValueSat at output %d, got %v, want %v",
i, got.Vout[i].ValueSat, tt.want.Vout[i].ValueSat)
}
}
if got.LockTime != tt.want.LockTime {
t.Errorf("Check LockTime, got %v, want %v", got.LockTime, tt.want.LockTime)
}
if got.Txid != tt.want.Txid {
t.Errorf("Check TxId, got %v, want %v", got.Txid, tt.want.Txid)
}
})
}
}

View File

@ -0,0 +1,243 @@
package firo
import (
"encoding/hex"
"encoding/json"
"github.com/golang/glog"
"github.com/juju/errors"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
)
type FiroRPC struct {
*btc.BitcoinRPC
}
func NewFiroRPC(config json.RawMessage, pushHandler func(bchain.NotificationType)) (bchain.BlockChain, error) {
// init base implementation
bc, err := btc.NewBitcoinRPC(config, pushHandler)
if err != nil {
return nil, err
}
// init firo implementation
zc := &FiroRPC{
BitcoinRPC: bc.(*btc.BitcoinRPC),
}
zc.ChainConfig.Parse = true
zc.ChainConfig.SupportsEstimateFee = true
zc.ChainConfig.SupportsEstimateSmartFee = false
zc.ParseBlocks = true
zc.RPCMarshaler = btc.JSONMarshalerV1{}
return zc, nil
}
func (zc *FiroRPC) Initialize() error {
ci, err := zc.GetChainInfo()
if err != nil {
return err
}
chainName := ci.Chain
params := GetChainParams(chainName)
// always create parser
zc.Parser = NewFiroParser(params, zc.ChainConfig)
// parameters for getInfo request
if params.Net == MainnetMagic {
zc.Testnet = false
zc.Network = "livenet"
} else {
zc.Testnet = true
zc.Network = "testnet"
}
glog.Info("rpc: block chain ", params.Name)
return nil
}
func (zc *FiroRPC) GetBlock(hash string, height uint32) (*bchain.Block, error) {
var err error
if hash == "" {
hash, err = zc.GetBlockHash(height)
if err != nil {
return nil, err
}
}
// optimization
if height > 0 {
return zc.GetBlockWithoutHeader(hash, height)
}
header, err := zc.GetBlockHeader(hash)
if err != nil {
return nil, err
}
data, err := zc.GetBlockRaw(hash)
if err != nil {
return nil, err
}
block, err := zc.Parser.ParseBlock(data)
if err != nil {
return nil, errors.Annotatef(err, "hash %v", hash)
}
block.BlockHeader = *header
return block, nil
}
func (zc *FiroRPC) GetBlockInfo(hash string) (*bchain.BlockInfo, error) {
glog.V(1).Info("rpc: getblock (verbosity=true) ", hash)
res := btc.ResGetBlockInfo{}
req := cmdGetBlock{Method: "getblock"}
req.Params.BlockHash = hash
req.Params.Verbosity = true
err := zc.Call(&req, &res)
if err != nil {
return nil, errors.Annotatef(err, "hash %v", hash)
}
if res.Error != nil {
if btc.IsErrBlockNotFound(res.Error) {
return nil, bchain.ErrBlockNotFound
}
return nil, errors.Annotatef(res.Error, "hash %v", hash)
}
return &res.Result, nil
}
func (zc *FiroRPC) GetBlockWithoutHeader(hash string, height uint32) (*bchain.Block, error) {
data, err := zc.GetBlockRaw(hash)
if err != nil {
return nil, err
}
block, err := zc.Parser.ParseBlock(data)
if err != nil {
return nil, errors.Annotatef(err, "%v %v", height, hash)
}
block.BlockHeader.Hash = hash
block.BlockHeader.Height = height
return block, nil
}
func (zc *FiroRPC) GetBlockRaw(hash string) ([]byte, error) {
glog.V(1).Info("rpc: getblock (verbosity=false) ", hash)
res := btc.ResGetBlockRaw{}
req := cmdGetBlock{Method: "getblock"}
req.Params.BlockHash = hash
req.Params.Verbosity = false
err := zc.Call(&req, &res)
if err != nil {
return nil, errors.Annotatef(err, "hash %v", hash)
}
if res.Error != nil {
if btc.IsErrBlockNotFound(res.Error) {
return nil, bchain.ErrBlockNotFound
}
return nil, errors.Annotatef(res.Error, "hash %v", hash)
}
return hex.DecodeString(res.Result)
}
func (zc *FiroRPC) GetTransactionForMempool(txid string) (*bchain.Tx, error) {
glog.V(1).Info("rpc: getrawtransaction nonverbose ", txid)
res := btc.ResGetRawTransactionNonverbose{}
req := cmdGetRawTransaction{Method: "getrawtransaction"}
req.Params.Txid = txid
req.Params.Verbose = 0
err := zc.Call(&req, &res)
if err != nil {
return nil, errors.Annotatef(err, "txid %v", txid)
}
if res.Error != nil {
if btc.IsMissingTx(res.Error) {
return nil, bchain.ErrTxNotFound
}
return nil, errors.Annotatef(res.Error, "txid %v", txid)
}
data, err := hex.DecodeString(res.Result)
if err != nil {
return nil, errors.Annotatef(err, "txid %v", txid)
}
tx, err := zc.Parser.ParseTx(data)
if err != nil {
return nil, errors.Annotatef(err, "txid %v", txid)
}
return tx, nil
}
func (zc *FiroRPC) GetTransaction(txid string) (*bchain.Tx, error) {
r, err := zc.getRawTransaction(txid)
if err != nil {
return nil, err
}
tx, err := zc.Parser.ParseTxFromJson(r)
tx.CoinSpecificData = r
if err != nil {
return nil, errors.Annotatef(err, "txid %v", txid)
}
return tx, nil
}
func (zc *FiroRPC) GetTransactionSpecific(tx *bchain.Tx) (json.RawMessage, error) {
if csd, ok := tx.CoinSpecificData.(json.RawMessage); ok {
return csd, nil
}
return zc.getRawTransaction(tx.Txid)
}
func (zc *FiroRPC) getRawTransaction(txid string) (json.RawMessage, error) {
glog.V(1).Info("rpc: getrawtransaction ", txid)
res := btc.ResGetRawTransaction{}
req := cmdGetRawTransaction{Method: "getrawtransaction"}
req.Params.Txid = txid
req.Params.Verbose = 1
err := zc.Call(&req, &res)
if err != nil {
return nil, errors.Annotatef(err, "txid %v", txid)
}
if res.Error != nil {
if btc.IsMissingTx(res.Error) {
return nil, bchain.ErrTxNotFound
}
return nil, errors.Annotatef(res.Error, "txid %v", txid)
}
return res.Result, nil
}
type cmdGetBlock struct {
Method string `json:"method"`
Params struct {
BlockHash string `json:"blockhash"`
Verbosity bool `json:"verbosity"`
} `json:"params"`
}
type cmdGetRawTransaction struct {
Method string `json:"method"`
Params struct {
Txid string `json:"txid"`
Verbose int `json:"verbose"`
} `json:"params"`
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,79 @@
package flo
import (
"github.com/martinboehm/btcd/wire"
"github.com/martinboehm/btcutil/chaincfg"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
)
// magic numbers
const (
MainnetMagic wire.BitcoinNet = 0xf1a5c0fd
TestnetMagic wire.BitcoinNet = 0xf25ac0fd
RegtestMagic wire.BitcoinNet = 0xdab5bffa
)
// chain parameters
var (
MainNetParams chaincfg.Params
TestNetParams chaincfg.Params
)
func init() {
MainNetParams = chaincfg.MainNetParams
MainNetParams.Net = MainnetMagic
MainNetParams.PubKeyHashAddrID = []byte{35}
MainNetParams.ScriptHashAddrID = []byte{94}
MainNetParams.Bech32HRPSegwit = "flo"
TestNetParams = chaincfg.TestNet3Params
TestNetParams.Net = TestnetMagic
TestNetParams.PubKeyHashAddrID = []byte{115}
TestNetParams.ScriptHashAddrID = []byte{198}
TestNetParams.Bech32HRPSegwit = "tflo"
}
// FloParser handle
type FloParser struct {
*btc.BitcoinParser
baseparser *bchain.BaseParser
}
// NewFloParser returns new FloParser instance
func NewFloParser(params *chaincfg.Params, c *btc.Configuration) *FloParser {
return &FloParser{
BitcoinParser: btc.NewBitcoinParser(params, c),
baseparser: &bchain.BaseParser{},
}
}
// GetChainParams contains network parameters for the main Flo network,
// and the test Flo network
func GetChainParams(chain string) *chaincfg.Params {
if !chaincfg.IsRegistered(&MainNetParams) {
err := chaincfg.Register(&MainNetParams)
if err == nil {
err = chaincfg.Register(&TestNetParams)
}
if err != nil {
panic(err)
}
}
switch chain {
case "test":
return &TestNetParams
default:
return &MainNetParams
}
}
// PackTx packs transaction to byte array using protobuf
func (p *FloParser) PackTx(tx *bchain.Tx, height uint32, blockTime int64) ([]byte, error) {
return p.baseparser.PackTx(tx, height, blockTime)
}
// UnpackTx unpacks transaction from protobuf byte array
func (p *FloParser) UnpackTx(buf []byte) (*bchain.Tx, uint32, error) {
return p.baseparser.UnpackTx(buf)
}

View File

@ -0,0 +1,93 @@
// +build unittest
package flo
import (
"encoding/hex"
"os"
"reflect"
"testing"
"github.com/martinboehm/btcutil/chaincfg"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
)
func TestMain(m *testing.M) {
c := m.Run()
chaincfg.ResetParams()
os.Exit(c)
}
func Test_GetAddrDescFromAddress_Testnet(t *testing.T) {
type args struct {
address string
}
tests := []struct {
name string
args args
want string
wantErr bool
}{
{
name: "P2PKH1",
args: args{address: "oX8YUMFwRhQdqGvuVGpLJz8BcYmM87e9ee"},
want: "76a9149c708c27ce34ead174a7a9b4f47afafb3d906d0d88ac",
wantErr: false,
},
}
parser := NewFloParser(GetChainParams("test"), &btc.Configuration{})
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := parser.GetAddrDescFromAddress(tt.args.address)
if (err != nil) != tt.wantErr {
t.Errorf("GetAddrDescFromAddress() error = %v, wantErr %v", err, tt.wantErr)
return
}
h := hex.EncodeToString(got)
if !reflect.DeepEqual(h, tt.want) {
t.Errorf("GetAddrDescFromAddress() = %v, want %v", h, tt.want)
}
})
}
}
func Test_GetAddrDescFromAddress_Mainnet(t *testing.T) {
type args struct {
address string
}
tests := []struct {
name string
args args
want string
wantErr bool
}{
{
name: "P2PKH1",
args: args{address: "FAPiw7EFMYmYK1mUuQQekyLsmimUBQT9zd"},
want: "76a914320b6c674c8bc353942046981ff7ac73f5ceae4688ac",
wantErr: false,
},
{
name: "P2PKH2",
args: args{address: "FMg9M7GPuUAGKvhWmgWjoqYtMqmckD4tRF"},
want: "76a914adcfd792793fb204ec4e8cf2d0215fea6963b97388ac",
wantErr: false,
},
}
parser := NewFloParser(GetChainParams("main"), &btc.Configuration{})
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := parser.GetAddrDescFromAddress(tt.args.address)
if (err != nil) != tt.wantErr {
t.Errorf("GetAddrDescFromAddress() error = %v, wantErr %v", err, tt.wantErr)
return
}
h := hex.EncodeToString(got)
if !reflect.DeepEqual(h, tt.want) {
t.Errorf("GetAddrDescFromAddress() = %v, want %v", h, tt.want)
}
})
}
}

View File

@ -0,0 +1,133 @@
package flo
import (
"encoding/json"
"github.com/golang/glog"
"github.com/juju/errors"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
)
// FloRPC is an interface to JSON-RPC bitcoind service.
type FloRPC struct {
*btc.BitcoinRPC
}
// NewFloRPC returns new FloRPC instance.
func NewFloRPC(config json.RawMessage, pushHandler func(bchain.NotificationType)) (bchain.BlockChain, error) {
b, err := btc.NewBitcoinRPC(config, pushHandler)
if err != nil {
return nil, err
}
s := &FloRPC{
b.(*btc.BitcoinRPC),
}
s.RPCMarshaler = btc.JSONMarshalerV2{}
s.ChainConfig.SupportsEstimateFee = false
return s, nil
}
// Initialize initializes FloRPC instance.
func (f *FloRPC) Initialize() error {
ci, err := f.GetChainInfo()
if err != nil {
return err
}
chainName := ci.Chain
glog.Info("Chain name ", chainName)
params := GetChainParams(chainName)
// always create parser
f.Parser = NewFloParser(params, f.ChainConfig)
// parameters for getInfo request
if params.Net == MainnetMagic {
f.Testnet = false
f.Network = "livenet"
} else {
f.Testnet = true
f.Network = "testnet"
}
glog.Info("rpc: block chain ", params.Name)
return nil
}
// GetBlock returns block with given hash.
func (f *FloRPC) GetBlock(hash string, height uint32) (*bchain.Block, error) {
var err error
if hash == "" {
hash, err = f.GetBlockHash(height)
if err != nil {
return nil, err
}
}
if !f.ParseBlocks {
return f.GetBlockFull(hash)
}
// optimization
if height > 0 {
return f.GetBlockWithoutHeader(hash, height)
}
header, err := f.GetBlockHeader(hash)
if err != nil {
return nil, err
}
data, err := f.GetBlockRaw(hash)
if err != nil {
return nil, err
}
block, err := f.Parser.ParseBlock(data)
if err != nil {
return nil, errors.Annotatef(err, "hash %v", hash)
}
block.BlockHeader = *header
return block, nil
}
// GetBlockFull returns block with given hash
func (f *FloRPC) GetBlockFull(hash string) (*bchain.Block, error) {
glog.V(1).Info("rpc: getblock (verbosity=2) ", hash)
res := btc.ResGetBlockFull{}
req := btc.CmdGetBlock{Method: "getblock"}
req.Params.BlockHash = hash
req.Params.Verbosity = 2
err := f.Call(&req, &res)
if err != nil {
return nil, errors.Annotatef(err, "hash %v", hash)
}
if res.Error != nil {
if btc.IsErrBlockNotFound(res.Error) {
return nil, bchain.ErrBlockNotFound
}
return nil, errors.Annotatef(res.Error, "hash %v", hash)
}
for i := range res.Result.Txs {
tx := &res.Result.Txs[i]
for j := range tx.Vout {
vout := &tx.Vout[j]
// convert vout.JsonValue to big.Int and clear it, it is only temporary value used for unmarshal
vout.ValueSat, err = f.Parser.AmountToBigInt(vout.JsonValue)
if err != nil {
return nil, err
}
vout.JsonValue = ""
}
}
return &res.Result, nil
}
// GetTransactionForMempool returns a transaction by the transaction ID.
// It could be optimized for mempool, i.e. without block time and confirmations
func (f *FloRPC) GetTransactionForMempool(txid string) (*bchain.Tx, error) {
return f.GetTransaction(txid)
}

View File

@ -1,20 +1,24 @@
package fujicoin
import (
"blockbook/bchain/coins/btc"
"github.com/btcsuite/btcd/wire"
"github.com/jakm/btcutil/chaincfg"
"github.com/martinboehm/btcd/wire"
"github.com/martinboehm/btcutil/chaincfg"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
)
const (
// MainnetMagic is mainnet network constant
MainnetMagic wire.BitcoinNet = 0x696a7566
// TestnetMagic is testnet network constant
TestnetMagic wire.BitcoinNet = 0x66756a69
// RegtestMagic is regtest network constant
RegtestMagic wire.BitcoinNet = 0x66756a69
)
var (
// MainNetParams are parser parameters for mainnet
MainNetParams chaincfg.Params
// TestNetParams are parser parameters for testnet
TestNetParams chaincfg.Params
)

View File

@ -3,15 +3,15 @@
package fujicoin
import (
"blockbook/bchain"
"blockbook/bchain/coins/btc"
"encoding/hex"
"math/big"
"os"
"reflect"
"testing"
"github.com/jakm/btcutil/chaincfg"
"github.com/martinboehm/btcutil/chaincfg"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
)
func TestMain(m *testing.M) {

View File

@ -1,11 +1,11 @@
package fujicoin
import (
"blockbook/bchain"
"blockbook/bchain/coins/btc"
"encoding/json"
"github.com/golang/glog"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
)
// FujicoinRPC is an interface to JSON-RPC bitcoind service.
@ -31,10 +31,11 @@ func NewFujicoinRPC(config json.RawMessage, pushHandler func(bchain.Notification
// Initialize initializes FujicoinRPC instance.
func (b *FujicoinRPC) Initialize() error {
chainName, err := b.GetChainInfoAndInitializeMempool(b)
ci, err := b.GetChainInfo()
if err != nil {
return err
}
chainName := ci.Chain
glog.Info("Chain name ", chainName)
params := GetChainParams(chainName)

View File

@ -1,18 +1,19 @@
package gamecredits
import (
"blockbook/bchain/coins/btc"
"github.com/btcsuite/btcd/wire"
"github.com/jakm/btcutil/chaincfg"
"github.com/martinboehm/btcd/wire"
"github.com/martinboehm/btcutil/chaincfg"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
)
// magic numbers
const (
MainnetMagic wire.BitcoinNet = 0xdbb6c0fb
TestnetMagic wire.BitcoinNet = 0x0709110b
RegtestMagic wire.BitcoinNet = 0xdab5bffa
)
// chain parameters
var (
MainNetParams chaincfg.Params
TestNetParams chaincfg.Params

View File

@ -3,15 +3,15 @@
package gamecredits
import (
"blockbook/bchain"
"blockbook/bchain/coins/btc"
"encoding/hex"
"math/big"
"os"
"reflect"
"testing"
"github.com/jakm/btcutil/chaincfg"
"github.com/martinboehm/btcutil/chaincfg"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
)
func TestMain(m *testing.M) {

View File

@ -1,11 +1,11 @@
package gamecredits
import (
"blockbook/bchain"
"blockbook/bchain/coins/btc"
"encoding/json"
"github.com/golang/glog"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
)
// GameCreditsRPC is an interface to JSON-RPC bitcoind service.
@ -31,10 +31,11 @@ func NewGameCreditsRPC(config json.RawMessage, pushHandler func(bchain.Notificat
// Initialize initializes GameCreditsRPC instance.
func (b *GameCreditsRPC) Initialize() error {
chainName, err := b.GetChainInfoAndInitializeMempool(b)
ci, err := b.GetChainInfo()
if err != nil {
return err
}
chainName := ci.Chain
glog.Info("Chain name ", chainName)
params := GetChainParams(chainName)

View File

@ -1,19 +1,20 @@
package grs
import (
"blockbook/bchain"
"blockbook/bchain/coins/btc"
"github.com/btcsuite/btcd/wire"
"github.com/jakm/btcutil/base58"
"github.com/jakm/btcutil/chaincfg"
"github.com/martinboehm/btcd/wire"
"github.com/martinboehm/btcutil/base58"
"github.com/martinboehm/btcutil/chaincfg"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
)
// magic numbers
const (
MainnetMagic wire.BitcoinNet = 0xd4b4bef9
TestnetMagic wire.BitcoinNet = 0x0709110b
)
// chain parameters
var (
MainNetParams chaincfg.Params
TestNetParams chaincfg.Params

View File

@ -3,8 +3,6 @@
package grs
import (
"blockbook/bchain"
"blockbook/bchain/coins/btc"
"bytes"
"encoding/hex"
"math/big"
@ -12,14 +10,16 @@ import (
"reflect"
"testing"
"github.com/jakm/btcutil/chaincfg"
"github.com/martinboehm/btcutil/chaincfg"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
)
var (
testTx1, testTx2 bchain.Tx
testTxPacked1 = "0a20f56521b17b828897f72b30dd21b0192fd942342e89acbb06abf1d446282c30f512bf0101000000014a9d1fdba915e0907ab02f04f88898863112a2b4fdcf872c7414588c47c874cb000000006a47304402201fb96d20d0778f54520ab59afe70d5fb20e500ecc9f02281cf57934e8029e8e10220383d5a3e80f2e1eb92765b6da0f23d454aecbd8236f083d483e9a7430236876101210331693756f749180aeed0a65a0fab0625a2250bd9abca502282a4cf0723152e67ffffffff01a0330300000000001976a914fe40329c95c5598ac60752a5310b320cb52d18e688ac0000000018ffff87da05200028a6f383013298010a001220cb74c8478c5814742c87cffdb4a21231869888f8042fb07a90e015a9db1f9d4a1800226a47304402201fb96d20d0778f54520ab59afe70d5fb20e500ecc9f02281cf57934e8029e8e10220383d5a3e80f2e1eb92765b6da0f23d454aecbd8236f083d483e9a7430236876101210331693756f749180aeed0a65a0fab0625a2250bd9abca502282a4cf0723152e6728ffffffff0f3a460a030333a010001a1976a914fe40329c95c5598ac60752a5310b320cb52d18e688ac222246744d347a416e39615659674867786d616d5742675750795a7362365268766b4139"
testTxPacked2 = "0a209b5c4859a8a31e69788cb4402812bb28f14ad71cbd8c60b09903478bc56f79a312e00101000000000101d1613f483f2086d076c82fe34674385a86beb08f052d5405fe1aed397f852f4f0000000000feffffff02404b4c000000000017a9147a55d61848e77ca266e79a39bfc85c580a6426c987a8386f0000000000160014cc8067093f6f843d6d3e22004a4290cd0c0f336b02483045022100ea8780bc1e60e14e945a80654a41748bbf1aa7d6f2e40a88d91dfc2de1f34bd10220181a474a3420444bd188501d8d270736e1e9fe379da9970de992ff445b0972e3012103adc58245cf28406af0ef5cc24b8afba7f1be6c72f279b642d85c48798685f862d9ed090018caa384da0520d9db2728dadb27322c0a0012204f2f857f39ed1afe05542d058fb0be865a387446e32fc876d086203f483f61d1180028feffffff0f3a450a034c4b4010001a17a9147a55d61848e77ca266e79a39bfc85c580a6426c9872223324e345135466855323439374272794666556762716b414a453837614b4476335633653a4d0a036f38a810011a160014cc8067093f6f843d6d3e22004a4290cd0c0f336b222c746772733171656a7178777a666c64377a72366d663779677179357335736535787137766d74396c6b643537"
testTxPacked1 = "0a20f56521b17b828897f72b30dd21b0192fd942342e89acbb06abf1d446282c30f512bf0101000000014a9d1fdba915e0907ab02f04f88898863112a2b4fdcf872c7414588c47c874cb000000006a47304402201fb96d20d0778f54520ab59afe70d5fb20e500ecc9f02281cf57934e8029e8e10220383d5a3e80f2e1eb92765b6da0f23d454aecbd8236f083d483e9a7430236876101210331693756f749180aeed0a65a0fab0625a2250bd9abca502282a4cf0723152e67ffffffff01a0330300000000001976a914fe40329c95c5598ac60752a5310b320cb52d18e688ac0000000018ffff87da05200028a6f383013298010a001220cb74c8478c5814742c87cffdb4a21231869888f8042fb07a90e015a9db1f9d4a1800226a47304402201fb96d20d0778f54520ab59afe70d5fb20e500ecc9f02281cf57934e8029e8e10220383d5a3e80f2e1eb92765b6da0f23d454aecbd8236f083d483e9a7430236876101210331693756f749180aeed0a65a0fab0625a2250bd9abca502282a4cf0723152e6728ffffffff0f3a460a030333a010001a1976a914fe40329c95c5598ac60752a5310b320cb52d18e688ac222246744d347a416e39615659674867786d616d5742675750795a7362365268766b41394000"
testTxPacked2 = "0a209b5c4859a8a31e69788cb4402812bb28f14ad71cbd8c60b09903478bc56f79a312e00101000000000101d1613f483f2086d076c82fe34674385a86beb08f052d5405fe1aed397f852f4f0000000000feffffff02404b4c000000000017a9147a55d61848e77ca266e79a39bfc85c580a6426c987a8386f0000000000160014cc8067093f6f843d6d3e22004a4290cd0c0f336b02483045022100ea8780bc1e60e14e945a80654a41748bbf1aa7d6f2e40a88d91dfc2de1f34bd10220181a474a3420444bd188501d8d270736e1e9fe379da9970de992ff445b0972e3012103adc58245cf28406af0ef5cc24b8afba7f1be6c72f279b642d85c48798685f862d9ed090018caa384da0520d9db2728dadb27322c0a0012204f2f857f39ed1afe05542d058fb0be865a387446e32fc876d086203f483f61d1180028feffffff0f3a450a034c4b4010001a17a9147a55d61848e77ca266e79a39bfc85c580a6426c9872223324e345135466855323439374272794666556762716b414a453837614b4476335633653a4d0a036f38a810011a160014cc8067093f6f843d6d3e22004a4290cd0c0f336b222c746772733171656a7178777a666c64377a72366d663779677179357335736535787137766d74396c6b6435374000"
)
func init() {

View File

@ -1,18 +1,20 @@
package grs
import (
"blockbook/bchain"
"blockbook/bchain/coins/btc"
"encoding/json"
"github.com/golang/glog"
"github.com/juju/errors"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
)
// GroestlcoinRPC is an interface to JSON-RPC service
type GroestlcoinRPC struct {
*btc.BitcoinRPC
}
// NewGroestlcoinRPC returns new GroestlcoinRPC instance
func NewGroestlcoinRPC(config json.RawMessage, pushHandler func(bchain.NotificationType)) (bchain.BlockChain, error) {
b, err := btc.NewBitcoinRPC(config, pushHandler)
if err != nil {
@ -27,10 +29,11 @@ func NewGroestlcoinRPC(config json.RawMessage, pushHandler func(bchain.Notificat
// Initialize initializes GroestlcoinRPC instance.
func (g *GroestlcoinRPC) Initialize() error {
chainName, err := g.GetChainInfoAndInitializeMempool(g)
ci, err := g.GetChainInfo()
if err != nil {
return err
}
chainName := ci.Chain
params := GetChainParams(chainName)
@ -79,7 +82,7 @@ func (g *GroestlcoinRPC) GetBlock(hash string, height uint32) (*bchain.Block, er
for _, txid := range res.Result.Txids {
tx, err := g.GetTransaction(txid)
if err != nil {
if isInvalidTx(err) {
if err == bchain.ErrTxNotFound {
glog.Errorf("rpc: getblock: skipping transanction in block %s due error: %s", hash, err)
continue
}
@ -94,20 +97,6 @@ func (g *GroestlcoinRPC) GetBlock(hash string, height uint32) (*bchain.Block, er
return block, nil
}
func isInvalidTx(err error) bool {
switch e1 := err.(type) {
case *errors.Err:
switch e2 := e1.Cause().(type) {
case *bchain.RPCError:
if e2.Code == -5 { // "No information available about transaction"
return true
}
}
}
return false
}
// GetTransactionForMempool returns a transaction by the transaction ID.
// It could be optimized for mempool, i.e. without block time and confirmations
func (g *GroestlcoinRPC) GetTransactionForMempool(txid string) (*bchain.Tx, error) {

View File

@ -0,0 +1,93 @@
package koto
import (
"github.com/martinboehm/btcd/wire"
"github.com/martinboehm/btcutil/chaincfg"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
)
// magic numbers
const (
MainnetMagic wire.BitcoinNet = 0x6f746f4b
TestnetMagic wire.BitcoinNet = 0x6f6b6f54
RegtestMagic wire.BitcoinNet = 0x6f6b6552
)
// chain parameters
var (
MainNetParams chaincfg.Params
TestNetParams chaincfg.Params
RegtestParams chaincfg.Params
)
func init() {
MainNetParams = chaincfg.MainNetParams
MainNetParams.Net = MainnetMagic
// Address encoding magics
MainNetParams.AddressMagicLen = 2
MainNetParams.PubKeyHashAddrID = []byte{0x18, 0x36} // base58 prefix: k1
MainNetParams.ScriptHashAddrID = []byte{0x18, 0x3B} // base58 prefix: k3
TestNetParams = chaincfg.TestNet3Params
TestNetParams.Net = TestnetMagic
// Address encoding magics
TestNetParams.AddressMagicLen = 2
TestNetParams.PubKeyHashAddrID = []byte{0x18, 0xA4} // base58 prefix: km
TestNetParams.ScriptHashAddrID = []byte{0x18, 0x39} // base58 prefix: k2
RegtestParams = chaincfg.RegressionNetParams
RegtestParams.Net = RegtestMagic
}
// KotoParser handle
type KotoParser struct {
*btc.BitcoinParser
baseparser *bchain.BaseParser
}
// NewKotoParser returns new KotoParser instance
func NewKotoParser(params *chaincfg.Params, c *btc.Configuration) *KotoParser {
return &KotoParser{
BitcoinParser: btc.NewBitcoinParser(params, c),
baseparser: &bchain.BaseParser{},
}
}
// GetChainParams contains network parameters for the main Koto network,
// the regression test Koto network, the test Koto network and
// the simulation test Koto network, in this order
func GetChainParams(chain string) *chaincfg.Params {
if !chaincfg.IsRegistered(&MainNetParams) {
err := chaincfg.Register(&MainNetParams)
if err == nil {
err = chaincfg.Register(&TestNetParams)
}
if err == nil {
err = chaincfg.Register(&RegtestParams)
}
if err != nil {
panic(err)
}
}
switch chain {
case "test":
return &TestNetParams
case "regtest":
return &RegtestParams
default:
return &MainNetParams
}
}
// PackTx packs transaction to byte array using protobuf
func (p *KotoParser) PackTx(tx *bchain.Tx, height uint32, blockTime int64) ([]byte, error) {
return p.baseparser.PackTx(tx, height, blockTime)
}
// UnpackTx unpacks transaction from protobuf byte array
func (p *KotoParser) UnpackTx(buf []byte) (*bchain.Tx, uint32, error) {
return p.baseparser.UnpackTx(buf)
}

View File

@ -0,0 +1,239 @@
// +build unittest
package koto
import (
"bytes"
"encoding/hex"
"math/big"
"os"
"reflect"
"testing"
"github.com/martinboehm/btcutil/chaincfg"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
)
var (
testTx1, testTx2 bchain.Tx
testTxPacked1 = "0a2097f944e3558cc784f4013b3753ce9570fe4707893eda724b12eb4c69686113a612970f020000000001036b2048020000001976a9142df466d79cf4be0f7d1091512f1c297e4988fdd188ac000000000100000000000000001392204802000000a8a3d9a54a3fe3b5ae208fa2d96faea96dac6cb76b03bb2e32c5dd892a5d6f6490f05d8f6fb4401228df1be9f22c5ad69706c461bbc9253ffbc3531770e5075e149ec6af7f2cda0cb87862620792340bc425095115adbf0f16f4d3ece3f5c467cbcb02b01ced7192ab644f96fa01f04a7a450a42da2bfad1748634f7bc141467d3c94961ed6250dd23fca62b30b4a2ca6fbad0724372a429bcb97a28954e1681c0bb974877dac26eb2b994eaa23d56ecfdedd93f8331f9432f12adc37da9f049585179b7bbb370b76c6c37a438a20bc3b410a6a72ff8d11408a337a37bbc73dd15df8a34f2c878dc6d6db4f0cee504680fe53e0a158f1e1c82b84e065a4764fc57f8bf75d28899126917a05bf3230036f2d6b38f8a51214d1c2d0588a95e82f0032c2dfa6916c689f8daa648c01517bbf0826d2d4082067b0d17071920eb6dab6c0307603b825f3aae347db349628d4fcaa97a155ef1a2c601170fe825609efa964f0a06700afc135542ea7b06fc989424d7e100652c0ad5be4ca01c1fd676530e6f60606c5feb7de5da0d69544d8b7be8de06b27ba96a1bfa6bc07cc0269982acb722032a938ab36089c0eeda7a4076cd258a1752486a3d52af16db8dff072bcc61f17503185b5bc8aa0ea3a181663bb0ddd3cca1a19293764b01569b9878a60c0ce82e21020751a4ecc1a2a9b9a123042ee8d8a5d4d3f4764b1cdb13d57d2a77c3b56bd89102302d118ebc14969ade27bf83f0e707a97b26c7292a6c20e850abce5fad0ab59d032fec0d6278bbe0f2fb3fbc61697ba10b6ec3f2c4196e46e98dfb65bb28ec6afff81de5a4be7be8c4f56ab4c03043a3cf9987b630ac4b6d8aa74ce8ed5b61040262239172c6450f9ab642e2f2d258c200c3bb4ad69011ef2dfe1dd63d758c86270ac4925b248b9bb4b6c9ff7bf2e56260cba02b2429648dc20eb034c8f9b18e1f6a38b8651c236554546585b4dd0f07fd5ee1696bf792527ce84b22012439300797103f22d3969df725e4414d899ec32a2ebbb857cc911e374e84738f4e007ee5260ff666a286e45c465525e2e3fc5e5e0e9ad82e53d364e4fd355619711c616d508470b997af44f62f283871cd892552128135aadb40c6f8cf69ee72acf349e9f4d33e8673450b9f69d4022a8d886b0cdbaba0798e0bf57b42dedfafb0bbd5495ca1c0030bbf460b48f9a138f6ab748df9046d9995f895062583ce0818e40afb9704653e11d58ca42bd3f60f4e908589ad9144c76067dc433cad13a5bbd9c168691b8c6cddb19d812f3e3f98e2cdd20dbb170936fd5cd2ef0bca72af8931a1b01d6081ffbef5be4416e696a7c762a375b368f71dc31362a4005750992a48e55311dde8d2013180d62e507ffc3e468c4a27acc763a9651b19f37e1ffca7e656225617368e79c1d18f9b14d770993d3d1dc42dcfd9adfa02a8ddf0ebc8fbb850fab307fd1d239cf6ad4e5ff40992dd974bc43fa351ce807cc0036c2f7d80bbd052f496216304fbc63e8d728bf129acaedb0073aff077e584ce04bc1ccf9c91f41f3c8804dd65da4ecdfdba32590e04b4d1b6895dea8edacd1f40313e8a1d4900d0dba54056eee72e3d155e9c67e7a51df581c33cbd39f16549d590ae5387fe2c5ad3484ad5c7da320066b79083c49879e45938b3bbb063726008a2ebb8847c9e57be6ec489c7aaa80f5e8e430040cb8d60298363df850cb7b4e98e97192882d10d2fd96cc490dd18b263d96aac6aa4f5583770e0917fa9b566dd0e0b218c6684007ec10cf11747e8f039fac5250170de2835ab88fea356b6a7d0f5e81ffe9b78d191a745e0237a256a2a840880689d83503b72462e3955b61e22afde947c1f1527ea94151c5b7d3a72ce68979603911c08bcec01097899fd30347be7f2e246f70d2af6a1e29b54988978a91f79b2ed8be76ffd62f79de5418933ed166be919d9bbb7524347d87d31afaed05e71a82b09c18c196b3ba6e226939b375903f7d889422863567203814484af89fd223ce1c959b1fdffaf26461630c630d2bbf99228a096ea6cb0d61df70d24414c76bf9371c4abff0ad257098189af6ea32200fbe092d875aa4d3f72a7ec138439e4b08fb1dcde6a90f25fde1498773e693c9b21c40505d42edcbcaed8a2dc4642750e9df73e169f9986ddb3a57991ac2cb3b540d788e2c2c22c2c51b2d74a98ed59a8cec89ba54342fb9660449a116f8691da60cb447afe4d5e80f37b4669e6007c1cadc41933fb27bab41afd312c37e5cb43715cb4013efcd91221ed06249540b733c05e81131aba75ba0f427d9bd975554b2d49a8048f0b3a84477e75290235fd3bbcaee6c4438ba72299dd960f3f6ee9241f7e399684e894d7bb1c302ecbe24d0f19dca982a82ee44f36211d23b0ea623c9c9f4f527f4e452fd06ebb943cadeea3d7fa42cabd25324bc5851e40f9952823f56b50b97729e6561f2100c2b5922860c6cf447a668324ca931f2f35a5edb7d306f8b8802f98cf67140a3fe73099ab86bb65c439a8593e64816bcd46aa4c254918f4a3a0f3f47b4ebcfb2824703f9a7d163824484ce6fe1852c4ba131ee2635de14822a8cb3782697fecc6f69514edd3f42fcf2751075b838bf14ae91e9dcff517bf3cec4db1b986b4c966a4fa40d38f2ebb7fc60218c397a2d705200028d09a0c3a490a050248206b0310001a1976a9142df466d79cf4be0f7d1091512f1c297e4988fdd188ac22236b31323257767a46415565444b3667353238563376726547673870614a5137624248364000"
testTxPacked2 = "0a203aebcf5a223450bca3c0312d3d87b6070447e795d09a266a3a01c70e44c7cc4812e1010100000001cbc2c0b14b26f563ceee8201971b2caae2a4f964d0fd91267290c51a6a171411010000006a473044022032dd5d573c3a7f729da1cb9d9ba02a08e05d50b4f74d5aeb7cb22284526f70340220661ca4a192d02684f0b6b52768b9e9ae5fad41b962aa918537b91bba275e92e70121024e98e62782ba44e5677b52b1e4e973a027c7d873915a6d62ba967b2c07467224ffffffff02c0c62d00000000001976a914dd985697513887236c484acc605ece839e2204ac88ac989e8ce0000000001976a91482bfe75940a6d46238f55e258fcae5bef4e847ea88ac0000000018ff98a2d705200028d49a0c3298010a0012201114176a1ac590722691fdd064f9a4e2aa2c1b970182eece63f5264bb1c0c2cb1801226a473044022032dd5d573c3a7f729da1cb9d9ba02a08e05d50b4f74d5aeb7cb22284526f70340220661ca4a192d02684f0b6b52768b9e9ae5fad41b962aa918537b91bba275e92e70121024e98e62782ba44e5677b52b1e4e973a027c7d873915a6d62ba967b2c0746722428ffffffff0f3a470a032dc6c010001a1976a914dd985697513887236c484acc605ece839e2204ac88ac22236b314a334461347236356653616b6571555953616a6f506f74656376633768384861513a480a04e08c9e9810011a1976a91482bfe75940a6d46238f55e258fcae5bef4e847ea88ac22236b31396b7355666462355139584b556a3565645570314451686e6343503868396845374000"
)
func init() {
testTx1 = bchain.Tx{
Hex: "020000000001036b2048020000001976a9142df466d79cf4be0f7d1091512f1c297e4988fdd188ac000000000100000000000000001392204802000000a8a3d9a54a3fe3b5ae208fa2d96faea96dac6cb76b03bb2e32c5dd892a5d6f6490f05d8f6fb4401228df1be9f22c5ad69706c461bbc9253ffbc3531770e5075e149ec6af7f2cda0cb87862620792340bc425095115adbf0f16f4d3ece3f5c467cbcb02b01ced7192ab644f96fa01f04a7a450a42da2bfad1748634f7bc141467d3c94961ed6250dd23fca62b30b4a2ca6fbad0724372a429bcb97a28954e1681c0bb974877dac26eb2b994eaa23d56ecfdedd93f8331f9432f12adc37da9f049585179b7bbb370b76c6c37a438a20bc3b410a6a72ff8d11408a337a37bbc73dd15df8a34f2c878dc6d6db4f0cee504680fe53e0a158f1e1c82b84e065a4764fc57f8bf75d28899126917a05bf3230036f2d6b38f8a51214d1c2d0588a95e82f0032c2dfa6916c689f8daa648c01517bbf0826d2d4082067b0d17071920eb6dab6c0307603b825f3aae347db349628d4fcaa97a155ef1a2c601170fe825609efa964f0a06700afc135542ea7b06fc989424d7e100652c0ad5be4ca01c1fd676530e6f60606c5feb7de5da0d69544d8b7be8de06b27ba96a1bfa6bc07cc0269982acb722032a938ab36089c0eeda7a4076cd258a1752486a3d52af16db8dff072bcc61f17503185b5bc8aa0ea3a181663bb0ddd3cca1a19293764b01569b9878a60c0ce82e21020751a4ecc1a2a9b9a123042ee8d8a5d4d3f4764b1cdb13d57d2a77c3b56bd89102302d118ebc14969ade27bf83f0e707a97b26c7292a6c20e850abce5fad0ab59d032fec0d6278bbe0f2fb3fbc61697ba10b6ec3f2c4196e46e98dfb65bb28ec6afff81de5a4be7be8c4f56ab4c03043a3cf9987b630ac4b6d8aa74ce8ed5b61040262239172c6450f9ab642e2f2d258c200c3bb4ad69011ef2dfe1dd63d758c86270ac4925b248b9bb4b6c9ff7bf2e56260cba02b2429648dc20eb034c8f9b18e1f6a38b8651c236554546585b4dd0f07fd5ee1696bf792527ce84b22012439300797103f22d3969df725e4414d899ec32a2ebbb857cc911e374e84738f4e007ee5260ff666a286e45c465525e2e3fc5e5e0e9ad82e53d364e4fd355619711c616d508470b997af44f62f283871cd892552128135aadb40c6f8cf69ee72acf349e9f4d33e8673450b9f69d4022a8d886b0cdbaba0798e0bf57b42dedfafb0bbd5495ca1c0030bbf460b48f9a138f6ab748df9046d9995f895062583ce0818e40afb9704653e11d58ca42bd3f60f4e908589ad9144c76067dc433cad13a5bbd9c168691b8c6cddb19d812f3e3f98e2cdd20dbb170936fd5cd2ef0bca72af8931a1b01d6081ffbef5be4416e696a7c762a375b368f71dc31362a4005750992a48e55311dde8d2013180d62e507ffc3e468c4a27acc763a9651b19f37e1ffca7e656225617368e79c1d18f9b14d770993d3d1dc42dcfd9adfa02a8ddf0ebc8fbb850fab307fd1d239cf6ad4e5ff40992dd974bc43fa351ce807cc0036c2f7d80bbd052f496216304fbc63e8d728bf129acaedb0073aff077e584ce04bc1ccf9c91f41f3c8804dd65da4ecdfdba32590e04b4d1b6895dea8edacd1f40313e8a1d4900d0dba54056eee72e3d155e9c67e7a51df581c33cbd39f16549d590ae5387fe2c5ad3484ad5c7da320066b79083c49879e45938b3bbb063726008a2ebb8847c9e57be6ec489c7aaa80f5e8e430040cb8d60298363df850cb7b4e98e97192882d10d2fd96cc490dd18b263d96aac6aa4f5583770e0917fa9b566dd0e0b218c6684007ec10cf11747e8f039fac5250170de2835ab88fea356b6a7d0f5e81ffe9b78d191a745e0237a256a2a840880689d83503b72462e3955b61e22afde947c1f1527ea94151c5b7d3a72ce68979603911c08bcec01097899fd30347be7f2e246f70d2af6a1e29b54988978a91f79b2ed8be76ffd62f79de5418933ed166be919d9bbb7524347d87d31afaed05e71a82b09c18c196b3ba6e226939b375903f7d889422863567203814484af89fd223ce1c959b1fdffaf26461630c630d2bbf99228a096ea6cb0d61df70d24414c76bf9371c4abff0ad257098189af6ea32200fbe092d875aa4d3f72a7ec138439e4b08fb1dcde6a90f25fde1498773e693c9b21c40505d42edcbcaed8a2dc4642750e9df73e169f9986ddb3a57991ac2cb3b540d788e2c2c22c2c51b2d74a98ed59a8cec89ba54342fb9660449a116f8691da60cb447afe4d5e80f37b4669e6007c1cadc41933fb27bab41afd312c37e5cb43715cb4013efcd91221ed06249540b733c05e81131aba75ba0f427d9bd975554b2d49a8048f0b3a84477e75290235fd3bbcaee6c4438ba72299dd960f3f6ee9241f7e399684e894d7bb1c302ecbe24d0f19dca982a82ee44f36211d23b0ea623c9c9f4f527f4e452fd06ebb943cadeea3d7fa42cabd25324bc5851e40f9952823f56b50b97729e6561f2100c2b5922860c6cf447a668324ca931f2f35a5edb7d306f8b8802f98cf67140a3fe73099ab86bb65c439a8593e64816bcd46aa4c254918f4a3a0f3f47b4ebcfb2824703f9a7d163824484ce6fe1852c4ba131ee2635de14822a8cb3782697fecc6f69514edd3f42fcf2751075b838bf14ae91e9dcff517bf3cec4db1b986b4c966a4fa40d38f2ebb7fc602",
Blocktime: 1525189571,
Time: 1525189571,
Txid: "97f944e3558cc784f4013b3753ce9570fe4707893eda724b12eb4c69686113a6",
LockTime: 0,
Vin: []bchain.Vin{},
Vout: []bchain.Vout{
{
ValueSat: *big.NewInt(9800018691),
N: 0,
ScriptPubKey: bchain.ScriptPubKey{
Hex: "76a9142df466d79cf4be0f7d1091512f1c297e4988fdd188ac",
Addresses: []string{
"k122WvzFAUeDK6g528V3vreGg8paJQ7bBH6",
},
},
},
},
}
testTx2 = bchain.Tx{
Hex: "0100000001cbc2c0b14b26f563ceee8201971b2caae2a4f964d0fd91267290c51a6a171411010000006a473044022032dd5d573c3a7f729da1cb9d9ba02a08e05d50b4f74d5aeb7cb22284526f70340220661ca4a192d02684f0b6b52768b9e9ae5fad41b962aa918537b91bba275e92e70121024e98e62782ba44e5677b52b1e4e973a027c7d873915a6d62ba967b2c07467224ffffffff02c0c62d00000000001976a914dd985697513887236c484acc605ece839e2204ac88ac989e8ce0000000001976a91482bfe75940a6d46238f55e258fcae5bef4e847ea88ac00000000",
Blocktime: 1525189759,
Time: 1525189759,
Txid: "3aebcf5a223450bca3c0312d3d87b6070447e795d09a266a3a01c70e44c7cc48",
LockTime: 0,
Vin: []bchain.Vin{
{
ScriptSig: bchain.ScriptSig{
Hex: "473044022032dd5d573c3a7f729da1cb9d9ba02a08e05d50b4f74d5aeb7cb22284526f70340220661ca4a192d02684f0b6b52768b9e9ae5fad41b962aa918537b91bba275e92e70121024e98e62782ba44e5677b52b1e4e973a027c7d873915a6d62ba967b2c07467224",
},
Txid: "1114176a1ac590722691fdd064f9a4e2aa2c1b970182eece63f5264bb1c0c2cb",
Vout: 1,
Sequence: 4294967295,
},
},
Vout: []bchain.Vout{
{
ValueSat: *big.NewInt(3000000),
N: 0,
ScriptPubKey: bchain.ScriptPubKey{
Hex: "76a914dd985697513887236c484acc605ece839e2204ac88ac",
Addresses: []string{
"k1J3Da4r65fSakeqUYSajoPotecvc7h8HaQ",
},
},
},
{
ValueSat: *big.NewInt(3767312024),
N: 1,
ScriptPubKey: bchain.ScriptPubKey{
Hex: "76a91482bfe75940a6d46238f55e258fcae5bef4e847ea88ac",
Addresses: []string{
"k19ksUfdb5Q9XKUj5edUp1DQhncCP8h9hE7",
},
},
},
},
}
}
func TestMain(m *testing.M) {
c := m.Run()
chaincfg.ResetParams()
os.Exit(c)
}
func TestGetAddrDesc(t *testing.T) {
type args struct {
tx bchain.Tx
parser *KotoParser
}
tests := []struct {
name string
args args
}{
{
name: "koto-1",
args: args{
tx: testTx1,
parser: NewKotoParser(GetChainParams("main"), &btc.Configuration{}),
},
},
{
name: "koto-2",
args: args{
tx: testTx2,
parser: NewKotoParser(GetChainParams("main"), &btc.Configuration{}),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
for n, vout := range tt.args.tx.Vout {
got1, err := tt.args.parser.GetAddrDescFromVout(&vout)
if err != nil {
t.Errorf("getAddrDescFromVout() error = %v, vout = %d", err, n)
return
}
got2, err := tt.args.parser.GetAddrDescFromAddress(vout.ScriptPubKey.Addresses[0])
if err != nil {
t.Errorf("getAddrDescFromAddress() error = %v, vout = %d", err, n)
return
}
if !bytes.Equal(got1, got2) {
t.Errorf("Address descriptors mismatch: got1 = %v, got2 = %v", got1, got2)
}
}
})
}
}
func TestPackTx(t *testing.T) {
type args struct {
tx bchain.Tx
height uint32
blockTime int64
parser *KotoParser
}
tests := []struct {
name string
args args
want string
wantErr bool
}{
{
name: "koto-1",
args: args{
tx: testTx1,
height: 200016,
blockTime: 1525189571,
parser: NewKotoParser(GetChainParams("main"), &btc.Configuration{}),
},
want: testTxPacked1,
wantErr: false,
},
{
name: "koto-2",
args: args{
tx: testTx2,
height: 200020,
blockTime: 1525189759,
parser: NewKotoParser(GetChainParams("main"), &btc.Configuration{}),
},
want: testTxPacked2,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := tt.args.parser.PackTx(&tt.args.tx, tt.args.height, tt.args.blockTime)
if (err != nil) != tt.wantErr {
t.Errorf("packTx() error = %v, wantErr %v", err, tt.wantErr)
return
}
h := hex.EncodeToString(got)
if !reflect.DeepEqual(h, tt.want) {
t.Errorf("packTx() = %v, want %v", h, tt.want)
}
})
}
}
func TestUnpackTx(t *testing.T) {
type args struct {
packedTx string
parser *KotoParser
}
tests := []struct {
name string
args args
want *bchain.Tx
want1 uint32
wantErr bool
}{
{
name: "koto-1",
args: args{
packedTx: testTxPacked1,
parser: NewKotoParser(GetChainParams("main"), &btc.Configuration{}),
},
want: &testTx1,
want1: 200016,
wantErr: false,
},
{
name: "koto-2",
args: args{
packedTx: testTxPacked2,
parser: NewKotoParser(GetChainParams("main"), &btc.Configuration{}),
},
want: &testTx2,
want1: 200020,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
b, _ := hex.DecodeString(tt.args.packedTx)
got, got1, err := tt.args.parser.UnpackTx(b)
if (err != nil) != tt.wantErr {
t.Errorf("unpackTx() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("unpackTx() got = %v, want %v", got, tt.want)
}
if got1 != tt.want1 {
t.Errorf("unpackTx() got1 = %v, want %v", got1, tt.want1)
}
})
}
}

View File

@ -0,0 +1,115 @@
package koto
import (
"encoding/json"
"github.com/golang/glog"
"github.com/juju/errors"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
)
// KotoRPC is an interface to JSON-RPC bitcoind service
type KotoRPC struct {
*btc.BitcoinRPC
}
// NewKotoRPC returns new LitecoinRPC instance
func NewKotoRPC(config json.RawMessage, pushHandler func(bchain.NotificationType)) (bchain.BlockChain, error) {
b, err := btc.NewBitcoinRPC(config, pushHandler)
if err != nil {
return nil, err
}
z := &KotoRPC{
BitcoinRPC: b.(*btc.BitcoinRPC),
}
z.RPCMarshaler = btc.JSONMarshalerV1{}
z.ChainConfig.SupportsEstimateSmartFee = false
return z, nil
}
// Initialize initializes KotoRPC instance.
func (z *KotoRPC) Initialize() error {
ci, err := z.GetChainInfo()
if err != nil {
return err
}
chainName := ci.Chain
params := GetChainParams(chainName)
z.Parser = NewKotoParser(params, z.ChainConfig)
// parameters for getInfo request
if params.Net == MainnetMagic {
z.Testnet = false
z.Network = "livenet"
} else {
z.Testnet = true
z.Network = "testnet"
}
glog.Info("rpc: block chain ", params.Name)
return nil
}
// GetBlock returns block with given hash.
func (z *KotoRPC) GetBlock(hash string, height uint32) (*bchain.Block, error) {
var err error
if hash == "" && height > 0 {
hash, err = z.GetBlockHash(height)
if err != nil {
return nil, err
}
}
glog.V(1).Info("rpc: getblock (verbosity=1) ", hash)
res := btc.ResGetBlockThin{}
req := btc.CmdGetBlock{Method: "getblock"}
req.Params.BlockHash = hash
req.Params.Verbosity = 1
err = z.Call(&req, &res)
if err != nil {
return nil, errors.Annotatef(err, "hash %v", hash)
}
if res.Error != nil {
return nil, errors.Annotatef(res.Error, "hash %v", hash)
}
txs := make([]bchain.Tx, 0, len(res.Result.Txids))
for _, txid := range res.Result.Txids {
tx, err := z.GetTransaction(txid)
if err != nil {
if err == bchain.ErrTxNotFound {
glog.Errorf("rpc: getblock: skipping transanction in block %s due error: %s", hash, err)
continue
}
return nil, err
}
txs = append(txs, *tx)
}
block := &bchain.Block{
BlockHeader: res.Result.BlockHeader,
Txs: txs,
}
return block, nil
}
// GetTransactionForMempool returns a transaction by the transaction ID.
// It could be optimized for mempool, i.e. without block time and confirmations
func (z *KotoRPC) GetTransactionForMempool(txid string) (*bchain.Tx, error) {
return z.GetTransaction(txid)
}
// GetMempoolEntry returns mempool data for given transaction
func (z *KotoRPC) GetMempoolEntry(txid string) (*bchain.MempoolEntry, error) {
return nil, errors.New("GetMempoolEntry: not implemented")
}
func isErrBlockNotFound(err *bchain.RPCError) bool {
return err.Message == "Block not found" ||
err.Message == "Block height out of range"
}

Some files were not shown because too many files have changed in this diff Show More