Detect type of the block chain and use correct chain params

This commit is contained in:
Martin Boehm 2018-03-07 09:38:19 +01:00
parent cac412527d
commit 0783cac294
8 changed files with 134 additions and 74 deletions

View File

@ -11,6 +11,8 @@ import (
"net/http" "net/http"
"time" "time"
"github.com/btcsuite/btcd/wire"
"github.com/golang/glog" "github.com/golang/glog"
"github.com/juju/errors" "github.com/juju/errors"
) )
@ -60,6 +62,22 @@ type resGetBlockCount struct {
Result uint32 `json:"result"` 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 // getrawmempool
type cmdGetMempool struct { type cmdGetMempool struct {
@ -166,32 +184,50 @@ type resSendRawTransaction struct {
Result string `json:"result"` Result string `json:"result"`
} }
type BlockParser interface {
ParseBlock(b []byte) (*Block, error)
}
// BitcoinRPC is an interface to JSON-RPC bitcoind service. // BitcoinRPC is an interface to JSON-RPC bitcoind service.
type BitcoinRPC struct { type BitcoinRPC struct {
client http.Client client http.Client
URL string URL string
User string User string
Password string Password string
Parser BlockParser Parser *BitcoinBlockParser
Testnet bool
Network string
} }
// NewBitcoinRPC returns new BitcoinRPC instance. // 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{ transport := &http.Transport{
Dial: (&net.Dialer{KeepAlive: 600 * time.Second}).Dial, Dial: (&net.Dialer{KeepAlive: 600 * time.Second}).Dial,
MaxIdleConns: 100, MaxIdleConns: 100,
MaxIdleConnsPerHost: 100, // necessary to not to deplete ports MaxIdleConnsPerHost: 100, // necessary to not to deplete ports
} }
return &BitcoinRPC{ s := &BitcoinRPC{
client: http.Client{Timeout: timeout, Transport: transport}, client: http.Client{Timeout: timeout, Transport: transport},
URL: url, URL: url,
User: user, User: user,
Password: password, 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. // 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 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. // GetBlockHash returns hash of block in best-block-chain at given height.
func (b *BitcoinRPC) GetBlockHash(height uint32) (string, error) { func (b *BitcoinRPC) GetBlockHash(height uint32) (string, error) {
glog.V(1).Info("rpc: getblockhash ", height) glog.V(1).Info("rpc: getblockhash ", height)

View File

@ -15,13 +15,14 @@ import (
// GetChainParams contains network parameters for the main Bitcoin network, // GetChainParams contains network parameters for the main Bitcoin network,
// the regression test Bitcoin network, the test Bitcoin network and // the regression test Bitcoin network, the test Bitcoin network and
// the simulation test Bitcoin network, in this order // the simulation test Bitcoin network, in this order
func GetChainParams() []*chaincfg.Params { func GetChainParams(chain string) *chaincfg.Params {
return []*chaincfg.Params{ switch chain {
&chaincfg.MainNetParams, case "test":
&chaincfg.RegressionNetParams, return &chaincfg.TestNet3Params
&chaincfg.TestNet3Params, case "regtest":
&chaincfg.SimNetParams, return &chaincfg.RegressionNetParams
} }
return &chaincfg.MainNetParams
} }
type BitcoinBlockParser struct { type BitcoinBlockParser struct {
@ -29,8 +30,8 @@ type BitcoinBlockParser struct {
} }
// AddressToOutputScript converts bitcoin address to ScriptPubKey // AddressToOutputScript converts bitcoin address to ScriptPubKey
func AddressToOutputScript(address string) ([]byte, error) { func (p *BitcoinBlockParser) AddressToOutputScript(address string) ([]byte, error) {
da, err := btcutil.DecodeAddress(address, GetChainParams()[0]) da, err := btcutil.DecodeAddress(address, p.Params)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -42,8 +43,8 @@ func AddressToOutputScript(address string) ([]byte, error) {
} }
// OutputScriptToAddresses converts ScriptPubKey to bitcoin addresses // OutputScriptToAddresses converts ScriptPubKey to bitcoin addresses
func OutputScriptToAddresses(script []byte) ([]string, error) { func (p *BitcoinBlockParser) OutputScriptToAddresses(script []byte) ([]string, error) {
_, addresses, _, err := txscript.ExtractPkScriptAddrs(script, GetChainParams()[0]) _, addresses, _, err := txscript.ExtractPkScriptAddrs(script, p.Params)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -55,18 +56,18 @@ func OutputScriptToAddresses(script []byte) ([]string, error) {
} }
// ParseTx parses byte array containing transaction and returns Tx struct // 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{} t := wire.MsgTx{}
r := bytes.NewReader(b) r := bytes.NewReader(b)
if err := t.Deserialize(r); err != nil { if err := t.Deserialize(r); err != nil {
return nil, err return nil, err
} }
tx := txFromMsgTx(&t, true) tx := p.txFromMsgTx(&t, true)
tx.Hex = hex.EncodeToString(b) tx.Hex = hex.EncodeToString(b)
return &tx, nil 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)) vin := make([]Vin, len(t.TxIn))
for i, in := range t.TxIn { for i, in := range t.TxIn {
if blockchain.IsCoinBaseTx(t) { if blockchain.IsCoinBaseTx(t) {
@ -91,7 +92,7 @@ func txFromMsgTx(t *wire.MsgTx, parseAddresses bool) Tx {
for i, out := range t.TxOut { for i, out := range t.TxOut {
addrs := []string{} addrs := []string{}
if parseAddresses { if parseAddresses {
addrs, _ = OutputScriptToAddresses(out.PkScript) addrs, _ = p.OutputScriptToAddresses(out.PkScript)
} }
s := ScriptPubKey{ s := ScriptPubKey{
Hex: hex.EncodeToString(out.PkScript), Hex: hex.EncodeToString(out.PkScript),
@ -130,7 +131,7 @@ func (p *BitcoinBlockParser) ParseBlock(b []byte) (*Block, error) {
txs := make([]Tx, len(w.Transactions)) txs := make([]Tx, len(w.Transactions))
for ti, t := range w.Transactions { for ti, t := range w.Transactions {
txs[ti] = txFromMsgTx(t, false) txs[ti] = p.txFromMsgTx(t, false)
} }
return &Block{Txs: txs}, nil return &Block{Txs: txs}, nil

View File

@ -41,9 +41,11 @@ func TestAddressToOutputScript(t *testing.T) {
wantErr: false, wantErr: false,
}, },
} }
parser := &BitcoinBlockParser{Params: GetChainParams("main")}
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { 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 { if (err != nil) != tt.wantErr {
t.Errorf("AddressToOutputScript() error = %v, wantErr %v", err, tt.wantErr) t.Errorf("AddressToOutputScript() error = %v, wantErr %v", err, tt.wantErr)
return return
@ -91,10 +93,11 @@ func TestOutputScriptToAddresses(t *testing.T) {
wantErr: false, wantErr: false,
}, },
} }
parser := &BitcoinBlockParser{Params: GetChainParams("main")}
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
b, _ := hex.DecodeString(tt.args.script) b, _ := hex.DecodeString(tt.args.script)
got, err := OutputScriptToAddresses(b) got, err := parser.OutputScriptToAddresses(b)
if (err != nil) != tt.wantErr { if (err != nil) != tt.wantErr {
t.Errorf("OutputScriptToAddresses() error = %v, wantErr %v", err, tt.wantErr) t.Errorf("OutputScriptToAddresses() error = %v, wantErr %v", err, tt.wantErr)
return return

View File

@ -102,22 +102,14 @@ func main() {
return return
} }
chain = bchain.NewBitcoinRPC( var err error
*rpcURL, if chain, err = bchain.NewBitcoinRPC(*rpcURL, *rpcUser, *rpcPass, time.Duration(*rpcTimeout)*time.Second, *parse); err != nil {
*rpcUser, glog.Fatal("NewBitcoinRPC ", err)
*rpcPass,
time.Duration(*rpcTimeout)*time.Second)
if *parse {
chain.Parser = &bchain.BitcoinBlockParser{
Params: bchain.GetChainParams()[0],
}
} }
mempool = bchain.NewMempool(chain) mempool = bchain.NewMempool(chain)
var err error index, err = db.NewRocksDB(*dbPath, chain.Parser)
index, err = db.NewRocksDB(*dbPath)
if err != nil { if err != nil {
glog.Fatalf("NewRocksDB %v", err) glog.Fatalf("NewRocksDB %v", err)
} }
@ -164,7 +156,7 @@ func main() {
var httpServer *server.HTTPServer var httpServer *server.HTTPServer
if *httpServerBinding != "" { if *httpServerBinding != "" {
httpServer, err = server.NewHTTPServer(*httpServerBinding, *certFiles, index, mempool) httpServer, err = server.NewHTTPServer(*httpServerBinding, *certFiles, index, mempool, chain, txCache)
if err != nil { if err != nil {
glog.Error("https: ", err) glog.Error("https: ", err)
return return
@ -232,7 +224,7 @@ func main() {
address := *queryAddress address := *queryAddress
if address != "" { if address != "" {
script, err := bchain.AddressToOutputScript(address) script, err := chain.Parser.AddressToOutputScript(address)
if err != nil { if err != nil {
glog.Error("GetTransactions ", err) glog.Error("GetTransactions ", err)
return return

View File

@ -29,11 +29,12 @@ func RepairRocksDB(name string) error {
// RocksDB handle // RocksDB handle
type RocksDB struct { type RocksDB struct {
path string path string
db *gorocksdb.DB db *gorocksdb.DB
wo *gorocksdb.WriteOptions wo *gorocksdb.WriteOptions
ro *gorocksdb.ReadOptions ro *gorocksdb.ReadOptions
cfh []*gorocksdb.ColumnFamilyHandle cfh []*gorocksdb.ColumnFamilyHandle
chainParser *bchain.BitcoinBlockParser
} }
const ( const (
@ -93,13 +94,13 @@ func openDB(path string) (*gorocksdb.DB, []*gorocksdb.ColumnFamilyHandle, error)
// NewRocksDB opens an internal handle to RocksDB environment. Close // NewRocksDB opens an internal handle to RocksDB environment. Close
// needs to be called to release it. // 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) glog.Infof("rocksdb: open %s", path)
db, cfh, err := openDB(path) db, cfh, err := openDB(path)
wo := gorocksdb.NewDefaultWriteOptions() wo := gorocksdb.NewDefaultWriteOptions()
ro := gorocksdb.NewDefaultReadOptions() ro := gorocksdb.NewDefaultReadOptions()
ro.SetFillCache(false) 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 { func (d *RocksDB) closeDB() error {
@ -541,7 +542,7 @@ func (d *RocksDB) GetTx(txid string) (*bchain.Tx, uint32, error) {
defer val.Free() defer val.Free()
data := val.Data() data := val.Data()
if len(data) > 4 { if len(data) > 4 {
return unpackTx(data) return unpackTx(data, d.chainParser)
} }
return nil, 0, nil return nil, 0, nil
} }
@ -649,10 +650,10 @@ func packTx(tx *bchain.Tx, height uint32, blockTime int64) ([]byte, error) {
return buf, err 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) height := unpackUint(buf)
bt, l := unpackVarint64(buf[4:]) bt, l := unpackVarint64(buf[4:])
tx, err := bchain.ParseTx(buf[4+l:]) tx, err := parser.ParseTx(buf[4+l:])
if err != nil { if err != nil {
return nil, 0, err return nil, 0, err
} }

View File

@ -120,6 +120,7 @@ func Test_packTx(t *testing.T) {
func Test_unpackTx(t *testing.T) { func Test_unpackTx(t *testing.T) {
type args struct { type args struct {
packedTx string packedTx string
parser *bchain.BitcoinBlockParser
} }
tests := []struct { tests := []struct {
name string name string
@ -129,16 +130,21 @@ func Test_unpackTx(t *testing.T) {
wantErr bool wantErr bool
}{ }{
{ {
name: "btc-1", name: "btc-1",
args: args{packedTx: testTxPacked1}, args: args{
packedTx: testTxPacked1,
parser: &bchain.BitcoinBlockParser{Params: bchain.GetChainParams("main")},
},
want: &testTx1, want: &testTx1,
want1: 123456, want1: 123456,
wantErr: false, wantErr: false,
}, },
// this test fails now, needs testnet chaincfg.TestNet3Params
{ {
name: "testnet-1", name: "testnet-1",
args: args{packedTx: testTxPacked2}, args: args{
packedTx: testTxPacked2,
parser: &bchain.BitcoinBlockParser{Params: bchain.GetChainParams("test")},
},
want: &testTx2, want: &testTx2,
want1: 510234, want1: 510234,
wantErr: false, wantErr: false,
@ -147,7 +153,7 @@ func Test_unpackTx(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
b, _ := hex.DecodeString(tt.args.packedTx) b, _ := hex.DecodeString(tt.args.packedTx)
got, got1, err := unpackTx(b) got, got1, err := unpackTx(b, tt.args.parser)
if (err != nil) != tt.wantErr { if (err != nil) != tt.wantErr {
t.Errorf("unpackTx() error = %v, wantErr %v", err, tt.wantErr) t.Errorf("unpackTx() error = %v, wantErr %v", err, tt.wantErr)
return return

View File

@ -21,11 +21,13 @@ type HTTPServer struct {
https *http.Server https *http.Server
certFiles string certFiles string
db *db.RocksDB db *db.RocksDB
txCache *db.TxCache
mempool *bchain.Mempool mempool *bchain.Mempool
chain *bchain.BitcoinRPC
} }
// NewHTTPServer creates new REST interface to blockbook and returns its handle // 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{ https := &http.Server{
Addr: httpServerBinding, Addr: httpServerBinding,
} }
@ -33,7 +35,9 @@ func NewHTTPServer(httpServerBinding string, certFiles string, db *db.RocksDB, m
https: https, https: https,
certFiles: certFiles, certFiles: certFiles,
db: db, db: db,
txCache: txCache,
mempool: mempool, mempool: mempool,
chain: chain,
} }
r := mux.NewRouter() 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"] address = mux.Vars(r)["address"]
script, err = bchain.AddressToOutputScript(address) script, err = s.chain.Parser.AddressToOutputScript(address)
return return
} }
func getAddressAndHeightRange(r *http.Request) (address string, script []byte, lower, higher uint32, err error) { func (s *HTTPServer) getAddressAndHeightRange(r *http.Request) (address string, script []byte, lower, higher uint32, err error) {
address, script, err = getAddress(r) address, script, err = s.getAddress(r)
if err != nil { if err != nil {
return return
} }
@ -156,7 +160,7 @@ type transactionList struct {
} }
func (s *HTTPServer) unconfirmedTransactions(w http.ResponseWriter, r *http.Request) { func (s *HTTPServer) unconfirmedTransactions(w http.ResponseWriter, r *http.Request) {
address, script, err := getAddress(r) address, script, err := s.getAddress(r)
if err != nil { if err != nil {
respondError(w, err, fmt.Sprint("unconfirmedTransactions for address", address)) 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) { 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 { if err != nil {
respondError(w, err, fmt.Sprint("confirmedTransactions for address", address)) 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) { 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 { if err != nil {
respondError(w, err, fmt.Sprint("transactions for address", address)) respondError(w, err, fmt.Sprint("transactions for address", address))
} }

View File

@ -241,7 +241,7 @@ func (s *SocketIoServer) getAddressTxids(addr []string, rr *reqRange) (res resul
txids := make([]string, 0) txids := make([]string, 0)
lower, higher := uint32(rr.To), uint32(rr.Start) lower, higher := uint32(rr.To), uint32(rr.Start)
for _, address := range addr { for _, address := range addr {
script, err := bchain.AddressToOutputScript(address) script, err := s.chain.Parser.AddressToOutputScript(address)
if err != nil { if err != nil {
return res, err return res, err
} }
@ -558,19 +558,19 @@ func (s *SocketIoServer) estimateSmartFee(blocks int, conservative bool) (res re
type resultGetInfo struct { type resultGetInfo struct {
Result struct { Result struct {
Version int `json:"version"` Version int `json:"version,omitempty"`
ProtocolVersion int `json:"protocolVersion"` ProtocolVersion int `json:"protocolVersion,omitempty"`
Blocks int `json:"blocks"` Blocks int `json:"blocks"`
TimeOffset int `json:"timeOffset"` TimeOffset int `json:"timeOffset,omitempty"`
Connections int `json:"connections"` Connections int `json:"connections,omitempty"`
Proxy string `json:"proxy"` Proxy string `json:"proxy,omitempty"`
Difficulty float64 `json:"difficulty"` Difficulty float64 `json:"difficulty,omitempty"`
Testnet bool `json:"testnet"` Testnet bool `json:"testnet"`
RelayFee float64 `json:"relayFee"` RelayFee float64 `json:"relayFee,omitempty"`
Errors string `json:"errors"` Errors string `json:"errors,omitempty"`
Network string `json:"network"` Network string `json:"network,omitempty"`
Subversion string `json:"subversion"` Subversion string `json:"subversion,omitempty"`
LocalServices string `json:"localServices"` LocalServices string `json:"localServices,omitempty"`
} `json:"result"` } `json:"result"`
} }