diff --git a/bchain/bitcoinrpc.go b/bchain/bitcoinrpc.go index d83f8a81..1a5bdc4a 100644 --- a/bchain/bitcoinrpc.go +++ b/bchain/bitcoinrpc.go @@ -11,6 +11,8 @@ import ( "net/http" "time" + "github.com/btcsuite/btcd/wire" + "github.com/golang/glog" "github.com/juju/errors" ) @@ -60,6 +62,22 @@ type resGetBlockCount struct { Result uint32 `json:"result"` } +// getblockchaininfo + +type cmdGetBlockChainInfo struct { + Method string `json:"method"` +} + +type resGetBlockChainInfo struct { + Error *RPCError `json:"error"` + Result struct { + Chain string `json:"chain"` + Blocks int `json:"blocks"` + Headers int `json:"headers"` + Bestblockhash string `json:"bestblockhash"` + } `json:"result"` +} + // getrawmempool type cmdGetMempool struct { @@ -166,32 +184,50 @@ type resSendRawTransaction struct { Result string `json:"result"` } -type BlockParser interface { - ParseBlock(b []byte) (*Block, error) -} - // BitcoinRPC is an interface to JSON-RPC bitcoind service. type BitcoinRPC struct { client http.Client URL string User string Password string - Parser BlockParser + Parser *BitcoinBlockParser + Testnet bool + Network string } // NewBitcoinRPC returns new BitcoinRPC instance. -func NewBitcoinRPC(url string, user string, password string, timeout time.Duration) *BitcoinRPC { +func NewBitcoinRPC(url string, user string, password string, timeout time.Duration, parse bool) (*BitcoinRPC, error) { transport := &http.Transport{ Dial: (&net.Dialer{KeepAlive: 600 * time.Second}).Dial, MaxIdleConns: 100, MaxIdleConnsPerHost: 100, // necessary to not to deplete ports } - return &BitcoinRPC{ + s := &BitcoinRPC{ client: http.Client{Timeout: timeout, Transport: transport}, URL: url, User: user, Password: password, } + chain, err := s.GetBlockChainInfo() + if err != nil { + return nil, err + } + + // always create parser + s.Parser = &BitcoinBlockParser{ + Params: GetChainParams(chain), + } + + // parameters for getInfo request + if s.Parser.Params.Net == wire.MainNet { + s.Testnet = false + s.Network = "livenet" + } else { + s.Testnet = true + s.Network = "testnet" + } + glog.Info("rpc: block chain ", s.Parser.Params.Name) + return s, nil } // GetBestBlockHash returns hash of the tip of the best-block-chain. @@ -229,6 +265,23 @@ func (b *BitcoinRPC) GetBestBlockHeight() (uint32, error) { return res.Result, nil } +// GetBlockChainInfo returns the name of the block chain: main/test/regtest. +func (b *BitcoinRPC) GetBlockChainInfo() (string, error) { + glog.V(1).Info("rpc: getblockchaininfo") + + res := resGetBlockChainInfo{} + req := cmdGetBlockChainInfo{Method: "getblockchaininfo"} + err := b.call(&req, &res) + + if err != nil { + return "", err + } + if res.Error != nil { + return "", res.Error + } + return res.Result.Chain, nil +} + // GetBlockHash returns hash of block in best-block-chain at given height. func (b *BitcoinRPC) GetBlockHash(height uint32) (string, error) { glog.V(1).Info("rpc: getblockhash ", height) diff --git a/bchain/bitcoinwire.go b/bchain/bitcoinwire.go index bf77d591..baf960b2 100644 --- a/bchain/bitcoinwire.go +++ b/bchain/bitcoinwire.go @@ -15,13 +15,14 @@ import ( // 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() []*chaincfg.Params { - return []*chaincfg.Params{ - &chaincfg.MainNetParams, - &chaincfg.RegressionNetParams, - &chaincfg.TestNet3Params, - &chaincfg.SimNetParams, +func GetChainParams(chain string) *chaincfg.Params { + switch chain { + case "test": + return &chaincfg.TestNet3Params + case "regtest": + return &chaincfg.RegressionNetParams } + return &chaincfg.MainNetParams } type BitcoinBlockParser struct { @@ -29,8 +30,8 @@ type BitcoinBlockParser struct { } // AddressToOutputScript converts bitcoin address to ScriptPubKey -func AddressToOutputScript(address string) ([]byte, error) { - da, err := btcutil.DecodeAddress(address, GetChainParams()[0]) +func (p *BitcoinBlockParser) AddressToOutputScript(address string) ([]byte, error) { + da, err := btcutil.DecodeAddress(address, p.Params) if err != nil { return nil, err } @@ -42,8 +43,8 @@ func AddressToOutputScript(address string) ([]byte, error) { } // OutputScriptToAddresses converts ScriptPubKey to bitcoin addresses -func OutputScriptToAddresses(script []byte) ([]string, error) { - _, addresses, _, err := txscript.ExtractPkScriptAddrs(script, GetChainParams()[0]) +func (p *BitcoinBlockParser) OutputScriptToAddresses(script []byte) ([]string, error) { + _, addresses, _, err := txscript.ExtractPkScriptAddrs(script, p.Params) if err != nil { return nil, err } @@ -55,18 +56,18 @@ func OutputScriptToAddresses(script []byte) ([]string, error) { } // ParseTx parses byte array containing transaction and returns Tx struct -func ParseTx(b []byte) (*Tx, error) { +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 := txFromMsgTx(&t, true) + tx := p.txFromMsgTx(&t, true) tx.Hex = hex.EncodeToString(b) return &tx, nil } -func txFromMsgTx(t *wire.MsgTx, parseAddresses bool) Tx { +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) { @@ -91,7 +92,7 @@ func txFromMsgTx(t *wire.MsgTx, parseAddresses bool) Tx { for i, out := range t.TxOut { addrs := []string{} if parseAddresses { - addrs, _ = OutputScriptToAddresses(out.PkScript) + addrs, _ = p.OutputScriptToAddresses(out.PkScript) } s := ScriptPubKey{ Hex: hex.EncodeToString(out.PkScript), @@ -130,7 +131,7 @@ func (p *BitcoinBlockParser) ParseBlock(b []byte) (*Block, error) { txs := make([]Tx, len(w.Transactions)) for ti, t := range w.Transactions { - txs[ti] = txFromMsgTx(t, false) + txs[ti] = p.txFromMsgTx(t, false) } return &Block{Txs: txs}, nil diff --git a/bchain/bitcoinwire_test.go b/bchain/bitcoinwire_test.go index f8e10a4f..0710c99d 100644 --- a/bchain/bitcoinwire_test.go +++ b/bchain/bitcoinwire_test.go @@ -41,9 +41,11 @@ func TestAddressToOutputScript(t *testing.T) { wantErr: false, }, } + parser := &BitcoinBlockParser{Params: GetChainParams("main")} + for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := AddressToOutputScript(tt.args.address) + got, err := parser.AddressToOutputScript(tt.args.address) if (err != nil) != tt.wantErr { t.Errorf("AddressToOutputScript() error = %v, wantErr %v", err, tt.wantErr) return @@ -91,10 +93,11 @@ func TestOutputScriptToAddresses(t *testing.T) { wantErr: false, }, } + parser := &BitcoinBlockParser{Params: GetChainParams("main")} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { b, _ := hex.DecodeString(tt.args.script) - got, err := OutputScriptToAddresses(b) + got, err := parser.OutputScriptToAddresses(b) if (err != nil) != tt.wantErr { t.Errorf("OutputScriptToAddresses() error = %v, wantErr %v", err, tt.wantErr) return diff --git a/blockbook.go b/blockbook.go index 4f36649b..1f697a0f 100644 --- a/blockbook.go +++ b/blockbook.go @@ -102,22 +102,14 @@ func main() { return } - chain = bchain.NewBitcoinRPC( - *rpcURL, - *rpcUser, - *rpcPass, - time.Duration(*rpcTimeout)*time.Second) - - if *parse { - chain.Parser = &bchain.BitcoinBlockParser{ - Params: bchain.GetChainParams()[0], - } + var err error + if chain, err = bchain.NewBitcoinRPC(*rpcURL, *rpcUser, *rpcPass, time.Duration(*rpcTimeout)*time.Second, *parse); err != nil { + glog.Fatal("NewBitcoinRPC ", err) } mempool = bchain.NewMempool(chain) - var err error - index, err = db.NewRocksDB(*dbPath) + index, err = db.NewRocksDB(*dbPath, chain.Parser) if err != nil { glog.Fatalf("NewRocksDB %v", err) } @@ -164,7 +156,7 @@ func main() { var httpServer *server.HTTPServer if *httpServerBinding != "" { - httpServer, err = server.NewHTTPServer(*httpServerBinding, *certFiles, index, mempool) + httpServer, err = server.NewHTTPServer(*httpServerBinding, *certFiles, index, mempool, chain, txCache) if err != nil { glog.Error("https: ", err) return @@ -232,7 +224,7 @@ func main() { address := *queryAddress if address != "" { - script, err := bchain.AddressToOutputScript(address) + script, err := chain.Parser.AddressToOutputScript(address) if err != nil { glog.Error("GetTransactions ", err) return diff --git a/db/rocksdb.go b/db/rocksdb.go index 17625339..de53549a 100644 --- a/db/rocksdb.go +++ b/db/rocksdb.go @@ -29,11 +29,12 @@ func RepairRocksDB(name string) error { // RocksDB handle type RocksDB struct { - path string - db *gorocksdb.DB - wo *gorocksdb.WriteOptions - ro *gorocksdb.ReadOptions - cfh []*gorocksdb.ColumnFamilyHandle + path string + db *gorocksdb.DB + wo *gorocksdb.WriteOptions + ro *gorocksdb.ReadOptions + cfh []*gorocksdb.ColumnFamilyHandle + chainParser *bchain.BitcoinBlockParser } const ( @@ -93,13 +94,13 @@ func openDB(path string) (*gorocksdb.DB, []*gorocksdb.ColumnFamilyHandle, error) // NewRocksDB opens an internal handle to RocksDB environment. Close // needs to be called to release it. -func NewRocksDB(path string) (d *RocksDB, err error) { +func NewRocksDB(path string, parser *bchain.BitcoinBlockParser) (d *RocksDB, err error) { glog.Infof("rocksdb: open %s", path) db, cfh, err := openDB(path) wo := gorocksdb.NewDefaultWriteOptions() ro := gorocksdb.NewDefaultReadOptions() ro.SetFillCache(false) - return &RocksDB{path, db, wo, ro, cfh}, nil + return &RocksDB{path, db, wo, ro, cfh, parser}, nil } func (d *RocksDB) closeDB() error { @@ -541,7 +542,7 @@ func (d *RocksDB) GetTx(txid string) (*bchain.Tx, uint32, error) { defer val.Free() data := val.Data() if len(data) > 4 { - return unpackTx(data) + return unpackTx(data, d.chainParser) } return nil, 0, nil } @@ -649,10 +650,10 @@ func packTx(tx *bchain.Tx, height uint32, blockTime int64) ([]byte, error) { return buf, err } -func unpackTx(buf []byte) (*bchain.Tx, uint32, error) { +func unpackTx(buf []byte, parser *bchain.BitcoinBlockParser) (*bchain.Tx, uint32, error) { height := unpackUint(buf) bt, l := unpackVarint64(buf[4:]) - tx, err := bchain.ParseTx(buf[4+l:]) + tx, err := parser.ParseTx(buf[4+l:]) if err != nil { return nil, 0, err } diff --git a/db/rocksdb_test.go b/db/rocksdb_test.go index 6b5e2bd4..cedf4e33 100644 --- a/db/rocksdb_test.go +++ b/db/rocksdb_test.go @@ -120,6 +120,7 @@ func Test_packTx(t *testing.T) { func Test_unpackTx(t *testing.T) { type args struct { packedTx string + parser *bchain.BitcoinBlockParser } tests := []struct { name string @@ -129,16 +130,21 @@ func Test_unpackTx(t *testing.T) { wantErr bool }{ { - name: "btc-1", - args: args{packedTx: testTxPacked1}, + name: "btc-1", + args: args{ + packedTx: testTxPacked1, + parser: &bchain.BitcoinBlockParser{Params: bchain.GetChainParams("main")}, + }, want: &testTx1, want1: 123456, wantErr: false, }, - // this test fails now, needs testnet chaincfg.TestNet3Params { - name: "testnet-1", - args: args{packedTx: testTxPacked2}, + name: "testnet-1", + args: args{ + packedTx: testTxPacked2, + parser: &bchain.BitcoinBlockParser{Params: bchain.GetChainParams("test")}, + }, want: &testTx2, want1: 510234, wantErr: false, @@ -147,7 +153,7 @@ func Test_unpackTx(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { b, _ := hex.DecodeString(tt.args.packedTx) - got, got1, err := unpackTx(b) + got, got1, err := unpackTx(b, tt.args.parser) if (err != nil) != tt.wantErr { t.Errorf("unpackTx() error = %v, wantErr %v", err, tt.wantErr) return diff --git a/server/https.go b/server/https.go index 4c2e34e1..c0e67a29 100644 --- a/server/https.go +++ b/server/https.go @@ -21,11 +21,13 @@ type HTTPServer struct { https *http.Server certFiles string db *db.RocksDB + txCache *db.TxCache mempool *bchain.Mempool + chain *bchain.BitcoinRPC } // NewHTTPServer creates new REST interface to blockbook and returns its handle -func NewHTTPServer(httpServerBinding string, certFiles string, db *db.RocksDB, mempool *bchain.Mempool) (*HTTPServer, error) { +func NewHTTPServer(httpServerBinding string, certFiles string, db *db.RocksDB, mempool *bchain.Mempool, chain *bchain.BitcoinRPC, txCache *db.TxCache) (*HTTPServer, error) { https := &http.Server{ Addr: httpServerBinding, } @@ -33,7 +35,9 @@ func NewHTTPServer(httpServerBinding string, certFiles string, db *db.RocksDB, m https: https, certFiles: certFiles, db: db, + txCache: txCache, mempool: mempool, + chain: chain, } r := mux.NewRouter() @@ -129,14 +133,14 @@ func (s *HTTPServer) blockHash(w http.ResponseWriter, r *http.Request) { } } -func getAddress(r *http.Request) (address string, script []byte, err error) { +func (s *HTTPServer) getAddress(r *http.Request) (address string, script []byte, err error) { address = mux.Vars(r)["address"] - script, err = bchain.AddressToOutputScript(address) + script, err = s.chain.Parser.AddressToOutputScript(address) return } -func getAddressAndHeightRange(r *http.Request) (address string, script []byte, lower, higher uint32, err error) { - address, script, err = getAddress(r) +func (s *HTTPServer) getAddressAndHeightRange(r *http.Request) (address string, script []byte, lower, higher uint32, err error) { + address, script, err = s.getAddress(r) if err != nil { return } @@ -156,7 +160,7 @@ type transactionList struct { } func (s *HTTPServer) unconfirmedTransactions(w http.ResponseWriter, r *http.Request) { - address, script, err := getAddress(r) + address, script, err := s.getAddress(r) if err != nil { respondError(w, err, fmt.Sprint("unconfirmedTransactions for address", address)) } @@ -169,7 +173,7 @@ func (s *HTTPServer) unconfirmedTransactions(w http.ResponseWriter, r *http.Requ } func (s *HTTPServer) confirmedTransactions(w http.ResponseWriter, r *http.Request) { - address, script, lower, higher, err := getAddressAndHeightRange(r) + address, script, lower, higher, err := s.getAddressAndHeightRange(r) if err != nil { respondError(w, err, fmt.Sprint("confirmedTransactions for address", address)) } @@ -185,7 +189,7 @@ func (s *HTTPServer) confirmedTransactions(w http.ResponseWriter, r *http.Reques } func (s *HTTPServer) transactions(w http.ResponseWriter, r *http.Request) { - address, script, lower, higher, err := getAddressAndHeightRange(r) + address, script, lower, higher, err := s.getAddressAndHeightRange(r) if err != nil { respondError(w, err, fmt.Sprint("transactions for address", address)) } diff --git a/server/socketio.go b/server/socketio.go index 80cfda66..26b33508 100644 --- a/server/socketio.go +++ b/server/socketio.go @@ -241,7 +241,7 @@ func (s *SocketIoServer) getAddressTxids(addr []string, rr *reqRange) (res resul txids := make([]string, 0) lower, higher := uint32(rr.To), uint32(rr.Start) for _, address := range addr { - script, err := bchain.AddressToOutputScript(address) + script, err := s.chain.Parser.AddressToOutputScript(address) if err != nil { return res, err } @@ -558,19 +558,19 @@ func (s *SocketIoServer) estimateSmartFee(blocks int, conservative bool) (res re type resultGetInfo struct { Result struct { - Version int `json:"version"` - ProtocolVersion int `json:"protocolVersion"` + Version int `json:"version,omitempty"` + ProtocolVersion int `json:"protocolVersion,omitempty"` Blocks int `json:"blocks"` - TimeOffset int `json:"timeOffset"` - Connections int `json:"connections"` - Proxy string `json:"proxy"` - Difficulty float64 `json:"difficulty"` + TimeOffset int `json:"timeOffset,omitempty"` + Connections int `json:"connections,omitempty"` + Proxy string `json:"proxy,omitempty"` + Difficulty float64 `json:"difficulty,omitempty"` Testnet bool `json:"testnet"` - RelayFee float64 `json:"relayFee"` - Errors string `json:"errors"` - Network string `json:"network"` - Subversion string `json:"subversion"` - LocalServices string `json:"localServices"` + RelayFee float64 `json:"relayFee,omitempty"` + Errors string `json:"errors,omitempty"` + Network string `json:"network,omitempty"` + Subversion string `json:"subversion,omitempty"` + LocalServices string `json:"localServices,omitempty"` } `json:"result"` }