blockbook/bchain/coins/btc/bitcoinrpc.go

616 lines
15 KiB
Go
Raw Normal View History

package btc
2017-08-28 09:50:57 -06:00
import (
"blockbook/bchain"
2018-03-13 04:34:49 -06:00
"blockbook/common"
"bytes"
2017-08-28 09:50:57 -06:00
"encoding/hex"
"encoding/json"
"io"
"io/ioutil"
"net"
2017-08-28 09:50:57 -06:00
"net/http"
"time"
"github.com/btcsuite/btcd/wire"
"github.com/golang/glog"
"github.com/juju/errors"
2017-08-28 09:50:57 -06:00
)
// BitcoinRPC is an interface to JSON-RPC bitcoind service.
type BitcoinRPC struct {
client http.Client
rpcURL string
user string
password string
2018-03-08 11:39:21 -07:00
Parser *BitcoinBlockParser
Testnet bool
Network string
Mempool *bchain.Mempool
ParseBlocks bool
2018-03-13 04:34:49 -06:00
metrics *common.Metrics
}
// NewBitcoinRPC returns new BitcoinRPC instance.
2018-03-13 04:34:49 -06:00
func NewBitcoinRPC(url string, user string, password string, timeout time.Duration, parse bool, metrics *common.Metrics) (bchain.BlockChain, error) {
transport := &http.Transport{
Dial: (&net.Dialer{KeepAlive: 600 * time.Second}).Dial,
MaxIdleConns: 100,
MaxIdleConnsPerHost: 100, // necessary to not to deplete ports
}
s := &BitcoinRPC{
client: http.Client{Timeout: timeout, Transport: transport},
rpcURL: url,
user: user,
password: password,
2018-03-08 11:39:21 -07:00
ParseBlocks: parse,
2018-03-13 04:34:49 -06:00
metrics: metrics,
}
chainName, err := s.GetBlockChainInfo()
if err != nil {
return nil, err
}
// always create parser
2018-03-08 11:39:21 -07:00
s.Parser = &BitcoinBlockParser{
Params: GetChainParams(chainName),
}
// parameters for getInfo request
2018-03-08 11:39:21 -07:00
if s.Parser.Params.Net == wire.MainNet {
s.Testnet = false
s.Network = "livenet"
} else {
2018-03-08 11:39:21 -07:00
s.Testnet = true
s.Network = "testnet"
}
2018-03-13 04:34:49 -06:00
s.Mempool = bchain.NewMempool(s, metrics)
2018-03-08 11:39:21 -07:00
glog.Info("rpc: block chain ", s.Parser.Params.Name)
return s, nil
}
func (b *BitcoinRPC) IsTestnet() bool {
2018-03-08 11:39:21 -07:00
return b.Testnet
}
func (b *BitcoinRPC) GetNetworkName() string {
2018-03-08 11:39:21 -07:00
return b.Network
}
// getblockhash
type cmdGetBlockHash struct {
Method string `json:"method"`
Params struct {
Height uint32 `json:"height"`
} `json:"params"`
}
type resGetBlockHash struct {
Error *bchain.RPCError `json:"error"`
Result string `json:"result"`
}
// getbestblockhash
type cmdGetBestBlockHash struct {
Method string `json:"method"`
}
type resGetBestBlockHash struct {
Error *bchain.RPCError `json:"error"`
Result string `json:"result"`
}
2018-01-30 01:45:47 -07:00
// getblockcount
type cmdGetBlockCount struct {
Method string `json:"method"`
}
type resGetBlockCount struct {
Error *bchain.RPCError `json:"error"`
Result uint32 `json:"result"`
2018-01-30 01:45:47 -07:00
}
// getblockchaininfo
type cmdGetBlockChainInfo struct {
Method string `json:"method"`
}
type resGetBlockChainInfo struct {
Error *bchain.RPCError `json:"error"`
Result struct {
Chain string `json:"chain"`
Blocks int `json:"blocks"`
Headers int `json:"headers"`
Bestblockhash string `json:"bestblockhash"`
} `json:"result"`
}
2018-01-31 07:04:54 -07:00
// getrawmempool
type cmdGetMempool struct {
Method string `json:"method"`
}
type resGetMempool struct {
Error *bchain.RPCError `json:"error"`
Result []string `json:"result"`
2018-01-31 07:04:54 -07:00
}
// getblockheader
type cmdGetBlockHeader struct {
Method string `json:"method"`
Params struct {
BlockHash string `json:"blockhash"`
Verbose bool `json:"verbose"`
} `json:"params"`
}
type resGetBlockHeaderRaw struct {
Error *bchain.RPCError `json:"error"`
Result string `json:"result"`
}
type resGetBlockHeaderVerbose struct {
Error *bchain.RPCError `json:"error"`
Result bchain.BlockHeader `json:"result"`
}
// getblock
type cmdGetBlock struct {
Method string `json:"method"`
Params struct {
BlockHash string `json:"blockhash"`
2017-09-12 16:36:08 -06:00
Verbosity int `json:"verbosity"`
} `json:"params"`
}
2017-09-12 16:36:08 -06:00
type resGetBlockRaw struct {
Error *bchain.RPCError `json:"error"`
Result string `json:"result"`
}
2017-09-12 16:36:08 -06:00
type resGetBlockThin struct {
Error *bchain.RPCError `json:"error"`
Result bchain.ThinBlock `json:"result"`
2017-09-12 16:36:08 -06:00
}
type resGetBlockFull struct {
Error *bchain.RPCError `json:"error"`
Result bchain.Block `json:"result"`
}
// getrawtransaction
type cmdGetRawTransaction struct {
Method string `json:"method"`
Params struct {
Txid string `json:"txid"`
Verbose bool `json:"verbose"`
} `json:"params"`
}
type resGetRawTransactionRaw struct {
Error *bchain.RPCError `json:"error"`
Result string `json:"result"`
}
type resGetRawTransactionVerbose struct {
Error *bchain.RPCError `json:"error"`
Result bchain.Tx `json:"result"`
}
// estimatesmartfee
type cmdEstimateSmartFee struct {
Method string `json:"method"`
Params struct {
ConfTarget int `json:"conf_target"`
EstimateMode string `json:"estimate_mode"`
} `json:"params"`
}
type resEstimateSmartFee struct {
Error *bchain.RPCError `json:"error"`
Result struct {
Feerate float64 `json:"feerate"`
Blocks int `json:"blocks"`
} `json:"result"`
}
// sendrawtransaction
type cmdSendRawTransaction struct {
Method string `json:"method"`
Params []string `json:"params"`
}
type resSendRawTransaction struct {
Error *bchain.RPCError `json:"error"`
Result string `json:"result"`
}
2018-03-07 04:08:37 -07:00
// getmempoolentry
type cmdGetMempoolEntry struct {
Method string `json:"method"`
Params []string `json:"params"`
}
type resGetMempoolEntry struct {
Error *bchain.RPCError `json:"error"`
Result *bchain.MempoolEntry `json:"result"`
2018-03-07 04:08:37 -07:00
}
// GetBestBlockHash returns hash of the tip of the best-block-chain.
func (b *BitcoinRPC) GetBestBlockHash() (string, error) {
glog.V(1).Info("rpc: getbestblockhash")
res := resGetBestBlockHash{}
req := cmdGetBestBlockHash{Method: "getbestblockhash"}
2018-03-13 04:34:49 -06:00
err := b.observeRPCLatency(req.Method, func() error { return b.call(&req, &res) })
2017-08-28 09:50:57 -06:00
if err != nil {
return "", err
}
if res.Error != nil {
return "", res.Error
2017-08-28 09:50:57 -06:00
}
return res.Result, nil
2017-08-28 09:50:57 -06:00
}
2018-01-30 01:45:47 -07:00
// GetBestBlockHeight returns height of the tip of the best-block-chain.
func (b *BitcoinRPC) GetBestBlockHeight() (uint32, error) {
glog.V(1).Info("rpc: getblockcount")
2018-01-30 01:45:47 -07:00
res := resGetBlockCount{}
req := cmdGetBlockCount{Method: "getblockcount"}
2018-03-13 04:34:49 -06:00
err := b.observeRPCLatency(req.Method, func() error { return b.call(&req, &res) })
2018-01-30 01:45:47 -07:00
if err != nil {
return 0, err
}
if res.Error != nil {
return 0, res.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"}
2018-03-13 04:34:49 -06:00
err := b.observeRPCLatency(req.Method, func() error { return 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)
res := resGetBlockHash{}
req := cmdGetBlockHash{Method: "getblockhash"}
req.Params.Height = height
2018-03-13 04:34:49 -06:00
err := b.observeRPCLatency(req.Method, func() error { return b.call(&req, &res) })
if err != nil {
return "", errors.Annotatef(err, "height %v", height)
2017-08-28 09:50:57 -06:00
}
if res.Error != nil {
return "", errors.Annotatef(res.Error, "height %v", height)
}
return res.Result, nil
2017-08-28 09:50:57 -06:00
}
// GetBlockHeader returns header of block with given hash.
func (b *BitcoinRPC) GetBlockHeader(hash string) (*bchain.BlockHeader, error) {
glog.V(1).Info("rpc: getblockheader")
res := resGetBlockHeaderVerbose{}
req := cmdGetBlockHeader{Method: "getblockheader"}
req.Params.BlockHash = hash
req.Params.Verbose = true
2018-03-13 04:34:49 -06:00
err := b.observeRPCLatency(req.Method, func() error { return b.call(&req, &res) })
if err != nil {
return nil, errors.Annotatef(err, "hash %v", hash)
2017-08-28 09:50:57 -06:00
}
if res.Error != nil {
return nil, errors.Annotatef(res.Error, "hash %v", hash)
}
return &res.Result, nil
2017-08-28 09:50:57 -06:00
}
// GetBlock returns block with given hash.
func (b *BitcoinRPC) GetBlock(hash string, height uint32) (*bchain.Block, error) {
2018-03-08 11:39:21 -07:00
if !b.ParseBlocks {
2017-09-12 16:36:08 -06:00
return b.GetBlockFull(hash)
}
// optimization
if height > 0 {
return b.getBlockWithoutHeader(hash, height)
}
header, err := b.GetBlockHeader(hash)
2017-09-06 02:59:40 -06:00
if err != nil {
return nil, err
2017-09-06 02:59:40 -06:00
}
data, err := b.GetBlockRaw(hash)
2017-08-28 09:50:57 -06:00
if err != nil {
return nil, err
2017-08-28 09:50:57 -06:00
}
2018-03-08 11:39:21 -07:00
block, err := b.Parser.ParseBlock(data)
2017-08-28 09:50:57 -06:00
if err != nil {
return nil, errors.Annotatef(err, "hash %v", hash)
2017-08-28 09:50:57 -06:00
}
block.BlockHeader = *header
return block, nil
}
// 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) (*bchain.Block, error) {
2018-03-08 11:39:21 -07:00
if !b.ParseBlocks {
return b.GetBlockFull(hash)
}
data, err := b.GetBlockRaw(hash)
if err != nil {
return nil, err
}
2018-03-08 11:39:21 -07:00
block, err := b.Parser.ParseBlock(data)
if err != nil {
return nil, errors.Annotatef(err, "%v %v", height, hash)
}
block.BlockHeader.Hash = hash
block.BlockHeader.Height = height
return block, nil
}
// GetBlockRaw returns block with given hash as bytes.
func (b *BitcoinRPC) GetBlockRaw(hash string) ([]byte, error) {
glog.V(1).Info("rpc: getblock (verbosity=0) ", hash)
2017-09-12 16:36:08 -06:00
res := resGetBlockRaw{}
req := cmdGetBlock{Method: "getblock"}
req.Params.BlockHash = hash
2017-09-12 16:36:08 -06:00
req.Params.Verbosity = 0
2018-03-13 04:34:49 -06:00
err := b.observeRPCLatency(req.Method, func() error { return b.call(&req, &res) })
if err != nil {
return nil, errors.Annotatef(err, "hash %v", hash)
2017-08-28 09:50:57 -06:00
}
if res.Error != nil {
return nil, errors.Annotatef(res.Error, "hash %v", hash)
}
return hex.DecodeString(res.Result)
2017-08-28 09:50:57 -06:00
}
2017-09-12 16:36:08 -06:00
// GetBlockList returns block with given hash by downloading block
// transactions one by one.
func (b *BitcoinRPC) GetBlockList(hash string) (*bchain.Block, error) {
glog.V(1).Info("rpc: getblock (verbosity=1) ", hash)
2017-09-12 16:36:08 -06:00
res := resGetBlockThin{}
req := cmdGetBlock{Method: "getblock"}
req.Params.BlockHash = hash
2017-09-12 16:36:08 -06:00
req.Params.Verbosity = 1
2018-03-13 04:34:49 -06:00
err := b.observeRPCLatency(req.Method, func() error { return b.call(&req, &res) })
2017-08-28 09:50:57 -06:00
if err != nil {
return nil, errors.Annotatef(err, "hash %v", hash)
2017-08-28 09:50:57 -06:00
}
if res.Error != nil {
return nil, errors.Annotatef(res.Error, "hash %v", hash)
}
txs := make([]bchain.Tx, len(res.Result.Txids))
2017-09-12 16:36:08 -06:00
for i, txid := range res.Result.Txids {
2017-08-28 09:50:57 -06:00
tx, err := b.GetTransaction(txid)
if err != nil {
return nil, err
}
2017-09-12 16:36:08 -06:00
txs[i] = *tx
}
block := &bchain.Block{
2017-09-12 16:36:08 -06:00
BlockHeader: res.Result.BlockHeader,
Txs: txs,
}
return block, nil
}
// GetBlockFull returns block with given hash.
func (b *BitcoinRPC) GetBlockFull(hash string) (*bchain.Block, error) {
glog.V(1).Info("rpc: getblock (verbosity=2) ", hash)
2017-09-12 16:36:08 -06:00
res := resGetBlockFull{}
req := cmdGetBlock{Method: "getblock"}
req.Params.BlockHash = hash
req.Params.Verbosity = 2
2018-03-13 04:34:49 -06:00
err := b.observeRPCLatency(req.Method, func() error { return b.call(&req, &res) })
2017-09-12 16:36:08 -06:00
if err != nil {
return nil, errors.Annotatef(err, "hash %v", hash)
2017-09-12 16:36:08 -06:00
}
if res.Error != nil {
return nil, errors.Annotatef(res.Error, "hash %v", hash)
2017-08-28 09:50:57 -06:00
}
return &res.Result, nil
2017-08-28 09:50:57 -06:00
}
2018-01-31 07:04:54 -07:00
// GetMempool returns transactions in mempool.
func (b *BitcoinRPC) GetMempool() ([]string, error) {
glog.V(1).Info("rpc: getrawmempool")
res := resGetMempool{}
req := cmdGetMempool{Method: "getrawmempool"}
2018-03-13 04:34:49 -06:00
err := b.observeRPCLatency(req.Method, func() error { return b.call(&req, &res) })
2018-01-31 07:04:54 -07:00
if err != nil {
return nil, err
}
if res.Error != nil {
return nil, res.Error
}
return res.Result, nil
}
// GetTransaction returns a transaction by the transaction ID.
func (b *BitcoinRPC) GetTransaction(txid string) (*bchain.Tx, error) {
glog.V(1).Info("rpc: getrawtransaction ", txid)
2017-09-06 02:59:40 -06:00
res := resGetRawTransactionVerbose{}
req := cmdGetRawTransaction{Method: "getrawtransaction"}
req.Params.Txid = txid
req.Params.Verbose = true
2018-03-13 04:34:49 -06:00
err := b.observeRPCLatency(req.Method, func() error { return b.call(&req, &res) })
2017-08-28 09:50:57 -06:00
if err != nil {
return nil, errors.Annotatef(err, "txid %v", txid)
2017-08-28 09:50:57 -06:00
}
if res.Error != nil {
return nil, errors.Annotatef(res.Error, "txid %v", txid)
2017-08-28 09:50:57 -06:00
}
return &res.Result, nil
2017-08-28 09:50:57 -06:00
}
// ResyncMempool gets mempool transactions and maps output scripts to transactions.
// ResyncMempool is not reentrant, it should be called from a single thread.
func (b *BitcoinRPC) ResyncMempool(onNewTxAddr func(txid string, addr string)) error {
2018-03-08 11:39:21 -07:00
return b.Mempool.Resync(onNewTxAddr)
}
// GetMempoolTransactions returns slice of mempool transactions for given output script.
func (b *BitcoinRPC) GetMempoolTransactions(outputScript []byte) ([]string, error) {
2018-03-08 11:39:21 -07:00
return b.Mempool.GetTransactions(outputScript)
}
// GetMempoolSpentOutput returns transaction in mempool which spends given outpoint
func (b *BitcoinRPC) GetMempoolSpentOutput(outputTxid string, vout uint32) string {
2018-03-08 11:39:21 -07:00
return b.Mempool.GetSpentOutput(outputTxid, vout)
}
// EstimateSmartFee returns fee estimation.
func (b *BitcoinRPC) EstimateSmartFee(blocks int, conservative bool) (float64, error) {
glog.V(1).Info("rpc: estimatesmartfee ", blocks)
res := resEstimateSmartFee{}
req := cmdEstimateSmartFee{Method: "estimatesmartfee"}
req.Params.ConfTarget = blocks
if conservative {
req.Params.EstimateMode = "CONSERVATIVE"
} else {
req.Params.EstimateMode = "ECONOMICAL"
}
2018-03-13 04:34:49 -06:00
err := b.observeRPCLatency(req.Method, func() error { return b.call(&req, &res) })
if err != nil {
return 0, err
}
if res.Error != nil {
return 0, res.Error
}
return res.Result.Feerate, nil
}
// SendRawTransaction sends raw transaction.
func (b *BitcoinRPC) SendRawTransaction(tx string) (string, error) {
glog.V(1).Info("rpc: sendrawtransaction")
res := resSendRawTransaction{}
req := cmdSendRawTransaction{Method: "sendrawtransaction"}
req.Params = []string{tx}
2018-03-13 04:34:49 -06:00
err := b.observeRPCLatency(req.Method, func() error { return b.call(&req, &res) })
if err != nil {
return "", err
}
if res.Error != nil {
return "", res.Error
}
return res.Result, nil
}
func (b *BitcoinRPC) GetMempoolEntry(txid string) (*bchain.MempoolEntry, error) {
2018-03-07 04:08:37 -07:00
glog.V(1).Info("rpc: getmempoolentry")
res := resGetMempoolEntry{}
req := cmdGetMempoolEntry{
Method: "getmempoolentry",
Params: []string{txid},
}
2018-03-13 04:34:49 -06:00
err := b.observeRPCLatency(req.Method, func() error { return b.call(&req, &res) })
2018-03-07 04:08:37 -07:00
if err != nil {
return nil, err
}
if res.Error != nil {
return nil, res.Error
}
return res.Result, nil
}
2018-03-13 04:34:49 -06:00
func (b *BitcoinRPC) observeRPCLatency(method string, fn func() error) error {
start := time.Now()
err := fn()
if err == nil {
b.metrics.RPCLatency.With(common.Labels{"method": method}).Observe(float64(time.Since(start)) / 1e6) // in milliseconds
2018-03-13 04:34:49 -06:00
}
return err
}
func (b *BitcoinRPC) call(req interface{}, res interface{}) error {
httpData, err := json.Marshal(req)
if err != nil {
return err
}
httpReq, err := http.NewRequest("POST", b.rpcURL, bytes.NewBuffer(httpData))
if err != nil {
return err
}
httpReq.SetBasicAuth(b.user, b.password)
httpRes, err := b.client.Do(httpReq)
// in some cases the httpRes can contain data even if it returns error
// see http://devs.cloudimmunity.com/gotchas-and-common-mistakes-in-go-golang/
if httpRes != nil {
defer httpRes.Body.Close()
}
if err != nil {
return err
}
// read the entire response body until the end to avoid memory leak when reusing http connection
// see http://devs.cloudimmunity.com/gotchas-and-common-mistakes-in-go-golang/
defer io.Copy(ioutil.Discard, httpRes.Body)
// if server returns HTTP error code it might not return json with response
// handle both cases
if httpRes.StatusCode != 200 {
err = json.NewDecoder(httpRes.Body).Decode(&res)
if err != nil {
return errors.New(httpRes.Status)
}
return nil
}
return json.NewDecoder(httpRes.Body).Decode(&res)
}
// GetChainParser returns BlockChainParser
func (b *BitcoinRPC) GetChainParser() bchain.BlockChainParser {
2018-03-08 11:39:21 -07:00
return b.Parser
}