Detect type of the block chain and use correct chain params
parent
cac412527d
commit
0783cac294
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
20
blockbook.go
20
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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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"`
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue