diff --git a/bchain/coins/blockchain.go b/bchain/coins/blockchain.go index c4662e4a..eae2d43c 100644 --- a/bchain/coins/blockchain.go +++ b/bchain/coins/blockchain.go @@ -25,6 +25,7 @@ import ( "blockbook/bchain/coins/polis" "blockbook/bchain/coins/qtum" "blockbook/bchain/coins/vertcoin" + "blockbook/bchain/coins/viacoin" "blockbook/bchain/coins/xzc" "blockbook/bchain/coins/zec" "blockbook/common" @@ -81,6 +82,7 @@ func init() { BlockChainFactories["Flo"] = flo.NewFloRPC BlockChainFactories["Bellcoin"] = bellcoin.NewBellcoinRPC BlockChainFactories["Qtum"] = qtum.NewQtumRPC + BlockChainFactories["Viacoin"] = viacoin.NewViacoinRPC BlockChainFactories["Qtum Testnet"] = qtum.NewQtumRPC BlockChainFactories["NULS"] = nuls.NewNulsRPC } diff --git a/bchain/coins/viacoin/viacoinparser.go b/bchain/coins/viacoin/viacoinparser.go new file mode 100644 index 00000000..cd8ea879 --- /dev/null +++ b/bchain/coins/viacoin/viacoinparser.go @@ -0,0 +1,101 @@ +package viacoin + +import ( + "blockbook/bchain" + "blockbook/bchain/coins/btc" + "blockbook/bchain/coins/utils" + "bytes" + "github.com/martinboehm/btcd/wire" + "github.com/martinboehm/btcutil/chaincfg" +) + +const ( + MainnetMagic wire.BitcoinNet = 0xcbc6680f + RegtestMagic wire.BitcoinNet = 0x377b972d +) + +var ( + MainNetParams chaincfg.Params + RegtestParams chaincfg.Params +) + +func init() { + MainNetParams = chaincfg.MainNetParams + MainNetParams.Net = MainnetMagic + + // Mainnet address encoding magics + MainNetParams.PubKeyHashAddrID = []byte{71} // base58 prefix: v + MainNetParams.ScriptHashAddrID = []byte{33} // base68 prefix: E + MainNetParams.Bech32HRPSegwit = "via" + + RegtestParams = chaincfg.RegressionNetParams + RegtestParams.Net = RegtestMagic + + // Regtest address encoding magics + RegtestParams.PubKeyHashAddrID = []byte{111} // base58 prefix: m or n + RegtestParams.ScriptHashAddrID = []byte{196} // base58 prefix: 2 + RegtestParams.Bech32HRPSegwit = "tvia" + + err := chaincfg.Register(&MainNetParams) + if err == nil { + err = chaincfg.Register(&RegtestParams) + } + if err != nil { + panic(err) + } +} + +// ViacoinParser handle +type ViacoinParser struct { + *btc.BitcoinParser +} + +// NewViacoinParser returns new VertcoinParser instance +func NewViacoinParser(params *chaincfg.Params, c *btc.Configuration) *ViacoinParser { + return &ViacoinParser{BitcoinParser: btc.NewBitcoinParser(params, c)} +} + +func GetChainParams(chain string) *chaincfg.Params { + switch chain { + case "regtest": + return &RegtestParams + default: + return &MainNetParams + } +} + +// ParseBlock parses raw block to our Block struct +// it has special handling for Auxpow blocks that cannot be parsed by standard btc wire parse +func (p *ViacoinParser) 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, err + } + + if (h.Version & utils.VersionAuxpow) != 0 { + if err = utils.SkipAuxpow(r); err != nil { + return nil, err + } + } + + err = utils.DecodeTransactions(r, 0, wire.WitnessEncoding, &w) + if 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 +} diff --git a/bchain/coins/viacoin/viacoinparser_test.go b/bchain/coins/viacoin/viacoinparser_test.go new file mode 100644 index 00000000..45e91b1d --- /dev/null +++ b/bchain/coins/viacoin/viacoinparser_test.go @@ -0,0 +1,207 @@ +// +build unittest + +package viacoin + +import ( + "blockbook/bchain" + "blockbook/bchain/coins/btc" + "encoding/hex" + "math/big" + "os" + "reflect" + "testing" + + "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: "VhyGT8kJU9x28dHwjf1jEDG8gMY8yhckDR"}, + want: "76a91457757edd001d16528c7aa337b314a7bab303ee8088ac", + wantErr: false, + }, + { + name: "P2PKH2", + args: args{address: "VdMPvn7vUTSzbYjiMDs1jku9wAh1Ri2Y1A"}, + want: "76a91424cc424c1e5e977175d2b20012554d39024bd68f88ac", + wantErr: false, + }, + { + name: "P2SH1", + args: args{address: "EUtqKT17p1LdHTDyGL1b2WPJiUFidS6gVq"}, + want: "a91480c7c8faece680bab1ae81a5969815a05b7565f087", + wantErr: false, + }, + { + name: "P2SH2", + args: args{address: "EMdC3QPzx2MsJG56x2QbSR727dRM73B1rK"}, + want: "a91431098c569891a8ff1fa11d1cbd3d46ca5e245c6b87", + wantErr: false, + }, + { + name: "witness_v0_keyhash", + args: args{address: "via1q95qlu98cpj23xy6w9tdnfn65n5vkpkey99g6wl"}, + want: "00142d01fe14f80c9513134e2adb34cf549d1960db24", + wantErr: false, + }, + } + parser := NewViacoinParser(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 = "004eb96d8bb3b6c6140200000001ddc431a8f5c4e74296de8a6bff796ece148b9bd6827a80ecde8671df41a51fc7000000006a47304402204f929a1e1e40bd352bbd5d3c5ae6c29740e5a8b29dd8c53a15d3eab29aecee7c02206a514e5e4561cfb9330d98f4a4fe1385af56d87eb1d3e1a379d7a50276788cfe0121034a9305644fbcb56d4fc0bc15959b917f7753ae8e581acc97f9cfe771ad1e8249feffffff0200ca9a3b000000001976a91456c7359ed52d61c1ca371d7dc136632148169c5e88acd0e8cc10000000001976a914112e29df5df4866e40ef98e0857036b275380fe088ac6ab94e00" +) + +func init() { + testTx1 = bchain.Tx{ + Hex: "0200000001ddc431a8f5c4e74296de8a6bff796ece148b9bd6827a80ecde8671df41a51fc7000000006a47304402204f929a1e1e40bd352bbd5d3c5ae6c29740e5a8b29dd8c53a15d3eab29aecee7c02206a514e5e4561cfb9330d98f4a4fe1385af56d87eb1d3e1a379d7a50276788cfe0121034a9305644fbcb56d4fc0bc15959b917f7753ae8e581acc97f9cfe771ad1e8249feffffff0200ca9a3b000000001976a91456c7359ed52d61c1ca371d7dc136632148169c5e88acd0e8cc10000000001976a914112e29df5df4866e40ef98e0857036b275380fe088ac6ab94e00", + Blocktime: 1530319242, + Txid: "d0284c75a389a07cc256e0bb913110d8d8059efd04daa8147ecf2fa0b3bdf6ff", + LockTime: 5159274, + Version: 2, + Vin: []bchain.Vin{ + { + ScriptSig: bchain.ScriptSig{ + Hex: "47304402204f929a1e1e40bd352bbd5d3c5ae6c29740e5a8b29dd8c53a15d3eab29aecee7c02206a514e5e4561cfb9330d98f4a4fe1385af56d87eb1d3e1a379d7a50276788cfe0121034a9305644fbcb56d4fc0bc15959b917f7753ae8e581acc97f9cfe771ad1e8249", + }, + Txid: "c71fa541df7186deec807a82d69b8b14ce6e79ff6b8ade9642e7c4f5a831c4dd", + Vout: 0, + Sequence: 4294967294, + }, + }, + Vout: []bchain.Vout{ + { + ValueSat: *big.NewInt(1000000000), + N: 0, + ScriptPubKey: bchain.ScriptPubKey{ + Hex: "76a91456c7359ed52d61c1ca371d7dc136632148169c5e88ac", + Addresses: []string{ + "VhuffXKNA3j9hgp2JYGrj6uHQ6KUU6zNbS", + }, + }, + }, + { + ValueSat: *big.NewInt(281864400), + N: 1, + ScriptPubKey: bchain.ScriptPubKey{ + Hex: "76a914112e29df5df4866e40ef98e0857036b275380fe088ac", + Addresses: []string{ + "VbZfhUMCUJHDjqjby6ynYFPZSNVYhfe4cK", + }, + }, + }, + }, + } +} + +func Test_PackTx(t *testing.T) { + type args struct { + tx bchain.Tx + height uint32 + blockTime int64 + parser *ViacoinParser + } + tests := []struct { + name string + args args + want string + wantErr bool + }{ + { + name: "viacoin-1", + args: args{ + tx: testTx1, + height: 5159277, + blockTime: 1530319242, + parser: NewViacoinParser(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 *ViacoinParser + } + tests := []struct { + name string + args args + want *bchain.Tx + want1 uint32 + wantErr bool + }{ + { + name: "viacoin-1", + args: args{ + packedTx: testTxPacked1, + parser: NewViacoinParser(GetChainParams("main"), &btc.Configuration{}), + }, + want: &testTx1, + want1: 5159277, + 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/viacoin/viacoinrpc.go b/bchain/coins/viacoin/viacoinrpc.go new file mode 100644 index 00000000..877a987c --- /dev/null +++ b/bchain/coins/viacoin/viacoinrpc.go @@ -0,0 +1,73 @@ +package viacoin + +import ( + "blockbook/bchain" + "blockbook/bchain/coins/btc" + "encoding/json" + "github.com/golang/glog" +) + +// ViacoinRPC is an interface to JSON-RPC bitcoind service + +type ViacoinRPC struct { + *btc.BitcoinRPC +} + +// NewViacoinRPC returns new ViacoinRPC instance +func NewViacoinRPC(config json.RawMessage, pushHandler func(notificationType bchain.NotificationType)) (bchain.BlockChain, error) { + b, err := btc.NewBitcoinRPC(config, pushHandler) + if err != nil { + return nil, err + } + + s := &ViacoinRPC{ + b.(*btc.BitcoinRPC), + } + s.RPCMarshaler = btc.JSONMarshalerV1{} + s.ChainConfig.SupportsEstimateSmartFee = false + + return s, nil +} + +// Initialize initializes ViacoinRPC instance. +func (b *ViacoinRPC) 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 = NewViacoinParser(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 *ViacoinRPC) 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/coins/viacoin.json b/configs/coins/viacoin.json new file mode 100644 index 00000000..95e7aecb --- /dev/null +++ b/configs/coins/viacoin.json @@ -0,0 +1,72 @@ +{ + "coin": { + "name": "Viacoin", + "shortcut": "VIA", + "label": "Viacoin", + "alias": "viacoin" + }, + "ports": { + "backend_rpc": 8055, + "backend_message_queue": 38355, + "blockbook_internal": 9055, + "blockbook_public": 9155 + }, + "ipc": { + "rpc_url_template": "http://127.0.0.1:{{.Ports.BackendRPC}}", + "rpc_user": "rpc", + "rpc_pass": "rpcp", + "rpc_timeout": 25, + "message_queue_binding_template": "tcp://127.0.0.1:{{.Ports.BackendMessageQueue}}" + }, + "backend": { + "package_name": "backend-viacoin", + "package_revision": "satoshilabs-1", + "system_user": "viacoin", + "version": "1.14-beta-1", + "binary_url": "https://github.com/viacoin/viacoin/releases/download/v0.15.2/viacoin-0.15.2-x86_64-linux-gnu.tar.gz", + "verification_type": "sha256", + "verification_source": "bdbd432645a8b4baadddb7169ea4bef3d03f80dc2ce53dce5783d8582ac63bab", + "extract_command": "tar -C backend --strip 1 -xf", + "exclude_files": [ + "bin/viacoin-qt" + ], + "exec_command_template": "{{.Env.BackendInstallPath}}/{{.Coin.Alias}}/bin/viacoind -datadir={{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend -conf={{.Env.BackendInstallPath}}/{{.Coin.Alias}}/{{.Coin.Alias}}.conf -pid=/run/{{.Coin.Alias}}/{{.Coin.Alias}}.pid", + "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": { + "discover": 0, + "rpcthreads": 16, + "upnp": 0, + "whitelist": "127.0.0.1" + } + }, + "blockbook": { + "package_name": "blockbook-viacoin", + "system_user": "blockbook-viacoin", + "internal_binding_template": ":{{.Ports.BlockbookInternal}}", + "public_binding_template": ":{{.Ports.BlockbookPublic}}", + "explorer_url": "", + "additional_params": "-resyncindexperiod=30011 -resyncmempoolperiod=2011", + "block_chain": { + "parse": true, + "mempool_workers": 8, + "mempool_sub_workers": 2, + "block_addresses_to_keep": 300, + "xpub_magic": 76067358, + "xpub_magic_segwit_p2sh": 77429938, + "xpub_magic_segwit_native": 78792518, + "slip44": 14, + "additional_params": {} + } + }, + "meta": { + "package_maintainer": "Romano", + "package_maintainer_email": "romanornr@gmail.com" + } +} \ No newline at end of file diff --git a/docs/ports.md b/docs/ports.md index 84e5eaf5..462b16e3 100644 --- a/docs/ports.md +++ b/docs/ports.md @@ -26,6 +26,7 @@ | Koto | 9051 | 9151 | 8051 | 38351 | | Bellcoin | 9052 | 9152 | 8052 | 38352 | | NULS | 9053 | 9153 | 8053 | 38353 | +| Viacoin | 9055 | 9155 | 8055 | 38355 | | Flo | 9066 | 9166 | 8066 | 38366 | | Polis | 9067 | 9167 | 8067 | 38367 | | Qtum | 9088 | 9188 | 8088 | 38388 | diff --git a/tests/rpc/testdata/viacoin.json b/tests/rpc/testdata/viacoin.json new file mode 100644 index 00000000..e6312836 --- /dev/null +++ b/tests/rpc/testdata/viacoin.json @@ -0,0 +1,36 @@ +{ + "blockHeight": 6197648, + "blockHash": "7bfaf2048ce581f05ea6c87cb043bb5908e6afa83ef49621bf558a027cfe367e", + "blockTime": 1555275169, + "blockTxs": [ + "79f2c2690cae8e3e4631a30462197316228fe0e5cf4f2f2eee904d8dcd0206c6" + ], + "txDetails": { + "79f2c2690cae8e3e4631a30462197316228fe0e5cf4f2f2eee904d8dcd0206c6": { + "hex": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff090390915e045cb39da1ffffffff01ca9a3b00000000001976a91424cc424c1e5e977175d2b20012554d39024bd68f88ac00000000", + "txid": "79f2c2690cae8e3e4631a30462197316228fe0e5cf4f2f2eee904d8dcd0206c6", + "blocktime": 1555275169, + "time": 1555275169, + "locktime": 0, + "version": 1, + "vin": [ + { + "coinbase": "0390915e045cb39da1", + "sequence": 4294967295 + } + ], + "vout": [ + { + "value": 0.0390625, + "n": 0, + "scriptPubKey": { + "hex": "76a91424cc424c1e5e977175d2b20012554d39024bd68f88ac", + "addresses": [ + "VdMPvn7vUTSzbYjiMDs1jku9wAh1Ri2Y1A" + ] + } + } + ] + } + } +} \ No newline at end of file diff --git a/tests/sync/testdata/viacoin.json b/tests/sync/testdata/viacoin.json new file mode 100644 index 00000000..1a53002c --- /dev/null +++ b/tests/sync/testdata/viacoin.json @@ -0,0 +1,71 @@ +{ + "connectBlocks": { + "syncRanges": [ + {"lower": 1503776, "upper": 1503796} + ], + "blocks": { + "1503796": { + "height": 1503796, + "hash": "f907e10100b3ec0be53c4dd40d0efb563a9d1de1d2b7a156ffec03b8d364ffb4", + "noTxs": 1, + "txDetails": [ + { + "txid": "d819588eab648eb7d99f2acc2e4cf21c546a5b15d7090620f703fd9a0d384e59", + "version": 1, + "vin": [ + { + "coinbase": "0334f2160101", + "sequence": 4294967295 + } + ], + "vout": [ + { + "value": 5.00000000, + "n": 0, + "scriptPubKey": { + "hex": "76a914d02029d9dffe8f7cbe08122cacb8f57596132f3488ac" + } + } + ], + "hex": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff060334f2160101ffffffff010065cd1d000000001976a914d02029d9dffe8f7cbe08122cacb8f57596132f3488ac00000000", + "time": 1442385302, + "blocktime": 1442385302 + } + ] + } + } + }, + "handleFork": { + "syncRanges": [ + {"lower": 1503790, "upper": 1503796} + ], + "fakeBlocks": { + "1503794": { + "height": 1503794, + "hash": "a1b30026cfd15e4f46c5ffbd57f615d71e5c5d2366b079131123bf055d9e812f" + }, + "1503795": { + "height": 1503795, + "hash": "2d92315148dd3568c1a9ab7bf6bd893ed0ca012372f69e139e026453f23d92c4" + }, + "1503796": { + "height": 1503796, + "hash": "dbc84d9af0a74252f337cbcc2fdd6295db53c2cea2bd94ba8afe66f24bc53a5b" + } + }, + "realBlocks": { + "1503794": { + "height": 1503794, + "hash": "9f1ad78ac7a7f4bf2ca32a2be4117495510ec517525209c854b51d9b7bfefa95" + }, + "1503795": { + "height": 1503795, + "hash": "88084fc69161eb5868bd146d78fb10fe7d80a474029b23728809e0a744bcf5dd" + }, + "1503796": { + "height": 1503796, + "hash": "f907e10100b3ec0be53c4dd40d0efb563a9d1de1d2b7a156ffec03b8d364ffb4" + } + } + } +} \ No newline at end of file diff --git a/tests/tests.json b/tests/tests.json index 69833495..7e9d5aa3 100644 --- a/tests/tests.json +++ b/tests/tests.json @@ -131,6 +131,11 @@ "EstimateSmartFee", "GetBestBlockHash", "GetBestBlockHeight", "GetBlockHeader"], "sync": ["ConnectBlocksParallel", "ConnectBlocks", "HandleFork"] }, + "viacoin": { + "rpc": ["GetBlock", "GetBlockHash", "GetTransaction", "GetTransactionForMempool", "MempoolSync", + "EstimateSmartFee", "GetBestBlockHash", "GetBestBlockHeight", "GetBlockHeader"], + "sync": ["ConnectBlocksParallel", "ConnectBlocks", "HandleFork"] + }, "nuls": { "rpc": ["GetBlock", "GetBlockHash", "GetTransaction", "GetBestBlockHash", "GetBestBlockHeight", "GetBlockHeader"], "sync": ["ConnectBlocksParallel", "ConnectBlocks", "HandleFork"]