diff --git a/bchain/coins/blockchain.go b/bchain/coins/blockchain.go new file mode 100644 index 00000000..e4d78716 --- /dev/null +++ b/bchain/coins/blockchain.go @@ -0,0 +1,28 @@ +package coins + +import ( + "blockbook/bchain" + "blockbook/bchain/coins/btc" + "fmt" + "reflect" + "time" + + "github.com/juju/errors" +) + +type blockChainFactory func(url string, user string, password string, timeout time.Duration, parse bool) (bchain.BlockChain, error) + +var blockChainFactories = make(map[string]blockChainFactory) + +func init() { + blockChainFactories["btc"] = btc.NewBitcoinRPC +} + +// NewBlockChain creates bchain.BlockChain of type defined by parameter coin +func NewBlockChain(coin string, url string, user string, password string, timeout time.Duration, parse bool) (bchain.BlockChain, error) { + bcf, ok := blockChainFactories[coin] + if !ok { + return nil, errors.New(fmt.Sprint("Unsupported coin ", coin, ". Must be one of ", reflect.ValueOf(blockChainFactories).MapKeys())) + } + return bcf(url, user, password, timeout, parse) +} diff --git a/bchain/coins/btc/bitcoinparser.go b/bchain/coins/btc/bitcoinparser.go new file mode 100644 index 00000000..d60a41d5 --- /dev/null +++ b/bchain/coins/btc/bitcoinparser.go @@ -0,0 +1,163 @@ +package btc + +import ( + "blockbook/bchain" + "bytes" + "encoding/binary" + "encoding/hex" + + vlq "github.com/bsm/go-vlq" + "github.com/btcsuite/btcd/blockchain" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcutil" +) + +// bitcoinwire parsing + +type BitcoinBlockParser struct { + Params *chaincfg.Params +} + +// getChainParams contains network parameters for the main Bitcoin network, +// the regression test Bitcoin network, the test Bitcoin network and +// the simulation test Bitcoin network, in this order +func GetChainParams(chain string) *chaincfg.Params { + switch chain { + case "test": + return &chaincfg.TestNet3Params + case "regtest": + return &chaincfg.RegressionNetParams + } + return &chaincfg.MainNetParams +} + +// AddressToOutputScript converts bitcoin address to ScriptPubKey +func (p *BitcoinBlockParser) AddressToOutputScript(address string) ([]byte, error) { + da, err := btcutil.DecodeAddress(address, p.Params) + if err != nil { + return nil, err + } + script, err := txscript.PayToAddrScript(da) + if err != nil { + return nil, err + } + return script, nil +} + +// OutputScriptToAddresses converts ScriptPubKey to bitcoin addresses +func (p *BitcoinBlockParser) OutputScriptToAddresses(script []byte) ([]string, error) { + _, addresses, _, err := txscript.ExtractPkScriptAddrs(script, p.Params) + if err != nil { + return nil, err + } + rv := make([]string, len(addresses)) + for i, a := range addresses { + rv[i] = a.EncodeAddress() + } + return rv, nil +} + +func (p *BitcoinBlockParser) txFromMsgTx(t *wire.MsgTx, parseAddresses bool) bchain.Tx { + vin := make([]bchain.Vin, len(t.TxIn)) + for i, in := range t.TxIn { + if blockchain.IsCoinBaseTx(t) { + vin[i] = bchain.Vin{ + Coinbase: hex.EncodeToString(in.SignatureScript), + Sequence: in.Sequence, + } + break + } + s := bchain.ScriptSig{ + Hex: hex.EncodeToString(in.SignatureScript), + // missing: Asm, + } + vin[i] = bchain.Vin{ + Txid: in.PreviousOutPoint.Hash.String(), + Vout: in.PreviousOutPoint.Index, + Sequence: in.Sequence, + ScriptSig: s, + } + } + vout := make([]bchain.Vout, len(t.TxOut)) + for i, out := range t.TxOut { + addrs := []string{} + if parseAddresses { + addrs, _ = p.OutputScriptToAddresses(out.PkScript) + } + s := bchain.ScriptPubKey{ + Hex: hex.EncodeToString(out.PkScript), + Addresses: addrs, + // missing: Asm, + // missing: Type, + } + vout[i] = bchain.Vout{ + Value: float64(out.Value) / 1E8, + N: uint32(i), + ScriptPubKey: s, + } + } + tx := bchain.Tx{ + Txid: t.TxHash().String(), + // skip: Version, + LockTime: t.LockTime, + Vin: vin, + Vout: vout, + // skip: BlockHash, + // skip: Confirmations, + // skip: Time, + // skip: Blocktime, + } + return tx +} + +// ParseTx parses byte array containing transaction and returns Tx struct +func (p *BitcoinBlockParser) ParseTx(b []byte) (*bchain.Tx, error) { + t := wire.MsgTx{} + r := bytes.NewReader(b) + if err := t.Deserialize(r); err != nil { + return nil, err + } + tx := p.txFromMsgTx(&t, true) + tx.Hex = hex.EncodeToString(b) + return &tx, nil +} + +// ParseBlock parses raw block to our Block struct +func (p *BitcoinBlockParser) ParseBlock(b []byte) (*bchain.Block, error) { + w := wire.MsgBlock{} + r := bytes.NewReader(b) + + if err := w.Deserialize(r); 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{Txs: txs}, nil +} + +// PackTx packs transaction to byte array +func (p *BitcoinBlockParser) PackTx(tx *bchain.Tx, height uint32, blockTime int64) ([]byte, error) { + buf := make([]byte, 4+vlq.MaxLen64+len(tx.Hex)/2) + binary.BigEndian.PutUint32(buf[0:4], height) + vl := vlq.PutInt(buf[4:4+vlq.MaxLen64], blockTime) + hl, err := hex.Decode(buf[4+vl:], []byte(tx.Hex)) + return buf[0 : 4+vl+hl], err +} + +// UnpackTx unpacks transaction from byte array +func (p *BitcoinBlockParser) UnpackTx(buf []byte) (*bchain.Tx, uint32, error) { + height := binary.BigEndian.Uint32(buf) + bt, l := vlq.Int(buf[4:]) + tx, err := p.ParseTx(buf[4+l:]) + if err != nil { + return nil, 0, err + } + tx.Blocktime = bt + return tx, height, nil +} diff --git a/bchain/bitcoinrpc_test.go b/bchain/coins/btc/bitcoinparser_test.go similarity index 95% rename from bchain/bitcoinrpc_test.go rename to bchain/coins/btc/bitcoinparser_test.go index 1ddec9c7..761bd9a6 100644 --- a/bchain/bitcoinrpc_test.go +++ b/bchain/coins/btc/bitcoinparser_test.go @@ -1,6 +1,7 @@ -package bchain +package btc import ( + "blockbook/bchain" "encoding/hex" "reflect" "testing" @@ -109,14 +110,14 @@ func TestOutputScriptToAddresses(t *testing.T) { } } -var testTx1 = Tx{ +var testTx1 = bchain.Tx{ Hex: "01000000017f9a22c9cbf54bd902400df746f138f37bcf5b4d93eb755820e974ba43ed5f42040000006a4730440220037f4ed5427cde81d55b9b6a2fd08c8a25090c2c2fff3a75c1a57625ca8a7118022076c702fe55969fa08137f71afd4851c48e31082dd3c40c919c92cdbc826758d30121029f6da5623c9f9b68a9baf9c1bc7511df88fa34c6c2f71f7c62f2f03ff48dca80feffffff019c9700000000000017a9146144d57c8aff48492c9dfb914e120b20bad72d6f8773d00700", Blocktime: 1519053802, Txid: "056e3d82e5ffd0e915fb9b62797d76263508c34fe3e5dbed30dd3e943930f204", LockTime: 512115, - Vin: []Vin{ + Vin: []bchain.Vin{ { - ScriptSig: ScriptSig{ + ScriptSig: bchain.ScriptSig{ Hex: "4730440220037f4ed5427cde81d55b9b6a2fd08c8a25090c2c2fff3a75c1a57625ca8a7118022076c702fe55969fa08137f71afd4851c48e31082dd3c40c919c92cdbc826758d30121029f6da5623c9f9b68a9baf9c1bc7511df88fa34c6c2f71f7c62f2f03ff48dca80", }, Txid: "425fed43ba74e9205875eb934d5bcf7bf338f146f70d4002d94bf5cbc9229a7f", @@ -124,11 +125,11 @@ var testTx1 = Tx{ Sequence: 4294967294, }, }, - Vout: []Vout{ + Vout: []bchain.Vout{ { Value: 0.00038812, N: 0, - ScriptPubKey: ScriptPubKey{ + ScriptPubKey: bchain.ScriptPubKey{ Hex: "a9146144d57c8aff48492c9dfb914e120b20bad72d6f87", Addresses: []string{ "3AZKvpKhSh1o8t1QrX3UeXG9d2BhCRnbcK", @@ -139,14 +140,14 @@ var testTx1 = Tx{ } var testTxPacked1 = "0001e2408ba8d7af5401000000017f9a22c9cbf54bd902400df746f138f37bcf5b4d93eb755820e974ba43ed5f42040000006a4730440220037f4ed5427cde81d55b9b6a2fd08c8a25090c2c2fff3a75c1a57625ca8a7118022076c702fe55969fa08137f71afd4851c48e31082dd3c40c919c92cdbc826758d30121029f6da5623c9f9b68a9baf9c1bc7511df88fa34c6c2f71f7c62f2f03ff48dca80feffffff019c9700000000000017a9146144d57c8aff48492c9dfb914e120b20bad72d6f8773d00700" -var testTx2 = Tx{ +var testTx2 = bchain.Tx{ Hex: "010000000001019d64f0c72a0d206001decbffaa722eb1044534c74eee7a5df8318e42a4323ec10000000017160014550da1f5d25a9dae2eafd6902b4194c4c6500af6ffffffff02809698000000000017a914cd668d781ece600efa4b2404dc91fd26b8b8aed8870553d7360000000017a914246655bdbd54c7e477d0ea2375e86e0db2b8f80a8702473044022076aba4ad559616905fa51d4ddd357fc1fdb428d40cb388e042cdd1da4a1b7357022011916f90c712ead9a66d5f058252efd280439ad8956a967e95d437d246710bc9012102a80a5964c5612bb769ef73147b2cf3c149bc0fd4ecb02f8097629c94ab013ffd00000000", Blocktime: 1235678901, Txid: "474e6795760ebe81cb4023dc227e5a0efe340e1771c89a0035276361ed733de7", LockTime: 0, - Vin: []Vin{ + Vin: []bchain.Vin{ { - ScriptSig: ScriptSig{ + ScriptSig: bchain.ScriptSig{ Hex: "160014550da1f5d25a9dae2eafd6902b4194c4c6500af6", }, Txid: "c13e32a4428e31f85d7aee4ec7344504b12e72aaffcbde0160200d2ac7f0649d", @@ -154,11 +155,11 @@ var testTx2 = Tx{ Sequence: 4294967295, }, }, - Vout: []Vout{ + Vout: []bchain.Vout{ { Value: .1, N: 0, - ScriptPubKey: ScriptPubKey{ + ScriptPubKey: bchain.ScriptPubKey{ Hex: "a914cd668d781ece600efa4b2404dc91fd26b8b8aed887", Addresses: []string{ "2NByHN6A8QYkBATzxf4pRGbCSHD5CEN2TRu", @@ -168,7 +169,7 @@ var testTx2 = Tx{ { Value: 9.20081157, N: 1, - ScriptPubKey: ScriptPubKey{ + ScriptPubKey: bchain.ScriptPubKey{ Hex: "a914246655bdbd54c7e477d0ea2375e86e0db2b8f80a87", Addresses: []string{ "2MvZguYaGjM7JihBgNqgLF2Ca2Enb76Hj9D", @@ -181,7 +182,7 @@ var testTxPacked2 = "0007c91a899ab7da6a010000000001019d64f0c72a0d206001decbffaa7 func Test_PackTx(t *testing.T) { type args struct { - tx Tx + tx bchain.Tx height uint32 blockTime int64 parser *BitcoinBlockParser @@ -238,7 +239,7 @@ func Test_UnpackTx(t *testing.T) { tests := []struct { name string args args - want *Tx + want *bchain.Tx want1 uint32 wantErr bool }{ diff --git a/bchain/bitcoinrpc.go b/bchain/coins/btc/bitcoinrpc.go similarity index 68% rename from bchain/bitcoinrpc.go rename to bchain/coins/btc/bitcoinrpc.go index 93ac5ef1..3aa28a73 100644 --- a/bchain/bitcoinrpc.go +++ b/bchain/coins/btc/bitcoinrpc.go @@ -1,23 +1,17 @@ -package bchain +package btc import ( + "blockbook/bchain" "bytes" - "encoding/binary" "encoding/hex" "encoding/json" - "fmt" "io" "io/ioutil" "net" "net/http" "time" - vlq "github.com/bsm/go-vlq" - "github.com/btcsuite/btcd/blockchain" - "github.com/btcsuite/btcd/chaincfg" - "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/golang/glog" "github.com/juju/errors" @@ -32,12 +26,12 @@ type BitcoinRPC struct { parser *BitcoinBlockParser testnet bool network string - mempool *Mempool + mempool *bchain.Mempool parseBlocks bool } // NewBitcoinRPC returns new BitcoinRPC instance. -func NewBitcoinRPC(url string, user string, password string, timeout time.Duration, parse bool) (BlockChain, error) { +func NewBitcoinRPC(url string, user string, password string, timeout time.Duration, parse bool) (bchain.BlockChain, error) { transport := &http.Transport{ Dial: (&net.Dialer{KeepAlive: 600 * time.Second}).Dial, MaxIdleConns: 100, @@ -69,7 +63,7 @@ func NewBitcoinRPC(url string, user string, password string, timeout time.Durati s.network = "testnet" } - s.mempool = NewMempool(s) + s.mempool = bchain.NewMempool(s) glog.Info("rpc: block chain ", s.parser.Params.Name) return s, nil @@ -83,10 +77,6 @@ func (b *BitcoinRPC) GetNetworkName() string { return b.network } -func (e *RPCError) Error() string { - return fmt.Sprintf("%d: %s", e.Code, e.Message) -} - // getblockhash type cmdGetBlockHash struct { @@ -97,8 +87,8 @@ type cmdGetBlockHash struct { } type resGetBlockHash struct { - Error *RPCError `json:"error"` - Result string `json:"result"` + Error *bchain.RPCError `json:"error"` + Result string `json:"result"` } // getbestblockhash @@ -108,8 +98,8 @@ type cmdGetBestBlockHash struct { } type resGetBestBlockHash struct { - Error *RPCError `json:"error"` - Result string `json:"result"` + Error *bchain.RPCError `json:"error"` + Result string `json:"result"` } // getblockcount @@ -119,8 +109,8 @@ type cmdGetBlockCount struct { } type resGetBlockCount struct { - Error *RPCError `json:"error"` - Result uint32 `json:"result"` + Error *bchain.RPCError `json:"error"` + Result uint32 `json:"result"` } // getblockchaininfo @@ -130,7 +120,7 @@ type cmdGetBlockChainInfo struct { } type resGetBlockChainInfo struct { - Error *RPCError `json:"error"` + Error *bchain.RPCError `json:"error"` Result struct { Chain string `json:"chain"` Blocks int `json:"blocks"` @@ -146,8 +136,8 @@ type cmdGetMempool struct { } type resGetMempool struct { - Error *RPCError `json:"error"` - Result []string `json:"result"` + Error *bchain.RPCError `json:"error"` + Result []string `json:"result"` } // getblockheader @@ -161,13 +151,13 @@ type cmdGetBlockHeader struct { } type resGetBlockHeaderRaw struct { - Error *RPCError `json:"error"` - Result string `json:"result"` + Error *bchain.RPCError `json:"error"` + Result string `json:"result"` } type resGetBlockHeaderVerbose struct { - Error *RPCError `json:"error"` - Result BlockHeader `json:"result"` + Error *bchain.RPCError `json:"error"` + Result bchain.BlockHeader `json:"result"` } // getblock @@ -181,18 +171,18 @@ type cmdGetBlock struct { } type resGetBlockRaw struct { - Error *RPCError `json:"error"` - Result string `json:"result"` + Error *bchain.RPCError `json:"error"` + Result string `json:"result"` } type resGetBlockThin struct { - Error *RPCError `json:"error"` - Result ThinBlock `json:"result"` + Error *bchain.RPCError `json:"error"` + Result bchain.ThinBlock `json:"result"` } type resGetBlockFull struct { - Error *RPCError `json:"error"` - Result Block `json:"result"` + Error *bchain.RPCError `json:"error"` + Result bchain.Block `json:"result"` } // getrawtransaction @@ -206,13 +196,13 @@ type cmdGetRawTransaction struct { } type resGetRawTransactionRaw struct { - Error *RPCError `json:"error"` - Result string `json:"result"` + Error *bchain.RPCError `json:"error"` + Result string `json:"result"` } type resGetRawTransactionVerbose struct { - Error *RPCError `json:"error"` - Result Tx `json:"result"` + Error *bchain.RPCError `json:"error"` + Result bchain.Tx `json:"result"` } // estimatesmartfee @@ -226,7 +216,7 @@ type cmdEstimateSmartFee struct { } type resEstimateSmartFee struct { - Error *RPCError `json:"error"` + Error *bchain.RPCError `json:"error"` Result struct { Feerate float64 `json:"feerate"` Blocks int `json:"blocks"` @@ -241,8 +231,8 @@ type cmdSendRawTransaction struct { } type resSendRawTransaction struct { - Error *RPCError `json:"error"` - Result string `json:"result"` + Error *bchain.RPCError `json:"error"` + Result string `json:"result"` } // getmempoolentry @@ -253,8 +243,8 @@ type cmdGetMempoolEntry struct { } type resGetMempoolEntry struct { - Error *RPCError `json:"error"` - Result *MempoolEntry `json:"result"` + Error *bchain.RPCError `json:"error"` + Result *bchain.MempoolEntry `json:"result"` } // GetBestBlockHash returns hash of the tip of the best-block-chain. @@ -328,7 +318,7 @@ func (b *BitcoinRPC) GetBlockHash(height uint32) (string, error) { } // GetBlockHeader returns header of block with given hash. -func (b *BitcoinRPC) GetBlockHeader(hash string) (*BlockHeader, error) { +func (b *BitcoinRPC) GetBlockHeader(hash string) (*bchain.BlockHeader, error) { glog.V(1).Info("rpc: getblockheader") res := resGetBlockHeaderVerbose{} @@ -347,7 +337,7 @@ func (b *BitcoinRPC) GetBlockHeader(hash string) (*BlockHeader, error) { } // GetBlock returns block with given hash. -func (b *BitcoinRPC) GetBlock(hash string) (*Block, error) { +func (b *BitcoinRPC) GetBlock(hash string) (*bchain.Block, error) { if !b.parseBlocks { return b.GetBlockFull(hash) } @@ -369,7 +359,7 @@ func (b *BitcoinRPC) GetBlock(hash string) (*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) (*Block, error) { +func (b *BitcoinRPC) GetBlockWithoutHeader(hash string, height uint32) (*bchain.Block, error) { if !b.parseBlocks { return b.GetBlockFull(hash) } @@ -407,7 +397,7 @@ func (b *BitcoinRPC) GetBlockRaw(hash string) ([]byte, error) { // GetBlockList returns block with given hash by downloading block // transactions one by one. -func (b *BitcoinRPC) GetBlockList(hash string) (*Block, error) { +func (b *BitcoinRPC) GetBlockList(hash string) (*bchain.Block, error) { glog.V(1).Info("rpc: getblock (verbosity=1) ", hash) res := resGetBlockThin{} @@ -423,7 +413,7 @@ func (b *BitcoinRPC) GetBlockList(hash string) (*Block, error) { return nil, errors.Annotatef(res.Error, "hash %v", hash) } - txs := make([]Tx, len(res.Result.Txids)) + txs := make([]bchain.Tx, len(res.Result.Txids)) for i, txid := range res.Result.Txids { tx, err := b.GetTransaction(txid) if err != nil { @@ -431,7 +421,7 @@ func (b *BitcoinRPC) GetBlockList(hash string) (*Block, error) { } txs[i] = *tx } - block := &Block{ + block := &bchain.Block{ BlockHeader: res.Result.BlockHeader, Txs: txs, } @@ -439,7 +429,7 @@ func (b *BitcoinRPC) GetBlockList(hash string) (*Block, error) { } // GetBlockFull returns block with given hash. -func (b *BitcoinRPC) GetBlockFull(hash string) (*Block, error) { +func (b *BitcoinRPC) GetBlockFull(hash string) (*bchain.Block, error) { glog.V(1).Info("rpc: getblock (verbosity=2) ", hash) res := resGetBlockFull{} @@ -475,7 +465,7 @@ func (b *BitcoinRPC) GetMempool() ([]string, error) { } // GetTransaction returns a transaction by the transaction ID. -func (b *BitcoinRPC) GetTransaction(txid string) (*Tx, error) { +func (b *BitcoinRPC) GetTransaction(txid string) (*bchain.Tx, error) { glog.V(1).Info("rpc: getrawtransaction ", txid) res := resGetRawTransactionVerbose{} @@ -550,7 +540,7 @@ func (b *BitcoinRPC) SendRawTransaction(tx string) (string, error) { return res.Result, nil } -func (b *BitcoinRPC) GetMempoolEntry(txid string) (*MempoolEntry, error) { +func (b *BitcoinRPC) GetMempoolEntry(txid string) (*bchain.MempoolEntry, error) { glog.V(1).Info("rpc: getmempoolentry") res := resGetMempoolEntry{} @@ -595,154 +585,6 @@ func (b *BitcoinRPC) call(req interface{}, res interface{}) error { } // GetChainParser returns BlockChainParser -func (b *BitcoinRPC) GetChainParser() BlockChainParser { +func (b *BitcoinRPC) GetChainParser() bchain.BlockChainParser { return b.parser } - -// bitcoinwire parsing - -type BitcoinBlockParser struct { - Params *chaincfg.Params -} - -// getChainParams contains network parameters for the main Bitcoin network, -// the regression test Bitcoin network, the test Bitcoin network and -// the simulation test Bitcoin network, in this order -func GetChainParams(chain string) *chaincfg.Params { - switch chain { - case "test": - return &chaincfg.TestNet3Params - case "regtest": - return &chaincfg.RegressionNetParams - } - return &chaincfg.MainNetParams -} - -// AddressToOutputScript converts bitcoin address to ScriptPubKey -func (p *BitcoinBlockParser) AddressToOutputScript(address string) ([]byte, error) { - da, err := btcutil.DecodeAddress(address, p.Params) - if err != nil { - return nil, err - } - script, err := txscript.PayToAddrScript(da) - if err != nil { - return nil, err - } - return script, nil -} - -// OutputScriptToAddresses converts ScriptPubKey to bitcoin addresses -func (p *BitcoinBlockParser) OutputScriptToAddresses(script []byte) ([]string, error) { - _, addresses, _, err := txscript.ExtractPkScriptAddrs(script, p.Params) - if err != nil { - return nil, err - } - rv := make([]string, len(addresses)) - for i, a := range addresses { - rv[i] = a.EncodeAddress() - } - return rv, nil -} - -func (p *BitcoinBlockParser) txFromMsgTx(t *wire.MsgTx, parseAddresses bool) Tx { - vin := make([]Vin, len(t.TxIn)) - for i, in := range t.TxIn { - if blockchain.IsCoinBaseTx(t) { - vin[i] = Vin{ - Coinbase: hex.EncodeToString(in.SignatureScript), - Sequence: in.Sequence, - } - break - } - s := ScriptSig{ - Hex: hex.EncodeToString(in.SignatureScript), - // missing: Asm, - } - vin[i] = Vin{ - Txid: in.PreviousOutPoint.Hash.String(), - Vout: in.PreviousOutPoint.Index, - Sequence: in.Sequence, - ScriptSig: s, - } - } - vout := make([]Vout, len(t.TxOut)) - for i, out := range t.TxOut { - addrs := []string{} - if parseAddresses { - addrs, _ = p.OutputScriptToAddresses(out.PkScript) - } - s := ScriptPubKey{ - Hex: hex.EncodeToString(out.PkScript), - Addresses: addrs, - // missing: Asm, - // missing: Type, - } - vout[i] = Vout{ - Value: float64(out.Value) / 1E8, - N: uint32(i), - ScriptPubKey: s, - } - } - tx := Tx{ - Txid: t.TxHash().String(), - // skip: Version, - LockTime: t.LockTime, - Vin: vin, - Vout: vout, - // skip: BlockHash, - // skip: Confirmations, - // skip: Time, - // skip: Blocktime, - } - return tx -} - -// ParseTx parses byte array containing transaction and returns Tx struct -func (p *BitcoinBlockParser) ParseTx(b []byte) (*Tx, error) { - t := wire.MsgTx{} - r := bytes.NewReader(b) - if err := t.Deserialize(r); err != nil { - return nil, err - } - tx := p.txFromMsgTx(&t, true) - tx.Hex = hex.EncodeToString(b) - return &tx, nil -} - -// ParseBlock parses raw block to our Block struct -func (p *BitcoinBlockParser) ParseBlock(b []byte) (*Block, error) { - w := wire.MsgBlock{} - r := bytes.NewReader(b) - - if err := w.Deserialize(r); err != nil { - return nil, err - } - - txs := make([]Tx, len(w.Transactions)) - for ti, t := range w.Transactions { - txs[ti] = p.txFromMsgTx(t, false) - } - - return &Block{Txs: txs}, nil -} - -// PackTx packs transaction to byte array -func (p *BitcoinBlockParser) PackTx(tx *Tx, height uint32, blockTime int64) ([]byte, error) { - buf := make([]byte, 4+vlq.MaxLen64+len(tx.Hex)/2) - binary.BigEndian.PutUint32(buf[0:4], height) - vl := vlq.PutInt(buf[4:4+vlq.MaxLen64], blockTime) - hl, err := hex.Decode(buf[4+vl:], []byte(tx.Hex)) - return buf[0 : 4+vl+hl], err -} - -// UnpackTx unpacks transaction from byte array -func (p *BitcoinBlockParser) UnpackTx(buf []byte) (*Tx, uint32, error) { - height := binary.BigEndian.Uint32(buf) - bt, l := vlq.Int(buf[4:]) - tx, err := p.ParseTx(buf[4+l:]) - if err != nil { - return nil, 0, err - } - tx.Blocktime = bt - return tx, height, nil -} diff --git a/bchain/types.go b/bchain/types.go index bdf73b2e..97a9fb06 100644 --- a/bchain/types.go +++ b/bchain/types.go @@ -1,5 +1,7 @@ package bchain +import "fmt" + type ScriptSig struct { // Asm string `json:"asm"` Hex string `json:"hex"` @@ -79,6 +81,10 @@ type RPCError struct { Message string `json:"message"` } +func (e *RPCError) Error() string { + return fmt.Sprintf("%d: %s", e.Code, e.Message) +} + type BlockChain interface { // chain info IsTestnet() bool diff --git a/blockbook.go b/blockbook.go index 57140b41..c053c96a 100644 --- a/blockbook.go +++ b/blockbook.go @@ -12,6 +12,7 @@ import ( "github.com/juju/errors" "blockbook/bchain" + "blockbook/bchain/coins" "blockbook/db" "blockbook/server" @@ -32,7 +33,7 @@ const resyncMempoolPeriodMs = 60017 const debounceResyncMempoolMs = 1009 var ( - rpcURL = flag.String("rpcurl", "http://localhost:8332", "url of bitcoin RPC service") + rpcURL = flag.String("rpcurl", "http://localhost:8332", "url of blockchain RPC service") rpcUser = flag.String("rpcuser", "rpc", "rpc username") rpcPass = flag.String("rpcpass", "rpc", "rpc password") rpcTimeout = flag.Uint("rpctimeout", 25, "rpc timeout in seconds") @@ -62,7 +63,9 @@ var ( zeroMQBinding = flag.String("zeromq", "", "binding to zeromq, if missing no zeromq connection") - explorerURL = flag.String("explorer", "", "address of the Bitcoin blockchain explorer") + explorerURL = flag.String("explorer", "", "address of blockchain explorer") + + coin = flag.String("coin", "btc", "coin name (default btc)") ) var ( @@ -102,13 +105,13 @@ func main() { } var err error - if chain, err = bchain.NewBitcoinRPC(*rpcURL, *rpcUser, *rpcPass, time.Duration(*rpcTimeout)*time.Second, *parse); err != nil { - glog.Fatal("NewBitcoinRPC ", err) + if chain, err = coins.NewBlockChain(*coin, *rpcURL, *rpcUser, *rpcPass, time.Duration(*rpcTimeout)*time.Second, *parse); err != nil { + glog.Fatal("NewBlockChain: ", err) } index, err = db.NewRocksDB(*dbPath, chain.GetChainParser()) if err != nil { - glog.Fatalf("NewRocksDB %v", err) + glog.Fatal("NewRocksDB: ", err) } defer index.Close()