From 91691ed7e76edce31470e32a1e77407dd3204c57 Mon Sep 17 00:00:00 2001 From: Migwi Ndung'u <22055953+dmigwi@users.noreply.github.com> Date: Sun, 7 Jul 2019 23:41:01 +0300 Subject: [PATCH] Add decred support (#216) --- Gopkg.lock | 68 +- bchain/coins/blockchain.go | 3 + bchain/coins/dcr/decredparser.go | 231 +++++++ bchain/coins/dcr/decredparser_test.go | 238 +++++++ bchain/coins/dcr/decredrpc.go | 882 ++++++++++++++++++++++++ configs/coins/decred.json | 68 ++ configs/coins/decred_testnet.json | 68 ++ tests/rpc/testdata/decred.json | 98 +++ tests/rpc/testdata/decred_testnet.json | 115 +++ tests/sync/testdata/decred.json | 217 ++++++ tests/sync/testdata/decred_testnet.json | 214 ++++++ tests/tests.json | 10 + 12 files changed, 2211 insertions(+), 1 deletion(-) create mode 100644 bchain/coins/dcr/decredparser.go create mode 100644 bchain/coins/dcr/decredparser_test.go create mode 100644 bchain/coins/dcr/decredrpc.go create mode 100644 configs/coins/decred.json create mode 100644 configs/coins/decred_testnet.json create mode 100644 tests/rpc/testdata/decred.json create mode 100644 tests/rpc/testdata/decred_testnet.json create mode 100644 tests/sync/testdata/decred.json create mode 100644 tests/sync/testdata/decred_testnet.json diff --git a/Gopkg.lock b/Gopkg.lock index 505f7c86..89c0e2b7 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -26,6 +26,13 @@ pruneopts = "" revision = "8e7d5b18fe7ad671e07097d5445dbc70422663b2" +[[projects]] + branch = "master" + name = "github.com/agl/ed25519" + packages = [".","edwards25519"] + revision = "5312a61534124124185d41f09206b9fef1d88403" + + [[projects]] branch = "master" name = "github.com/beorn7/perks" @@ -50,12 +57,36 @@ packages = ["."] revision = "84c8d2346e9fc8c7b947e243b9c24e6df9fd206a" + [[projects]] + branch = "master" + name = "github.com/dchest/blake256" + packages = ["."] + revision = "dee3fe6eb0e98dc774a94fc231f85baf7c29d360" + [[projects]] name = "github.com/deckarep/golang-set" packages = ["."] revision = "1d4478f51bed434f1dadf96dcd9b43aabac66795" version = "v1.7" +[[projects]] + branch = "master" + name = "github.com/decred/base58" + packages = ["."] + revision = "dbeddd8aab76c31eb2ea98351a63fa2c6bf46888" + +[[projects]] + name = "github.com/decred/dcrd" + packages = ["chaincfg","chaincfg/chainec","chaincfg/chainhash","dcrec","dcrec/edwards","dcrec/secp256k1","dcrec/secp256k1/schnorr","dcrjson","dcrutil","txscript","wire"] + revision = "0fe564967f03160b9dbe0a147420d8aa13371d12" + version = "v1.3.0" + +[[projects]] + name = "github.com/decred/slog" + packages = ["."] + revision = "fbd821ef791ba2b8ae945f5d44f4e49396d230c5" + version = "v1.0.0" + [[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"] @@ -227,6 +258,41 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "3a7b863bec3806ba9460f3961e2d6e8ff4b77e5f66d196b1f79bd37fa6bf8d82" + input-imports = [ + "github.com/bsm/go-vlq", + "github.com/deckarep/golang-set", + "github.com/decred/dcrd/chaincfg", + "github.com/decred/dcrd/dcrjson", + "github.com/decred/dcrd/txscript", + "github.com/ethereum/go-ethereum", + "github.com/ethereum/go-ethereum/common", + "github.com/ethereum/go-ethereum/common/hexutil", + "github.com/ethereum/go-ethereum/core/types", + "github.com/ethereum/go-ethereum/ethclient", + "github.com/ethereum/go-ethereum/rpc", + "github.com/gobuffalo/packr", + "github.com/gogo/protobuf/proto", + "github.com/golang/glog", + "github.com/golang/protobuf/proto", + "github.com/gorilla/websocket", + "github.com/juju/errors", + "github.com/martinboehm/bchutil", + "github.com/martinboehm/btcd/blockchain", + "github.com/martinboehm/btcd/chaincfg/chainhash", + "github.com/martinboehm/btcd/txscript", + "github.com/martinboehm/btcd/wire", + "github.com/martinboehm/btcutil", + "github.com/martinboehm/btcutil/base58", + "github.com/martinboehm/btcutil/chaincfg", + "github.com/martinboehm/btcutil/hdkeychain", + "github.com/martinboehm/btcutil/txscript", + "github.com/martinboehm/golang-socketio", + "github.com/martinboehm/golang-socketio/transport", + "github.com/pebbe/zmq4", + "github.com/prometheus/client_golang/prometheus", + "github.com/prometheus/client_golang/prometheus/promhttp", + "github.com/schancel/cashaddr-converter/address", + "github.com/tecbot/gorocksdb", + ] solver-name = "gps-cdcl" solver-version = 1 diff --git a/bchain/coins/blockchain.go b/bchain/coins/blockchain.go index 9256f048..4456ae6a 100644 --- a/bchain/coins/blockchain.go +++ b/bchain/coins/blockchain.go @@ -7,6 +7,7 @@ import ( "blockbook/bchain/coins/btc" "blockbook/bchain/coins/btg" "blockbook/bchain/coins/dash" + "blockbook/bchain/coins/dcr" "blockbook/bchain/coins/digibyte" "blockbook/bchain/coins/dogecoin" "blockbook/bchain/coins/eth" @@ -62,6 +63,8 @@ func init() { BlockChainFactories["Bgold"] = 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 diff --git a/bchain/coins/dcr/decredparser.go b/bchain/coins/dcr/decredparser.go new file mode 100644 index 00000000..a50611d1 --- /dev/null +++ b/bchain/coins/dcr/decredparser.go @@ -0,0 +1,231 @@ +package dcr + +import ( + "bytes" + "encoding/hex" + "encoding/json" + "math" + "math/big" + + "blockbook/bchain" + "blockbook/bchain/coins/btc" + "blockbook/bchain/coins/utils" + + cfg "github.com/decred/dcrd/chaincfg" + "github.com/decred/dcrd/txscript" + "github.com/martinboehm/btcd/wire" + "github.com/martinboehm/btcutil/chaincfg" +) + +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 +} + +// NewDecredParser returns new DecredParser instance +func NewDecredParser(params *chaincfg.Params, c *btc.Configuration) *DecredParser { + return &DecredParser{ + BitcoinParser: btc.NewBitcoinParser(params, c), + baseParser: &bchain.BaseParser{}, + } +} + +// 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 +} + +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, + 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 +} + +func (p *DecredParser) GetAddrDescFromAddress(address string) (bchain.AddressDescriptor, error) { + addressByte := []byte(address) + return bchain.AddressDescriptor(addressByte), nil +} + +func (p *DecredParser) GetAddrDescFromVout(output *bchain.Vout) (bchain.AddressDescriptor, error) { + script, err := hex.DecodeString(output.ScriptPubKey.Hex) + if err != nil { + return nil, err + } + + var params cfg.Params + if p.Params.Name == "mainnet" { + params = cfg.MainNetParams + } else { + params = cfg.TestNet3Params + } + + scriptClass, addresses, _, err := txscript.ExtractPkScriptAddrs(txscript.DefaultScriptVersion, script, ¶ms) + 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 +} + +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) +} diff --git a/bchain/coins/dcr/decredparser_test.go b/bchain/coins/dcr/decredparser_test.go new file mode 100644 index 00000000..cdd887a1 --- /dev/null +++ b/bchain/coins/dcr/decredparser_test.go @@ -0,0 +1,238 @@ +// +build unittest + +package dcr + +import ( + "blockbook/bchain" + "blockbook/bchain/coins/btc" + "encoding/hex" + "math/big" + "os" + "reflect" + "testing" +) + +var ( + parser *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: "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) { + parser = NewDecredParser(GetChainParams("testnet3"), &btc.Configuration{}) + 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 := parser.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 := parser.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 := parser.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) + } + }) + } +} diff --git a/bchain/coins/dcr/decredrpc.go b/bchain/coins/dcr/decredrpc.go new file mode 100644 index 00000000..60cd1474 --- /dev/null +++ b/bchain/coins/dcr/decredrpc.go @@ -0,0 +1,882 @@ +package dcr + +import ( + "blockbook/bchain" + "bytes" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "math/big" + "net" + "net/http" + "runtime/debug" + "strconv" + "strings" + "sync" + "time" + + "blockbook/bchain/coins/btc" + + "github.com/decred/dcrd/dcrjson" + "github.com/golang/glog" + "github.com/juju/errors" +) + +// 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 json.Number `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 json.Number `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 json.Number `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 json.Number `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 +} + +func (d *DecredRPC) GetBlockHeaderByHeight(height uint32) (*bchain.BlockHeader, error) { + return nil, nil +} + +// GetBlock returns the block retreived 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() + var bestBlockHeight = d.bestBlock + if height > bestBlockHeight { + bestBlock, err := d.getBestBlock() + if err != nil || height > bestBlock.Result.Height { + // If an error occured 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 + bestBlockHeight = 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: json.Number(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) + } +} diff --git a/configs/coins/decred.json b/configs/coins/decred.json new file mode 100644 index 00000000..69a06b1d --- /dev/null +++ b/configs/coins/decred.json @@ -0,0 +1,68 @@ +{ + "coin": { + "name": "Decred", + "shortcut": "DCR", + "label": "Decred", + "alias": "decred" + }, + "ports": { + "backend_rpc": 8061, + "backend_message_queue": 38361, + "blockbook_internal": 9061, + "blockbook_public": 9161 + }, + "ipc": { + "rpc_url_template": "http://127.0.0.1:{{.Ports.BackendRPC}}", + "rpc_user": "rpc", + "rpc_pass": "rpc", + "rpc_timeout": 25, + "message_queue_binding_template": "tcp://127.0.0.1:{{.Ports.BackendMessageQueue}}" + }, + "backend": { + "package_name": "backend-decred", + "package_revision": "decred-1", + "system_user": "decred", + "version": "1.4.0", + "binary_url": "https://github.com/decred/decred-binaries/releases/download/v1.4.0/decred-linux-amd64-v1.4.0.tar.gz", + "verification_type": "sha256", + "verification_source": "36375985df1ba9a45bc11b4f6cdaed4f14ff6e5e9c46e17ef6e4f70a3349aba2", + "extract_command": "tar -C backend --strip 1 -xf", + "exclude_files": [], + "exec_command_template": "{{.Env.BackendInstallPath}}/{{.Coin.Alias}}/dcrd --datadir={{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend --rpcuser={{.IPC.RPCUser}} --rpcpass={{.IPC.RPCPass}} -C={{.Env.BackendDataPath}}/{{.Coin.Alias}}/dcrd.conf --nofilelogging --appdata={{.Env.BackendDataPath}}/{{.Coin.Alias}} --notls --txindex --addrindex --rpclisten=[127.0.0.1]:8061", + "logrotate_files_template": "{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/*.log", + "postinst_script_template": "", + "service_type": "forking", + "service_additional_params_template": "", + "protect_memory": false, + "mainnet": true, + "server_config_file": "bitcoin_like.conf", + "client_config_file": "bitcoin_like_client.conf", + "additional_params": { + "rpcmaxclients": 16, + "upnp": 0, + "whitelist": "127.0.0.1" + } + }, + "blockbook": { + "package_name": "blockbook-decred", + "system_user": "blockbook-decred", + "internal_binding_template": ":{{.Ports.BlockbookInternal}}", + "public_binding_template": ":{{.Ports.BlockbookPublic}}", + "explorer_url": "https://mainnet.dcrdata.org/", + "additional_params": "", + "block_chain": { + "parse": true, + "subversion":"/Decred dcrd:1.4.0", + "mempool_workers": 8, + "mempool_sub_workers": 2, + "block_addresses_to_keep": 30, + "xpub_magic": 49990397, + "slip44": 3, + "additional_params": {} + } + }, + "meta": { + "package_maintainer": "Ugwueze Michael", + "package_maintainer_email": "code.maestro64@gmail.com" + } + } \ No newline at end of file diff --git a/configs/coins/decred_testnet.json b/configs/coins/decred_testnet.json new file mode 100644 index 00000000..0f5e6d3b --- /dev/null +++ b/configs/coins/decred_testnet.json @@ -0,0 +1,68 @@ +{ + "coin": { + "name": "Decred Testnet", + "shortcut": "tDCR", + "label": "Decred Testnet", + "alias": "decred_testnet" + }, + "ports": { + "backend_rpc": 18061, + "backend_message_queue": 48361, + "blockbook_internal": 19061, + "blockbook_public": 19161 + }, + "ipc": { + "rpc_url_template": "http://127.0.0.1:{{.Ports.BackendRPC}}", + "rpc_user": "rpc", + "rpc_pass": "rpc", + "rpc_timeout": 25, + "message_queue_binding_template": "tcp://127.0.0.1:{{.Ports.BackendMessageQueue}}" + }, + "backend": { + "package_name": "backend-decred-testnet", + "package_revision": "decred-testnet-1", + "system_user": "decred", + "version": "1.4.0", + "binary_url": "https://github.com/decred/decred-binaries/releases/download/v1.4.0/decred-linux-amd64-v1.4.0.tar.gz", + "verification_type": "sha256", + "verification_source": "36375985df1ba9a45bc11b4f6cdaed4f14ff6e5e9c46e17ef6e4f70a3349aba2", + "extract_command": "tar -C backend --strip 1 -xf", + "exclude_files": [], + "exec_command_template": "{{.Env.BackendInstallPath}}/{{.Coin.Alias}}/dcrd --datadir={{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend --rpcuser={{.IPC.RPCUser}} --rpcpass={{.IPC.RPCPass}} -C={{.Env.BackendDataPath}}/{{.Coin.Alias}}/dcrd.conf --nofilelogging --appdata={{.Env.BackendDataPath}}/{{.Coin.Alias}} --notls --txindex --addrindex --testnet --rpclisten=[127.0.0.1]18061", + "logrotate_files_template": "{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/*.log", + "postinst_script_template": "", + "service_type": "forking", + "service_additional_params_template": "", + "protect_memory": false, + "mainnet": true, + "server_config_file": "bitcoin_like.conf", + "client_config_file": "bitcoin_like_client.conf", + "additional_params": { + "rpcmaxclients": 16, + "upnp": 0, + "whitelist": "127.0.0.1" + } + }, + "blockbook": { + "package_name": "blockbook-decred-testnet", + "system_user": "blockbook-decred-testnet", + "internal_binding_template": ":{{.Ports.BlockbookInternal}}", + "public_binding_template": ":{{.Ports.BlockbookPublic}}", + "explorer_url": "https://testnet.dcrdata.org/", + "additional_params": "", + "block_chain": { + "parse": true, + "subversion":"/Decred dcrd:1.4.0", + "mempool_workers": 8, + "mempool_sub_workers": 2, + "block_addresses_to_keep": 30, + "xpub_magic": 49990397, + "slip44": 3, + "additional_params": {} + } + }, + "meta": { + "package_maintainer": "Ugwueze Michael", + "package_maintainer_email": "code.maestro64@gmail.com" + } + } \ No newline at end of file diff --git a/tests/rpc/testdata/decred.json b/tests/rpc/testdata/decred.json new file mode 100644 index 00000000..3112a5ce --- /dev/null +++ b/tests/rpc/testdata/decred.json @@ -0,0 +1,98 @@ +{ + "blockHeight": 30000, + "blockHash": "000000000000039135acfe97d4c769156856318997ff9afa6635873344b4e26f", + "blockTime": 1463843967, + "blockTxs": [ + "4cfab09f97f04e065f9a9552dcdda9ce1b2aabec436330adea330f0b5f7a359d", + "fb422109aa8db4dafdd16a0e9c38d49a4ea448a3e739513d5aaf2cc056e366a6", + "a768ec0314cde1e497893d6825f4780f5342e24a841c50d48189e540d3d08a2a", + "7058766ffef2e9cee61ee4b7604a39bc91c3000cb951c4f93f3307f6e0bf4def", + "ea66736c9488fee34d85042a6cfdb6c53a0b6da4a3b69f9014d9037ffc672173", + "798b8a038b96719dddb544a82a9e626ea548353064e17541b3a4f4bf91663501", + "8794b8c3b72fccddeb5acfb61fe8671de697a3e43d9111c59fc80b0890ff9ba9", + "b285a6e33b3d1bdf329fa0d169a5ddb21c1e9517df021ecf5235c0ebfd21c6fc" + ], + "blockSize": 5050, + "txDetails": { + "7058766ffef2e9cee61ee4b7604a39bc91c3000cb951c4f93f3307f6e0bf4def": { + "hex": "0100000001193c189c71dff482b70ccb10ec9cf0ea3421a7fc51e4c7b0cf59c98a293a2f960200000000ffffffff027c87f00b0000000000001976a91418f10131a859912119c4a8510199f87f0a4cec2488ac9889495f0000000000001976a914631fb783b1e06c3f6e71777e16da6de13450465e88ac0000000000000000015ced3d6b0000000030740000000000006a47304402204e6afc21f6d065b9c082dad81a5f29136320e2b54c6cdf6b8722e4507e1a8d8902203933c5e592df3b0bbb0568f121f48ef6cbfae9cf479a57229742b5780dedc57a012103b89bb443b6ab17724458285b302291b082c59e5a022f273af0f61d47a414a537", + "txid": "7058766ffef2e9cee61ee4b7604a39bc91c3000cb951c4f93f3307f6e0bf4def", + "blocktime": 1463843967, + "time": 1463843967, + "locktime": 0, + "version": 1, + "vin": [ + { + "txid": "962f3a298ac959cfb0c7e451fca72134eaf09cec10cb0cb782f4df719c183c19", + "vout": 2, + "sequence": 4294967295, + "scriptSig": { + "hex": "47304402204e6afc21f6d065b9c082dad81a5f29136320e2b54c6cdf6b8722e4507e1a8d8902203933c5e592df3b0bbb0568f121f48ef6cbfae9cf479a57229742b5780dedc57a012103b89bb443b6ab17724458285b302291b082c59e5a022f273af0f61d47a414a537" + } + } + ], + "vout": [ + { + "value": 2.003127, + "n": 0, + "scriptPubKey": { + "hex": "76a91418f10131a859912119c4a8510199f87f0a4cec2488ac", + "addresses": [ + "DsTEnRLDEjQNeQ4A47fdS2pqtaFrGNzkqNa" + ] + } + }, + { + "value": 15.98654872, + "n": 1, + "scriptPubKey": { + "hex": "76a914631fb783b1e06c3f6e71777e16da6de13450465e88ac", + "addresses": [ + "Dsa12P9VnCd55hTnUXpvGgFKSeGkFkzRvYb" + ] + } + } + ] + }, + "798b8a038b96719dddb544a82a9e626ea548353064e17541b3a4f4bf91663501": { + "hex": "0100000001188e1dd0e4708b0921afdd75d013b7cd63f7952e558ceb1040f45681717a27100000000000ffffffff029eb7de050000000000001976a91437b2be41e6b04cc29e4421b2bc2f7ab31a8a926d88aca7eade600000000000001976a914c450e55bb7f766ce35f1c16c3ddbc8eefcc7719688ac0000000000000000018d7ec166000000002d750000020000006b483045022100dd83f7cf6832e78bcbaac959ee38833cf2da2bac537223d8d80c61ea1578716f02202aa925285793c372dc8dfe5ef4febdae94c853b8230443fbb61ef59805b229530121031d5fdebe075585f15d8dad8eecb62bdf5e5d8c353097ee08ed7c1a9bc0de0a77", + "txid": "798b8a038b96719dddb544a82a9e626ea548353064e17541b3a4f4bf91663501", + "blocktime": 1463843967, + "time": 1463843967, + "locktime": 0, + "version": 1, + "vin": [ + { + "txid": "10277a718156f44010eb8c552e95f763cdb713d075ddaf21098b70e4d01d8e18", + "vout": 0, + "sequence": 4294967295, + "scriptSig": { + "hex": "483045022100dd83f7cf6832e78bcbaac959ee38833cf2da2bac537223d8d80c61ea1578716f02202aa925285793c372dc8dfe5ef4febdae94c853b8230443fbb61ef59805b229530121031d5fdebe075585f15d8dad8eecb62bdf5e5d8c353097ee08ed7c1a9bc0de0a77" + } + } + ], + "vout": [ + { + "value": 0.98482078, + "n": 0, + "scriptPubKey": { + "hex": "76a91437b2be41e6b04cc29e4421b2bc2f7ab31a8a926d88ac", + "addresses": [ + "DsW3QnGxXzyvZAvFbLeuDE53aWD55deJiBk" + ] + } + }, + { + "value": 16.25221799, + "n": 1, + "scriptPubKey": { + "hex": "76a914c450e55bb7f766ce35f1c16c3ddbc8eefcc7719688ac", + "addresses": [ + "Dsirvqn2J4AzZSns3DpoB3iCzV8iWmspa9m" + ] + } + } + ] + } + } +} diff --git a/tests/rpc/testdata/decred_testnet.json b/tests/rpc/testdata/decred_testnet.json new file mode 100644 index 00000000..d006c1ea --- /dev/null +++ b/tests/rpc/testdata/decred_testnet.json @@ -0,0 +1,115 @@ +{ + "blockHeight": 18720, + "blockHash": "00000000056aa5d3268088dd958da4aeb73586815a2974ef40a907ecbbf8893c", + "blockTime": 1535980876, + "blockTxs": [ + "47ceedca164da150ce1c677f6d6f1eae41b792d9c16a956204cc0dcbf8caa905", + "0c7a348bbcc0da20ac5f304627e8154b90ed2b56ec828a5513bce0dd521713cc" + ], + "blockSize": 2531, + "txDetails": { + "326e54f7909ef8b3d696367039c5eec67b505ef82c3edd2368dd39b6aa272dee": { + "hex": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff03fbed9f0d00000000000017a914d585cd7426d25b4ea5faf1e6987aacfeda3db94287000000000000000000000e6a0c2049000039c63f0b763dfcffa145c0510000000000001976a9149bb0ab1ab455c220c575304aa6bb31dad3eff19888ac000000000000000001e0815f5f0000000000000000ffffffff0800002f646372642f", + "blocktime": 1535980876, + "time": 1535980876, + "locktime": 0, + "version": 1, + "vin": [ + { + "blockindex": 4294967295, + "coinbase": "00002f646372642f", + "sequence": 4294967295 + } + ], + "vout": [ + { + "value": 2.28584955, + "n": 0, + "scriptPubKey": { + "hex": "a914d585cd7426d25b4ea5faf1e6987aacfeda3db94287", + "addresses": [ + "TcrypGAcGCRVXrES7hWqVZb5oLJKCZEtoL1" + ] + } + }, + { + "value": 0, + "n": 1, + "scriptPubKey": { + "hex": "6a0c2049000039c63f0b763dfcff", + "addresses": [ + "OP_RETURN 2049000039c63f0b763dfcff" + ] + } + }, + { + "value": 13.71555233, + "n": 2, + "scriptPubKey": { + "hex": "76a9149bb0ab1ab455c220c575304aa6bb31dad3eff19888ac", + "addresses": [ + "TsfDLrRkk9ciUuwfp2b8PawwnukYD7yAjGd" + ] + } + } + ] + }, + "5cf42c027886df5281df2d15fa876281a362ebb742efe963682258ef201d58d3": { + "hex": "01000000022071ad6dd7c1f463d1cdd7bf15879397be44da6d98bf03c89d571eedf77ffc530200000001ffffffff39880e89917a590c0b010d87064812019e619048d2c4e8dbe8115048c067ecd50200000001ffffffff03cd1ecb340000000000001976a914ae99f28cb5e00dd88b1669c19995f5c84586c33388accd1ecb340000000000001976a914ae99f28cb5e00dd88b1669c19995f5c84586c33388ac1a0801070000000000001976a914fe3d3bc633ef2321d479332412b71ef6bc60e86a88ac000000000000000002362e54360000000010490000010000006b483045022100dbb703dea8c4fc87357be19a9af114242bc80c4eafa387d654da1a427c56cd6102207cb00d4fedbfaa4f2aa7b4530683e286ad13fe127dd9be5edee4c1cb594d9da401210372321040dccd62d5171718e49713a804ceb566fa65ea762a86e7b89e8448d0ed3ac9433a0000000010490000030000006a4730440220729ca901c17ef7008d32df19bfdedf91821d10f0a7511be4d78bdf1027f5598c022077c4a7df6388d1107927f9f95b6aca4b3fd02ae1ff2c422fe47ad79b51c42b64012103adde046e4f13579bba4104ec9b5064158df0ea6648876a8667d88c6abffc0e22", + "blocktime": 1535980876, + "time": 1535980876, + "locktime": 0, + "version": 1, + "vin": [ + { + "txid": "53fc7ff7ed1e579dc803bf986dda44be97938715bfd7cdd163f4c1d76dad7120", + "vout": 2, + "sequence": 4294967295, + "scriptSig": { + "hex": "483045022100dbb703dea8c4fc87357be19a9af114242bc80c4eafa387d654da1a427c56cd6102207cb00d4fedbfaa4f2aa7b4530683e286ad13fe127dd9be5edee4c1cb594d9da401210372321040dccd62d5171718e49713a804ceb566fa65ea762a86e7b89e8448d0ed" + } + }, + { + "txid": "d5ec67c0485011e8dbe8c4d24890619e01124806870d010b0c597a91890e8839", + "vout": 2, + "sequence": 4294967295, + "scriptSig": { + "hex": "4730440220729ca901c17ef7008d32df19bfdedf91821d10f0a7511be4d78bdf1027f5598c022077c4a7df6388d1107927f9f95b6aca4b3fd02ae1ff2c422fe47ad79b51c42b64012103adde046e4f13579bba4104ec9b5064158df0ea6648876a8667d88c6abffc0e22" + } + } + ], + "vout": [ + { + "value": 8.85726925, + "n": 0, + "scriptPubKey": { + "hex": "76a914ae99f28cb5e00dd88b1669c19995f5c84586c33388ac", + "addresses": [ + "TsgwLUPtU1PF6nFe6RohUdhwUURJX7bv5R1" + ] + } + }, + { + "value": 8.85726925, + "n": 1, + "scriptPubKey": { + "hex": "76a914ae99f28cb5e00dd88b1669c19995f5c84586c33388ac", + "addresses": [ + "TsgwLUPtU1PF6nFe6RohUdhwUURJX7bv5R1" + ] + } + }, + { + "value": 1.17508122, + "n": 2, + "scriptPubKey": { + "hex": "76a914fe3d3bc633ef2321d479332412b71ef6bc60e86a88ac", + "addresses": [ + "TspCRTrypUAyeE3yNqg6Wp1a2w9bmko3oED" + ] + } + } + ] + } + } +} diff --git a/tests/sync/testdata/decred.json b/tests/sync/testdata/decred.json new file mode 100644 index 00000000..92b44f47 --- /dev/null +++ b/tests/sync/testdata/decred.json @@ -0,0 +1,217 @@ +{ + "connectBlocks": { + "syncRanges": [ + {"lower": 100000, "upper": 100020} + ], + "blocks": { + "100005": { + "height": 100005, + "hash": "0000000000000521f66d113ea7ca9a5a03e0c1ade0c440bbd32c459b24d5d47e", + "noTxs": 3, + "txDetails": [ + { + "hex": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff03e184db0f00000000000017a914f5916158e3e2c4551c1796708db8367207ed13bb8700000000000000000000266a24a586010000000000000000000000000000000000000000000000000084efffe9a8e93376eb569b5f0000000000001976a914b3c2069c496bc13228c154b03993809f278233d188ac0000000000000000012ba2006f0000000000000000ffffffff0800002f646372642f", + "txid": "111e559788e2213ad354d0e293d79ce6ebda98340016fba5314ea62cfa157562", + "version": 1, + "vin": [ + { + "txid": "", + "vout": 0, + "coinbase": "00002f646372642f", + "sequence": 4294967294 + } + ], + "vout": [ + { + "value": 2.66044641, + "n": 0, + "scriptPubKey": { + "hex": "a914f5916158e3e2c4551c1796708db8367207ed13bb87", + "addresses": [ + "Dcur2mcGjmENx4DhNqDctW5wJCVyT3Qeqkx" + ] + } + }, + { + "value": 0, + "n": 1, + "scriptPubKey": { + "hex": "6a24a586010000000000000000000000000000000000000000000000000084efffe9a8e93376" + } + }, + { + "value": 16.04015851, + "n": 2, + "scriptPubKey": { + "hex": "76a914b3c2069c496bc13228c154b03993809f278233d188ac", + "addresses": [ + "DshMNsvETDWpVoCe1re9NTAChiJagzsFV7J" + ] + } + } + ], + "time": 1484891644, + "blocktime": 1484891644 + }, + { + "hex": "010000000771c0d98b5e604efb69b5dcccf6e934dd7fb40baa6572bbeb9e4ff75e5899aa1c0100000000ffffffff72d9c5d4a2798360f102badc1a0283934eb1ba04c69b7b152088d3c0f72069bd0100000000ffffffff73c8832d83ad240cbc582c0084279cdc9bfba725ae85d847024d25f3bfd26ce10100000000ffffffff7874bdd4ddd605ec3cfec5e5efbb73d482d2fc03227c6f5d210d1f41d706dad91600000000ffffffff7874bdd4ddd605ec3cfec5e5efbb73d482d2fc03227c6f5d210d1f41d706dad92f00000000ffffffff790d74bd9ff9178fc3ba60b8faaf05b8f78b604d4a90696791169c6682275c2f0100000000ffffffff7a64c28e2b9dfb8248744b234af4a151f7d223924d806869ac682d1861c99f860000000000ffffffff0291a9bad30900000000001976a914ae06b5f3af5d9e81f1a1a63dd0a64f19dafd7bc588ac32a285e90200000000001976a9148f2273ad9bb74f78fc3e24a9b29ed4756958001f88ac0000000000000000076c980a060000000039860100080000006b483045022100bbad991e492adc128329c01fc8a69cad490311192997d89fbb673a1dc3acd9e002205bc58d4db2c75cc9306a404f1878aa4e0bc0ef4774555fab802c84a5e9c5dd1a012103e4aa9749379b85116621c6308bcfc48d7d293f6866187fc26f6534987497912bd456de050000000010830100020000006a47304402204eeae188a6b7d983f3a316df1cb71c1884007b3557e554adfc53a7143a81c0cc02204c200cdaeb97ac0e268f5445163c320d0af67a7efc55bce14e47bf3e741395e20121035a0f902dc68ffe0e8e0559d3833d84dad9466f0ac467ca85ac0dac2abbbd6784da41de020000000022830100070000006b48304502210099eefa905ef76102a5aa15f256c75b60a5ac688b148ed2b2f2baa87531dee255022027f6bf71df34d63921ecb817adbac0b369f7350847550344504fdb51d3ddbec50121036d8a38a3e7219b42008bcc88a1d6de11479deaf077327b864b5181122c049884a6f71c0100000000c9840100030000006b48304502210097c20b800de44d256b9078e26408605295f511e92a01222e36fdc129eeb264d2022003e976c07cd3d2e2a955a28bd4510b7d6dd883c7699c76af43d09687f4500ab4012102d4828ba360e1409872b269c9a66a5bbdc04e2478cf38faaab370c953b683e8dbaf4d0e0100000000c9840100030000006b483045022100de0d039786d03fd1fbf2ba4383ed17b4120ab9ef5e5f197b413808bf74aede1402206bf601514f8a27777fad96f51ed3da65f129df03abd42fedf69af0a415b82f2b012102261c5be43f15ff9c6af795759047ccaec9f4805d5a31e3240a97313d7de858c057dfe2050000000002850100090000006b483045022100917523b954ea3e27c18c63d791070cbf37efa0066741719af153d80336b2b08102206fed9413ab193cfee63177a4b4ae44383a444669165438ec77f4193f9abcbe5f012103de17ca81a7ce2655abc63f2a5d0f85ab41c0cadd6e564eadb409fbe614bc679ee5047ea60c0000008d850100030000006b483045022100bc8d6aa9da128d9cb98bdb949fa25bb3a9d4e126b03d0ad66caa39c00ec1abff02200b90a3717c4f179ddbdeffe203a2c1ca58d3f4c51317955a47eef7ea0eea447f012102d73d8c7df63c5dcab39d0b22ce2e4380611be0e71c4a324f369c91e3818cdb9a", + "txid": "114694eebaddaf48adaffb097fb4ed338d0fe5650a337047b6c34c0c2ed48981", + "version": 2, + "vin": [ + { + "txid": "1caa99585ef74f9eebbb7265aa0bb47fdd34e9f6ccdcb569fb4e605e8bd9c071", + "vout": 1, + "sequence": 4294967295, + "scriptSig": { + "hex": "483045022100bbad991e492adc128329c01fc8a69cad490311192997d89fbb673a1dc3acd9e002205bc58d4db2c75cc9306a404f1878aa4e0bc0ef4774555fab802c84a5e9c5dd1a012103e4aa9749379b85116621c6308bcfc48d7d293f6866187fc26f6534987497912b" + } + }, + { + "txid": "bd6920f7c0d38820157b9bc604bab14e9383021adcba02f1608379a2d4c5d972", + "vout": 1, + "sequence": 4294967295, + "scriptSig": { + "hex": "47304402204eeae188a6b7d983f3a316df1cb71c1884007b3557e554adfc53a7143a81c0cc02204c200cdaeb97ac0e268f5445163c320d0af67a7efc55bce14e47bf3e741395e20121035a0f902dc68ffe0e8e0559d3833d84dad9466f0ac467ca85ac0dac2abbbd6784" + } + }, + { + "txid": "e16cd2bff3254d0247d885ae25a7fb9bdc9c2784002c58bc0c24ad832d83c873", + "vout": 1, + "sequence": 4294967295, + "scriptSig": { + "hex": "48304502210099eefa905ef76102a5aa15f256c75b60a5ac688b148ed2b2f2baa87531dee255022027f6bf71df34d63921ecb817adbac0b369f7350847550344504fdb51d3ddbec50121036d8a38a3e7219b42008bcc88a1d6de11479deaf077327b864b5181122c049884" + } + }, + { + "txid": "d9da06d7411f0d215d6f7c2203fcd282d473bbefe5c5fe3cec05d6ddd4bd7478", + "vout": 22, + "sequence": 4294967295, + "scriptSig": { + "hex": "48304502210097c20b800de44d256b9078e26408605295f511e92a01222e36fdc129eeb264d2022003e976c07cd3d2e2a955a28bd4510b7d6dd883c7699c76af43d09687f4500ab4012102d4828ba360e1409872b269c9a66a5bbdc04e2478cf38faaab370c953b683e8db" + } + }, + { + "txid": "d9da06d7411f0d215d6f7c2203fcd282d473bbefe5c5fe3cec05d6ddd4bd7478", + "vout": 47, + "sequence": 4294967295, + "scriptSig": { + "hex": "483045022100de0d039786d03fd1fbf2ba4383ed17b4120ab9ef5e5f197b413808bf74aede1402206bf601514f8a27777fad96f51ed3da65f129df03abd42fedf69af0a415b82f2b012102261c5be43f15ff9c6af795759047ccaec9f4805d5a31e3240a97313d7de858c0" + } + }, + { + "txid": "2f5c2782669c16916769904a4d608bf7b805affab860bac38f17f99fbd740d79", + "vout": 1, + "sequence": 4294967295, + "scriptSig": { + "hex": "483045022100917523b954ea3e27c18c63d791070cbf37efa0066741719af153d80336b2b08102206fed9413ab193cfee63177a4b4ae44383a444669165438ec77f4193f9abcbe5f012103de17ca81a7ce2655abc63f2a5d0f85ab41c0cadd6e564eadb409fbe614bc679e" + } + }, + { + "txid": "869fc961182d68ac6968804d9223d2f751a1f44a234b744882fb9d2b8ec2647a", + "vout": 0, + "sequence": 4294967295, + "scriptSig": { + "hex": "483045022100bc8d6aa9da128d9cb98bdb949fa25bb3a9d4e126b03d0ad66caa39c00ec1abff02200b90a3717c4f179ddbdeffe203a2c1ca58d3f4c51317955a47eef7ea0eea447f012102d73d8c7df63c5dcab39d0b22ce2e4380611be0e71c4a324f369c91e3818cdb9a" + } + } + ], + "vout": [ + { + "value": 422.06931345, + "n": 0, + "scriptPubKey": { + "hex": "76a914ae06b5f3af5d9e81f1a1a63dd0a64f19dafd7bc588ac", + "addresses": [ + "Dsgq575bWosNScm9HigQDVHQdTkFQd3g7rC" + ] + } + }, + { + "value": 125.0778373, + "n": 1, + "scriptPubKey": { + "hex": "76a9148f2273ad9bb74f78fc3e24a9b29ed4756958001f88ac", + "addresses": [ + "Dse1jPcustc7sMudDycgH4HGbV85HCY3MMr" + ] + } + } + ], + "time": 1484891644, + "blocktime": 1484891644 + } + ] + }, + "100011": { + "height": 100011, + "hash": "00000000000000399eee039c7ebcdeaf7940e3b0bfa6b2d1dd332285e9572749", + "noTxs": 5, + "txDetails": [ + { + "hex": "0100000004a1e19215642f11d809bc002b515d7b4dfa14c9488800f1f43650ca0c9ae03fac0200000000ffffffffa585dd9ddd253c81bed3641bfa41ec13e6de72f52d2f76845833e4c4a8f0d2120000000000ffffffffaaa6f277fc9ec80d875fd9ce57c1a3598665f30f627ed4c40c56856c7acec1ab0000000000ffffffffc50f02dd06e930f3e20093fcc53749e30564084665bb7cf2868092d508c8be010200000000ffffffff020756d01f0000000000001976a91486dbe10bdd00a6b8e16456966525dc484f52ce8288ac2ca6f9320100000000001976a914a472eab42acbe0a80b96e1245877223f45e889d688ac00000000000000000424b2305f000000008c850100000000006b483045022100c34624406214b85e80dafe5a6ac5c3651caf71d64f35f2472ca0f40187c5215c02202c401683ec1428076f03a7f30168892de2c2ff4e77d0001a7638ba57670f34c3012103da2b1c13507c9b96b399520c3233f6c81eb4beed2d30be8077ad3a04854e4fecdc2ff75800000000a0860100010000006a47304402202043e50984305d8a81867270dd1a78fd75c5a4cadca6ffec801b0333f1c3f50c0220187dc23684e2f0257cb264a606c9fd2325d4579716763f2f2946e6951f112a7101210309a4f12413c7524e330e5dc08611a968f076d547ef7b62be13ae54629e7554105fc06e3b00000000aa860100020000006a47304402205efdee5709a934bf18634aac3b4a15f461537e71e8cc0e071bf8375c40b59aad02203a13124efcf243ac853016120e66714b2127b0b369792bf0d22aecee39e304e80121023d74b0497fbdc497ad116f4e9dd1ccb08c1cad4baaf81257b9e6b706ff872e9b6ccf3e5f000000009c850100000000006a473044022041c129f846ede6cce7eb027700d669c1c5e4b5fbd54b4f19577449072381db58022072a0715a22922cd2f3abcb7b760f4120e1f84b7ed53ff3ffe3534a4a55cec04b012103da2b1c13507c9b96b399520c3233f6c81eb4beed2d30be8077ad3a04854e4fec", + "txid": "fbbb2c7c8e2e6c3d840b614089f238077cc60940f4d9089d413daf770e0ab976", + "version": 1, + "vin": [ + { + "txid": "ac3fe09a0cca5036f4f1008848c914fa4d7b5d512b00bc09d8112f641592e1a1", + "vout": 2, + "sequence": 4294967295, + "scriptSig": { + "hex": "483045022100c34624406214b85e80dafe5a6ac5c3651caf71d64f35f2472ca0f40187c5215c02202c401683ec1428076f03a7f30168892de2c2ff4e77d0001a7638ba57670f34c3012103da2b1c13507c9b96b399520c3233f6c81eb4beed2d30be8077ad3a04854e4fec" + } + }, + { + "txid": "12d2f0a8c4e4335884762f2df572dee613ec41fa1b64d3be813c25dd9ddd85a5", + "vout": 0, + "sequence": 4294967295, + "scriptSig": { + "hex": "47304402202043e50984305d8a81867270dd1a78fd75c5a4cadca6ffec801b0333f1c3f50c0220187dc23684e2f0257cb264a606c9fd2325d4579716763f2f2946e6951f112a7101210309a4f12413c7524e330e5dc08611a968f076d547ef7b62be13ae54629e755410" + } + }, + { + "txid": "abc1ce7a6c85560cc4d47e620ff3658659a3c157ced95f870dc89efc77f2a6aa", + "vout": 0, + "sequence": 4294967295, + "scriptSig": { + "hex": "47304402205efdee5709a934bf18634aac3b4a15f461537e71e8cc0e071bf8375c40b59aad02203a13124efcf243ac853016120e66714b2127b0b369792bf0d22aecee39e304e80121023d74b0497fbdc497ad116f4e9dd1ccb08c1cad4baaf81257b9e6b706ff872e9b" + } + }, + { + "txid": "01bec808d5928086f27cbb6546086405e34937c5fc9300e2f330e906dd020fc5", + "vout": 2, + "sequence": 4294967295, + "scriptSig": { + "hex": "473044022041c129f846ede6cce7eb027700d669c1c5e4b5fbd54b4f19577449072381db58022072a0715a22922cd2f3abcb7b760f4120e1f84b7ed53ff3ffe3534a4a55cec04b012103da2b1c13507c9b96b399520c3233f6c81eb4beed2d30be8077ad3a04854e4fec" + } + } + ], + "vout": [ + { + "value": 5.33747207, + "n": 0, + "scriptPubKey": { + "hex": "76a91486dbe10bdd00a6b8e16456966525dc484f52ce8288ac", + "addresses": [ + "DsdFySbuMDf5DuLT9y2fKWMhHvbe1p3wFyt" + ] + } + }, + { + "value": 51.501891, + "n": 1, + "scriptPubKey": { + "hex": "76a914a472eab42acbe0a80b96e1245877223f45e889d688ac", + "addresses": [ + "DsfxRyT6X6Rkq1RdecTsjrA6yhFx7eZX3K6" + ] + } + } + ], + "time": 1484894155, + "blocktime": 1484894155 + } + ] + } + } + } +} diff --git a/tests/sync/testdata/decred_testnet.json b/tests/sync/testdata/decred_testnet.json new file mode 100644 index 00000000..474afc22 --- /dev/null +++ b/tests/sync/testdata/decred_testnet.json @@ -0,0 +1,214 @@ +{ + "connectBlocks": { + "syncRanges": [ + {"lower": 120000, "upper": 120050} + ], + "blocks": { + "120017": { + "height": 120017, + "hash": "0000000003cff9fc185bc527c022b0a6fc44746863efcf2b61fc6fc3580e4871", + "noTxs": 3, + "txDetails": [ + { + "hex": "01000000026953d41701ea33dd3bb2aecaf69e42ad9659f21d4a3659ac33e612c92b910fe60400000000ffffffff69866379514807b1dbbef897d0cbb72fded8898ed2e99a048a2d265481d562460300000001ffffffff036a5c5d000000000000001976a91499c0d32d658f014238ceeb8ef3cdf7ba397c999688ac870d48df0000000000001976a91499c0d32d658f014238ceeb8ef3cdf7ba397c999688ac5c5ddc110000000000001976a914557d1cdbdc4fd6bb7f8f703eb843c256f4b2526588ac00000000000000000265b0170f00000000d0d40100020000006b483045022100def05196b7a16030045d9e667ee5dfc3f327481b46125b908e3bfc3efbb5116602204c1833285de9ae89bc8e07bf538b9c77da0bf27bab65fc01b42dab03fe67b3c701210350c221d43e52e6949a46aaa0db4e4a57870fa1752fdd0695b18c342077601839ae286ae200000000c0d40100010000006a47304402206d6ffc181b98f00d2470adf18b157f1a1c4a3d6b181b8d7bb6163f3d8ad560e602206fbbe567e1895fb51b306785056f0d4585aaaa334cdc6590e6cd8b13f24ddceb012103c7935596dd3242629002f8fb52038ded4c273244ae59d543657596b595816b99", + "txid": "226091c0ab80e59759790afbbb94055aa221a806d7d0e479fcc9b9c9325c9e1a", + "version": 2, + "vin": [ + { + "txid": "e60f912bc912e633ac59364a1df25996ad429ef6caaeb23bdd33ea0117d45369", + "vout": 4, + "sequence": 4294967295, + "scriptSig": { + "hex": "483045022100def05196b7a16030045d9e667ee5dfc3f327481b46125b908e3bfc3efbb5116602204c1833285de9ae89bc8e07bf538b9c77da0bf27bab65fc01b42dab03fe67b3c701210350c221d43e52e6949a46aaa0db4e4a57870fa1752fdd0695b18c342077601839" + } + }, + { + "txid": "4662d58154262d8a049ae9d28e89d8de2fb7cbd097f8bedbb107485179638669", + "vout": 3, + "sequence": 4294967295, + "scriptSig": { + "hex": "47304402206d6ffc181b98f00d2470adf18b157f1a1c4a3d6b181b8d7bb6163f3d8ad560e602206fbbe567e1895fb51b306785056f0d4585aaaa334cdc6590e6cd8b13f24ddceb012103c7935596dd3242629002f8fb52038ded4c273244ae59d543657596b595816b99" + } + } + ], + "vout": [ + { + "value": 0.06118506, + "n": 0, + "scriptPubKey": { + "hex": "76a91499c0d32d658f014238ceeb8ef3cdf7ba397c999688ac", + "addresses": [ + "Tsf36rctvWvXBq4tkLzuAfH3MfjHPbEBdiZ" + ] + } + }, + { + "value": 37.46041223, + "n": 1, + "scriptPubKey": { + "hex": "76a91499c0d32d658f014238ceeb8ef3cdf7ba397c999688ac", + "addresses": [ + "Tsf36rctvWvXBq4tkLzuAfH3MfjHPbEBdiZ" + ] + } + }, + { + "value": 2.99654492, + "n": 2, + "scriptPubKey": { + "hex": "76a914557d1cdbdc4fd6bb7f8f703eb843c256f4b2526588ac", + "addresses": [ + "TsYp9nrGuJvxsjMaEgXjgLUgaUQ2KmT84Ps" + ] + } + } + ], + "time": 1548564206, + "blocktime": 1548564206 + } + ] + }, + "120030": { + "height": 120030, + "hash": "0000000000960cd08532a50b860f627251619252eb3f4038db6ddf0defff2b79", + "noTxs": 3, + "txDetails": [ + { + "hex": "010000000238f6741258c584e3ecc2700acf7b65d4bcab45cc2b2652ac3dd2bcd123364f740300000001ffffffffadc1972f460461dc4ec85ef705a6472e1f9312877dc49f6abed23a888bd2cd220300000001ffffffff056a5c5d000000000000001976a914eb3d6580786e0c1ea7be0bd79547ca2d020bae9d88ac870d48df0000000000001976a914eb3d6580786e0c1ea7be0bd79547ca2d020bae9d88ac6a5c5d000000000000001976a914eb3d6580786e0c1ea7be0bd79547ca2d020bae9d88ac870d48df0000000000001976a914eb3d6580786e0c1ea7be0bd79547ca2d020bae9d88acce1d05010000000000001976a914b7132cd2bfdb2c17c376dd1745b60004563d0a6a88ac000000000000000002ae286ae200000000cdd40100000000006b4830450221008b5bb27b0464586ef8c4d9e02985cc88189c0e65a79a5b1dbd36e73e7728c276022015435d1c31b3c2560ef03e2091870db6f04946fefffc33dc0986bc582f2356e0012103c7935596dd3242629002f8fb52038ded4c273244ae59d543657596b595816b9998dde5dd00000000cdd40100010000006a47304402206897b511153501779cce981b392ea4e352df2f585b8c8951331009739c51939b02204c8308c55b3822356c91869b9b73b85122dfabbcbf2b40d96102c99fc08ab6ab012103c7935596dd3242629002f8fb52038ded4c273244ae59d543657596b595816b99", + "txid": "c95c7409a0fee08e0fcb8fdab14e17f9a8313abafd7cd4bf69af9fde727a4e98", + "version": 1, + "vout": [ + { + "value": 0.06118506, + "n": 0, + "scriptPubKey": { + "hex": "76a914eb3d6580786e0c1ea7be0bd79547ca2d020bae9d88ac", + "addresses": [ + "TsnTxpYTCjpBRo6NdiEeGFksV7s8dEixaMc" + ] + } + }, + { + "value": 37.46041223, + "n": 1, + "scriptPubKey": { + "hex": "76a914eb3d6580786e0c1ea7be0bd79547ca2d020bae9d88ac", + "addresses": [ + "TsnTxpYTCjpBRo6NdiEeGFksV7s8dEixaMc" + ] + } + }, + { + "value": 0.06118506, + "n": 2, + "scriptPubKey": { + "hex": "76a914eb3d6580786e0c1ea7be0bd79547ca2d020bae9d88ac", + "addresses": [ + "TsnTxpYTCjpBRo6NdiEeGFksV7s8dEixaMc" + ] + } + }, + { + "value": 37.46041223, + "n": 3, + "scriptPubKey": { + "hex": "76a914eb3d6580786e0c1ea7be0bd79547ca2d020bae9d88ac", + "addresses": [ + "TsnTxpYTCjpBRo6NdiEeGFksV7s8dEixaMc" + ] + } + }, + { + "value": 0.17112526, + "n": 4, + "scriptPubKey": { + "hex": "76a914b7132cd2bfdb2c17c376dd1745b60004563d0a6a88ac", + "addresses": [ + "Tshi96yRHGfPJWz31yTo5UvDrNQ64VTq56c" + ] + } + } + ], + "time": 1548565264, + "blocktime": 1548565264 + }, + { + "hex": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff032b015e0800000000000017a914d585cd7426d25b4ea5faf1e6987aacfeda3db94287000000000000000000000e6a0cded401003387c553de73dc715cea34320000000000001976a9149bb0ab1ab455c220c575304aa6bb31dad3eff19888ac0000000000000000012e08923a0000000000000000ffffffff0800002f646372642f", + "txid": "72cb7f936a46014fdcef8fc10148274ea95c562948f0aac490ea942290553066", + "version": 1, + "vin": [ + { + "coinbase": "00002f646372642f", + "sequence": 4294967295 + } + ], + "vout": [ + { + "value": 1.40378411, + "n": 0, + "scriptPubKey": { + "hex": "a914d585cd7426d25b4ea5faf1e6987aacfeda3db94287", + "addresses": [ + "TcrypGAcGCRVXrES7hWqVZb5oLJKCZEtoL1" + ] + } + }, + { + "value": 0, + "n": 1, + "scriptPubKey": { + "hex": "6a0cded401003387c553de73dc71" + } + }, + { + "value": 8.42328668, + "n": 2, + "scriptPubKey": { + "hex": "76a9149bb0ab1ab455c220c575304aa6bb31dad3eff19888ac", + "addresses": [ + "TsfDLrRkk9ciUuwfp2b8PawwnukYD7yAjGd" + ] + } + } + ], + "time": 1548565264, + "blocktime": 1548565264 + } + ] + } + } + }, + "connectBlocks": { + "syncRanges": [ + {"lower": 120000, "upper": 120047} + ], + "fakeBlocks": { + "120001": { + "height": 120001, + "hash": "00000000000000e463222f52668f5b66f15c65ee4db3c60ca6853ffa93ed21f3" + }, + "120023": { + "height": 120023, + "hash": "00000000000002327a29e7fdf539e773e960e631622c46cf859efebdbb78f9a8" + }, + "120047": { + "height": 120047, + "hash": "000000000000034fa12d7d3167b5cc37ae8b8e5e305b243c4842fdc576453837" + } + }, + "realBlocks": { + "120001": { + "height": 120001, + "hash": "0000000007bf728a0fd7b41825763da0bf41bc86aea1813fe52d658ca76d1b2c" + }, + "120023": { + "height": 120023, + "hash": "0000000003d011dc3b52c13ea2098367bc6d8de9c66ba322a02461d1bc125395" + }, + "120047": { + "height": 120047, + "hash": "00000000038ec6283472cb71d404eacef8d62045a3bb9a45f7c314cf98ee4d4e" + } + } + } +} diff --git a/tests/tests.json b/tests/tests.json index c55a665f..d554d075 100644 --- a/tests/tests.json +++ b/tests/tests.json @@ -37,6 +37,16 @@ "EstimateSmartFee", "EstimateFee", "GetBestBlockHash", "GetBestBlockHeight", "GetBlockHeader"], "sync": ["ConnectBlocksParallel", "ConnectBlocks", "HandleFork"] }, + "decred": { + "rpc": ["GetBlock", "GetBlockHash", "GetTransaction", "MempoolSync", + "EstimateFee", "GetBestBlockHash", "GetBestBlockHeight", "GetBlockHeader"], + "sync": ["ConnectBlocksParallel", "ConnectBlocks"] + }, + "decred_testnet": { + "rpc": ["GetBlock", "GetBlockHash", "GetTransaction", "MempoolSync", + "EstimateFee", "GetBestBlockHash", "GetBestBlockHeight", "GetBlockHeader"], + "sync": ["ConnectBlocksParallel", "ConnectBlocks"] + }, "digibyte": { "rpc": ["GetBlock", "GetBlockHash", "GetTransaction", "GetTransactionForMempool", "MempoolSync", "EstimateSmartFee", "EstimateFee", "GetBestBlockHash", "GetBestBlockHeight", "GetBlockHeader"]