diff --git a/bchain/coins/blockchain.go b/bchain/coins/blockchain.go index 33ced306..a49aa685 100644 --- a/bchain/coins/blockchain.go +++ b/bchain/coins/blockchain.go @@ -29,6 +29,7 @@ import ( "blockbook/bchain/coins/qtum" "blockbook/bchain/coins/ravencoin" "blockbook/bchain/coins/ritocoin" + "blockbook/bchain/coins/unobtanium" "blockbook/bchain/coins/vertcoin" "blockbook/bchain/coins/viacoin" "blockbook/bchain/coins/vipstarcoin" @@ -99,6 +100,7 @@ func init() { BlockChainFactories["Ravencoin"] = ravencoin.NewRavencoinRPC BlockChainFactories["Ritocoin"] = ritocoin.NewRitocoinRPC BlockChainFactories["Divi"] = divi.NewDiviRPC + BlockChainFactories["Unobtanium"] = unobtanium.NewUnobtaniumRPC } // GetCoinNameFromConfig gets coin name and coin shortcut from config file diff --git a/bchain/coins/unobtanium/unobtaniumparser.go b/bchain/coins/unobtanium/unobtaniumparser.go new file mode 100644 index 00000000..eac2cc95 --- /dev/null +++ b/bchain/coins/unobtanium/unobtaniumparser.go @@ -0,0 +1,88 @@ +package unobtanium + +import ( + "blockbook/bchain" + "blockbook/bchain/coins/btc" + "blockbook/bchain/coins/utils" + "bytes" + + "github.com/martinboehm/btcd/wire" + "github.com/martinboehm/btcutil/chaincfg" +) + +// magic numbers +const ( + MainnetMagic wire.BitcoinNet = 0x03d5b503 +) + +// chain parameters +var ( + MainNetParams chaincfg.Params +) + +func init() { + MainNetParams = chaincfg.MainNetParams + MainNetParams.Net = MainnetMagic + + // Mainnet address encoding magics + MainNetParams.PubKeyHashAddrID = []byte{130} + MainNetParams.ScriptHashAddrID = []byte{30} + + err := chaincfg.Register(&MainNetParams) + if err != nil { + panic(err) + } +} + +// UnobtaniumParser handle +type UnobtaniumParser struct { + *btc.BitcoinParser +} + +// NewUnobtaniumParser returns new UnobtaniumParser instance +func NewUnobtaniumParser(params *chaincfg.Params, c *btc.Configuration) *UnobtaniumParser { + return &UnobtaniumParser{BitcoinParser: btc.NewBitcoinParser(params, c)} +} + +func GetChainParams(chain string) *chaincfg.Params { + switch chain { + 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 *UnobtaniumParser) 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/unobtanium/unobtaniumparser_test.go b/bchain/coins/unobtanium/unobtaniumparser_test.go new file mode 100644 index 00000000..c7400970 --- /dev/null +++ b/bchain/coins/unobtanium/unobtaniumparser_test.go @@ -0,0 +1,197 @@ +// +build unittest + +package unobtanium + +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: "uNv31DrZT8DMGK4eV3FmvvxteCW2h26xXK"}, + want: "76a9142b9db958ed02331e9fe78eff8c33cc9b276e40f188ac", + wantErr: false, + }, + { + name: "P2PKH2", + args: args{address: "ued4doshafG2qqzGZ5T7RBEm34sdMVm46e"}, + want: "76a914d7ea06ca9357862a9d5855cc54ceb093e69a4bc088ac", + wantErr: false, + }, + } + parser := NewUnobtaniumParser(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 = "0004bd8d8ab4b387540100000002128690c8080ff5b648ad4e38e391f28843cb92c55fe1611a952ea3c9b8eafb2f000000006a47304402200df61ba8dcc1b7228f50eb40346ad237a1d5fae9445a4251f3acfce5728148c402202c43452804855fc08f42fe7db612d9f622a5ee46044279a7a9f7f7c18154a1a1012102248a489fb192cdf37124e15a898f08794a42c5ae2cab3de09a6f92073ae6c904ffffffffe0e35a761650d32866b56d5b4417acd5c6be9d27517be55682da1c4fe83fc6e2010000006c493046022100c40143c5fb6051986921b42565dd20f3201ff1a5365bf0d4110ab0f33c38b22d022100e851372081f381a3edee1b74b54dc6a7806fee6b152a2be855d6bc39974b461a0121028f6f386e276c56ea7ff8894c417a20e82c488ac51400fe51f41c2a243e2de736ffffffff0245420f00000000001976a914d7ea06ca9357862a9d5855cc54ceb093e69a4bc088acb86ef808000000001976a914f943a1cc03a501143ad57fd82394c2952b99e47888ac00000000" +) + +func init() { + testTx1 = bchain.Tx{ + Hex: "0100000002128690c8080ff5b648ad4e38e391f28843cb92c55fe1611a952ea3c9b8eafb2f000000006a47304402200df61ba8dcc1b7228f50eb40346ad237a1d5fae9445a4251f3acfce5728148c402202c43452804855fc08f42fe7db612d9f622a5ee46044279a7a9f7f7c18154a1a1012102248a489fb192cdf37124e15a898f08794a42c5ae2cab3de09a6f92073ae6c904ffffffffe0e35a761650d32866b56d5b4417acd5c6be9d27517be55682da1c4fe83fc6e2010000006c493046022100c40143c5fb6051986921b42565dd20f3201ff1a5365bf0d4110ab0f33c38b22d022100e851372081f381a3edee1b74b54dc6a7806fee6b152a2be855d6bc39974b461a0121028f6f386e276c56ea7ff8894c417a20e82c488ac51400fe51f41c2a243e2de736ffffffff0245420f00000000001976a914d7ea06ca9357862a9d5855cc54ceb093e69a4bc088acb86ef808000000001976a914f943a1cc03a501143ad57fd82394c2952b99e47888ac00000000", + Blocktime: 1397121514, + Txid: "9888815899d3b2e0f26b1eab51229082cf1faf4cd03a12fea2c8afa66701541f", + LockTime: 0, + Version: 1, + Vin: []bchain.Vin{ + { + ScriptSig: bchain.ScriptSig{ + Hex: "47304402200df61ba8dcc1b7228f50eb40346ad237a1d5fae9445a4251f3acfce5728148c402202c43452804855fc08f42fe7db612d9f622a5ee46044279a7a9f7f7c18154a1a1012102248a489fb192cdf37124e15a898f08794a42c5ae2cab3de09a6f92073ae6c904", + }, + Txid: "2ffbeab8c9a32e951a61e15fc592cb4388f291e3384ead48b6f50f08c8908612", + Vout: 0, + Sequence: 4294967295, + }, + { + ScriptSig: bchain.ScriptSig{ + Hex: "493046022100c40143c5fb6051986921b42565dd20f3201ff1a5365bf0d4110ab0f33c38b22d022100e851372081f381a3edee1b74b54dc6a7806fee6b152a2be855d6bc39974b461a0121028f6f386e276c56ea7ff8894c417a20e82c488ac51400fe51f41c2a243e2de736", + }, + Txid: "e2c63fe84f1cda8256e57b51279dbec6d5ac17445b6db56628d35016765ae3e0", + Vout: 1, + Sequence: 4294967295, + }, + }, + Vout: []bchain.Vout{ + { + ValueSat: *big.NewInt(1000005), + N: 0, + ScriptPubKey: bchain.ScriptPubKey{ + Hex: "76a914d7ea06ca9357862a9d5855cc54ceb093e69a4bc088ac", + Addresses: []string{ + "ued4doshafG2qqzGZ5T7RBEm34sdMVm46e", + }, + }, + }, + { + ValueSat: *big.NewInt(150499000), + N: 1, + ScriptPubKey: bchain.ScriptPubKey{ + Hex: "76a914f943a1cc03a501143ad57fd82394c2952b99e47888ac", + Addresses: []string{ + "uhfQH3AD3huadZuzHTB7TWHoWXbJpJhS6B", + }, + }, + }, + }, + } +} + +func Test_PackTx(t *testing.T) { + type args struct { + tx bchain.Tx + height uint32 + blockTime int64 + parser *UnobtaniumParser + } + tests := []struct { + name string + args args + want string + wantErr bool + }{ + { + name: "Unobtanium-1", + args: args{ + tx: testTx1, + height: 310669, + blockTime: 1397121514, + parser: NewUnobtaniumParser(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 *UnobtaniumParser + } + tests := []struct { + name string + args args + want *bchain.Tx + want1 uint32 + wantErr bool + }{ + { + name: "Unobtanium-1", + args: args{ + packedTx: testTxPacked1, + parser: NewUnobtaniumParser(GetChainParams("main"), &btc.Configuration{}), + }, + want: &testTx1, + want1: 310669, + 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/unobtanium/unobtaniumrpc.go b/bchain/coins/unobtanium/unobtaniumrpc.go new file mode 100644 index 00000000..d1f56afe --- /dev/null +++ b/bchain/coins/unobtanium/unobtaniumrpc.go @@ -0,0 +1,73 @@ +package unobtanium + +import ( + "blockbook/bchain" + "blockbook/bchain/coins/btc" + "encoding/json" + + "github.com/golang/glog" +) + +// UnobtaniumRPC is an interface to JSON-RPC bitcoind service +type UnobtaniumRPC struct { + *btc.BitcoinRPC +} + +// NewUnobtaniumRPC returns new UnobtaniumRPC instance +func NewUnobtaniumRPC(config json.RawMessage, pushHandler func(notificationType bchain.NotificationType)) (bchain.BlockChain, error) { + b, err := btc.NewBitcoinRPC(config, pushHandler) + if err != nil { + return nil, err + } + + s := &UnobtaniumRPC{ + b.(*btc.BitcoinRPC), + } + s.RPCMarshaler = btc.JSONMarshalerV1{} + s.ChainConfig.SupportsEstimateSmartFee = false + + return s, nil +} + +// Initialize initializes UnobtaniumRPC instance. +func (b *UnobtaniumRPC) 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 = NewUnobtaniumParser(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 *UnobtaniumRPC) 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/unobtanium.json b/configs/coins/unobtanium.json new file mode 100644 index 00000000..7c845b12 --- /dev/null +++ b/configs/coins/unobtanium.json @@ -0,0 +1,71 @@ +{ + "coin": { + "name": "Unobtanium", + "shortcut": "UNO", + "label": "Unobtanium", + "alias": "unobtanium" + }, + "ports": { + "backend_rpc": 65535, + "backend_message_queue": 38392, + "blockbook_internal": 9092, + "blockbook_public": 9192 + }, + "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-unobtanium", + "package_revision": "satoshilabs-1", + "system_user": "unobtanium", + "version": "0.11.0.0", + "binary_url": "http://flurbo.xyz/unobtanium-0.11.0.0.tar.gz", + "verification_type": "sha256", + "verification_source": "f972d5bb2ee778b224d1f7a6004c510bbedc92e5882937412781389b1da9a38c", + "extract_command": "tar -C backend --strip 1 -xf", + "exclude_files": [ + "bin/unobtanium-qt" + ], + "exec_command_template": "{{.Env.BackendInstallPath}}/{{.Coin.Alias}}/bin/unobtaniumd -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": { + "reindex": 1, + "discover": 0, + "rpcthreads": 16, + "upnp": 0, + "whitelist": "127.0.0.1" + } + }, + "blockbook": { + "package_name": "blockbook-unobtanium", + "system_user": "blockbook-unobtanium", + "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, + "slip44": 92, + "additional_params": {} + } + }, + "meta": { + "package_maintainer": "BadChoicesZ", + "package_maintainer_email": "choicesZ@unobtanium.uno" + } +} diff --git a/docs/ports.md b/docs/ports.md index 7a172857..4db4927c 100644 --- a/docs/ports.md +++ b/docs/ports.md @@ -36,6 +36,7 @@ | Flo | 9066 | 9166 | 8066 | 38366 | | Polis | 9067 | 9167 | 8067 | 38367 | | Qtum | 9088 | 9188 | 8088 | 38388 | +| Unobtanium | 9092 | 9192 | 65535 | 38392 | | Divi Project | 9089 | 9189 | 8089 | 38389 | | Bitcoin Testnet | 19030 | 19130 | 18030 | 48330 | | Bitcoin Cash Testnet | 19031 | 19131 | 18031 | 48331 | diff --git a/tests/rpc/testdata/unobtanium.json b/tests/rpc/testdata/unobtanium.json new file mode 100644 index 00000000..354f4c64 --- /dev/null +++ b/tests/rpc/testdata/unobtanium.json @@ -0,0 +1,37 @@ +{ + "blockHeight": 792323, + "blockHash": "b06166e7ec4da9759838415fe8d24a52d069e58731f1b926fce694f8a0b87229", + "blockTime": 1465970243, + "blockTxs": [ + "1e79423a628b5ea3a73b6d516c0e6b0e238e2597d6ca0532b98625ef01cb7a12" + ], + "txDetails": { + "1e79423a628b5ea3a73b6d516c0e6b0e238e2597d6ca0532b98625ef01cb7a12": { + "hex": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0d0303170c0101062f503253482fffffffff01c2eb0b00000000001976a91430c95cc63c3e1c651b7153b152fe0c598f1ad25e88ac00000000", + "txid": "1e79423a628b5ea3a73b6d516c0e6b0e238e2597d6ca0532b98625ef01cb7a12", + "blocktime": 1465970243, + "time": 1465970243, + "locktime": 0, + "version": 1, + "vin": [ + { + "coinbase": "0303170c0101062f503253482f", + "sequence": 4294967295 + } + ], + "vout": [ + { + "value": 0.0078125, + "n": 0, + "scriptPubKey": { + "hex": "76a91430c95cc63c3e1c651b7153b152fe0c598f1ad25e88ac", + "addresses": [ + "uPPNfEHW2D8gVm4GXNbVWTWCj7fQYk62Ah" + ] + } + } + ] + } + } +} + diff --git a/tests/sync/testdata/unobtanium.json b/tests/sync/testdata/unobtanium.json new file mode 100644 index 00000000..d8241f4b --- /dev/null +++ b/tests/sync/testdata/unobtanium.json @@ -0,0 +1,39 @@ +{ + "connectBlocks": { + "syncRanges": [ + {"lower": 150380, "upper": 150385} + ], + "blocks": { + "150385": { + "height": 150385, + "hash": "000000000001493c5a4c4dc4caedc7f0a568c4a32006e54e553471228d32d102", + "noTxs": 1, + "txDetails": [ + { + "txid": "c62731cae95f441efbc1891b9faf4f5ed7a6d0febd661f452084df8a195f59ea", + "version": 1, + "vin": [ + { + "coinbase": "03714b0202c202062f503253482f", + "sequence": 4294967295 + } + ], + "vout": [ + { + "value": 0.5, + "n": 0, + "scriptPubKey": { + "hex": "21039d41fe2f9d453a08915e0f3a845a666f2b0d013aa920c001bb4874a5d8a73e0bac" + } + } + ], + "hex": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0e03714b0202c202062f503253482fffffffff0180f0fa02000000002321039d41fe2f9d453a08915e0f3a845a666f2b0d013aa920c001bb4874a5d8a73e0bac00000000", + "time": 1389367526, + "blocktime": 1389367526 + } + ] + } + } + } +} + diff --git a/tests/tests.json b/tests/tests.json index 24b8b905..2208c250 100644 --- a/tests/tests.json +++ b/tests/tests.json @@ -180,5 +180,10 @@ "rpc": ["GetBlock", "GetBlockHash", "GetTransaction", "GetTransactionForMempool", "MempoolSync", "EstimateSmartFee", "GetBestBlockHash", "GetBestBlockHeight", "GetBlockHeader"], "sync": ["ConnectBlocksParallel", "ConnectBlocks", "HandleFork"] + }, + "unobtanium": { + "rpc": ["GetBlock", "GetBlockHash", "GetTransaction", "GetTransactionForMempool", "MempoolSync", + "GetBestBlockHash", "GetBestBlockHeight"], + "sync": ["ConnectBlocksParallel", "ConnectBlocks"] } -} \ No newline at end of file +}