Detect type of the block chain and use correct chain params

indexv1
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"
"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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
}

View File

@ -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

View File

@ -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))
}

View File

@ -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"`
}