Compare commits

...

391 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
337 changed files with 18436 additions and 3299 deletions

1
.gitignore vendored
View File

@ -8,6 +8,7 @@ debug*
docker/blockbook
build/pkg-defs
build/blockbook
build/blockchaincfg.json
build/ldb
build/sst_dump
build/*.deb

View File

@ -104,6 +104,17 @@ backend-deploy-and-test-litecoin:
- blockbook
script: ./contrib/scripts/backend-deploy-and-test.sh litecoin
backend-deploy-and-test-namecoin:
stage: backend-deploy-and-test
only:
refs:
- master
changes:
- configs/coins/namecoin.json
tags:
- blockbook
script: ./contrib/scripts/backend-deploy-and-test.sh namecoin
backend-deploy-and-test-vertcoin:
stage: backend-deploy-and-test
only:

View File

@ -3,8 +3,8 @@
Blockbook is back-end service for Trezor wallet. Although it is open source, the design and development of the core packages
is done by Trezor developers in order to keep Blockbook compatible with Trezor.
Bug fixes and support for new coins are welcome. Please take note that non-fixing pull requests that change base
packages or another coin code will not be accepted. If you have a need to change some of the existing code, please file
Bug fixes and support for new coins are welcome. **Please take note that non-fixing pull requests that change base
packages or another coin code will not be accepted.** If you have a need to change some of the existing code, please file
an issue and discuss your request with Blockbook maintainers.
## Development environment

213
Gopkg.lock generated
View File

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

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/martinboehm/btcd"
[[constraint]]
branch = "master"
name = "github.com/martinboehm/btcutil"
[[constraint]]
branch = "master"
name = "github.com/golang/glog"
[[constraint]]
name = "github.com/gorilla/mux"
version = "1.6.1"
[[constraint]]
branch = "master"
name = "github.com/juju/errors"
[[constraint]]
branch = "master"
name = "github.com/martinboehm/golang-socketio"
[[constraint]]
branch = "master"
name = "github.com/pebbe/zmq4"
[[constraint]]
name = "github.com/prometheus/client_golang"
version = "0.8.0"
[[constraint]]
branch = "master"
name = "github.com/tecbot/gorocksdb"
[[constraint]]
name = "github.com/ethereum/go-ethereum"
version = "1.8.2"
[[constraint]]
name = "github.com/golang/protobuf"
version = "1.0.0"
[[constraint]]
branch = "master"
name = "github.com/martinboehm/bchutil"

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,74 +1,57 @@
[![Go Report Card](https://goreportcard.com/badge/trezor/blockbook)](https://goreportcard.com/report/trezor/blockbook)
# Fork
Fork of Trezor Blockbook.
# Blockbook
**Blockbook** is back-end service for Trezor wallet. Main features of **Blockbook** are:
The differences:
- index of addresses and address balances of the connected block chain
- fast searches in the indexes
- simple blockchain explorer
- websocket, API and legacy Bitcore Insight compatible socket.io interfaces
- support of multiple coins (Bitcoin and Ethereum type), with easy extensibility for other coins
- scripts for easy creation of debian packages for backend and blockbook
* Just for Ethereum.
## Build and installation instructions
* Use existing `geth --full` server.
Officially supported platform is **Debian Linux** and **AMD64** architecture.
* Don't require `backend-*` package.
Memory and disk requirements for initial synchronization of **Bitcoin mainnet** are around 32 GB RAM and over 180 GB of disk space. After initial synchronization, fully synchronized instance uses about 10 GB RAM.
Other coins should have lower requirements, depending on the size of their block chain. Note that fast SSD disks are highly
recommended.
* Minimal UI, dark theme.
User installation guide is [here](https://wiki.trezor.io/User_manual:Running_a_local_instance_of_Trezor_Wallet_backend_(Blockbook)).
* Listen only on localhost, no SSL.
Developer build guide is [here](/docs/build.md).
* Use spacecruft repos, not github.
Contribution guide is [here](CONTRIBUTING.md).
* Don't use CDN.
## Implemented coins
# Install
Blockbook currently supports over 30 coins. The Trezor team implemented
```
# Install docker, etc.
git clone https://spacecruft.org/spacecruft/blockbook
cd blockbook
make deb-blockbook-ethereum
dpkg -i build/blockbook-ethereum_0.3.4_amd64.deb
```
- Bitcoin, Bitcoin Cash, Zcash, Dash, Litecoin, Bitcoin Gold, Ethereum, Ethereum Classic, Dogecoin, Namecoin, Vertcoin, DigiByte, Liquid
Edit config:
```
vim /opt/coins/blockbook/ethereum/config/blockchaincfg.json
```
the rest of coins were implemented by the community.
XXX Hardcoded into systemd script, set `geth` node:
Testnets for some coins are also supported, for example:
- Bitcoin Testnet, Bitcoin Cash Testnet, ZCash Testnet, Ethereum Testnet Ropsten
```
vim /lib/systemd/system/blockbook-ethereum.service
systemctl daemon-reload
```
List of all implemented coins is in [the registry of ports](/docs/ports.md).
Start:
```
systemctl start blockbook-ethereum.service
```
## Common issues when running Blockbook or implementing additional coins
Logs:
```
tail -f /opt/coins/blockbook/ethereum/logs/blockbook.INFO
```
#### Out of memory when doing initial synchronization
# Upstream
Fork of Trezor Blockbook. See `README-upstream.md`.
How to reduce memory footprint of the initial sync:
* https://github.com/trezor/blockbook
- disable rocksdb cache by parameter `-dbcache=0`, the default size is 500MB
- run blockbook with parameter `-workers=1`. This disables bulk import mode, which caches a lot of data in memory (not in rocksdb cache). It will run about twice as slowly but especially for smaller blockchains it is no problem at all.
Please add your experience to this [issue](https://github.com/trezor/blockbook/issues/43).
#### Error `internalState: database is in inconsistent state and cannot be used`
Blockbook was killed during the initial import, most commonly by OOM killer. By default, Blockbook performs the initial import in bulk import mode, which for performance reasons does not store all the data immediately to the database. If Blockbook is killed during this phase, the database is left in an inconsistent state.
See above how to reduce the memory footprint, delete the database files and run the import again.
Check [this](https://github.com/trezor/blockbook/issues/89) or [this](https://github.com/trezor/blockbook/issues/147) issue for more info.
#### Running on Ubuntu
[This issue](https://github.com/trezor/blockbook/issues/45) discusses how to run Blockbook on Ubuntu. If you have some additional experience with Blockbook on Ubuntu, please add it to [this issue](https://github.com/trezor/blockbook/issues/45).
#### My coin implementation is reporting parse errors when importing blockchain
Your coin's block/transaction data may not be compatible with `BitcoinParser` `ParseBlock`/`ParseTx`, which is used by default. In that case, implement your coin in a similar way we used in case of [zcash](https://github.com/trezor/blockbook/tree/master/bchain/coins/zec) and some other coins. The principle is not to parse the block/transaction data in Blockbook but instead to get parsed transactions as json from the backend.
## Data storage in RocksDB
Blockbook stores data the key-value store RocksDB. Database format is described [here](/docs/rocksdb.md).
## API
Blockbook API is described [here](/docs/api.md).

View File

@ -1,13 +1,16 @@
package api
import (
"blockbook/bchain"
"blockbook/common"
"blockbook/db"
"encoding/json"
"errors"
"math/big"
"sort"
"time"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/bchain/coins/eth"
"spacecruft.org/spacecruft/blockbook/common"
"spacecruft.org/spacecruft/blockbook/db"
)
const maxUint32 = ^uint32(0)
@ -101,17 +104,17 @@ func (a *Amount) AsInt64() int64 {
// Vin contains information about single transaction input
type Vin struct {
Txid string `json:"txid,omitempty"`
Vout uint32 `json:"vout,omitempty"`
Sequence int64 `json:"sequence,omitempty"`
N int `json:"n"`
AddrDesc bchain.AddressDescriptor `json:"-"`
Addresses []string `json:"addresses,omitempty"`
Searchable bool `json:"-"`
ValueSat *Amount `json:"value,omitempty"`
Hex string `json:"hex,omitempty"`
Asm string `json:"asm,omitempty"`
Coinbase string `json:"coinbase,omitempty"`
Txid string `json:"txid,omitempty"`
Vout uint32 `json:"vout,omitempty"`
Sequence int64 `json:"sequence,omitempty"`
N int `json:"n"`
AddrDesc bchain.AddressDescriptor `json:"-"`
Addresses []string `json:"addresses,omitempty"`
IsAddress bool `json:"isAddress"`
ValueSat *Amount `json:"value,omitempty"`
Hex string `json:"hex,omitempty"`
Asm string `json:"asm,omitempty"`
Coinbase string `json:"coinbase,omitempty"`
}
// Vout contains information about single transaction output
@ -126,7 +129,7 @@ type Vout struct {
Asm string `json:"asm,omitempty"`
AddrDesc bchain.AddressDescriptor `json:"-"`
Addresses []string `json:"addresses"`
Searchable bool `json:"-"`
IsAddress bool `json:"isAddress"`
Type string `json:"type,omitempty"`
}
@ -168,11 +171,12 @@ type TokenTransfer struct {
// EthereumSpecific contains ethereum specific transaction data
type EthereumSpecific struct {
Status int `json:"status"` // 1 OK, 0 Fail, -1 pending
Nonce uint64 `json:"nonce"`
GasLimit *big.Int `json:"gasLimit"`
GasUsed *big.Int `json:"gasUsed"`
GasPrice *Amount `json:"gasPrice"`
Status eth.TxStatus `json:"status"` // 1 OK, 0 Fail, -1 pending
Nonce uint64 `json:"nonce"`
GasLimit *big.Int `json:"gasLimit"`
GasUsed *big.Int `json:"gasUsed"`
GasPrice *Amount `json:"gasPrice"`
Data string `json:"data,omitempty"`
}
// Tx holds information about a transaction
@ -191,12 +195,20 @@ type Tx struct {
ValueInSat *Amount `json:"valueIn,omitempty"`
FeesSat *Amount `json:"fees,omitempty"`
Hex string `json:"hex,omitempty"`
CoinSpecificData interface{} `json:"-"`
CoinSpecificJSON json.RawMessage `json:"-"`
Rbf bool `json:"rbf,omitempty"`
CoinSpecificData json.RawMessage `json:"coinSpecificData,omitempty"`
TokenTransfers []TokenTransfer `json:"tokenTransfers,omitempty"`
EthereumSpecific *EthereumSpecific `json:"ethereumSpecific,omitempty"`
}
// FeeStats contains detailed block fee statistics
type FeeStats struct {
TxCount int `json:"txCount"`
TotalFeesSat *Amount `json:"totalFeesSat"`
AverageFeePerKb int64 `json:"averageFeePerKb"`
DecilesFeePerKb [11]int64 `json:"decilesFeePerKb"`
}
// Paging contains information about paging for address, blocks and block
type Paging struct {
Page int `json:"page,omitempty"`
@ -214,6 +226,8 @@ const (
AddressFilterVoutInputs = -2
// AddressFilterVoutOutputs specifies that only txs where the address is as output are returned
AddressFilterVoutOutputs = -3
// AddressFilterVoutQueryNotNecessary signals that query for transactions is not necessary as there are no transactions for specified contract filter
AddressFilterVoutQueryNotNecessary = -4
// TokensToReturnNonzeroBalance - return only tokens with nonzero balance
TokensToReturnNonzeroBalance TokensToReturn = 0
@ -266,6 +280,7 @@ type Utxo struct {
Address string `json:"address,omitempty"`
Path string `json:"path,omitempty"`
Locktime uint32 `json:"lockTime,omitempty"`
Coinbase bool `json:"coinbase,omitempty"`
}
// Utxos is array of Utxo
@ -286,6 +301,73 @@ func (a Utxos) Less(i, j int) bool {
return hi >= hj
}
// BalanceHistory contains info about one point in time of balance history
type BalanceHistory struct {
Time uint32 `json:"time"`
Txs uint32 `json:"txs"`
ReceivedSat *Amount `json:"received"`
SentSat *Amount `json:"sent"`
SentToSelfSat *Amount `json:"sentToSelf"`
FiatRates map[string]float64 `json:"rates,omitempty"`
Txid string `json:"txid,omitempty"`
}
// BalanceHistories is array of BalanceHistory
type BalanceHistories []BalanceHistory
func (a BalanceHistories) Len() int { return len(a) }
func (a BalanceHistories) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a BalanceHistories) Less(i, j int) bool {
ti := a[i].Time
tj := a[j].Time
if ti == tj {
return a[i].Txid < a[j].Txid
}
return ti < tj
}
// SortAndAggregate sums BalanceHistories to groups defined by parameter groupByTime
func (a BalanceHistories) SortAndAggregate(groupByTime uint32) BalanceHistories {
bhs := make(BalanceHistories, 0)
if len(a) > 0 {
bha := BalanceHistory{
ReceivedSat: &Amount{},
SentSat: &Amount{},
SentToSelfSat: &Amount{},
}
sort.Sort(a)
for i := range a {
bh := &a[i]
time := bh.Time - bh.Time%groupByTime
if bha.Time != time {
if bha.Time != 0 {
// in aggregate, do not return txid as it could multiple of them
bha.Txid = ""
bhs = append(bhs, bha)
}
bha = BalanceHistory{
Time: time,
ReceivedSat: &Amount{},
SentSat: &Amount{},
SentToSelfSat: &Amount{},
}
}
if bha.Txid != bh.Txid {
bha.Txs += bh.Txs
bha.Txid = bh.Txid
}
(*big.Int)(bha.ReceivedSat).Add((*big.Int)(bha.ReceivedSat), (*big.Int)(bh.ReceivedSat))
(*big.Int)(bha.SentSat).Add((*big.Int)(bha.SentSat), (*big.Int)(bh.SentSat))
(*big.Int)(bha.SentToSelfSat).Add((*big.Int)(bha.SentToSelfSat), (*big.Int)(bh.SentToSelfSat))
}
if bha.Txs > 0 {
bha.Txid = ""
bhs = append(bhs, bha)
}
}
return bhs
}
// Blocks is list of blocks with paging information
type Blocks struct {
Paging
@ -294,19 +376,19 @@ type Blocks struct {
// 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 json.Number `json:"version"`
MerkleRoot string `json:"merkleRoot"`
Nonce string `json:"nonce"`
Bits string `json:"bits"`
Difficulty string `json:"difficulty"`
Txids []string `json:"tx,omitempty"`
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
@ -339,25 +421,10 @@ type BlockbookInfo struct {
About string `json:"about"`
}
// BackendInfo is used to get information about blockchain
type BackendInfo struct {
Chain string `json:"chain"`
Blocks int `json:"blocks"`
Headers int `json:"headers"`
Bestblockhash string `json:"bestBlockHash"`
Difficulty string `json:"difficulty"`
SizeOnDisk int64 `json:"sizeOnDisk"`
Version string `json:"version"`
Subversion string `json:"subversion"`
ProtocolVersion string `json:"protocolVersion"`
Timeoffset float64 `json:"timeOffset"`
Warnings string `json:"warnings"`
}
// SystemInfo contains information about the running blockbook and backend instance
type SystemInfo struct {
Blockbook *BlockbookInfo `json:"blockbook"`
Backend *BackendInfo `json:"backend"`
Blockbook *BlockbookInfo `json:"blockbook"`
Backend *common.BackendInfo `json:"backend"`
}
// MempoolTxid contains information about a transaction in mempool

View File

@ -49,3 +49,126 @@ func TestAmount_MarshalJSON(t *testing.T) {
})
}
}
func TestBalanceHistories_SortAndAggregate(t *testing.T) {
tests := []struct {
name string
a BalanceHistories
groupByTime uint32
want BalanceHistories
}{
{
name: "empty",
a: []BalanceHistory{},
groupByTime: 3600,
want: []BalanceHistory{},
},
{
name: "one",
a: []BalanceHistory{
{
ReceivedSat: (*Amount)(big.NewInt(1)),
SentSat: (*Amount)(big.NewInt(2)),
SentToSelfSat: (*Amount)(big.NewInt(1)),
Time: 1521514812,
Txid: "00b2c06055e5e90e9c82bd4181fde310104391a7fa4f289b1704e5d90caa3840",
Txs: 1,
},
},
groupByTime: 3600,
want: []BalanceHistory{
{
ReceivedSat: (*Amount)(big.NewInt(1)),
SentSat: (*Amount)(big.NewInt(2)),
SentToSelfSat: (*Amount)(big.NewInt(1)),
Time: 1521514800,
Txs: 1,
},
},
},
{
name: "aggregate",
a: []BalanceHistory{
{
ReceivedSat: (*Amount)(big.NewInt(1)),
SentSat: (*Amount)(big.NewInt(2)),
SentToSelfSat: (*Amount)(big.NewInt(0)),
Time: 1521504812,
Txid: "0011223344556677889900112233445566778899001122334455667788990011",
Txs: 1,
},
{
ReceivedSat: (*Amount)(big.NewInt(3)),
SentSat: (*Amount)(big.NewInt(4)),
SentToSelfSat: (*Amount)(big.NewInt(2)),
Time: 1521504812,
Txid: "00b2c06055e5e90e9c82bd4181fde310104391a7fa4f289b1704e5d90caa3840",
Txs: 1,
},
{
ReceivedSat: (*Amount)(big.NewInt(5)),
SentSat: (*Amount)(big.NewInt(6)),
SentToSelfSat: (*Amount)(big.NewInt(3)),
Time: 1521514812,
Txid: "00b2c06055e5e90e9c82bd4181fde310104391a7fa4f289b1704e5d90caa3840",
Txs: 1,
},
{
ReceivedSat: (*Amount)(big.NewInt(7)),
SentSat: (*Amount)(big.NewInt(8)),
SentToSelfSat: (*Amount)(big.NewInt(3)),
Time: 1521504812,
Txid: "00b2c06055e5e90e9c82bd4181fde310104391a7fa4f289b1704e5d90caa3840",
Txs: 1,
},
{
ReceivedSat: (*Amount)(big.NewInt(9)),
SentSat: (*Amount)(big.NewInt(10)),
SentToSelfSat: (*Amount)(big.NewInt(5)),
Time: 1521534812,
Txid: "0011223344556677889900112233445566778899001122334455667788990011",
Txs: 1,
},
{
ReceivedSat: (*Amount)(big.NewInt(11)),
SentSat: (*Amount)(big.NewInt(12)),
SentToSelfSat: (*Amount)(big.NewInt(6)),
Time: 1521534812,
Txid: "1122334455667788990011223344556677889900112233445566778899001100",
Txs: 1,
},
},
groupByTime: 3600,
want: []BalanceHistory{
{
ReceivedSat: (*Amount)(big.NewInt(11)),
SentSat: (*Amount)(big.NewInt(14)),
SentToSelfSat: (*Amount)(big.NewInt(5)),
Time: 1521504000,
Txs: 2,
},
{
ReceivedSat: (*Amount)(big.NewInt(5)),
SentSat: (*Amount)(big.NewInt(6)),
SentToSelfSat: (*Amount)(big.NewInt(3)),
Time: 1521514800,
Txs: 1,
},
{
ReceivedSat: (*Amount)(big.NewInt(20)),
SentSat: (*Amount)(big.NewInt(22)),
SentToSelfSat: (*Amount)(big.NewInt(11)),
Time: 1521532800,
Txs: 2,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.a.SortAndAggregate(tt.groupByTime); !reflect.DeepEqual(got, tt.want) {
t.Errorf("BalanceHistories.SortAndAggregate() = %+v, want %+v", got, tt.want)
}
})
}
}

View File

@ -1,8 +1,9 @@
package api
import (
"blockbook/bchain"
"math/big"
"spacecruft.org/spacecruft/blockbook/bchain"
)
// ScriptSigV1 is used for legacy api v1
@ -13,26 +14,26 @@ type ScriptSigV1 struct {
// VinV1 is used for legacy api v1
type VinV1 struct {
Txid string `json:"txid"`
Vout uint32 `json:"vout"`
Sequence int64 `json:"sequence,omitempty"`
N int `json:"n"`
ScriptSig ScriptSigV1 `json:"scriptSig"`
AddrDesc bchain.AddressDescriptor `json:"-"`
Addresses []string `json:"addresses"`
Searchable bool `json:"-"`
Value string `json:"value"`
ValueSat big.Int `json:"-"`
Txid string `json:"txid"`
Vout uint32 `json:"vout"`
Sequence int64 `json:"sequence,omitempty"`
N int `json:"n"`
ScriptSig ScriptSigV1 `json:"scriptSig"`
AddrDesc bchain.AddressDescriptor `json:"-"`
Addresses []string `json:"addresses"`
IsAddress bool `json:"-"`
Value string `json:"value"`
ValueSat big.Int `json:"-"`
}
// ScriptPubKeyV1 is used for legacy api v1
type ScriptPubKeyV1 struct {
Hex string `json:"hex,omitempty"`
Asm string `json:"asm,omitempty"`
AddrDesc bchain.AddressDescriptor `json:"-"`
Addresses []string `json:"addresses"`
Searchable bool `json:"-"`
Type string `json:"type,omitempty"`
Hex string `json:"hex,omitempty"`
Asm string `json:"asm,omitempty"`
AddrDesc bchain.AddressDescriptor `json:"-"`
Addresses []string `json:"addresses"`
IsAddress bool `json:"-"`
Type string `json:"type,omitempty"`
}
// VoutV1 is used for legacy api v1
@ -115,12 +116,12 @@ func (w *Worker) TxToV1(tx *Tx) *TxV1 {
Asm: v.Asm,
Hex: v.Hex,
},
Searchable: v.Searchable,
Sequence: v.Sequence,
Txid: v.Txid,
Value: v.ValueSat.DecimalString(d),
ValueSat: v.ValueSat.AsBigInt(),
Vout: v.Vout,
IsAddress: v.IsAddress,
Sequence: v.Sequence,
Txid: v.Txid,
Value: v.ValueSat.DecimalString(d),
ValueSat: v.ValueSat.AsBigInt(),
Vout: v.Vout,
}
}
voutV1 := make([]VoutV1, len(tx.Vout))
@ -129,12 +130,12 @@ func (w *Worker) TxToV1(tx *Tx) *TxV1 {
voutV1[i] = VoutV1{
N: v.N,
ScriptPubKey: ScriptPubKeyV1{
AddrDesc: v.AddrDesc,
Addresses: v.Addresses,
Asm: v.Asm,
Hex: v.Hex,
Searchable: v.Searchable,
Type: v.Type,
AddrDesc: v.AddrDesc,
Addresses: v.Addresses,
Asm: v.Asm,
Hex: v.Hex,
IsAddress: v.IsAddress,
Type: v.Type,
},
Spent: v.Spent,
SpentHeight: v.SpentHeight,

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,6 @@
package api
import (
"blockbook/bchain"
"blockbook/db"
"fmt"
"math/big"
"sort"
@ -11,9 +9,10 @@ import (
"github.com/golang/glog"
"github.com/juju/errors"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/db"
)
const xpubLen = 111
const defaultAddressesGap = 20
const maxAddressesGap = 10000
@ -101,7 +100,7 @@ func (w *Worker) xpubGetAddressTxids(addrDesc bchain.AddressDescriptor, mempool
l = len(txs)
callback(m.Txid, 0, []int32{m.Vout})
if len(txs) > l {
uniqueTxs[m.Txid] = l - 1
uniqueTxs[m.Txid] = l
}
} else {
if m.Vout < 0 {
@ -271,7 +270,7 @@ func evictXpubCacheItems() {
}
func (w *Worker) getXpubData(xpub string, page int, txsOnPage int, option AccountDetails, filter *AddressFilter, gap int) (*xpubData, uint32, error) {
if w.chainType != bchain.ChainBitcoinType || len(xpub) != xpubLen {
if w.chainType != bchain.ChainBitcoinType {
return nil, 0, ErrUnsupportedXpub
}
var (
@ -305,8 +304,7 @@ func (w *Worker) getXpubData(xpub string, page int, txsOnPage int, option Accoun
data = xpubData{gap: gap}
data.basePath, err = w.chainParser.DerivationBasePath(xpub)
if err != nil {
glog.Warning("DerivationBasePath error", err)
data.basePath = "unknown"
return nil, 0, err
}
} else {
hash, err := w.db.GetBlockHash(data.dataHeight)
@ -418,7 +416,7 @@ func (w *Worker) GetXpubAddress(xpub string, page int, txsOnPage int, option Acc
// the same tx can have multiple addresses from the same xpub, get it from backend it only once
tx, foundTx := txmMap[txid.txid]
if !foundTx {
tx, err = w.GetTransaction(txid.txid, false, false)
tx, err = w.GetTransaction(txid.txid, false, true)
// mempool transaction may fail
if err != nil || tx == nil {
glog.Warning("GetTransaction in mempool: ", err)
@ -590,3 +588,50 @@ func (w *Worker) GetXpubUtxo(xpub string, onlyConfirmed bool, gap int) (Utxos, e
glog.Info("GetXpubUtxo ", xpub[:16], ", ", len(r), " utxos, finished in ", time.Since(start))
return r, nil
}
// GetXpubBalanceHistory returns history of balance for given xpub
func (w *Worker) GetXpubBalanceHistory(xpub string, fromTimestamp, toTimestamp int64, currencies []string, gap int, groupBy uint32) (BalanceHistories, error) {
bhs := make(BalanceHistories, 0)
start := time.Now()
fromUnix, fromHeight, toUnix, toHeight := w.balanceHistoryHeightsFromTo(fromTimestamp, toTimestamp)
if fromHeight >= toHeight {
return bhs, nil
}
data, _, err := w.getXpubData(xpub, 0, 1, AccountDetailsTxidHistory, &AddressFilter{
Vout: AddressFilterVoutOff,
OnlyConfirmed: true,
FromHeight: fromHeight,
ToHeight: toHeight,
}, gap)
if err != nil {
return nil, err
}
selfAddrDesc := make(map[string]struct{})
for _, da := range [][]xpubAddress{data.addresses, data.changeAddresses} {
for i := range da {
selfAddrDesc[string(da[i].addrDesc)] = struct{}{}
}
}
for _, da := range [][]xpubAddress{data.addresses, data.changeAddresses} {
for i := range da {
ad := &da[i]
txids := ad.txids
for txi := len(txids) - 1; txi >= 0; txi-- {
bh, err := w.balanceHistoryForTxid(ad.addrDesc, txids[txi].txid, fromUnix, toUnix, selfAddrDesc)
if err != nil {
return nil, err
}
if bh != nil {
bhs = append(bhs, *bh)
}
}
}
}
bha := bhs.SortAndAggregate(groupBy)
err = w.setFiatRateToBalanceHistories(bha, currencies)
if err != nil {
return nil, err
}
glog.Info("GetUtxoBalanceHistory ", xpub[:16], ", blocks ", fromHeight, "-", toHeight, ", count ", len(bha), ", finished in ", time.Since(start))
return bha, nil
}

View File

@ -3,6 +3,7 @@ package bchain
import (
"sort"
"sync"
"time"
)
type addrIndex struct {
@ -27,6 +28,7 @@ type BaseMempool struct {
txEntries map[string]txEntry
addrDescToTx map[string][]Outpoint
OnNewTxAddr OnNewTxAddrFunc
OnNewTx OnNewTxFunc
}
// GetTransactions returns slice of mempool transactions for given address
@ -113,3 +115,22 @@ func (m *BaseMempool) GetTransactionTime(txid string) uint32 {
}
return e.time
}
func (m *BaseMempool) txToMempoolTx(tx *Tx) *MempoolTx {
mtx := MempoolTx{
Hex: tx.Hex,
Blocktime: time.Now().Unix(),
LockTime: tx.LockTime,
Txid: tx.Txid,
Version: tx.Version,
Vout: tx.Vout,
CoinSpecificData: tx.CoinSpecificData,
}
mtx.Vin = make([]MempoolVin, len(tx.Vin))
for i, vin := range tx.Vin {
mtx.Vin[i] = MempoolVin{
Vin: vin,
}
}
return &mtx
}

View File

@ -9,6 +9,7 @@ import (
"github.com/gogo/protobuf/proto"
"github.com/golang/glog"
"github.com/juju/errors"
"spacecruft.org/spacecruft/blockbook/common"
)
// BaseParser implements data parsing/handling functionality base for all other parsers
@ -39,9 +40,9 @@ func (p *BaseParser) GetAddrDescForUnknownInput(tx *Tx, input int) AddressDescri
const zeros = "0000000000000000000000000000000000000000"
// AmountToBigInt converts amount in json.Number (string) to big.Int
// AmountToBigInt converts amount in common.JSONNumber (string) to big.Int
// it uses string operations to avoid problems with rounding
func (p *BaseParser) AmountToBigInt(n json.Number) (big.Int, error) {
func (p *BaseParser) AmountToBigInt(n common.JSONNumber) (big.Int, error) {
var r big.Int
s := string(n)
i := strings.IndexByte(s, '.')
@ -161,6 +162,11 @@ func (p *BaseParser) GetChainType() ChainType {
return ChainBitcoinType
}
// MinimumCoinbaseConfirmations returns minimum number of confirmations a coinbase transaction must have before it can be spent
func (p *BaseParser) MinimumCoinbaseConfirmations() int {
return 0
}
// PackTx packs transaction to byte array using protobuf
func (p *BaseParser) PackTx(tx *Tx, height uint32, blockTime int64) ([]byte, error) {
var err error

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 {
@ -44,7 +47,7 @@ func TestBaseParser_AmountToDecimalString(t *testing.T) {
func TestBaseParser_AmountToBigInt(t *testing.T) {
for _, tt := range amounts {
t.Run(tt.s, func(t *testing.T) {
got, err := NewBaseParser(tt.adp).AmountToBigInt(json.Number(tt.s))
got, err := NewBaseParser(tt.adp).AmountToBigInt(common.JSONNumber(tt.s))
if err != nil {
t.Errorf("BaseParser.AmountToBigInt() error = %v", err)
return

View File

@ -1,8 +1,6 @@
package bch
import (
"blockbook/bchain"
"blockbook/bchain/coins/btc"
"fmt"
"github.com/martinboehm/bchutil"
@ -10,6 +8,8 @@ import (
"github.com/martinboehm/btcutil/chaincfg"
"github.com/martinboehm/btcutil/txscript"
"github.com/schancel/cashaddr-converter/address"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
)
// AddressFormat type is used to specify different formats of address
@ -151,7 +151,7 @@ func isCashAddr(addr string) bool {
func (p *BCashParser) outputScriptToAddresses(script []byte) ([]string, bool, error) {
// convert possible P2PK script to P2PK, which bchutil can process
var err error
script, err = txscript.ConvertP2PKtoP2PKH(script)
script, err = txscript.ConvertP2PKtoP2PKH(p.Params.Base58CksumHasher, script)
if err != nil {
return nil, false, err
}

View File

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

View File

@ -1,8 +1,6 @@
package bch
import (
"blockbook/bchain"
"blockbook/bchain/coins/btc"
"encoding/hex"
"encoding/json"
"math/big"
@ -10,6 +8,8 @@ import (
"github.com/golang/glog"
"github.com/juju/errors"
"github.com/martinboehm/bchutil"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
)
// BCashRPC is an interface to JSON-RPC bitcoind service.

View File

@ -1,10 +1,9 @@
package bellcoin
import (
"blockbook/bchain/coins/btc"
"github.com/martinboehm/btcd/wire"
"github.com/martinboehm/btcutil/chaincfg"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
)
// magic numbers

View File

@ -3,8 +3,6 @@
package bellcoin
import (
"blockbook/bchain"
"blockbook/bchain/coins/btc"
"encoding/hex"
"math/big"
"os"
@ -12,6 +10,8 @@ import (
"testing"
"github.com/martinboehm/btcutil/chaincfg"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
)
func TestMain(m *testing.M) {

View File

@ -1,11 +1,11 @@
package bellcoin
import (
"blockbook/bchain"
"blockbook/bchain/coins/btc"
"encoding/json"
"github.com/golang/glog"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
)
// BellcoinRPC is an interface to JSON-RPC bitcoind service.

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,37 +1,6 @@
package coins
import (
"blockbook/bchain"
"blockbook/bchain/coins/bch"
"blockbook/bchain/coins/bellcoin"
"blockbook/bchain/coins/btc"
"blockbook/bchain/coins/btg"
"blockbook/bchain/coins/dash"
"blockbook/bchain/coins/digibyte"
"blockbook/bchain/coins/dogecoin"
"blockbook/bchain/coins/eth"
"blockbook/bchain/coins/flo"
"blockbook/bchain/coins/fujicoin"
"blockbook/bchain/coins/gamecredits"
"blockbook/bchain/coins/grs"
"blockbook/bchain/coins/koto"
"blockbook/bchain/coins/liquid"
"blockbook/bchain/coins/litecoin"
"blockbook/bchain/coins/monacoin"
"blockbook/bchain/coins/monetaryunit"
"blockbook/bchain/coins/myriad"
"blockbook/bchain/coins/namecoin"
"blockbook/bchain/coins/nuls"
"blockbook/bchain/coins/pivx"
"blockbook/bchain/coins/polis"
"blockbook/bchain/coins/qtum"
"blockbook/bchain/coins/ravencoin"
"blockbook/bchain/coins/vertcoin"
"blockbook/bchain/coins/viacoin"
"blockbook/bchain/coins/vipstarcoin"
"blockbook/bchain/coins/xzc"
"blockbook/bchain/coins/zec"
"blockbook/common"
"context"
"encoding/json"
"fmt"
@ -41,6 +10,48 @@ import (
"time"
"github.com/juju/errors"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/bchain/coins/bch"
"spacecruft.org/spacecruft/blockbook/bchain/coins/bellcoin"
"spacecruft.org/spacecruft/blockbook/bchain/coins/bitcore"
"spacecruft.org/spacecruft/blockbook/bchain/coins/bitzeny"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btg"
"spacecruft.org/spacecruft/blockbook/bchain/coins/cpuchain"
"spacecruft.org/spacecruft/blockbook/bchain/coins/dash"
"spacecruft.org/spacecruft/blockbook/bchain/coins/dcr"
"spacecruft.org/spacecruft/blockbook/bchain/coins/deeponion"
"spacecruft.org/spacecruft/blockbook/bchain/coins/digibyte"
"spacecruft.org/spacecruft/blockbook/bchain/coins/divi"
"spacecruft.org/spacecruft/blockbook/bchain/coins/dogecoin"
"spacecruft.org/spacecruft/blockbook/bchain/coins/eth"
"spacecruft.org/spacecruft/blockbook/bchain/coins/firo"
"spacecruft.org/spacecruft/blockbook/bchain/coins/flo"
"spacecruft.org/spacecruft/blockbook/bchain/coins/fujicoin"
"spacecruft.org/spacecruft/blockbook/bchain/coins/gamecredits"
"spacecruft.org/spacecruft/blockbook/bchain/coins/grs"
"spacecruft.org/spacecruft/blockbook/bchain/coins/koto"
"spacecruft.org/spacecruft/blockbook/bchain/coins/liquid"
"spacecruft.org/spacecruft/blockbook/bchain/coins/litecoin"
"spacecruft.org/spacecruft/blockbook/bchain/coins/monacoin"
"spacecruft.org/spacecruft/blockbook/bchain/coins/monetaryunit"
"spacecruft.org/spacecruft/blockbook/bchain/coins/myriad"
"spacecruft.org/spacecruft/blockbook/bchain/coins/namecoin"
"spacecruft.org/spacecruft/blockbook/bchain/coins/nuls"
"spacecruft.org/spacecruft/blockbook/bchain/coins/omotenashicoin"
"spacecruft.org/spacecruft/blockbook/bchain/coins/pivx"
"spacecruft.org/spacecruft/blockbook/bchain/coins/polis"
"spacecruft.org/spacecruft/blockbook/bchain/coins/qtum"
"spacecruft.org/spacecruft/blockbook/bchain/coins/ravencoin"
"spacecruft.org/spacecruft/blockbook/bchain/coins/ritocoin"
"spacecruft.org/spacecruft/blockbook/bchain/coins/snowgem"
"spacecruft.org/spacecruft/blockbook/bchain/coins/trezarcoin"
"spacecruft.org/spacecruft/blockbook/bchain/coins/unobtanium"
"spacecruft.org/spacecruft/blockbook/bchain/coins/vertcoin"
"spacecruft.org/spacecruft/blockbook/bchain/coins/viacoin"
"spacecruft.org/spacecruft/blockbook/bchain/coins/vipstarcoin"
"spacecruft.org/spacecruft/blockbook/bchain/coins/zec"
"spacecruft.org/spacecruft/blockbook/common"
)
type blockChainFactory func(config json.RawMessage, pushHandler func(bchain.NotificationType)) (bchain.BlockChain, error)
@ -51,16 +62,21 @@ var BlockChainFactories = make(map[string]blockChainFactory)
func init() {
BlockChainFactories["Bitcoin"] = btc.NewBitcoinRPC
BlockChainFactories["Testnet"] = btc.NewBitcoinRPC
BlockChainFactories["Signet"] = btc.NewBitcoinRPC
BlockChainFactories["Zcash"] = zec.NewZCashRPC
BlockChainFactories["Zcash Testnet"] = zec.NewZCashRPC
BlockChainFactories["Ethereum"] = eth.NewEthereumRPC
BlockChainFactories["Ethereum Classic"] = eth.NewEthereumRPC
BlockChainFactories["Ethereum Testnet Ropsten"] = eth.NewEthereumRPC
BlockChainFactories["Ethereum Testnet Goerli"] = eth.NewEthereumRPC
BlockChainFactories["Bcash"] = bch.NewBCashRPC
BlockChainFactories["Bcash Testnet"] = bch.NewBCashRPC
BlockChainFactories["Bgold"] = btg.NewBGoldRPC
BlockChainFactories["Bgold Testnet"] = btg.NewBGoldRPC
BlockChainFactories["Dash"] = dash.NewDashRPC
BlockChainFactories["Dash Testnet"] = dash.NewDashRPC
BlockChainFactories["Decred"] = dcr.NewDecredRPC
BlockChainFactories["Decred Testnet"] = dcr.NewDecredRPC
BlockChainFactories["GameCredits"] = gamecredits.NewGameCreditsRPC
BlockChainFactories["Koto"] = koto.NewKotoRPC
BlockChainFactories["Koto Testnet"] = koto.NewKotoRPC
@ -74,6 +90,7 @@ func init() {
BlockChainFactories["Monacoin Testnet"] = monacoin.NewMonacoinRPC
BlockChainFactories["MonetaryUnit"] = monetaryunit.NewMonetaryUnitRPC
BlockChainFactories["DigiByte"] = digibyte.NewDigiByteRPC
BlockChainFactories["DigiByte Testnet"] = digibyte.NewDigiByteRPC
BlockChainFactories["Myriad"] = myriad.NewMyriadRPC
BlockChainFactories["Liquid"] = liquid.NewLiquidRPC
BlockChainFactories["Groestlcoin"] = grs.NewGroestlcoinRPC
@ -81,7 +98,7 @@ func init() {
BlockChainFactories["PIVX"] = pivx.NewPivXRPC
BlockChainFactories["PIVX Testnet"] = pivx.NewPivXRPC
BlockChainFactories["Polis"] = polis.NewPolisRPC
BlockChainFactories["Zcoin"] = xzc.NewZcoinRPC
BlockChainFactories["Firo"] = firo.NewFiroRPC
BlockChainFactories["Fujicoin"] = fujicoin.NewFujicoinRPC
BlockChainFactories["Flo"] = flo.NewFloRPC
BlockChainFactories["Bellcoin"] = bellcoin.NewBellcoinRPC
@ -92,6 +109,17 @@ func init() {
BlockChainFactories["VIPSTARCOIN"] = vipstarcoin.NewVIPSTARCOINRPC
BlockChainFactories["ZelCash"] = zec.NewZCashRPC
BlockChainFactories["Ravencoin"] = ravencoin.NewRavencoinRPC
BlockChainFactories["Ritocoin"] = ritocoin.NewRitocoinRPC
BlockChainFactories["Divi"] = divi.NewDiviRPC
BlockChainFactories["CPUchain"] = cpuchain.NewCPUchainRPC
BlockChainFactories["Unobtanium"] = unobtanium.NewUnobtaniumRPC
BlockChainFactories["DeepOnion"] = deeponion.NewDeepOnionRPC
BlockChainFactories["SnowGem"] = snowgem.NewSnowGemRPC
BlockChainFactories["Bitcore"] = bitcore.NewBitcoreRPC
BlockChainFactories["Omotenashicoin"] = omotenashicoin.NewOmotenashiCoinRPC
BlockChainFactories["Omotenashicoin Testnet"] = omotenashicoin.NewOmotenashiCoinRPC
BlockChainFactories["BitZeny"] = bitzeny.NewBitZenyRPC
BlockChainFactories["Trezarcoin"] = trezarcoin.NewTrezarcoinRPC
}
// GetCoinNameFromConfig gets coin name and coin shortcut from config file
@ -150,7 +178,7 @@ type blockChainWithMetrics struct {
func (c *blockChainWithMetrics) observeRPCLatency(method string, start time.Time, err error) {
var e string
if err != nil {
e = err.Error()
e = "failure"
}
c.m.RPCLatency.With(common.Labels{"method": method, "error": e}).Observe(float64(time.Since(start)) / 1e6) // in milliseconds
}
@ -163,8 +191,8 @@ func (c *blockChainWithMetrics) CreateMempool(chain bchain.BlockChain) (bchain.M
return c.b.CreateMempool(chain)
}
func (c *blockChainWithMetrics) InitializeMempool(addrDescForOutpoint bchain.AddrDescForOutpointFunc, onNewTxAddr bchain.OnNewTxAddrFunc) error {
return c.b.InitializeMempool(addrDescForOutpoint, onNewTxAddr)
func (c *blockChainWithMetrics) InitializeMempool(addrDescForOutpoint bchain.AddrDescForOutpointFunc, onNewTxAddr bchain.OnNewTxAddrFunc, onNewTx bchain.OnNewTxFunc) error {
return c.b.InitializeMempool(addrDescForOutpoint, onNewTxAddr, onNewTx)
}
func (c *blockChainWithMetrics) Shutdown(ctx context.Context) error {
@ -299,7 +327,7 @@ type mempoolWithMetrics struct {
func (c *mempoolWithMetrics) observeRPCLatency(method string, start time.Time, err error) {
var e string
if err != nil {
e = err.Error()
e = "failure"
}
c.m.RPCLatency.With(common.Labels{"method": method, "error": e}).Observe(float64(time.Since(start)) / 1e6) // in milliseconds
}

View File

@ -1,7 +1,6 @@
package btc
import (
"blockbook/bchain"
"bytes"
"encoding/binary"
"encoding/hex"
@ -16,20 +15,38 @@ import (
"github.com/martinboehm/btcutil/chaincfg"
"github.com/martinboehm/btcutil/hdkeychain"
"github.com/martinboehm/btcutil/txscript"
"spacecruft.org/spacecruft/blockbook/bchain"
)
// temp params for signet(wait btcd commit)
// magic numbers
const (
SignetMagic wire.BitcoinNet = 0x6a70c7f0
)
// chain parameters
var (
SigNetParams chaincfg.Params
)
func init() {
SigNetParams = chaincfg.TestNet3Params
SigNetParams.Net = SignetMagic
}
// OutputScriptToAddressesFunc converts ScriptPubKey to bitcoin addresses
type OutputScriptToAddressesFunc func(script []byte) ([]string, bool, error)
// BitcoinParser handle
type BitcoinParser struct {
*bchain.BaseParser
Params *chaincfg.Params
OutputScriptToAddressesFunc OutputScriptToAddressesFunc
XPubMagic uint32
XPubMagicSegwitP2sh uint32
XPubMagicSegwitNative uint32
Slip44 uint32
Params *chaincfg.Params
OutputScriptToAddressesFunc OutputScriptToAddressesFunc
XPubMagic uint32
XPubMagicSegwitP2sh uint32
XPubMagicSegwitNative uint32
Slip44 uint32
minimumCoinbaseConfirmations int
}
// NewBitcoinParser returns new BitcoinParser instance
@ -39,11 +56,12 @@ func NewBitcoinParser(params *chaincfg.Params, c *Configuration) *BitcoinParser
BlockAddressesToKeep: c.BlockAddressesToKeep,
AmountDecimalPoint: 8,
},
Params: params,
XPubMagic: c.XPubMagic,
XPubMagicSegwitP2sh: c.XPubMagicSegwitP2sh,
XPubMagicSegwitNative: c.XPubMagicSegwitNative,
Slip44: c.Slip44,
Params: params,
XPubMagic: c.XPubMagic,
XPubMagicSegwitP2sh: c.XPubMagicSegwitP2sh,
XPubMagicSegwitNative: c.XPubMagicSegwitNative,
Slip44: c.Slip44,
minimumCoinbaseConfirmations: c.MinimumCoinbaseConfirmations,
}
p.OutputScriptToAddressesFunc = p.outputScriptToAddresses
return p
@ -61,6 +79,8 @@ func GetChainParams(chain string) *chaincfg.Params {
return &chaincfg.TestNet3Params
case "regtest":
return &chaincfg.RegressionNetParams
case "signet":
return &SigNetParams
}
return &chaincfg.MainNetParams
}
@ -73,7 +93,7 @@ func (p *BitcoinParser) GetAddrDescFromVout(output *bchain.Vout) (bchain.Address
}
// convert possible P2PK script to P2PKH
// so that all transactions by given public key are indexed together
return txscript.ConvertP2PKtoP2PKH(ad)
return txscript.ConvertP2PKtoP2PKH(p.Params.Base58CksumHasher, ad)
}
// GetAddrDescFromAddress returns internal address representation (descriptor) of given address
@ -326,6 +346,11 @@ func (p *BitcoinParser) UnpackTx(buf []byte) (*bchain.Tx, uint32, error) {
return tx, height, nil
}
// MinimumCoinbaseConfirmations returns minimum number of confirmations a coinbase transaction must have before it can be spent
func (p *BitcoinParser) MinimumCoinbaseConfirmations() int {
return p.minimumCoinbaseConfirmations
}
func (p *BitcoinParser) addrDescFromExtKey(extKey *hdkeychain.ExtendedKey) (bchain.AddressDescriptor, error) {
var a btcutil.Address
var err error

View File

@ -3,7 +3,6 @@
package btc
import (
"blockbook/bchain"
"encoding/hex"
"math/big"
"os"
@ -11,6 +10,7 @@ import (
"testing"
"github.com/martinboehm/btcutil/chaincfg"
"spacecruft.org/spacecruft/blockbook/bchain"
)
func TestMain(m *testing.M) {
@ -259,10 +259,11 @@ func TestGetAddressesFromAddrDesc(t *testing.T) {
}
var (
testTx1, testTx2 bchain.Tx
testTx1, testTx2, testTx3 bchain.Tx
testTxPacked1 = "0001e2408ba8d7af5401000000017f9a22c9cbf54bd902400df746f138f37bcf5b4d93eb755820e974ba43ed5f42040000006a4730440220037f4ed5427cde81d55b9b6a2fd08c8a25090c2c2fff3a75c1a57625ca8a7118022076c702fe55969fa08137f71afd4851c48e31082dd3c40c919c92cdbc826758d30121029f6da5623c9f9b68a9baf9c1bc7511df88fa34c6c2f71f7c62f2f03ff48dca80feffffff019c9700000000000017a9146144d57c8aff48492c9dfb914e120b20bad72d6f8773d00700"
testTxPacked2 = "0007c91a899ab7da6a010000000001019d64f0c72a0d206001decbffaa722eb1044534c74eee7a5df8318e42a4323ec10000000017160014550da1f5d25a9dae2eafd6902b4194c4c6500af6ffffffff02809698000000000017a914cd668d781ece600efa4b2404dc91fd26b8b8aed8870553d7360000000017a914246655bdbd54c7e477d0ea2375e86e0db2b8f80a8702473044022076aba4ad559616905fa51d4ddd357fc1fdb428d40cb388e042cdd1da4a1b7357022011916f90c712ead9a66d5f058252efd280439ad8956a967e95d437d246710bc9012102a80a5964c5612bb769ef73147b2cf3c149bc0fd4ecb02f8097629c94ab013ffd00000000"
testTxPacked3 = "00003d818bfda9aa3e02000000000102deb1999a857ab0a13d6b12fbd95ea75b409edde5f2ff747507ce42d9986a8b9d0000000000fdffffff9fd2d3361e203b2375eba6438efbef5b3075531e7e583c7cc76b7294fe7f22980000000000fdffffff02a0860100000000001600148091746745464e7555c31e9a5afceac14a02978ae7fc1c0000000000160014565ea9ff4589d3e05ba149ae6e257752bfdc2a1e0247304402207d67d320a8e813f986b35e9791935fcb736754812b7038686f5de6cfdcda99cd02201c3bb2c178e0056016437ecfe365a7eef84aa9d293ebdc566177af82e22fcdd3012103abb30c1bbe878b07b58dc169b1d061d48c60be8107f632a59778b38bf7ceea5a02473044022044f54a478cfe086e870cb026c9dcd4e14e63778bef569a4d55a6332725cd9a9802202f0e94c04e6f328fc64ad9efe552888c299750d1b8d033324825a3ff29920e030121036fcd433428aa7dc65c4f5408fa31f208c54fe4b4c6c1ae9c39a825ed4f1ac039813d0000"
)
func init() {
@ -335,6 +336,54 @@ func init() {
},
},
}
testTx3 = bchain.Tx{
Hex: "02000000000102deb1999a857ab0a13d6b12fbd95ea75b409edde5f2ff747507ce42d9986a8b9d0000000000fdffffff9fd2d3361e203b2375eba6438efbef5b3075531e7e583c7cc76b7294fe7f22980000000000fdffffff02a0860100000000001600148091746745464e7555c31e9a5afceac14a02978ae7fc1c0000000000160014565ea9ff4589d3e05ba149ae6e257752bfdc2a1e0247304402207d67d320a8e813f986b35e9791935fcb736754812b7038686f5de6cfdcda99cd02201c3bb2c178e0056016437ecfe365a7eef84aa9d293ebdc566177af82e22fcdd3012103abb30c1bbe878b07b58dc169b1d061d48c60be8107f632a59778b38bf7ceea5a02473044022044f54a478cfe086e870cb026c9dcd4e14e63778bef569a4d55a6332725cd9a9802202f0e94c04e6f328fc64ad9efe552888c299750d1b8d033324825a3ff29920e030121036fcd433428aa7dc65c4f5408fa31f208c54fe4b4c6c1ae9c39a825ed4f1ac039813d0000",
Blocktime: 1607805599,
Txid: "24551a58a1d1fb89d7052e2bbac7cb69a7825ee1e39439befbec8c32148cf735",
LockTime: 15745,
Version: 2,
Vin: []bchain.Vin{
{
ScriptSig: bchain.ScriptSig{
Hex: "",
},
Txid: "9d8b6a98d942ce077574fff2e5dd9e405ba75ed9fb126b3da1b07a859a99b1de",
Vout: 0,
Sequence: 4294967293,
},
{
ScriptSig: bchain.ScriptSig{
Hex: "",
},
Txid: "98227ffe94726bc77c3c587e1e5375305beffb8e43a6eb75233b201e36d3d29f",
Vout: 0,
Sequence: 4294967293,
},
},
Vout: []bchain.Vout{
{
ValueSat: *big.NewInt(100000),
N: 0,
ScriptPubKey: bchain.ScriptPubKey{
Hex: "00148091746745464e7555c31e9a5afceac14a02978a",
Addresses: []string{
"tb1qszghge69ge8824wrr6d94l82c99q99u2ccgv5w",
},
},
},
{
ValueSat: *big.NewInt(1899751),
N: 1,
ScriptPubKey: bchain.ScriptPubKey{
Hex: "0014565ea9ff4589d3e05ba149ae6e257752bfdc2a1e",
Addresses: []string{
"tb1q2e02nl6938f7qkapfxhxufth22lac2s792vsxp",
},
},
},
},
}
}
func TestPackTx(t *testing.T) {
@ -372,6 +421,17 @@ func TestPackTx(t *testing.T) {
want: testTxPacked2,
wantErr: false,
},
{
name: "signet-1",
args: args{
tx: testTx3,
height: 15745,
blockTime: 1607805599,
parser: NewBitcoinParser(GetChainParams("signet"), &Configuration{}),
},
want: testTxPacked3,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@ -420,6 +480,16 @@ func TestUnpackTx(t *testing.T) {
want1: 510234,
wantErr: false,
},
{
name: "signet-1",
args: args{
packedTx: testTxPacked3,
parser: NewBitcoinParser(GetChainParams("signet"), &Configuration{}),
},
want: &testTx3,
want1: 15745,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {

View File

@ -1,7 +1,6 @@
package btc
import (
"blockbook/bchain"
"bytes"
"context"
"encoding/hex"
@ -17,6 +16,8 @@ import (
"github.com/golang/glog"
"github.com/juju/errors"
"github.com/martinboehm/btcd/wire"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/common"
)
// BitcoinRPC is an interface to JSON-RPC bitcoind service.
@ -55,8 +56,9 @@ type Configuration struct {
XPubMagicSegwitP2sh uint32 `json:"xpub_magic_segwit_p2sh,omitempty"`
XPubMagicSegwitNative uint32 `json:"xpub_magic_segwit_native,omitempty"`
Slip44 uint32 `json:"slip44,omitempty"`
AlternativeEstimateFee string `json:"alternativeEstimateFee,omitempty"`
AlternativeEstimateFeeParams string `json:"alternativeEstimateFeeParams,omitempty"`
AlternativeEstimateFee string `json:"alternative_estimate_fee,omitempty"`
AlternativeEstimateFeeParams string `json:"alternative_estimate_fee_params,omitempty"`
MinimumCoinbaseConfirmations int `json:"minimumCoinbaseConfirmations,omitempty"`
}
// NewBitcoinRPC returns new BitcoinRPC instance.
@ -71,6 +73,10 @@ func NewBitcoinRPC(config json.RawMessage, pushHandler func(bchain.NotificationT
if c.BlockAddressesToKeep < 100 {
c.BlockAddressesToKeep = 100
}
// default MinimumCoinbaseConfirmations is 100
if c.MinimumCoinbaseConfirmations == 0 {
c.MinimumCoinbaseConfirmations = 100
}
// at least 1 mempool worker/subworker for synchronous mempool synchronization
if c.MempoolWorkers < 1 {
c.MempoolWorkers = 1
@ -149,12 +155,13 @@ func (b *BitcoinRPC) CreateMempool(chain bchain.BlockChain) (bchain.Mempool, err
}
// InitializeMempool creates ZeroMQ subscription and sets AddrDescForOutpointFunc to the Mempool
func (b *BitcoinRPC) InitializeMempool(addrDescForOutpoint bchain.AddrDescForOutpointFunc, onNewTxAddr bchain.OnNewTxAddrFunc) error {
func (b *BitcoinRPC) InitializeMempool(addrDescForOutpoint bchain.AddrDescForOutpointFunc, onNewTxAddr bchain.OnNewTxAddrFunc, onNewTx bchain.OnNewTxFunc) error {
if b.Mempool == nil {
return errors.New("Mempool not created")
}
b.Mempool.AddrDescForOutpoint = addrDescForOutpoint
b.Mempool.OnNewTxAddr = onNewTxAddr
b.Mempool.OnNewTx = onNewTx
if b.mq == nil {
mq, err := bchain.NewMQ(b.ChainConfig.MessageQueueBinding, b.pushHandler)
if err != nil {
@ -232,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"`
}
@ -251,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"`
}
@ -353,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"`
}
@ -368,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

View File

@ -1,7 +1,6 @@
package btc
import (
"blockbook/bchain"
"bytes"
"encoding/json"
"fmt"
@ -12,8 +11,8 @@ import (
"time"
"github.com/golang/glog"
"github.com/juju/errors"
"spacecruft.org/spacecruft/blockbook/bchain"
)
// https://whatthefee.io returns

View File

@ -1,9 +1,6 @@
package btg
import (
"blockbook/bchain"
"blockbook/bchain/coins/btc"
"blockbook/bchain/coins/utils"
"bytes"
"encoding/binary"
"io"
@ -11,6 +8,9 @@ import (
"github.com/martinboehm/btcd/chaincfg/chainhash"
"github.com/martinboehm/btcd/wire"
"github.com/martinboehm/btcutil/chaincfg"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
"spacecruft.org/spacecruft/blockbook/bchain/coins/utils"
)
const (

View File

@ -3,7 +3,6 @@
package btg
import (
"blockbook/bchain/coins/btc"
"bytes"
"encoding/hex"
"fmt"
@ -13,6 +12,7 @@ import (
"testing"
"github.com/martinboehm/btcutil/chaincfg"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
)
func TestMain(m *testing.M) {

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.

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,11 +1,10 @@
package dash
import (
"blockbook/bchain"
"blockbook/bchain/coins/btc"
"github.com/martinboehm/btcd/wire"
"github.com/martinboehm/btcutil/chaincfg"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
)
const (

View File

@ -1,10 +1,8 @@
// build unittest
// +build unittest
package dash
import (
"blockbook/bchain"
"blockbook/bchain/coins/btc"
"bytes"
"encoding/hex"
"fmt"
@ -13,6 +11,9 @@ import (
"path/filepath"
"reflect"
"testing"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
)
type testBlock struct {

View File

@ -1,12 +1,12 @@
package dash
import (
"blockbook/bchain"
"blockbook/bchain/coins/btc"
"encoding/json"
"github.com/golang/glog"
"github.com/juju/errors"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
)
const firstBlockWithSpecialTransactions = 1028160

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

View File

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

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.

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,13 +1,13 @@
package dogecoin
import (
"blockbook/bchain"
"blockbook/bchain/coins/btc"
"blockbook/bchain/coins/utils"
"bytes"
"github.com/martinboehm/btcd/wire"
"github.com/martinboehm/btcutil/chaincfg"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
"spacecruft.org/spacecruft/blockbook/bchain/coins/utils"
)
// magic numbers

View File

@ -3,8 +3,6 @@
package dogecoin
import (
"blockbook/bchain"
"blockbook/bchain/coins/btc"
"bytes"
"encoding/hex"
"fmt"
@ -16,6 +14,8 @@ import (
"testing"
"github.com/martinboehm/btcutil/chaincfg"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
)
func TestMain(m *testing.M) {
@ -46,6 +46,12 @@ func Test_GetAddrDescFromAddress_Mainnet(t *testing.T) {
want: "76a914efb6158f75743c611858fdfd0f4aaec6cc6196bc88ac",
wantErr: false,
},
{
name: "P2PKH3",
args: args{address: "DHobAps6DjZ5n4xMV75n7kJv299Zi85FCG"},
want: "76a9148ae937291e72f7368421dbaa966c44950eb14db788ac",
wantErr: false,
},
{
name: "P2SH1",
args: args{address: "9tg1kVUk339Tk58ewu5T8QT82Z6cE4UvSU"},
@ -76,6 +82,81 @@ func Test_GetAddrDescFromAddress_Mainnet(t *testing.T) {
}
}
func Test_GetAddressesFromAddrDesc_Mainnet(t *testing.T) {
type args struct {
script string
}
tests := []struct {
name string
args args
want []string
want2 bool
wantErr bool
}{
{
name: "P2PKH1",
args: args{script: "76a9148841590909747c0f97af158f22fadacb1652522088ac"},
want: []string{"DHZYinsaM9nW5piCMN639ELRKbZomThPnZ"},
want2: true,
wantErr: false,
},
{
name: "P2PKH2",
args: args{script: "76a914efb6158f75743c611858fdfd0f4aaec6cc6196bc88ac"},
want: []string{"DSzaAYEYyy9ngjoJ294r7jzFM3xhD6bKHK"},
want2: true,
wantErr: false,
},
{
name: "P2PKH3",
args: args{script: "76a91450e86eeac599ad023b8981296d01b50bdabcdd9788ac"},
want: []string{"DCWu3MLz9xBGFuuLyNDf6QjuGp49f5tfc9"},
want2: true,
wantErr: false,
},
{
name: "P2SH1",
args: args{script: "a9141889a089400ea25d28694fd98aa7702b21eeeab187"},
want: []string{"9tg1kVUk339Tk58ewu5T8QT82Z6cE4UvSU"},
want2: true,
wantErr: false,
},
{
name: "OP_RETURN ascii",
args: args{script: "6a0461686f6a"},
want: []string{"OP_RETURN (ahoj)"},
want2: false,
wantErr: false,
},
{
name: "OP_RETURN hex",
args: args{script: "6a072020f1686f6a20"},
want: []string{"OP_RETURN 2020f1686f6a20"},
want2: false,
wantErr: false,
},
}
parser := NewDogecoinParser(GetChainParams("main"), &btc.Configuration{})
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
b, _ := hex.DecodeString(tt.args.script)
got, got2, err := parser.GetAddressesFromAddrDesc(b)
if (err != nil) != tt.wantErr {
t.Errorf("GetAddressesFromAddrDesc() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("GetAddressesFromAddrDesc() = %v, want %v", got, tt.want)
}
if !reflect.DeepEqual(got2, tt.want2) {
t.Errorf("GetAddressesFromAddrDesc() = %v, want %v", got2, tt.want2)
}
})
}
}
var (
testTx1 bchain.Tx
testTxPacked1 = "00030e6d8ba8d7aa2001000000016b3c0c53267964120acf7f7e72217e3f463e52ce622f89659f6a6bb8e69a4d91000000006c493046022100a96454237e3a020994534583e28c04757881374bceac89f933ea9ff00b4db259022100fbb757ff7ea4f02c4e42556b2834c61eba1f1af605db089d836a0614d90a3b46012103cebdde6d1046e285df4f48497bc50dc20a4a258ca5b7308cb0a929c9fdadcd9dffffffff0217e823ca7f0200001976a914eef21768a546590993e313c7f3dfadf6a6efa1e888acaddf4cba010000001976a914e0fee2ea29dd9c6c759d8341bd0da4c4f738cced88ac00000000"

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.

View File

@ -1,17 +1,18 @@
package eth
import (
"blockbook/bchain"
"bytes"
"context"
"encoding/hex"
"math/big"
"strings"
"sync"
"unicode/utf8"
ethcommon "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/golang/glog"
"github.com/juju/errors"
"spacecruft.org/spacecruft/blockbook/bchain"
)
var erc20abi = `[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function","signature":"0x06fdde03"},
@ -73,9 +74,9 @@ func erc20GetTransfersFromLog(logs []*rpcLog) ([]bchain.Erc20Transfer, error) {
return nil, err
}
r = append(r, bchain.Erc20Transfer{
Contract: strings.ToLower(l.Address),
From: strings.ToLower(from),
To: strings.ToLower(to),
Contract: EIP55AddressFromAddress(l.Address),
From: EIP55AddressFromAddress(from),
To: EIP55AddressFromAddress(to),
Tokens: t,
})
}
@ -96,9 +97,9 @@ func erc20GetTransfersFromTx(tx *rpcTransaction) ([]bchain.Erc20Transfer, error)
return nil, errors.New("Data is not a number")
}
r = append(r, bchain.Erc20Transfer{
Contract: strings.ToLower(tx.To),
From: strings.ToLower(tx.From),
To: strings.ToLower(to),
Contract: EIP55AddressFromAddress(tx.To),
From: EIP55AddressFromAddress(tx.From),
To: EIP55AddressFromAddress(to),
Tokens: t,
})
}
@ -144,7 +145,7 @@ func parseErc20StringProperty(contractDesc bchain.AddressDescriptor, data string
n := parseErc20NumericProperty(contractDesc, data[64:128])
if n != nil {
l := n.Uint64()
if 2*int(l) <= len(data)-128 {
if l > 0 && 2*int(l) <= len(data)-128 {
b, err := hex.DecodeString(data[128 : 128+2*l])
if err == nil {
return string(b)
@ -152,6 +153,20 @@ func parseErc20StringProperty(contractDesc bchain.AddressDescriptor, data string
}
}
}
// allow string properties as UTF-8 data
b, err := hex.DecodeString(data)
if err == nil {
i := bytes.Index(b, []byte{0})
if i > 32 {
i = 32
}
if i > 0 {
b = b[:i]
}
if utf8.Valid(b) {
return string(b)
}
}
if glog.V(1) {
glog.Warning("Cannot parse '", data, "' for contract ", contractDesc)
}
@ -165,21 +180,29 @@ func (b *EthereumRPC) EthereumTypeGetErc20ContractInfo(contractDesc bchain.Addre
contract, found := cachedContracts[cds]
cachedContractsMux.Unlock()
if !found {
address := hexutil.Encode(contractDesc)
address := EIP55Address(contractDesc)
data, err := b.ethCall(erc20NameSignature, address)
if err != nil {
return nil, err
// ignore the error from the eth_call - since geth v1.9.15 they changed the behavior
// and returning error "execution reverted" for some non contract addresses
// https://github.com/ethereum/go-ethereum/issues/21249#issuecomment-648647672
glog.Warning(errors.Annotatef(err, "erc20NameSignature %v", address))
return nil, nil
// return nil, errors.Annotatef(err, "erc20NameSignature %v", address)
}
name := parseErc20StringProperty(contractDesc, data)
if name != "" {
data, err = b.ethCall(erc20SymbolSignature, address)
if err != nil {
return nil, err
glog.Warning(errors.Annotatef(err, "erc20SymbolSignature %v", address))
return nil, nil
// return nil, errors.Annotatef(err, "erc20SymbolSignature %v", address)
}
symbol := parseErc20StringProperty(contractDesc, data)
data, err = b.ethCall(erc20DecimalsSignature, address)
if err != nil {
return nil, err
glog.Warning(errors.Annotatef(err, "erc20DecimalsSignature %v", address))
// return nil, errors.Annotatef(err, "erc20DecimalsSignature %v", address)
}
contract = &bchain.Erc20Contract{
Contract: address,
@ -204,8 +227,8 @@ func (b *EthereumRPC) EthereumTypeGetErc20ContractInfo(contractDesc bchain.Addre
// EthereumTypeGetErc20ContractBalance returns balance of ERC20 contract for given address
func (b *EthereumRPC) EthereumTypeGetErc20ContractBalance(addrDesc, contractDesc bchain.AddressDescriptor) (*big.Int, error) {
addr := hexutil.Encode(addrDesc)
contract := hexutil.Encode(contractDesc)
addr := EIP55Address(addrDesc)
contract := EIP55Address(contractDesc)
req := erc20BalanceOf + "0000000000000000000000000000000000000000000000000000000000000000"[len(addr)-2:] + addr[2:]
data, err := b.ethCall(req, contract)
if err != nil {

View File

@ -3,12 +3,13 @@
package eth
import (
"blockbook/bchain"
"blockbook/tests/dbtestdata"
fmt "fmt"
"fmt"
"math/big"
"strings"
"testing"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/tests/dbtestdata"
)
func TestErc20_erc20GetTransfersFromLog(t *testing.T) {
@ -128,6 +129,26 @@ func TestErc20_parseErc20StringProperty(t *testing.T) {
args: "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000022426974436c617665202d20436f6e73756d657220416374697669747920546f6b656e00000000000000",
want: "BitClave - Consumer Activity Token",
},
{
name: "short",
args: "0x44616920537461626c65636f696e2076312e3000000000000000000000000000",
want: "Dai Stablecoin v1.0",
},
{
name: "short2",
args: "0x44616920537461626c65636f696e2076312e3020444444444444444444444444",
want: "Dai Stablecoin v1.0 DDDDDDDDDDDD",
},
{
name: "long",
args: "0x556e6973776170205631000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
want: "Uniswap V1",
},
{
name: "garbage",
args: "0x2234880850896048596206002535425366538144616734015984380565810000",
want: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {

View File

@ -1,7 +1,6 @@
package eth
import (
"blockbook/bchain"
"encoding/hex"
"math/big"
"strconv"
@ -9,6 +8,8 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/golang/protobuf/proto"
"github.com/juju/errors"
"spacecruft.org/spacecruft/blockbook/bchain"
"golang.org/x/crypto/sha3"
)
// EthereumTypeAddressDescriptorLen - in case of EthereumType, the AddressDescriptor has fixed length
@ -75,12 +76,6 @@ type rpcReceipt struct {
Logs []*rpcLog `json:"logs"`
}
type rpcEtcReceipt struct {
GasUsed string `json:"gasUsed"`
Status int `json:"status"`
Logs []*rpcLog `json:"logs"`
}
type completeTransaction struct {
Tx *rpcTransaction `json:"tx"`
Receipt *rpcReceipt `json:"receipt,omitempty"`
@ -101,18 +96,31 @@ func ethNumber(n string) (int64, error) {
return 0, errors.Errorf("Not a number: '%v'", n)
}
func (p *EthereumParser) ethTxToTx(tx *rpcTransaction, receipt *rpcReceipt, blocktime int64, confirmations uint32) (*bchain.Tx, error) {
func (p *EthereumParser) ethTxToTx(tx *rpcTransaction, receipt *rpcReceipt, blocktime int64, confirmations uint32, fixEIP55 bool) (*bchain.Tx, error) {
txid := tx.Hash
var (
fa, ta []string
err error
)
if len(tx.From) > 2 {
if fixEIP55 {
tx.From = EIP55AddressFromAddress(tx.From)
}
fa = []string{tx.From}
}
if len(tx.To) > 2 {
if fixEIP55 {
tx.To = EIP55AddressFromAddress(tx.To)
}
ta = []string{tx.To}
}
if fixEIP55 && receipt != nil && receipt.Logs != nil {
for _, l := range receipt.Logs {
if len(l.Address) > 2 {
l.Address = EIP55AddressFromAddress(l.Address)
}
}
}
ct := completeTransaction{
Tx: tx,
Receipt: receipt,
@ -176,9 +184,46 @@ func (p *EthereumParser) GetAddrDescFromAddress(address string) (bchain.AddressD
return hex.DecodeString(address)
}
// EIP55Address returns an EIP55-compliant hex string representation of the address
func EIP55Address(addrDesc bchain.AddressDescriptor) string {
raw := hexutil.Encode(addrDesc)
if len(raw) != 42 {
return raw
}
sha := sha3.NewLegacyKeccak256()
result := []byte(raw)
sha.Write(result[2:])
hash := sha.Sum(nil)
for i := 2; i < len(result); i++ {
hashByte := hash[(i-2)>>1]
if i%2 == 0 {
hashByte = hashByte >> 4
} else {
hashByte &= 0xf
}
if result[i] > '9' && hashByte > 7 {
result[i] -= 32
}
}
return string(result)
}
// EIP55AddressFromAddress returns an EIP55-compliant hex string representation of the address
func EIP55AddressFromAddress(address string) string {
if has0xPrefix(address) {
address = address[2:]
}
b, err := hex.DecodeString(address)
if err != nil {
return address
}
return EIP55Address(b)
}
// GetAddressesFromAddrDesc returns addresses for given address descriptor with flag if the addresses are searchable
func (p *EthereumParser) GetAddressesFromAddrDesc(addrDesc bchain.AddressDescriptor) ([]string, bool, error) {
return []string{hexutil.Encode(addrDesc)}, true, nil
return []string{EIP55Address(addrDesc)}, true, nil
}
// GetScriptFromAddrDesc returns output script for given address descriptor
@ -266,8 +311,14 @@ func (p *EthereumParser) PackTx(tx *bchain.Tx, height uint32, blockTime int64) (
if pt.Receipt.GasUsed, err = hexDecodeBig(r.Receipt.GasUsed); err != nil {
return nil, errors.Annotatef(err, "GasUsed %v", r.Receipt.GasUsed)
}
if pt.Receipt.Status, err = hexDecodeBig(r.Receipt.Status); err != nil {
return nil, errors.Annotatef(err, "Status %v", r.Receipt.Status)
if r.Receipt.Status != "" {
if pt.Receipt.Status, err = hexDecodeBig(r.Receipt.Status); err != nil {
return nil, errors.Annotatef(err, "Status %v", r.Receipt.Status)
}
} else {
// unknown status, use 'U' as status bytes
// there is a potential for conflict with value 0x55 but this is not used by any chain at this moment
pt.Receipt.Status = []byte{'U'}
}
ptLogs := make([]*ProtoCompleteTransaction_ReceiptType_LogType, len(r.Receipt.Logs))
for i, l := range r.Receipt.Logs {
@ -308,7 +359,7 @@ func (p *EthereumParser) UnpackTx(buf []byte) (*bchain.Tx, uint32, error) {
rt := rpcTransaction{
AccountNonce: hexutil.EncodeUint64(pt.Tx.AccountNonce),
BlockNumber: hexutil.EncodeUint64(uint64(pt.BlockNumber)),
From: hexutil.Encode(pt.Tx.From),
From: EIP55Address(pt.Tx.From),
GasLimit: hexutil.EncodeUint64(pt.Tx.GasLimit),
Hash: hexutil.Encode(pt.Tx.Hash),
Payload: hexutil.Encode(pt.Tx.Payload),
@ -316,7 +367,7 @@ func (p *EthereumParser) UnpackTx(buf []byte) (*bchain.Tx, uint32, error) {
// R: hexEncodeBig(pt.R),
// S: hexEncodeBig(pt.S),
// V: hexEncodeBig(pt.V),
To: hexutil.Encode(pt.Tx.To),
To: EIP55Address(pt.Tx.To),
TransactionIndex: hexutil.EncodeUint64(uint64(pt.Tx.TransactionIndex)),
Value: hexEncodeBig(pt.Tx.Value),
}
@ -329,18 +380,23 @@ func (p *EthereumParser) UnpackTx(buf []byte) (*bchain.Tx, uint32, error) {
topics[j] = hexutil.Encode(t)
}
logs[i] = &rpcLog{
Address: hexutil.Encode(l.Address),
Address: EIP55Address(l.Address),
Data: hexutil.Encode(l.Data),
Topics: topics,
}
}
status := ""
// handle a special value []byte{'U'} as unknown state
if len(pt.Receipt.Status) != 1 || pt.Receipt.Status[0] != 'U' {
status = hexEncodeBig(pt.Receipt.Status)
}
rr = &rpcReceipt{
GasUsed: hexEncodeBig(pt.Receipt.GasUsed),
Status: hexEncodeBig(pt.Receipt.Status),
Status: status,
Logs: logs,
}
}
tx, err := p.ethTxToTx(&rt, rr, int64(pt.BlockTime), 0)
tx, err := p.ethTxToTx(&rt, rr, int64(pt.BlockTime), 0, false)
if err != nil {
return nil, 0, err
}
@ -416,40 +472,51 @@ func (p *EthereumParser) EthereumTypeGetErc20FromTx(tx *bchain.Tx) ([]bchain.Erc
return r, nil
}
// TxStatus is status of transaction
type TxStatus int
// statuses of transaction
const (
txStatusUnknown = iota - 2
txStatusPending
txStatusFailure
txStatusOK
TxStatusUnknown = TxStatus(iota - 2)
TxStatusPending
TxStatusFailure
TxStatusOK
)
// EthereumTxData contains ethereum specific transaction data
type EthereumTxData struct {
Status int `json:"status"` // 1 OK, 0 Fail, -1 pending, -2 unknown
Status TxStatus `json:"status"` // 1 OK, 0 Fail, -1 pending, -2 unknown
Nonce uint64 `json:"nonce"`
GasLimit *big.Int `json:"gaslimit"`
GasUsed *big.Int `json:"gasused"`
GasPrice *big.Int `json:"gasprice"`
Data string `json:"data"`
}
// GetEthereumTxData returns EthereumTxData from bchain.Tx
func GetEthereumTxData(tx *bchain.Tx) *EthereumTxData {
etd := EthereumTxData{Status: txStatusPending}
csd, ok := tx.CoinSpecificData.(completeTransaction)
return GetEthereumTxDataFromSpecificData(tx.CoinSpecificData)
}
// GetEthereumTxDataFromSpecificData returns EthereumTxData from coinSpecificData
func GetEthereumTxDataFromSpecificData(coinSpecificData interface{}) *EthereumTxData {
etd := EthereumTxData{Status: TxStatusPending}
csd, ok := coinSpecificData.(completeTransaction)
if ok {
if csd.Tx != nil {
etd.Nonce, _ = hexutil.DecodeUint64(csd.Tx.AccountNonce)
etd.GasLimit, _ = hexutil.DecodeBig(csd.Tx.GasLimit)
etd.GasPrice, _ = hexutil.DecodeBig(csd.Tx.GasPrice)
etd.Data = csd.Tx.Payload
}
if csd.Receipt != nil {
switch csd.Receipt.Status {
case "0x1":
etd.Status = txStatusOK
etd.Status = TxStatusOK
case "": // old transactions did not set status
etd.Status = txStatusUnknown
etd.Status = TxStatusUnknown
default:
etd.Status = txStatusFailure
etd.Status = TxStatusFailure
}
etd.GasUsed, _ = hexutil.DecodeBig(csd.Receipt.GasUsed)
}

View File

@ -3,13 +3,14 @@
package eth
import (
"blockbook/bchain"
"blockbook/tests/dbtestdata"
"encoding/hex"
"fmt"
"math/big"
"reflect"
"testing"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/tests/dbtestdata"
)
func TestEthParser_GetAddrDescFromAddress(t *testing.T) {
@ -67,7 +68,7 @@ func TestEthParser_GetAddrDescFromAddress(t *testing.T) {
}
}
var testTx1, testTx2 bchain.Tx
var testTx1, testTx2, testTx1Failed, testTx1NoStatus bchain.Tx
func init() {
@ -77,14 +78,14 @@ func init() {
Txid: "0xcd647151552b5132b2aef7c9be00dc6f73afc5901dde157aab131335baaa853b",
Vin: []bchain.Vin{
{
Addresses: []string{"0x3e3a3d69dc66ba10737f531ed088954a9ec89d97"},
Addresses: []string{"0x3E3a3D69dc66bA10737F531ed088954a9EC89d97"},
},
},
Vout: []bchain.Vout{
{
ValueSat: *big.NewInt(1999622000000000000),
ScriptPubKey: bchain.ScriptPubKey{
Addresses: []string{"0x555ee11fbddc0e49a9bab358a8941ad95ffdb48f"},
Addresses: []string{"0x555Ee11FBDDc0E49A9bAB358A8941AD95fFDB48f"},
},
},
},
@ -93,12 +94,12 @@ func init() {
AccountNonce: "0xb26c",
GasPrice: "0x430e23400",
GasLimit: "0x5208",
To: "0x555ee11fbddc0e49a9bab358a8941ad95ffdb48f",
To: "0x555Ee11FBDDc0E49A9bAB358A8941AD95fFDB48f",
Value: "0x1bc0159d530e6000",
Payload: "0x",
Hash: "0xcd647151552b5132b2aef7c9be00dc6f73afc5901dde157aab131335baaa853b",
BlockNumber: "0x41eee8",
From: "0x3e3a3d69dc66ba10737f531ed088954a9ec89d97",
From: "0x3E3a3D69dc66bA10737F531ed088954a9EC89d97",
TransactionIndex: "0xa",
},
Receipt: &rpcReceipt{
@ -115,14 +116,14 @@ func init() {
Txid: "0xa9cd088aba2131000da6f38a33c20169baee476218deea6b78720700b895b101",
Vin: []bchain.Vin{
{
Addresses: []string{"0x20cd153de35d469ba46127a0c8f18626b59a256a"},
Addresses: []string{"0x20cD153de35D469BA46127A0C8F18626b59a256A"},
},
},
Vout: []bchain.Vout{
{
ValueSat: *big.NewInt(0),
ScriptPubKey: bchain.ScriptPubKey{
Addresses: []string{"0x4af4114f73d1c1c903ac9e0361b379d1291808a2"},
Addresses: []string{"0x4af4114F73d1c1C903aC9E0361b379D1291808A2"},
},
},
},
@ -131,19 +132,19 @@ func init() {
AccountNonce: "0xd0",
GasPrice: "0x9502f9000",
GasLimit: "0x130d5",
To: "0x4af4114f73d1c1c903ac9e0361b379d1291808a2",
To: "0x4af4114F73d1c1C903aC9E0361b379D1291808A2",
Value: "0x0",
Payload: "0xa9059cbb000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f00000000000000000000000000000000000000000000021e19e0c9bab2400000",
Hash: "0xa9cd088aba2131000da6f38a33c20169baee476218deea6b78720700b895b101",
BlockNumber: "0x41eee8",
From: "0x20cd153de35d469ba46127a0c8f18626b59a256a",
From: "0x20cD153de35D469BA46127A0C8F18626b59a256A",
TransactionIndex: "0x0"},
Receipt: &rpcReceipt{
GasUsed: "0xcb39",
Status: "0x1",
Logs: []*rpcLog{
{
Address: "0x4af4114f73d1c1c903ac9e0361b379d1291808a2",
Address: "0x4af4114F73d1c1C903aC9E0361b379D1291808A2",
Data: "0x00000000000000000000000000000000000000000000021e19e0c9bab2400000",
Topics: []string{
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
@ -155,6 +156,83 @@ func init() {
},
},
}
testTx1Failed = bchain.Tx{
Blocktime: 1534858022,
Time: 1534858022,
Txid: "0xcd647151552b5132b2aef7c9be00dc6f73afc5901dde157aab131335baaa853b",
Vin: []bchain.Vin{
{
Addresses: []string{"0x3E3a3D69dc66bA10737F531ed088954a9EC89d97"},
},
},
Vout: []bchain.Vout{
{
ValueSat: *big.NewInt(1999622000000000000),
ScriptPubKey: bchain.ScriptPubKey{
Addresses: []string{"0x555Ee11FBDDc0E49A9bAB358A8941AD95fFDB48f"},
},
},
},
CoinSpecificData: completeTransaction{
Tx: &rpcTransaction{
AccountNonce: "0xb26c",
GasPrice: "0x430e23400",
GasLimit: "0x5208",
To: "0x555Ee11FBDDc0E49A9bAB358A8941AD95fFDB48f",
Value: "0x1bc0159d530e6000",
Payload: "0x",
Hash: "0xcd647151552b5132b2aef7c9be00dc6f73afc5901dde157aab131335baaa853b",
BlockNumber: "0x41eee8",
From: "0x3E3a3D69dc66bA10737F531ed088954a9EC89d97",
TransactionIndex: "0xa",
},
Receipt: &rpcReceipt{
GasUsed: "0x5208",
Status: "0x0",
Logs: []*rpcLog{},
},
},
}
testTx1NoStatus = bchain.Tx{
Blocktime: 1534858022,
Time: 1534858022,
Txid: "0xcd647151552b5132b2aef7c9be00dc6f73afc5901dde157aab131335baaa853b",
Vin: []bchain.Vin{
{
Addresses: []string{"0x3E3a3D69dc66bA10737F531ed088954a9EC89d97"},
},
},
Vout: []bchain.Vout{
{
ValueSat: *big.NewInt(1999622000000000000),
ScriptPubKey: bchain.ScriptPubKey{
Addresses: []string{"0x555Ee11FBDDc0E49A9bAB358A8941AD95fFDB48f"},
},
},
},
CoinSpecificData: completeTransaction{
Tx: &rpcTransaction{
AccountNonce: "0xb26c",
GasPrice: "0x430e23400",
GasLimit: "0x5208",
To: "0x555Ee11FBDDc0E49A9bAB358A8941AD95fFDB48f",
Value: "0x1bc0159d530e6000",
Payload: "0x",
Hash: "0xcd647151552b5132b2aef7c9be00dc6f73afc5901dde157aab131335baaa853b",
BlockNumber: "0x41eee8",
From: "0x3E3a3D69dc66bA10737F531ed088954a9EC89d97",
TransactionIndex: "0xa",
},
Receipt: &rpcReceipt{
GasUsed: "0x5208",
Status: "",
Logs: []*rpcLog{},
},
},
}
}
func TestEthereumParser_PackTx(t *testing.T) {
@ -188,6 +266,24 @@ func TestEthereumParser_PackTx(t *testing.T) {
},
want: dbtestdata.EthTx2Packed,
},
{
name: "3",
args: args{
tx: &testTx1Failed,
height: 4321000,
blockTime: 1534858022,
},
want: dbtestdata.EthTx1FailedPacked,
},
{
name: "4",
args: args{
tx: &testTx1NoStatus,
height: 4321000,
blockTime: 1534858022,
},
want: dbtestdata.EthTx1NoStatusPacked,
},
}
p := NewEthereumParser(1)
for _, tt := range tests {
@ -229,6 +325,18 @@ func TestEthereumParser_UnpackTx(t *testing.T) {
want: &testTx2,
want1: 4321000,
},
{
name: "3",
args: args{hex: dbtestdata.EthTx1FailedPacked},
want: &testTx1Failed,
want1: 4321000,
},
{
name: "4",
args: args{hex: dbtestdata.EthTx1NoStatusPacked},
want: &testTx1NoStatus,
want1: 4321000,
},
}
p := NewEthereumParser(1)
for _, tt := range tests {
@ -265,3 +373,30 @@ func TestEthereumParser_UnpackTx(t *testing.T) {
})
}
}
func TestEthereumParser_GetEthereumTxData(t *testing.T) {
tests := []struct {
name string
tx *bchain.Tx
want string
}{
{
name: "Test empty data",
tx: &testTx1,
want: "0x",
},
{
name: "Test non empty data",
tx: &testTx2,
want: "0xa9059cbb000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f00000000000000000000000000000000000000000000021e19e0c9bab2400000",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := GetEthereumTxData(tt.tx)
if got.Data != tt.want {
t.Errorf("EthereumParser.GetEthereumTxData() = %v, want %v", got.Data, tt.want)
}
})
}
}

View File

@ -1,7 +1,6 @@
package eth
import (
"blockbook/bchain"
"context"
"encoding/json"
"fmt"
@ -12,11 +11,14 @@ import (
ethereum "github.com/ethereum/go-ethereum"
ethcommon "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/rpc"
"github.com/golang/glog"
"github.com/juju/errors"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/common"
)
// EthereumNet type specifies the type of ethereum network
@ -27,6 +29,8 @@ const (
MainNet EthereumNet = 1
// TestNet is Ropsten test network
TestNet EthereumNet = 3
// TestNetGoerli is Goerli test network
TestNetGoerli EthereumNet = 5
)
// Configuration represents json config file
@ -57,7 +61,6 @@ type EthereumRPC struct {
chanNewTx chan ethcommon.Hash
newTxSubscription *rpc.ClientSubscription
ChainConfig *Configuration
isETC bool
}
// NewEthereumRPC returns new EthRPC instance.
@ -72,11 +75,11 @@ func NewEthereumRPC(config json.RawMessage, pushHandler func(bchain.Notification
if c.BlockAddressesToKeep < 100 {
c.BlockAddressesToKeep = 100
}
rc, err := rpc.Dial(c.RPCURL)
rc, ec, err := openRPC(c.RPCURL)
if err != nil {
return nil, err
}
ec := ethclient.NewClient(rc)
s := &EthereumRPC{
BaseChain: &bchain.BaseChain{},
@ -89,9 +92,6 @@ func NewEthereumRPC(config json.RawMessage, pushHandler func(bchain.Notification
s.Parser = NewEthereumParser(c.BlockAddressesToKeep)
s.timeout = time.Duration(c.RPCTimeout) * time.Second
// detect ethereum classic
s.isETC = s.ChainConfig.CoinName == "Ethereum Classic"
// new blocks notifications handling
// the subscription is done in Initialize
s.chanNewBlock = make(chan *ethtypes.Header)
@ -133,6 +133,15 @@ func NewEthereumRPC(config json.RawMessage, pushHandler func(bchain.Notification
return s, nil
}
func openRPC(url string) (*rpc.Client, *ethclient.Client, error) {
rc, err := rpc.Dial(url)
if err != nil {
return nil, nil, err
}
ec := ethclient.NewClient(rc)
return rc, ec, nil
}
// Initialize initializes ethereum rpc interface
func (b *EthereumRPC) Initialize() error {
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
@ -153,6 +162,9 @@ func (b *EthereumRPC) Initialize() error {
b.Testnet = true
b.Network = "testnet"
break
case TestNetGoerli:
b.Testnet = true
b.Network = "goerli"
default:
return errors.Errorf("Unknown network id %v", id)
}
@ -171,7 +183,7 @@ func (b *EthereumRPC) CreateMempool(chain bchain.BlockChain) (bchain.Mempool, er
}
// InitializeMempool creates subscriptions to newHeads and newPendingTransactions
func (b *EthereumRPC) InitializeMempool(addrDescForOutpoint bchain.AddrDescForOutpointFunc, onNewTxAddr bchain.OnNewTxAddrFunc) error {
func (b *EthereumRPC) InitializeMempool(addrDescForOutpoint bchain.AddrDescForOutpointFunc, onNewTxAddr bchain.OnNewTxAddrFunc, onNewTx bchain.OnNewTxFunc) error {
if b.Mempool == nil {
return errors.New("Mempool not created")
}
@ -186,26 +198,33 @@ func (b *EthereumRPC) InitializeMempool(addrDescForOutpoint bchain.AddrDescForOu
}
b.Mempool.OnNewTxAddr = onNewTxAddr
b.Mempool.OnNewTx = onNewTx
if b.isETC {
glog.Info(b.ChainConfig.CoinName, " does not support subscription to newHeads")
} else {
// 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.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) {
@ -224,8 +243,6 @@ func (b *EthereumRPC) InitializeMempool(addrDescForOutpoint bchain.AddrDescForOu
return err
}
b.mempoolInitialized = true
return nil
}
@ -246,7 +263,7 @@ func (b *EthereumRPC) subscribe(f func() (*rpc.ClientSubscription, error)) error
}
glog.Error("Subscription error ", e)
timer := time.NewTimer(time.Second * 2)
// try in 1 second interval to resubscribe
// try in 2 second interval to resubscribe
for {
select {
case e = <-s.Err():
@ -260,7 +277,7 @@ func (b *EthereumRPC) subscribe(f func() (*rpc.ClientSubscription, error)) error
s = ns
continue Loop
}
glog.Error("Resubscribe error ", e)
glog.Error("Resubscribe error ", err)
timer.Reset(time.Second * 2)
}
}
@ -269,8 +286,7 @@ func (b *EthereumRPC) subscribe(f func() (*rpc.ClientSubscription, error)) error
return nil
}
// Shutdown cleans up rpc interface to ethereum
func (b *EthereumRPC) Shutdown(ctx context.Context) error {
func (b *EthereumRPC) closeRPC() {
if b.newBlockSubscription != nil {
b.newBlockSubscription.Unsubscribe()
}
@ -280,6 +296,23 @@ func (b *EthereumRPC) Shutdown(ctx context.Context) error {
if b.rpc != nil {
b.rpc.Close()
}
}
func (b *EthereumRPC) reconnectRPC() error {
glog.Info("Reconnecting RPC")
b.closeRPC()
rc, ec, err := openRPC(b.ChainConfig.RPCURL)
if err != nil {
return err
}
b.rpc = rc
b.client = ec
return b.subscribeEvents()
}
// Shutdown cleans up rpc interface to ethereum
func (b *EthereumRPC) Shutdown(ctx context.Context) error {
b.closeRPC()
close(b.chanNewBlock)
glog.Info("rpc: shutdown")
return nil
@ -297,29 +330,25 @@ func (b *EthereumRPC) GetSubversion() string {
// GetChainInfo returns information about the connected backend
func (b *EthereumRPC) GetChainInfo() (*bchain.ChainInfo, error) {
h, err := b.getBestHeader()
if err != nil {
return nil, err
}
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
defer cancel()
id, err := b.client.NetworkID(ctx)
if err != nil {
return nil, err
}
h, err := b.getBestHeader()
if err != nil {
return nil, err
}
var ver, protocol string
var ver string
if err := b.rpc.CallContext(ctx, &ver, "web3_clientVersion"); err != nil {
return nil, err
}
if err := b.rpc.CallContext(ctx, &protocol, "eth_protocolVersion"); err != nil {
return nil, err
}
rv := &bchain.ChainInfo{
Blocks: int(h.Number.Int64()),
Bestblockhash: h.Hash().Hex(),
Difficulty: h.Difficulty.String(),
Version: ver,
ProtocolVersion: protocol,
Blocks: int(h.Number.Int64()),
Bestblockhash: h.Hash().Hex(),
Difficulty: h.Difficulty.String(),
Version: ver,
}
idi := int(id.Uint64())
if idi == 1 {
@ -333,11 +362,14 @@ func (b *EthereumRPC) GetChainInfo() (*bchain.ChainInfo, error) {
func (b *EthereumRPC) getBestHeader() (*ethtypes.Header, error) {
b.bestHeaderLock.Lock()
defer b.bestHeaderLock.Unlock()
// ETC does not have newBlocks subscription, bestHeader must be updated very often (each 1 second)
if b.isETC {
if b.bestHeaderTime.Add(1 * time.Second).Before(time.Now()) {
b.bestHeader = nil
// if the best header was not updated for 15 minutes, there could be a subscription problem, reconnect RPC
// do it only in case of normal operation, not initial synchronization
if b.bestHeaderTime.Add(15*time.Minute).Before(time.Now()) && !b.bestHeaderTime.IsZero() && b.mempoolInitialized {
err := b.reconnectRPC()
if err != nil {
return nil, err
}
b.bestHeader = nil
}
if b.bestHeader == nil {
var err error
@ -345,6 +377,7 @@ func (b *EthereumRPC) getBestHeader() (*ethtypes.Header, error) {
defer cancel()
b.bestHeader, err = b.client.HeaderByNumber(ctx, nil)
if err != nil {
b.bestHeader = nil
return nil, err
}
b.bestHeaderTime = time.Now()
@ -504,7 +537,7 @@ func (b *EthereumRPC) GetBlock(hash string, height uint32) (*bchain.Block, error
btxs := make([]bchain.Tx, len(body.Transactions))
for i := range body.Transactions {
tx := &body.Transactions[i]
btx, err := b.Parser.ethTxToTx(tx, &rpcReceipt{Logs: logs[tx.Hash]}, bbh.Time, uint32(bbh.Confirmations))
btx, err := b.Parser.ethTxToTx(tx, &rpcReceipt{Logs: logs[tx.Hash]}, bbh.Time, uint32(bbh.Confirmations), true)
if err != nil {
return nil, errors.Annotatef(err, "hash %v, height %v, txid %v", hash, height, tx.Hash)
}
@ -540,8 +573,8 @@ func (b *EthereumRPC) GetBlockInfo(hash string) (*bchain.BlockInfo, error) {
}
return &bchain.BlockInfo{
BlockHeader: *bch,
Difficulty: json.Number(head.Difficulty),
Nonce: json.Number(head.Nonce),
Difficulty: common.JSONNumber(head.Difficulty),
Nonce: common.JSONNumber(head.Nonce),
Txids: txs.Transactions,
}, nil
}
@ -570,7 +603,7 @@ func (b *EthereumRPC) GetTransaction(txid string) (*bchain.Tx, error) {
var btx *bchain.Tx
if tx.BlockNumber == "" {
// mempool tx
btx, err = b.Parser.ethTxToTx(tx, nil, 0, 0)
btx, err = b.Parser.ethTxToTx(tx, nil, 0, 0, true)
if err != nil {
return nil, errors.Annotatef(err, "txid %v", txid)
}
@ -591,33 +624,9 @@ func (b *EthereumRPC) GetTransaction(txid string) (*bchain.Tx, error) {
return nil, errors.Annotatef(err, "txid %v", txid)
}
var receipt rpcReceipt
if b.isETC {
var rawReceipt json.RawMessage
var etcReceipt rpcEtcReceipt
err = b.rpc.CallContext(ctx, &rawReceipt, "eth_getTransactionReceipt", hash)
if err != nil {
return nil, errors.Annotatef(err, "txid %v", txid)
}
err = json.Unmarshal(rawReceipt, &etcReceipt)
if err == nil {
receipt.GasUsed = etcReceipt.GasUsed
receipt.Logs = etcReceipt.Logs
if etcReceipt.Status == 0 {
receipt.Status = "0x0"
} else {
receipt.Status = "0x1"
}
} else {
err = json.Unmarshal(rawReceipt, &receipt)
if err != nil {
return nil, errors.Annotatef(err, "unmarshal receipt for txid %v, %v", txid, string(rawReceipt))
}
}
} else {
err = b.rpc.CallContext(ctx, &receipt, "eth_getTransactionReceipt", hash)
if err != nil {
return nil, errors.Annotatef(err, "txid %v", txid)
}
err = b.rpc.CallContext(ctx, &receipt, "eth_getTransactionReceipt", hash)
if err != nil {
return nil, errors.Annotatef(err, "txid %v", txid)
}
n, err := ethNumber(tx.BlockNumber)
if err != nil {
@ -627,7 +636,7 @@ func (b *EthereumRPC) GetTransaction(txid string) (*bchain.Tx, error) {
if err != nil {
return nil, errors.Annotatef(err, "txid %v", txid)
}
btx, err = b.Parser.ethTxToTx(tx, &receipt, time, confirmations)
btx, err = b.Parser.ethTxToTx(tx, &receipt, time, confirmations, true)
if err != nil {
return nil, errors.Annotatef(err, "txid %v", txid)
}
@ -715,6 +724,18 @@ func (b *EthereumRPC) EthereumTypeEstimateGas(params map[string]interface{}) (ui
if ok && len(s) > 0 {
msg.Data = ethcommon.FromHex(s)
}
s, ok = getStringFromMap("value", params)
if ok && len(s) > 0 {
msg.Value, _ = hexutil.DecodeBig(s)
}
s, ok = getStringFromMap("gas", params)
if ok && len(s) > 0 {
msg.Gas, _ = hexutil.DecodeUint64(s)
}
s, ok = getStringFromMap("gasPrice", params)
if ok && len(s) > 0 {
msg.GasPrice, _ = hexutil.DecodeBig(s)
}
return b.client.EstimateGas(ctx, msg)
}

View File

@ -1,11 +1,11 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: tx.proto
// source: bchain/coins/eth/ethtx.proto
/*
Package eth is a generated protocol buffer package.
It is generated from these files:
tx.proto
bchain/coins/eth/ethtx.proto
It has these top-level messages:
ProtoCompleteTransaction
@ -228,33 +228,34 @@ func init() {
proto.RegisterType((*ProtoCompleteTransaction_ReceiptType_LogType)(nil), "eth.ProtoCompleteTransaction.ReceiptType.LogType")
}
func init() { proto.RegisterFile("tx.proto", fileDescriptor0) }
func init() { proto.RegisterFile("bchain/coins/eth/ethtx.proto", fileDescriptor0) }
var fileDescriptor0 = []byte{
// 393 bytes of a gzipped FileDescriptorProto
// 409 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x92, 0xdf, 0x8a, 0xd4, 0x30,
0x14, 0xc6, 0xe9, 0x9f, 0xf9, 0xb3, 0xa7, 0x55, 0x24, 0x88, 0x84, 0xe2, 0x45, 0x59, 0xbc, 0xa8,
0x5e, 0x14, 0x5c, 0x7d, 0x81, 0x75, 0xc4, 0x55, 0x18, 0xd6, 0x21, 0x46, 0xef, 0xb3, 0x69, 0xd8,
0x29, 0xb6, 0x4d, 0x69, 0x52, 0xe8, 0xbe, 0x91, 0x2f, 0xe4, 0xbb, 0x78, 0x29, 0x39, 0x4d, 0xd7,
0x11, 0x51, 0xbc, 0x3b, 0xbf, 0x6f, 0xce, 0x37, 0xf9, 0xbe, 0xa4, 0xb0, 0xb5, 0x53, 0xd9, 0x0f,
0xda, 0x6a, 0x12, 0x29, 0x7b, 0x3c, 0xff, 0xb6, 0x02, 0x7a, 0x70, 0xb8, 0xd3, 0x6d, 0xdf, 0x28,
0xab, 0xf8, 0x20, 0x3a, 0x23, 0xa4, 0xad, 0x75, 0x47, 0x72, 0x48, 0xde, 0x34, 0x5a, 0x7e, 0xbd,
0x1e, 0xdb, 0x1b, 0x35, 0xd0, 0x20, 0x0f, 0x8a, 0x07, 0xec, 0x54, 0x22, 0x4f, 0xe1, 0x0c, 0x91,
0xd7, 0xad, 0xa2, 0x61, 0x1e, 0x14, 0x31, 0xfb, 0x25, 0x90, 0xd7, 0x10, 0xf2, 0x89, 0x46, 0x79,
0x50, 0x24, 0x17, 0xcf, 0x4a, 0x65, 0x8f, 0xe5, 0xdf, 0x8e, 0x2a, 0xf9, 0xc4, 0xef, 0x7a, 0xc5,
0x42, 0x3e, 0x91, 0x1d, 0x6c, 0x98, 0x92, 0xaa, 0xee, 0x2d, 0x8d, 0xd1, 0xfa, 0xfc, 0xdf, 0x56,
0xbf, 0x8c, 0xfe, 0xc5, 0x99, 0xfd, 0x08, 0x60, 0x3d, 0xff, 0x27, 0x39, 0x87, 0xf4, 0x52, 0x4a,
0x3d, 0x76, 0xf6, 0x5a, 0x77, 0x52, 0x61, 0x8d, 0x98, 0xfd, 0xa6, 0x91, 0x0c, 0xb6, 0x57, 0xc2,
0x1c, 0x86, 0x5a, 0xce, 0x35, 0x52, 0x76, 0xcf, 0xfe, 0xb7, 0x7d, 0xdd, 0xd6, 0x16, 0xbb, 0xc4,
0xec, 0x9e, 0xc9, 0x63, 0x58, 0x7d, 0x11, 0xcd, 0xa8, 0x30, 0x69, 0xca, 0x66, 0x20, 0x14, 0x36,
0x07, 0x71, 0xd7, 0x68, 0x51, 0xd1, 0x15, 0xea, 0x0b, 0x12, 0x02, 0xf1, 0x7b, 0x61, 0x8e, 0x74,
0x8d, 0x32, 0xce, 0xe4, 0x21, 0x84, 0x5c, 0xd3, 0x0d, 0x2a, 0x21, 0xd7, 0x6e, 0xe7, 0xdd, 0xa0,
0x5b, 0xba, 0x9d, 0x77, 0xdc, 0x4c, 0x5e, 0xc0, 0xa3, 0x93, 0xca, 0x1f, 0xba, 0x4a, 0x4d, 0xf4,
0x0c, 0x9f, 0xe3, 0x0f, 0x3d, 0xfb, 0x1e, 0x40, 0x72, 0x72, 0x27, 0x2e, 0xcd, 0x95, 0x30, 0x9f,
0x8d, 0xaa, 0xb0, 0x7a, 0xca, 0x16, 0x24, 0x4f, 0x60, 0xfd, 0xc9, 0x0a, 0x3b, 0x1a, 0xdf, 0xd9,
0x13, 0xd9, 0x41, 0xb4, 0xd7, 0xb7, 0x34, 0xca, 0xa3, 0x22, 0xb9, 0x78, 0xf9, 0xdf, 0xb7, 0x5f,
0xee, 0xf5, 0x2d, 0xbe, 0x82, 0x73, 0x67, 0x1f, 0x61, 0xe3, 0xd9, 0x25, 0xb8, 0xac, 0xaa, 0x41,
0x19, 0xb3, 0x24, 0xf0, 0xe8, 0xba, 0xbe, 0x15, 0x56, 0xf8, 0xf3, 0x71, 0x76, 0xa9, 0xb8, 0xee,
0x6b, 0x69, 0x30, 0x40, 0xca, 0x3c, 0xdd, 0xac, 0xf1, 0xb3, 0x7d, 0xf5, 0x33, 0x00, 0x00, 0xff,
0xff, 0xde, 0xd5, 0x28, 0xa3, 0xc2, 0x02, 0x00, 0x00,
0x18, 0xc5, 0xe9, 0x9f, 0x99, 0xd9, 0xfd, 0xa6, 0x8a, 0x04, 0x91, 0x30, 0xec, 0x45, 0x59, 0xbc,
0x18, 0xbd, 0xe8, 0xe2, 0xea, 0x0b, 0xac, 0x23, 0xae, 0xc2, 0xb0, 0x0e, 0x31, 0x7a, 0x9f, 0x49,
0xc3, 0x36, 0x38, 0x6d, 0x4a, 0x93, 0x42, 0xf7, 0x8d, 0x7c, 0x21, 0xdf, 0xc5, 0x4b, 0xc9, 0xd7,
0x74, 0x1d, 0x11, 0x65, 0x2f, 0x0a, 0xf9, 0x9d, 0x7e, 0xa7, 0x39, 0x27, 0x29, 0x9c, 0xed, 0x65,
0x25, 0x74, 0x73, 0x21, 0x8d, 0x6e, 0xec, 0x85, 0x72, 0x95, 0x7f, 0xdc, 0x50, 0xb4, 0x9d, 0x71,
0x86, 0x24, 0xca, 0x55, 0xe7, 0xdf, 0x67, 0x40, 0x77, 0x1e, 0x37, 0xa6, 0x6e, 0x0f, 0xca, 0x29,
0xde, 0x89, 0xc6, 0x0a, 0xe9, 0xb4, 0x69, 0x48, 0x0e, 0xcb, 0xb7, 0x07, 0x23, 0xbf, 0xdd, 0xf4,
0xf5, 0x5e, 0x75, 0x34, 0xca, 0xa3, 0xf5, 0x23, 0x76, 0x2c, 0x91, 0x33, 0x38, 0x45, 0xe4, 0xba,
0x56, 0x34, 0xce, 0xa3, 0x75, 0xca, 0x7e, 0x0b, 0xe4, 0x0d, 0xc4, 0x7c, 0xa0, 0x49, 0x1e, 0xad,
0x97, 0x97, 0xcf, 0x0b, 0xe5, 0xaa, 0xe2, 0x5f, 0x5b, 0x15, 0x7c, 0xe0, 0x77, 0xad, 0x62, 0x31,
0x1f, 0xc8, 0x06, 0x16, 0x4c, 0x49, 0xa5, 0x5b, 0x47, 0x53, 0xb4, 0xbe, 0xf8, 0xbf, 0x35, 0x0c,
0xa3, 0x7f, 0x72, 0xae, 0x7e, 0x46, 0x30, 0x1f, 0xbf, 0x49, 0xce, 0x21, 0xbb, 0x92, 0xd2, 0xf4,
0x8d, 0xbb, 0x31, 0x8d, 0x54, 0x58, 0x23, 0x65, 0x7f, 0x68, 0x64, 0x05, 0x27, 0xd7, 0xc2, 0xee,
0x3a, 0x2d, 0xc7, 0x1a, 0x19, 0xbb, 0xe7, 0xf0, 0x6e, 0xab, 0x6b, 0xed, 0xb0, 0x4b, 0xca, 0xee,
0x99, 0x3c, 0x85, 0xd9, 0x57, 0x71, 0xe8, 0x15, 0x26, 0xcd, 0xd8, 0x08, 0x84, 0xc2, 0x62, 0x27,
0xee, 0x0e, 0x46, 0x94, 0x74, 0x86, 0xfa, 0x84, 0x84, 0x40, 0xfa, 0x41, 0xd8, 0x8a, 0xce, 0x51,
0xc6, 0x35, 0x79, 0x0c, 0x31, 0x37, 0x74, 0x81, 0x4a, 0xcc, 0x8d, 0x9f, 0x79, 0xdf, 0x99, 0x9a,
0x9e, 0x8c, 0x33, 0x7e, 0x4d, 0x5e, 0xc2, 0x93, 0xa3, 0xca, 0x1f, 0x9b, 0x52, 0x0d, 0xf4, 0x14,
0xaf, 0xe3, 0x2f, 0x7d, 0xf5, 0x23, 0x82, 0xe5, 0xd1, 0x99, 0xf8, 0x34, 0xd7, 0xc2, 0x7e, 0xb1,
0xaa, 0xc4, 0xea, 0x19, 0x9b, 0x90, 0x3c, 0x83, 0xf9, 0x67, 0x27, 0x5c, 0x6f, 0x43, 0xe7, 0x40,
0x64, 0x03, 0xc9, 0xd6, 0xdc, 0xd2, 0x24, 0x4f, 0xd6, 0xcb, 0xcb, 0x57, 0x0f, 0x3e, 0xfd, 0x62,
0x6b, 0x6e, 0xf1, 0x16, 0xbc, 0x7b, 0xf5, 0x09, 0x16, 0x81, 0x7d, 0x82, 0xab, 0xb2, 0xec, 0x94,
0xb5, 0x53, 0x82, 0x80, 0xbe, 0xeb, 0x3b, 0xe1, 0x44, 0xd8, 0x1f, 0xd7, 0x3e, 0x15, 0x37, 0xad,
0x96, 0x16, 0x03, 0x64, 0x2c, 0xd0, 0x7e, 0x8e, 0xbf, 0xed, 0xeb, 0x5f, 0x01, 0x00, 0x00, 0xff,
0xff, 0xc2, 0x69, 0x8d, 0xdf, 0xd6, 0x02, 0x00, 0x00,
}

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

@ -1,8 +1,6 @@
package xzc
package firo
import (
"blockbook/bchain"
"blockbook/bchain/coins/btc"
"bytes"
"encoding/binary"
"encoding/json"
@ -11,11 +9,18 @@ import (
"github.com/martinboehm/btcd/chaincfg/chainhash"
"github.com/martinboehm/btcd/wire"
"github.com/martinboehm/btcutil/chaincfg"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
)
const (
OpZeroCoinMint = 0xc1
OpZeroCoinSpend = 0xc2
OpZeroCoinMint = 0xc1
OpZeroCoinSpend = 0xc2
OpSigmaMint = 0xc3
OpSigmaSpend = 0xc4
OpLelantusMint = 0xc5
OpLelantusJMint = 0xc6
OpLelantusJoinSplit = 0xc7
MainnetMagic wire.BitcoinNet = 0xe3d9fef1
TestnetMagic wire.BitcoinNet = 0xcffcbeea
@ -26,6 +31,8 @@ const (
MTPL = 64
SpendTxID = "0000000000000000000000000000000000000000000000000000000000000000"
TransactionQuorumCommitmentType = 6
)
var (
@ -56,21 +63,21 @@ func init() {
RegtestParams.Net = RegtestMagic
}
// ZcoinParser handle
type ZcoinParser struct {
// FiroParser handle
type FiroParser struct {
*btc.BitcoinParser
}
// NewZcoinParser returns new ZcoinParser instance
func NewZcoinParser(params *chaincfg.Params, c *btc.Configuration) *ZcoinParser {
return &ZcoinParser{
// NewFiroParser returns new FiroParser instance
func NewFiroParser(params *chaincfg.Params, c *btc.Configuration) *FiroParser {
return &FiroParser{
BitcoinParser: btc.NewBitcoinParser(params, c),
}
}
// GetChainParams contains network parameters for the main Zcoin network,
// the regression test Zcoin network, the test Zcoin network and
// the simulation test Zcoin network, in this order
// GetChainParams contains network parameters for the main Firo network,
// the regression test Firo network, the test Firo network and
// the simulation test Firo network, in this order
func GetChainParams(chain string) *chaincfg.Params {
if !chaincfg.IsRegistered(&MainNetParams) {
err := chaincfg.Register(&MainNetParams)
@ -95,30 +102,52 @@ func GetChainParams(chain string) *chaincfg.Params {
}
// GetAddressesFromAddrDesc returns addresses for given address descriptor with flag if the addresses are searchable
func (p *ZcoinParser) GetAddressesFromAddrDesc(addrDesc bchain.AddressDescriptor) ([]string, bool, error) {
if len(addrDesc) > 0 && addrDesc[0] == OpZeroCoinMint {
return []string{"Zeromint"}, false, nil
}
func (p *FiroParser) GetAddressesFromAddrDesc(addrDesc bchain.AddressDescriptor) ([]string, bool, error) {
if len(addrDesc) > 0 && addrDesc[0] == OpZeroCoinSpend {
return []string{"Zerospend"}, false, nil
if len(addrDesc) > 0 {
switch addrDesc[0] {
case OpZeroCoinMint:
return []string{"Zeromint"}, false, nil
case OpZeroCoinSpend:
return []string{"Zerospend"}, false, nil
case OpSigmaMint:
return []string{"Sigmamint"}, false, nil
case OpSigmaSpend:
return []string{"Sigmaspend"}, false, nil
case OpLelantusMint:
return []string{"LelantusMint"}, false, nil
case OpLelantusJMint:
return []string{"LelantusJMint"}, false, nil
case OpLelantusJoinSplit:
return []string{"LelantusJoinSplit"}, false, nil
}
}
return p.OutputScriptToAddressesFunc(addrDesc)
}
// PackTx packs transaction to byte array using protobuf
func (p *ZcoinParser) PackTx(tx *bchain.Tx, height uint32, blockTime int64) ([]byte, error) {
func (p *FiroParser) PackTx(tx *bchain.Tx, height uint32, blockTime int64) ([]byte, error) {
return p.BaseParser.PackTx(tx, height, blockTime)
}
// UnpackTx unpacks transaction from protobuf byte array
func (p *ZcoinParser) UnpackTx(buf []byte) (*bchain.Tx, uint32, error) {
func (p *FiroParser) UnpackTx(buf []byte) (*bchain.Tx, uint32, error) {
return p.BaseParser.UnpackTx(buf)
}
// TxFromFiroMsgTx converts bitcoin wire Tx to bchain.Tx
func (p *FiroParser) TxFromFiroMsgTx(t *FiroMsgTx, parseAddresses bool) bchain.Tx {
btx := p.TxFromMsgTx(&t.MsgTx, parseAddresses)
// NOTE: wire.MsgTx.TxHash() doesn't include extra
btx.Txid = t.TxHash().String()
return btx
}
// ParseBlock parses raw block to our Block struct
func (p *ZcoinParser) ParseBlock(b []byte) (*bchain.Block, error) {
func (p *FiroParser) ParseBlock(b []byte) (*bchain.Block, error) {
reader := bytes.NewReader(b)
// parse standard block header first
@ -173,16 +202,37 @@ func (p *ZcoinParser) ParseBlock(b []byte) (*bchain.Block, error) {
txs := make([]bchain.Tx, ntx)
for i := uint64(0); i < ntx; i++ {
tx := wire.MsgTx{}
tx := FiroMsgTx{}
err := tx.BtcDecode(reader, 0, wire.WitnessEncoding)
if err != nil {
// read version and seek back
var version uint32 = 0
if err = binary.Read(reader, binary.LittleEndian, &version); err != nil {
return nil, err
}
btx := p.TxFromMsgTx(&tx, false)
if _, err = reader.Seek(-4, io.SeekCurrent); err != nil {
return nil, err
}
p.parseZcoinTx(&btx)
txVersion := version & 0xffff
txType := (version >> 16) & 0xffff
enc := wire.WitnessEncoding
// transaction quorum commitment could not be parsed with witness flag
if txVersion == 3 && txType == TransactionQuorumCommitmentType {
enc = wire.BaseEncoding
}
if err = tx.FiroDecode(reader, 0, enc); err != nil {
return nil, err
}
btx := p.TxFromFiroMsgTx(&tx, false)
if err = p.parseFiroTx(&btx); err != nil {
return nil, err
}
txs[i] = btx
}
@ -197,7 +247,7 @@ func (p *ZcoinParser) ParseBlock(b []byte) (*bchain.Block, error) {
}
// ParseTxFromJson parses JSON message containing transaction and returns Tx struct
func (p *ZcoinParser) ParseTxFromJson(msg json.RawMessage) (*bchain.Tx, error) {
func (p *FiroParser) ParseTxFromJson(msg json.RawMessage) (*bchain.Tx, error) {
var tx bchain.Tx
err := json.Unmarshal(msg, &tx)
if err != nil {
@ -214,12 +264,12 @@ func (p *ZcoinParser) ParseTxFromJson(msg json.RawMessage) (*bchain.Tx, error) {
vout.JsonValue = ""
}
p.parseZcoinTx(&tx)
p.parseFiroTx(&tx)
return &tx, nil
}
func (p *ZcoinParser) parseZcoinTx(tx *bchain.Tx) error {
func (p *FiroParser) parseFiroTx(tx *bchain.Tx) error {
for i := range tx.Vin {
vin := &tx.Vin[i]

View File

@ -1,6 +1,6 @@
// +build unittest
package xzc
package firo
import (
"bytes"
@ -13,17 +13,18 @@ import (
"strings"
"testing"
"blockbook/bchain"
"blockbook/bchain/coins/btc"
"github.com/martinboehm/btcd/wire"
"github.com/martinboehm/btcutil/chaincfg"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
)
var (
testTx1, testTx2, testTx3, testTx4 bchain.Tx
testTxPacked1, testTxPacked2, testTxPacked3, testTxPacked4 string
rawBlock1, rawBlock2 string
jsonTx json.RawMessage
testTx1, testTx2, testTx3, testTx4, testTx5, testTx6 bchain.Tx
rawTestTx1, rawTestTx2, rawTestTx3, rawTestTx4, rawTestTx5, rawTestTx6 string
testTxPacked1, testTxPacked2, testTxPacked3, testTxPacked4, testTxPacked5, testTxPacked6 string
rawBlock1, rawBlock2, rawBlock3 string
jsonTx json.RawMessage
)
func readHexs(path string) []string {
@ -40,12 +41,15 @@ func init() {
rawBlocks := readHexs("./testdata/rawblock.hex")
rawBlock1 = rawBlocks[0]
rawBlock2 = rawBlocks[1]
rawBlock3 = rawBlocks[2]
hextxs := readHexs("./testdata/txs.hex")
rawTestTx1 := hextxs[0]
rawTestTx2 := hextxs[1]
rawTestTx3 := hextxs[2]
rawTestTx4 := hextxs[3]
rawTestTx1 = hextxs[0]
rawTestTx2 = hextxs[1]
rawTestTx3 = hextxs[2]
rawTestTx4 = hextxs[3]
rawTestTx5 = hextxs[4]
rawTestTx6 = hextxs[5]
rawSpendHex := readHexs("./testdata/rawspend.hex")[0]
@ -60,13 +64,15 @@ func init() {
testTxPacked2 = testTxPackeds[1]
testTxPacked3 = testTxPackeds[2]
testTxPacked4 = testTxPackeds[3]
testTxPacked5 = testTxPackeds[4]
testTxPacked6 = testTxPackeds[5]
testTx1 = bchain.Tx{
Hex: rawTestTx1,
Blocktime: 1533980594,
Time: 1533980594,
Txid: "9d9e759dd970d86df9e105a7d4f671543bc16a03b6c5d2b48895f2a00aa7dd23",
LockTime: 0,
LockTime: 99688,
Vin: []bchain.Vin{
{
ScriptSig: bchain.ScriptSig{
@ -79,14 +85,14 @@ func init() {
},
Vout: []bchain.Vout{
{
ValueSat: *big.NewInt(18188266638),
ValueSat: *big.NewInt(100000000),
N: 0,
ScriptPubKey: bchain.ScriptPubKey{
Hex: "c10280004c80f767f3ee79953c67a7ed386dcccf1243619eb4bbbe414a3982dd94a83c1b69ac52d6ab3b653a3e05c4e4516c8dfe1e58ada40461bc5835a4a0d0387a51c29ac11b72ae25bbcdef745f50ad08f08b3e9bc2c31a35444398a490e65ac090e9f341f1abdebe47e57e8237ac25d098e951b4164a35caea29f30acb50b12e4425df28",
},
},
{
ValueSat: *big.NewInt(18188266638),
ValueSat: *big.NewInt(871824000),
N: 1,
ScriptPubKey: bchain.ScriptPubKey{
Hex: "76a914c963f917c7f23cb4243e079db33107571b87690588ac",
@ -106,9 +112,7 @@ func init() {
LockTime: 0,
Vin: []bchain.Vin{
{
ScriptSig: bchain.ScriptSig{
Hex: rawSpendHex,
},
Coinbase: rawSpendHex,
Txid: "0000000000000000000000000000000000000000000000000000000000000000",
Vout: 4294967295,
Sequence: 2,
@ -227,7 +231,7 @@ func init() {
ScriptPubKey: bchain.ScriptPubKey{
Hex: "76a914ff71b0c9c2a90c6164a50a2fb523eb54a8a6b55088ac",
Addresses: []string{
"a1HwTdCmQV3NspP2QqCGpehoFpi8NY4Zg3",
"aQ18FBVFtnueucZKeVg4srhmzbpAeb1KoN",
},
},
},
@ -251,6 +255,103 @@ func init() {
},
},
},
// TODO: test segwit
},
}
testTx5 = bchain.Tx{
Hex: rawTestTx5,
Blocktime: 1591752749,
Time: 1591752749,
Txid: "8d1f32f35c32d2c127a7400dc1ec52049fbf0b8bcdf284cfaa3da59b6169a22d",
LockTime: 0,
Vin: []bchain.Vin{},
Vout: []bchain.Vout{},
}
testTx6 = bchain.Tx{
Hex: rawTestTx6,
Blocktime: 1591762049,
Time: 1591762049,
Txid: "e5767d3606230a65f150837a6f28b4f0e4c2702a683045df3883d57702739c61",
LockTime: 0,
Vin: []bchain.Vin{
{
Coinbase: "02b4140101",
Sequence: 4294967295,
},
},
Vout: []bchain.Vout{
{
ValueSat: *big.NewInt(1400000000),
N: 0,
ScriptPubKey: bchain.ScriptPubKey{
Hex: "2103fb09a216761d5e7f248294970c2370f7f84ce1ad564b8e7096b1e19116af1d52ac",
Addresses: []string{
"TAn9Ghkp31myXRgejCj11wWVHT14Lsj349",
},
},
},
{
ValueSat: *big.NewInt(50000000),
N: 1,
ScriptPubKey: bchain.ScriptPubKey{
Hex: "76a914296134d2415bf1f2b518b3f673816d7e603b160088ac",
Addresses: []string{
"TDk19wPKYq91i18qmY6U9FeTdTxwPeSveo",
},
},
},
{
ValueSat: *big.NewInt(50000000),
N: 2,
ScriptPubKey: bchain.ScriptPubKey{
Hex: "76a914e1e1dc06a889c1b6d3eb00eef7a96f6a7cfb884888ac",
Addresses: []string{
"TWZZcDGkNixTAMtRBqzZkkMHbq1G6vUTk5",
},
},
},
{
ValueSat: *big.NewInt(50000000),
N: 3,
ScriptPubKey: bchain.ScriptPubKey{
Hex: "76a914ab03ecfddee6330497be894d16c29ae341c123aa88ac",
Addresses: []string{
"TRZTFdNCKCKbLMQV8cZDkQN9Vwuuq4gDzT",
},
},
},
{
ValueSat: *big.NewInt(150000000),
N: 4,
ScriptPubKey: bchain.ScriptPubKey{
Hex: "76a9144281a58a1d5b2d3285e00cb45a8492debbdad4c588ac",
Addresses: []string{
"TG2ruj59E5b1u9G3F7HQVs6pCcVDBxrQve",
},
},
},
{
ValueSat: *big.NewInt(50000000),
N: 5,
ScriptPubKey: bchain.ScriptPubKey{
Hex: "76a9141fd264c0bb53bd9fef18e2248ddf1383d6e811ae88ac",
Addresses: []string{
"TCsTzQZKVn4fao8jDmB9zQBk9YQNEZ3XfS",
},
},
},
{
ValueSat: *big.NewInt(750000000),
N: 6,
ScriptPubKey: bchain.ScriptPubKey{
Hex: "76a91471a3892d164ffa3829078bf9ad5f114a3908ce5588ac",
Addresses: []string{
"TLL5GQULX4uBfz7yXL6VcZyvzdKVv1RGxm",
},
},
},
},
}
}
@ -264,32 +365,32 @@ func TestMain(m *testing.M) {
func TestGetAddrDesc(t *testing.T) {
type args struct {
tx bchain.Tx
parser *ZcoinParser
parser *FiroParser
}
tests := []struct {
name string
args args
}{
{
name: "xzc-1",
name: "firo-1",
args: args{
tx: testTx1,
parser: NewZcoinParser(GetChainParams("main"), &btc.Configuration{}),
parser: NewFiroParser(GetChainParams("main"), &btc.Configuration{}),
},
},
// FIXME: work around handle zerocoin spend as coinbase
// {
// name: "xzc-2",
// name: "firo-2",
// args: args{
// tx: testTx2,
// parser: NewZcoinParser(GetChainParams("main"), &btc.Configuration{}),
// parser: NewFiroParser(GetChainParams("main"), &btc.Configuration{}),
// },
// },
{
name: "xzc-3",
name: "firo-3",
args: args{
tx: testTx3,
parser: NewZcoinParser(GetChainParams("main"), &btc.Configuration{}),
parser: NewFiroParser(GetChainParams("main"), &btc.Configuration{}),
},
},
}
@ -340,8 +441,14 @@ func TestGetAddrDescFromVoutForMint(t *testing.T) {
want: "c10280004c80f767f3ee79953c67a7ed386dcccf1243619eb4bbbe414a3982dd94a83c1b69ac52d6ab3b653a3e05c4e4516c8dfe1e58ada40461bc5835a4a0d0387a51c29ac11b72ae25bbcdef745f50ad08f08b3e9bc2c31a35444398a490e65ac090e9f341f1abdebe47e57e8237ac25d098e951b4164a35caea29f30acb50b12e4425df28",
wantErr: false,
},
{
name: "OP_SIGMAMINT",
args: args{vout: bchain.Vout{ScriptPubKey: bchain.ScriptPubKey{Hex: "c317dcee5b8b2c5b79728abc3a39abc54682b31a4e18f5abb6f34dc8089544763b0000"}}},
want: "c317dcee5b8b2c5b79728abc3a39abc54682b31a4e18f5abb6f34dc8089544763b0000",
wantErr: false,
},
}
parser := NewZcoinParser(GetChainParams("main"), &btc.Configuration{})
parser := NewFiroParser(GetChainParams("main"), &btc.Configuration{})
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@ -383,8 +490,15 @@ func TestGetAddressesFromAddrDescForMint(t *testing.T) {
want2: false,
wantErr: false,
},
{
name: "OP_SIGMAMINT size hex",
args: args{script: "c317dcee5b8b2c5b79728abc3a39abc54682b31a4e18f5abb6f34dc8089544763b0000"},
want: []string{"Sigmamint"},
want2: false,
wantErr: false,
},
}
parser := NewZcoinParser(GetChainParams("main"), &btc.Configuration{})
parser := NewFiroParser(GetChainParams("main"), &btc.Configuration{})
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@ -409,7 +523,7 @@ func TestPackTx(t *testing.T) {
tx bchain.Tx
height uint32
blockTime int64
parser *ZcoinParser
parser *FiroParser
}
tests := []struct {
name string
@ -418,50 +532,72 @@ func TestPackTx(t *testing.T) {
wantErr bool
}{
{
name: "xzc-1",
name: "firo-1",
args: args{
tx: testTx1,
height: 100002,
blockTime: 1533980594,
parser: NewZcoinParser(GetChainParams("main"), &btc.Configuration{}),
parser: NewFiroParser(GetChainParams("main"), &btc.Configuration{}),
},
want: testTxPacked1,
wantErr: false,
},
// FIXME: work around handle zerocoin spend as coinbase
// {
// name: "xzc-2",
// name: "firo-2",
// args: args{
// tx: testTx2,
// height: 11002,
// blockTime: 1481277009,
// parser: NewZcoinParser(GetChainParams("main"), &btc.Configuration{}),
// parser: NewFiroParser(GetChainParams("main"), &btc.Configuration{}),
// },
// want: testTxPacked2,
// wantErr: true,
// },
{
name: "xzc-3",
name: "firo-3",
args: args{
tx: testTx3,
height: 126202,
blockTime: 1547091829,
parser: NewZcoinParser(GetChainParams("main"), &btc.Configuration{}),
parser: NewFiroParser(GetChainParams("main"), &btc.Configuration{}),
},
want: testTxPacked3,
wantErr: false,
},
{
name: "xzc-coinbase",
name: "firo-coinbase",
args: args{
tx: testTx4,
height: 100001,
blockTime: 1533977563,
parser: NewZcoinParser(GetChainParams("main"), &btc.Configuration{}),
parser: NewFiroParser(GetChainParams("main"), &btc.Configuration{}),
},
want: testTxPacked4,
wantErr: false,
},
{
name: "firo-quorum-commitment-tx",
args: args{
tx: testTx5,
height: 5268,
blockTime: 1591752749,
parser: NewFiroParser(GetChainParams("test"), &btc.Configuration{}),
},
want: testTxPacked5,
wantErr: false,
},
{
name: "firo-special-coinbase-tx",
args: args{
tx: testTx6,
height: 5300,
blockTime: 1591762049,
parser: NewFiroParser(GetChainParams("test"), &btc.Configuration{}),
},
want: testTxPacked6,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@ -483,7 +619,7 @@ func TestPackTx(t *testing.T) {
func TestUnpackTx(t *testing.T) {
type args struct {
packedTx string
parser *ZcoinParser
parser *FiroParser
}
tests := []struct {
name string
@ -493,10 +629,10 @@ func TestUnpackTx(t *testing.T) {
wantErr bool
}{
{
name: "xzc-1",
name: "firo-1",
args: args{
packedTx: testTxPacked1,
parser: NewZcoinParser(GetChainParams("main"), &btc.Configuration{}),
parser: NewFiroParser(GetChainParams("main"), &btc.Configuration{}),
},
want: &testTx1,
want1: 100002,
@ -504,35 +640,55 @@ func TestUnpackTx(t *testing.T) {
},
// FIXME: work around handle zerocoin spend as coinbase
// {
// name: "xzc-2",
// name: "firo-2",
// args: args{
// packedTx: testTxPacked2,
// parser: NewZcoinParser(GetChainParams("main"), &btc.Configuration{}),
// parser: NewFiroParser(GetChainParams("main"), &btc.Configuration{}),
// },
// want: &testTx2,
// want1: 11002,
// wantErr: true,
// },
{
name: "xzc-3",
name: "firo-3",
args: args{
packedTx: testTxPacked3,
parser: NewZcoinParser(GetChainParams("main"), &btc.Configuration{}),
parser: NewFiroParser(GetChainParams("main"), &btc.Configuration{}),
},
want: &testTx3,
want1: 126202,
wantErr: false,
},
{
name: "xzc-coinbase",
name: "firo-coinbase",
args: args{
packedTx: testTxPacked4,
parser: NewZcoinParser(GetChainParams("main"), &btc.Configuration{}),
parser: NewFiroParser(GetChainParams("main"), &btc.Configuration{}),
},
want: &testTx4,
want1: 100001,
wantErr: false,
},
{
name: "firo-special-tx",
args: args{
packedTx: testTxPacked5,
parser: NewFiroParser(GetChainParams("test"), &btc.Configuration{}),
},
want: &testTx5,
want1: 5268,
wantErr: false,
},
{
name: "firo-special-coinbase-tx",
args: args{
packedTx: testTxPacked6,
parser: NewFiroParser(GetChainParams("test"), &btc.Configuration{}),
},
want: &testTx6,
want1: 5300,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@ -558,7 +714,7 @@ func TestUnpackTx(t *testing.T) {
func TestParseBlock(t *testing.T) {
type args struct {
rawBlock string
parser *ZcoinParser
parser *FiroParser
}
tests := []struct {
name string
@ -571,7 +727,7 @@ func TestParseBlock(t *testing.T) {
name: "normal-block",
args: args{
rawBlock: rawBlock1,
parser: NewZcoinParser(GetChainParams("main"), &btc.Configuration{}),
parser: NewFiroParser(GetChainParams("main"), &btc.Configuration{}),
},
want: &bchain.Block{
BlockHeader: bchain.BlockHeader{
@ -586,7 +742,7 @@ func TestParseBlock(t *testing.T) {
name: "spend-block",
args: args{
rawBlock: rawBlock2,
parser: NewZcoinParser(GetChainParams("main"), &btc.Configuration{}),
parser: NewFiroParser(GetChainParams("main"), &btc.Configuration{}),
},
want: &bchain.Block{
BlockHeader: bchain.BlockHeader{
@ -597,6 +753,21 @@ func TestParseBlock(t *testing.T) {
wantTxs: 4,
wantErr: false,
},
{
name: "special-tx-block",
args: args{
rawBlock: rawBlock3,
parser: NewFiroParser(GetChainParams("test"), &btc.Configuration{}),
},
want: &bchain.Block{
BlockHeader: bchain.BlockHeader{
Size: 200062,
Time: 1591752749,
},
},
wantTxs: 3,
wantErr: false,
},
}
for _, tt := range tests {
@ -620,3 +791,191 @@ func TestParseBlock(t *testing.T) {
})
}
}
func TestDecodeTransaction(t *testing.T) {
type args struct {
enc wire.MessageEncoding
rawTransaction string
parser *FiroParser
privacyType byte // 0 as non privacy
}
tests := []struct {
name string
args args
want bchain.Tx
wantErr bool
}{
{
name: "normal-transaction",
args: args{
enc: wire.WitnessEncoding,
rawTransaction: rawTestTx1,
parser: NewFiroParser(GetChainParams("main"), &btc.Configuration{}),
},
want: testTx1,
},
{
name: "coinbase-firospend",
args: args{
enc: wire.WitnessEncoding,
rawTransaction: rawTestTx2,
parser: NewFiroParser(GetChainParams("main"), &btc.Configuration{}),
privacyType: OpSigmaSpend,
},
want: testTx2,
},
{
name: "normal-transaction-2",
args: args{
enc: wire.WitnessEncoding,
rawTransaction: rawTestTx3,
parser: NewFiroParser(GetChainParams("main"), &btc.Configuration{}),
},
want: testTx3,
},
{
name: "coinbase-transaction",
args: args{
enc: wire.WitnessEncoding,
rawTransaction: rawTestTx4,
parser: NewFiroParser(GetChainParams("main"), &btc.Configuration{}),
},
want: testTx4,
},
{
name: "quorum-commitment-transaction",
args: args{
enc: wire.BaseEncoding,
rawTransaction: rawTestTx5,
parser: NewFiroParser(GetChainParams("main"), &btc.Configuration{}),
},
want: testTx5,
},
{
name: "quorum-commitment-transaction-witness",
args: args{
enc: wire.WitnessEncoding,
rawTransaction: rawTestTx5,
parser: NewFiroParser(GetChainParams("main"), &btc.Configuration{}),
},
wantErr: true,
},
{
name: "special-coinbase",
args: args{
enc: wire.WitnessEncoding,
rawTransaction: rawTestTx6,
parser: NewFiroParser(GetChainParams("test"), &btc.Configuration{}),
},
want: testTx6,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
b, _ := hex.DecodeString(tt.args.rawTransaction)
r := bytes.NewReader(b)
msg := FiroMsgTx{}
err := msg.FiroDecode(r, 0, tt.args.enc)
if tt.wantErr {
if err == nil {
t.Errorf("Want error")
}
return
}
if err != nil {
t.Fatal(err)
}
got := tt.args.parser.TxFromFiroMsgTx(&msg, true)
if pErr := tt.args.parser.parseFiroTx(&got); pErr != nil {
t.Fatal(pErr)
}
if r.Len() != 0 {
t.Errorf("Expected EOF but there are remaining %d bytes to read", r.Len())
}
if len(got.Vin) != len(tt.want.Vin) {
t.Errorf("Check vin size, got %v, want %v", len(got.Vin), len(tt.want.Vin))
}
for i := 0; i != len(got.Vin); i++ {
if !reflect.DeepEqual(got.Vin[i].Addresses, tt.want.Vin[i].Addresses) {
t.Errorf("Check Addresses at input %d, got %v, want %v",
i, got.Vin[i].Addresses, tt.want.Vin[i].Addresses)
}
if !reflect.DeepEqual(got.Vin[i].Coinbase, tt.want.Vin[i].Coinbase) {
t.Errorf("Check Coinbase at input %d, got %v, want %v",
i, got.Vin[i].Coinbase, tt.want.Vin[i].Coinbase)
}
if !reflect.DeepEqual(got.Vin[i].ScriptSig, tt.want.Vin[i].ScriptSig) {
t.Errorf("Check ScriptSig at input %d, got %v, want %v",
i, got.Vin[i].ScriptSig, tt.want.Vin[i].ScriptSig)
}
if !reflect.DeepEqual(got.Vin[i].Sequence, tt.want.Vin[i].Sequence) {
t.Errorf("Check Sequence at input %d, got %v, want %v",
i, got.Vin[i].Sequence, tt.want.Vin[i].Sequence)
}
if tt.args.privacyType == 0 && !reflect.DeepEqual(got.Vin[i].Txid, tt.want.Vin[i].Txid) {
t.Errorf("Check Txid at input %d, got %v, want %v",
i, got.Vin[i].Txid, tt.want.Vin[i].Txid)
}
if tt.args.privacyType == 0 && !reflect.DeepEqual(got.Vin[i].Vout, tt.want.Vin[i].Vout) {
t.Errorf("Check Vout at input %d, got %v, want %v",
i, got.Vin[i].Vout, tt.want.Vin[i].Vout)
}
}
if len(got.Vout) != len(tt.want.Vout) {
t.Errorf("Check vout size, got %v, want %v", len(got.Vout), len(tt.want.Vout))
}
for i := 0; i != len(got.Vout); i++ {
if !reflect.DeepEqual(got.Vout[i].JsonValue, tt.want.Vout[i].JsonValue) {
t.Errorf("Check JsonValue at output %d, got %v, want %v",
i, got.Vout[i].JsonValue, tt.want.Vout[i].JsonValue)
}
if !reflect.DeepEqual(got.Vout[i].N, tt.want.Vout[i].N) {
t.Errorf("Check N at output %d, got %v, want %v",
i, got.Vout[i].N, tt.want.Vout[i].N)
}
// empty addresses and null should be the same
if !((len(got.Vout[i].ScriptPubKey.Addresses) == 0 && len(got.Vout[i].ScriptPubKey.Addresses) == len(tt.want.Vout[i].ScriptPubKey.Addresses)) ||
reflect.DeepEqual(got.Vout[i].ScriptPubKey.Addresses, tt.want.Vout[i].ScriptPubKey.Addresses)) {
t.Errorf("Check ScriptPubKey.Addresses at output %d, got %v, want %v",
i, got.Vout[i].ScriptPubKey.Addresses, tt.want.Vout[i].ScriptPubKey.Addresses)
}
if !reflect.DeepEqual(got.Vout[i].ScriptPubKey.Hex, tt.want.Vout[i].ScriptPubKey.Hex) {
t.Errorf("Check ScriptPubKey.Hex at output %d, got %v, want %v",
i, got.Vout[i].ScriptPubKey.Hex, tt.want.Vout[i].ScriptPubKey.Hex)
}
if !reflect.DeepEqual(got.Vout[i].ValueSat, tt.want.Vout[i].ValueSat) {
t.Errorf("Check ValueSat at output %d, got %v, want %v",
i, got.Vout[i].ValueSat, tt.want.Vout[i].ValueSat)
}
}
if got.LockTime != tt.want.LockTime {
t.Errorf("Check LockTime, got %v, want %v", got.LockTime, tt.want.LockTime)
}
if got.Txid != tt.want.Txid {
t.Errorf("Check TxId, got %v, want %v", got.Txid, tt.want.Txid)
}
})
}
}

View File

@ -1,28 +1,28 @@
package xzc
package firo
import (
"blockbook/bchain"
"blockbook/bchain/coins/btc"
"encoding/hex"
"encoding/json"
"github.com/golang/glog"
"github.com/juju/errors"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
)
type ZcoinRPC struct {
type FiroRPC struct {
*btc.BitcoinRPC
}
func NewZcoinRPC(config json.RawMessage, pushHandler func(bchain.NotificationType)) (bchain.BlockChain, error) {
func NewFiroRPC(config json.RawMessage, pushHandler func(bchain.NotificationType)) (bchain.BlockChain, error) {
// init base implementation
bc, err := btc.NewBitcoinRPC(config, pushHandler)
if err != nil {
return nil, err
}
// init zcoin implementation
zc := &ZcoinRPC{
// init firo implementation
zc := &FiroRPC{
BitcoinRPC: bc.(*btc.BitcoinRPC),
}
@ -35,7 +35,7 @@ func NewZcoinRPC(config json.RawMessage, pushHandler func(bchain.NotificationTyp
return zc, nil
}
func (zc *ZcoinRPC) Initialize() error {
func (zc *FiroRPC) Initialize() error {
ci, err := zc.GetChainInfo()
if err != nil {
return err
@ -45,7 +45,7 @@ func (zc *ZcoinRPC) Initialize() error {
params := GetChainParams(chainName)
// always create parser
zc.Parser = NewZcoinParser(params, zc.ChainConfig)
zc.Parser = NewFiroParser(params, zc.ChainConfig)
// parameters for getInfo request
if params.Net == MainnetMagic {
@ -61,7 +61,7 @@ func (zc *ZcoinRPC) Initialize() error {
return nil
}
func (zc *ZcoinRPC) GetBlock(hash string, height uint32) (*bchain.Block, error) {
func (zc *FiroRPC) GetBlock(hash string, height uint32) (*bchain.Block, error) {
var err error
if hash == "" {
@ -96,7 +96,7 @@ func (zc *ZcoinRPC) GetBlock(hash string, height uint32) (*bchain.Block, error)
return block, nil
}
func (zc *ZcoinRPC) GetBlockInfo(hash string) (*bchain.BlockInfo, error) {
func (zc *FiroRPC) GetBlockInfo(hash string) (*bchain.BlockInfo, error) {
glog.V(1).Info("rpc: getblock (verbosity=true) ", hash)
res := btc.ResGetBlockInfo{}
@ -117,7 +117,7 @@ func (zc *ZcoinRPC) GetBlockInfo(hash string) (*bchain.BlockInfo, error) {
return &res.Result, nil
}
func (zc *ZcoinRPC) GetBlockWithoutHeader(hash string, height uint32) (*bchain.Block, error) {
func (zc *FiroRPC) GetBlockWithoutHeader(hash string, height uint32) (*bchain.Block, error) {
data, err := zc.GetBlockRaw(hash)
if err != nil {
return nil, err
@ -134,7 +134,7 @@ func (zc *ZcoinRPC) GetBlockWithoutHeader(hash string, height uint32) (*bchain.B
return block, nil
}
func (zc *ZcoinRPC) GetBlockRaw(hash string) ([]byte, error) {
func (zc *FiroRPC) GetBlockRaw(hash string) ([]byte, error) {
glog.V(1).Info("rpc: getblock (verbosity=false) ", hash)
res := btc.ResGetBlockRaw{}
@ -155,7 +155,7 @@ func (zc *ZcoinRPC) GetBlockRaw(hash string) ([]byte, error) {
return hex.DecodeString(res.Result)
}
func (zc *ZcoinRPC) GetTransactionForMempool(txid string) (*bchain.Tx, error) {
func (zc *FiroRPC) GetTransactionForMempool(txid string) (*bchain.Tx, error) {
glog.V(1).Info("rpc: getrawtransaction nonverbose ", txid)
res := btc.ResGetRawTransactionNonverbose{}
@ -183,7 +183,7 @@ func (zc *ZcoinRPC) GetTransactionForMempool(txid string) (*bchain.Tx, error) {
return tx, nil
}
func (zc *ZcoinRPC) GetTransaction(txid string) (*bchain.Tx, error) {
func (zc *FiroRPC) GetTransaction(txid string) (*bchain.Tx, error) {
r, err := zc.getRawTransaction(txid)
if err != nil {
return nil, err
@ -198,14 +198,14 @@ func (zc *ZcoinRPC) GetTransaction(txid string) (*bchain.Tx, error) {
return tx, nil
}
func (zc *ZcoinRPC) GetTransactionSpecific(tx *bchain.Tx) (json.RawMessage, error) {
func (zc *FiroRPC) GetTransactionSpecific(tx *bchain.Tx) (json.RawMessage, error) {
if csd, ok := tx.CoinSpecificData.(json.RawMessage); ok {
return csd, nil
}
return zc.getRawTransaction(tx.Txid)
}
func (zc *ZcoinRPC) getRawTransaction(txid string) (json.RawMessage, error) {
func (zc *FiroRPC) getRawTransaction(txid string) (json.RawMessage, error) {
glog.V(1).Info("rpc: getrawtransaction ", txid)
res := btc.ResGetRawTransaction{}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

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

View File

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

View File

@ -1,13 +1,12 @@
package flo
import (
"blockbook/bchain"
"blockbook/bchain/coins/btc"
"encoding/json"
"github.com/juju/errors"
"github.com/golang/glog"
"github.com/juju/errors"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
)
// FloRPC is an interface to JSON-RPC bitcoind service.

View File

@ -1,10 +1,9 @@
package fujicoin
import (
"blockbook/bchain/coins/btc"
"github.com/martinboehm/btcd/wire"
"github.com/martinboehm/btcutil/chaincfg"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
)
const (

View File

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

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.

View File

@ -1,10 +1,9 @@
package gamecredits
import (
"blockbook/bchain/coins/btc"
"github.com/martinboehm/btcd/wire"
"github.com/martinboehm/btcutil/chaincfg"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
)
// magic numbers

View File

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

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.

View File

@ -1,12 +1,11 @@
package grs
import (
"blockbook/bchain"
"blockbook/bchain/coins/btc"
"github.com/martinboehm/btcd/wire"
"github.com/martinboehm/btcutil/base58"
"github.com/martinboehm/btcutil/chaincfg"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
)
// magic numbers

View File

@ -3,8 +3,6 @@
package grs
import (
"blockbook/bchain"
"blockbook/bchain/coins/btc"
"bytes"
"encoding/hex"
"math/big"
@ -13,6 +11,8 @@ import (
"testing"
"github.com/martinboehm/btcutil/chaincfg"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
)
var (

View File

@ -1,12 +1,12 @@
package grs
import (
"blockbook/bchain"
"blockbook/bchain/coins/btc"
"encoding/json"
"github.com/golang/glog"
"github.com/juju/errors"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
)
// GroestlcoinRPC is an interface to JSON-RPC service

View File

@ -1,19 +1,20 @@
package koto
import (
"blockbook/bchain"
"blockbook/bchain/coins/btc"
"github.com/martinboehm/btcd/wire"
"github.com/martinboehm/btcutil/chaincfg"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
)
// magic numbers
const (
MainnetMagic wire.BitcoinNet = 0x6f746f4b
TestnetMagic wire.BitcoinNet = 0x6f6b6f54
RegtestMagic wire.BitcoinNet = 0x6f6b6552
)
// chain parameters
var (
MainNetParams chaincfg.Params
TestNetParams chaincfg.Params

View File

@ -3,8 +3,6 @@
package koto
import (
"blockbook/bchain"
"blockbook/bchain/coins/btc"
"bytes"
"encoding/hex"
"math/big"
@ -13,6 +11,8 @@ import (
"testing"
"github.com/martinboehm/btcutil/chaincfg"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
)
var (

View File

@ -1,18 +1,20 @@
package koto
import (
"blockbook/bchain"
"blockbook/bchain/coins/btc"
"encoding/json"
"github.com/golang/glog"
"github.com/juju/errors"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
)
// KotoRPC is an interface to JSON-RPC bitcoind service
type KotoRPC struct {
*btc.BitcoinRPC
}
// NewKotoRPC returns new LitecoinRPC instance
func NewKotoRPC(config json.RawMessage, pushHandler func(bchain.NotificationType)) (bchain.BlockChain, error) {
b, err := btc.NewBitcoinRPC(config, pushHandler)
if err != nil {

View File

@ -1,16 +1,15 @@
package liquid
import (
"blockbook/bchain"
"blockbook/bchain/coins/btc"
"strconv"
vlq "github.com/bsm/go-vlq"
"github.com/golang/glog"
"github.com/martinboehm/btcd/txscript"
"github.com/martinboehm/btcd/wire"
"github.com/martinboehm/btcutil/chaincfg"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
)
const (

View File

@ -3,8 +3,6 @@
package liquid
import (
"blockbook/bchain"
"blockbook/bchain/coins/btc"
"encoding/hex"
"math/big"
"os"
@ -12,6 +10,8 @@ import (
"testing"
"github.com/martinboehm/btcutil/chaincfg"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
)
func TestMain(m *testing.M) {

View File

@ -1,12 +1,12 @@
package liquid
import (
"blockbook/bchain"
"blockbook/bchain/coins/btc"
"encoding/json"
"github.com/golang/glog"
"github.com/juju/errors"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
)
// LiquidRPC is an interface to JSON-RPC bitcoind service.

View File

@ -1,10 +1,9 @@
package litecoin
import (
"blockbook/bchain/coins/btc"
"github.com/martinboehm/btcd/wire"
"github.com/martinboehm/btcutil/chaincfg"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
)
// magic numbers

View File

@ -3,8 +3,6 @@
package litecoin
import (
"blockbook/bchain"
"blockbook/bchain/coins/btc"
"encoding/hex"
"math/big"
"os"
@ -12,6 +10,8 @@ import (
"testing"
"github.com/martinboehm/btcutil/chaincfg"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
)
func TestMain(m *testing.M) {
@ -100,6 +100,18 @@ func Test_GetAddrDescFromAddress_Mainnet(t *testing.T) {
want: "76a914feda50542e61108cf53b93dbffa0959f91ccb32588ac",
wantErr: false,
},
{
name: "P2PKH3 - bech32 prefix",
args: args{address: "LTC1eqUzePT9uvpvb413Ejd6P8Cx1Ei8Di"},
want: "76a91457630115300a625f5deaab64100faa5506c1422f88ac",
wantErr: false,
},
{
name: "P2PKH4 - bech32 prefix",
args: args{address: "LTC1f9gtb7bU6B4VjHXvPGDi8ACNZhkKPo"},
want: "76a9145763023d3f02509644dacbfc45f2c9102129749788ac",
wantErr: false,
},
{
name: "P2SH1",
args: args{address: "MLTQ8niHMnpJLNvK72zBeY91hQmUtoo8nX"},
@ -142,6 +154,73 @@ func Test_GetAddrDescFromAddress_Mainnet(t *testing.T) {
}
}
func TestGetAddressesFromAddrDesc_Mainnet(t *testing.T) {
type args struct {
script string
}
tests := []struct {
name string
args args
want []string
want2 bool
wantErr bool
}{
{
name: "P2PKH1",
args: args{script: "76a914feda50542e61108cf53b93dbffa0959f91ccb32588ac"},
want: []string{"LiTVReQ6N8rWc2pNg2XMwCWq7A9P15teWg"},
want2: true,
wantErr: false,
},
{
name: "P2PKH2",
args: args{script: "76A9145763023D3F02509644DACBFC45F2C9102129749788AC"},
want: []string{"LTC1f9gtb7bU6B4VjHXvPGDi8ACNZhkKPo"},
want2: true,
wantErr: false,
},
{
name: "P2SH1",
args: args{script: "a9141c6fbaf46d64221e80cbae182c33ddf81b9294ac87"},
want: []string{"MAVWzxXm8KGkZTesqLtqywzrvbs96FEoKy"},
want2: true,
wantErr: false,
},
{
name: "witness_v0_keyhash",
args: args{script: "0014a2516e770582864a6a56ed21a102044e388c62e3"},
want: []string{"ltc1q5fgkuac9s2ry56jka5s6zqsyfcugcchrqgz2yl"},
want2: true,
wantErr: false,
},
{
name: "witness_v0_scripthashx",
args: args{script: "0020e15a86a23178f433d514dbbce042e87d72662b8b5edcacfd2e37ab7a2d135f05"},
want: []string{"ltc1qu9dgdg330r6r84g5mw7wqshg04exv2uttmw2elfwx74h5tgntuzsk3x5nd"},
want2: true,
wantErr: false,
},
}
parser := NewLitecoinParser(GetChainParams("main"), &btc.Configuration{})
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
b, _ := hex.DecodeString(tt.args.script)
got, got2, err := parser.GetAddressesFromAddrDesc(b)
if (err != nil) != tt.wantErr {
t.Errorf("GetAddressesFromAddrDesc() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("GetAddressesFromAddrDesc() = %v, want %v", got, tt.want)
}
if !reflect.DeepEqual(got2, tt.want2) {
t.Errorf("GetAddressesFromAddrDesc() = %v, want %v", got2, tt.want2)
}
})
}
}
var (
testTx1 bchain.Tx

View File

@ -1,11 +1,11 @@
package litecoin
import (
"blockbook/bchain"
"blockbook/bchain/coins/btc"
"encoding/json"
"github.com/golang/glog"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
)
// LitecoinRPC is an interface to JSON-RPC bitcoind service.

View File

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

View File

@ -3,8 +3,6 @@
package monacoin
import (
"blockbook/bchain"
"blockbook/bchain/coins/btc"
"encoding/hex"
"math/big"
"os"
@ -12,6 +10,8 @@ import (
"testing"
"github.com/martinboehm/btcutil/chaincfg"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
)
func TestMain(m *testing.M) {

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