From 6945d1d7f3ed03036676f20e6aa517e0f73facf3 Mon Sep 17 00:00:00 2001 From: Martin Boehm Date: Thu, 14 Jun 2018 23:12:19 +0200 Subject: [PATCH] Add dogecoin blockbook implementation --- bchain/coins/blockchain.go | 2 + bchain/coins/btc/bitcoinrpc.go | 4 +- bchain/coins/dogecoin/dogecoinparser.go | 47 ++++ bchain/coins/dogecoin/dogecoinparser_test.go | 273 +++++++++++++++++++ bchain/coins/dogecoin/dogecoinrpc.go | 71 +++++ configs/dogecoin.json | 12 + 6 files changed, 407 insertions(+), 2 deletions(-) create mode 100644 bchain/coins/dogecoin/dogecoinparser.go create mode 100644 bchain/coins/dogecoin/dogecoinparser_test.go create mode 100644 bchain/coins/dogecoin/dogecoinrpc.go create mode 100644 configs/dogecoin.json diff --git a/bchain/coins/blockchain.go b/bchain/coins/blockchain.go index ea995562..12827037 100644 --- a/bchain/coins/blockchain.go +++ b/bchain/coins/blockchain.go @@ -6,6 +6,7 @@ import ( "blockbook/bchain/coins/btc" "blockbook/bchain/coins/btg" "blockbook/bchain/coins/dash" + "blockbook/bchain/coins/dogecoin" "blockbook/bchain/coins/eth" "blockbook/bchain/coins/litecoin" "blockbook/bchain/coins/zec" @@ -38,6 +39,7 @@ func init() { blockChainFactories["Dash Testnet"] = dash.NewDashRPC blockChainFactories["Litecoin"] = litecoin.NewLitecoinRPC blockChainFactories["Litecoin Testnet"] = litecoin.NewLitecoinRPC + blockChainFactories["Dogecoin"] = dogecoin.NewDogecoinRPC } // GetCoinNameFromConfig gets coin name from config file diff --git a/bchain/coins/btc/bitcoinrpc.go b/bchain/coins/btc/bitcoinrpc.go index df855f35..ac482b8b 100644 --- a/bchain/coins/btc/bitcoinrpc.go +++ b/bchain/coins/btc/bitcoinrpc.go @@ -458,7 +458,7 @@ func (b *BitcoinRPC) GetBlock(hash string, height uint32) (*bchain.Block, error) } // optimization if height > 0 { - return b.getBlockWithoutHeader(hash, height) + return b.GetBlockWithoutHeader(hash, height) } header, err := b.GetBlockHeader(hash) if err != nil { @@ -478,7 +478,7 @@ func (b *BitcoinRPC) GetBlock(hash string, height uint32) (*bchain.Block, error) // getBlockWithoutHeader is an optimization - it does not call GetBlockHeader to get prev, next hashes // instead it sets to header only block hash and height passed in parameters -func (b *BitcoinRPC) getBlockWithoutHeader(hash string, height uint32) (*bchain.Block, error) { +func (b *BitcoinRPC) GetBlockWithoutHeader(hash string, height uint32) (*bchain.Block, error) { data, err := b.GetBlockRaw(hash) if err != nil { return nil, err diff --git a/bchain/coins/dogecoin/dogecoinparser.go b/bchain/coins/dogecoin/dogecoinparser.go new file mode 100644 index 00000000..16b76c6f --- /dev/null +++ b/bchain/coins/dogecoin/dogecoinparser.go @@ -0,0 +1,47 @@ +package dogecoin + +import ( + "blockbook/bchain/coins/btc" + + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/wire" +) + +const ( + MainnetMagic wire.BitcoinNet = 0xc0c0c0c0 +) + +var ( + MainNetParams chaincfg.Params +) + +func init() { + MainNetParams = chaincfg.MainNetParams + MainNetParams.Net = MainnetMagic + MainNetParams.PubKeyHashAddrID = 30 + MainNetParams.ScriptHashAddrID = 22 + + err := chaincfg.Register(&MainNetParams) + if err != nil { + panic(err) + } +} + +// DogecoinParser handle +type DogecoinParser struct { + *btc.BitcoinParser +} + +// NewDogecoinParser returns new DogecoinParser instance +func NewDogecoinParser(params *chaincfg.Params, c *btc.Configuration) *DogecoinParser { + return &DogecoinParser{BitcoinParser: btc.NewBitcoinParser(params, c)} +} + +// GetChainParams contains network parameters for the main Dogecoin network, +// and the test Dogecoin network +func GetChainParams(chain string) *chaincfg.Params { + switch chain { + default: + return &MainNetParams + } +} diff --git a/bchain/coins/dogecoin/dogecoinparser_test.go b/bchain/coins/dogecoin/dogecoinparser_test.go new file mode 100644 index 00000000..eb03ead7 --- /dev/null +++ b/bchain/coins/dogecoin/dogecoinparser_test.go @@ -0,0 +1,273 @@ +package dogecoin + +import ( + "blockbook/bchain" + "blockbook/bchain/coins/btc" + "encoding/hex" + "reflect" + "testing" +) + +func TestAddressToOutputScript_Mainnet(t *testing.T) { + type args struct { + address string + } + tests := []struct { + name string + args args + want string + wantErr bool + }{ + { + name: "P2PKH1", + args: args{address: "DHZYinsaM9nW5piCMN639ELRKbZomThPnZ"}, + want: "76a9148841590909747c0f97af158f22fadacb1652522088ac", + wantErr: false, + }, + { + name: "P2PKH2", + args: args{address: "DSzaAYEYyy9ngjoJ294r7jzFM3xhD6bKHK"}, + want: "76a914efb6158f75743c611858fdfd0f4aaec6cc6196bc88ac", + wantErr: false, + }, + { + name: "P2SH1", + args: args{address: "9tg1kVUk339Tk58ewu5T8QT82Z6cE4UvSU"}, + want: "a9141889a089400ea25d28694fd98aa7702b21eeeab187", + wantErr: false, + }, + { + name: "P2SH2", + args: args{address: "9sLa1AKzjWuNTe1CkLh5GDYyRP9enb1Spp"}, + want: "a91409e41aff9f97412ab3d4a07cf0667fdba84caf4487", + wantErr: false, + }, + } + parser := NewDogecoinParser(GetChainParams("main"), &btc.Configuration{}) + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := parser.AddressToOutputScript(tt.args.address) + if (err != nil) != tt.wantErr { + t.Errorf("AddressToOutputScript() error = %v, wantErr %v", err, tt.wantErr) + return + } + h := hex.EncodeToString(got) + if !reflect.DeepEqual(h, tt.want) { + t.Errorf("AddressToOutputScript() = %v, want %v", h, tt.want) + } + }) + } +} + +var ( + testTx1 bchain.Tx + testTxPacked1 = "00030e6d8ba8d7aa2001000000016b3c0c53267964120acf7f7e72217e3f463e52ce622f89659f6a6bb8e69a4d91000000006c493046022100a96454237e3a020994534583e28c04757881374bceac89f933ea9ff00b4db259022100fbb757ff7ea4f02c4e42556b2834c61eba1f1af605db089d836a0614d90a3b46012103cebdde6d1046e285df4f48497bc50dc20a4a258ca5b7308cb0a929c9fdadcd9dffffffff0217e823ca7f0200001976a914eef21768a546590993e313c7f3dfadf6a6efa1e888acaddf4cba010000001976a914e0fee2ea29dd9c6c759d8341bd0da4c4f738cced88ac00000000" + + testTx2 bchain.Tx + testTxPacked2 = "0001193a8ba8d7835601000000016d0211b5656f1b8c2ac002445638e247082090ffc5d5fa7c38b445b84a2c2054000000006b4830450221008856f2f620df278c0fc6a5d5e2d50451c0a65a75aaf7a4a9cbfcac3918b5536802203dc685a784d49e2a95eb72763ad62f02094af78507c57b0a3c3f1d8a60f74db6012102db814cd43df584804fde1949365a6309714e342aef0794dc58385d7e413444cdffffffff0237daa2ee0a4715001976a9149355c01ed20057eac9fe0bbf8b07d87e62fe712d88ac8008389e7e8d03001976a9145b4f2511c94e4fcaa8f8835b2458f8cb6542ca7688ac00000000" +) + +func init() { + var ( + addr1, addr2, addr3, addr4 bchain.Address + err error + ) + addr1, err = bchain.NewBaseAddress("DSvXNiqvG42wdteLqh3i6inxgDTs8Y9w2i") + if err == nil { + addr2, err = bchain.NewBaseAddress("DRemF3ZcqJ1PFeM7e7sXzzwQJKR8GNUtwK") + } + if err == nil { + addr3, err = bchain.NewBaseAddress("DJa8bWDrZKu4HgsYRYWuJrvxt6iTYuvXJ6") + } + if err == nil { + addr4, err = bchain.NewBaseAddress("DDTtqnuZ5kfRT5qh2c7sNtqrJmV3iXYdGG") + } + if err != nil { + panic(err) + } + + testTx1 = bchain.Tx{ + Hex: "01000000016b3c0c53267964120acf7f7e72217e3f463e52ce622f89659f6a6bb8e69a4d91000000006c493046022100a96454237e3a020994534583e28c04757881374bceac89f933ea9ff00b4db259022100fbb757ff7ea4f02c4e42556b2834c61eba1f1af605db089d836a0614d90a3b46012103cebdde6d1046e285df4f48497bc50dc20a4a258ca5b7308cb0a929c9fdadcd9dffffffff0217e823ca7f0200001976a914eef21768a546590993e313c7f3dfadf6a6efa1e888acaddf4cba010000001976a914e0fee2ea29dd9c6c759d8341bd0da4c4f738cced88ac00000000", + Blocktime: 1519053456, + Txid: "097ea09ba284f3f2a9e880e11f837edf7e5cea81c8da2238f5bc7c2c4c407943", + LockTime: 0, + Vin: []bchain.Vin{ + { + ScriptSig: bchain.ScriptSig{ + Hex: "493046022100a96454237e3a020994534583e28c04757881374bceac89f933ea9ff00b4db259022100fbb757ff7ea4f02c4e42556b2834c61eba1f1af605db089d836a0614d90a3b46012103cebdde6d1046e285df4f48497bc50dc20a4a258ca5b7308cb0a929c9fdadcd9d", + }, + Txid: "914d9ae6b86b6a9f65892f62ce523e463f7e21727e7fcf0a12647926530c3c6b", + Vout: 0, + Sequence: 4294967295, + }, + }, + Vout: []bchain.Vout{ + { + Value: 27478.75452951, + N: 0, + ScriptPubKey: bchain.ScriptPubKey{ + Hex: "76a914eef21768a546590993e313c7f3dfadf6a6efa1e888ac", + Addresses: []string{ + "DSvXNiqvG42wdteLqh3i6inxgDTs8Y9w2i", + }, + }, + Address: addr1, + }, + { + Value: 74.20567469, + N: 1, + ScriptPubKey: bchain.ScriptPubKey{ + Hex: "76a914e0fee2ea29dd9c6c759d8341bd0da4c4f738cced88ac", + Addresses: []string{ + "DRemF3ZcqJ1PFeM7e7sXzzwQJKR8GNUtwK", + }, + }, + Address: addr2, + }, + }, + } + + testTx2 = bchain.Tx{ + Hex: "01000000016d0211b5656f1b8c2ac002445638e247082090ffc5d5fa7c38b445b84a2c2054000000006b4830450221008856f2f620df278c0fc6a5d5e2d50451c0a65a75aaf7a4a9cbfcac3918b5536802203dc685a784d49e2a95eb72763ad62f02094af78507c57b0a3c3f1d8a60f74db6012102db814cd43df584804fde1949365a6309714e342aef0794dc58385d7e413444cdffffffff0237daa2ee0a4715001976a9149355c01ed20057eac9fe0bbf8b07d87e62fe712d88ac8008389e7e8d03001976a9145b4f2511c94e4fcaa8f8835b2458f8cb6542ca7688ac00000000", + Blocktime: 1519050987, + Txid: "b276545af246e3ed5a4e3e5b60d359942a1808579effc53ff4f343e4f6cfc5a0", + LockTime: 0, + Vin: []bchain.Vin{ + { + ScriptSig: bchain.ScriptSig{ + Hex: "4830450221008856f2f620df278c0fc6a5d5e2d50451c0a65a75aaf7a4a9cbfcac3918b5536802203dc685a784d49e2a95eb72763ad62f02094af78507c57b0a3c3f1d8a60f74db6012102db814cd43df584804fde1949365a6309714e342aef0794dc58385d7e413444cd", + }, + Txid: "54202c4ab845b4387cfad5c5ff90200847e238564402c02a8c1b6f65b511026d", + Vout: 0, + Sequence: 4294967295, + }, + }, + Vout: []bchain.Vout{ + { + Value: 59890867.89818935, + N: 0, + ScriptPubKey: bchain.ScriptPubKey{ + Hex: "76a9149355c01ed20057eac9fe0bbf8b07d87e62fe712d88ac", + Addresses: []string{ + "DJa8bWDrZKu4HgsYRYWuJrvxt6iTYuvXJ6", + }, + }, + Address: addr3, + }, + { + Value: 9999998.90000000, + N: 1, + ScriptPubKey: bchain.ScriptPubKey{ + Hex: "76a9145b4f2511c94e4fcaa8f8835b2458f8cb6542ca7688ac", + Addresses: []string{ + "DDTtqnuZ5kfRT5qh2c7sNtqrJmV3iXYdGG", + }, + }, + Address: addr4, + }, + }, + } +} + +func Test_PackTx(t *testing.T) { + type args struct { + tx bchain.Tx + height uint32 + blockTime int64 + parser *DogecoinParser + } + tests := []struct { + name string + args args + want string + wantErr bool + }{ + { + name: "dogecoin-1", + args: args{ + tx: testTx1, + height: 200301, + blockTime: 1519053456, + parser: NewDogecoinParser(GetChainParams("main"), &btc.Configuration{}), + }, + want: testTxPacked1, + wantErr: false, + }, + { + name: "dogecoin-2", + args: args{ + tx: testTx2, + height: 71994, + blockTime: 1519050987, + parser: NewDogecoinParser(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 *DogecoinParser + } + tests := []struct { + name string + args args + want *bchain.Tx + want1 uint32 + wantErr bool + }{ + { + name: "dogecoin-1", + args: args{ + packedTx: testTxPacked1, + parser: NewDogecoinParser(GetChainParams("main"), &btc.Configuration{}), + }, + want: &testTx1, + want1: 200301, + wantErr: false, + }, + { + name: "dogecoin-2", + args: args{ + packedTx: testTxPacked2, + parser: NewDogecoinParser(GetChainParams("main"), &btc.Configuration{}), + }, + want: &testTx2, + want1: 71994, + 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) + } + }) + } +} diff --git a/bchain/coins/dogecoin/dogecoinrpc.go b/bchain/coins/dogecoin/dogecoinrpc.go new file mode 100644 index 00000000..84a28e7c --- /dev/null +++ b/bchain/coins/dogecoin/dogecoinrpc.go @@ -0,0 +1,71 @@ +package dogecoin + +import ( + "blockbook/bchain" + "blockbook/bchain/coins/btc" + "encoding/json" + + "github.com/golang/glog" +) + +// DogecoinRPC is an interface to JSON-RPC dogecoind service. +type DogecoinRPC struct { + *btc.BitcoinRPC +} + +// NewDogecoinRPC returns new DogecoinRPC instance. +func NewDogecoinRPC(config json.RawMessage, pushHandler func(bchain.NotificationType)) (bchain.BlockChain, error) { + b, err := btc.NewBitcoinRPC(config, pushHandler) + if err != nil { + return nil, err + } + + s := &DogecoinRPC{ + b.(*btc.BitcoinRPC), + } + s.RPCMarshaler = btc.JSONMarshalerV1{} + + return s, nil +} + +// Initialize initializes DogecoinRPC instance. +func (b *DogecoinRPC) Initialize() error { + chainName, err := b.GetChainInfoAndInitializeMempool(b) + if err != nil { + return err + } + + glog.Info("Chain name ", chainName) + params := GetChainParams(chainName) + + // always create parser + b.Parser = NewDogecoinParser(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 (b *DogecoinRPC) GetBlock(hash string, height uint32) (*bchain.Block, error) { + var err error + if hash == "" { + hash, err = b.GetBlockHash(height) + if err != nil { + return nil, err + } + } + if !b.ParseBlocks { + return b.GetBlockFull(hash) + } + return b.GetBlockWithoutHeader(hash, height) +} diff --git a/configs/dogecoin.json b/configs/dogecoin.json new file mode 100644 index 00000000..c16fdc3a --- /dev/null +++ b/configs/dogecoin.json @@ -0,0 +1,12 @@ +{ + "coin_name": "Dogecoin", + "rpcURL": "http://127.0.0.1:8038", + "rpcUser": "rpc", + "rpcPass": "rpcp", + "rpcTimeout": 25, + "parse": true, + "zeroMQBinding": "tcp://127.0.0.1:38338", + "mempoolWorkers": 8, + "mempoolSubWorkers": 2, + "blockAddressesToKeep": 300 +}