decred.org/dcrdex@v1.0.5/server/asset/btc/rpcclient.go (about)

     1  // This code is available on the terms of the project LICENSE.md file,
     2  // also available online at https://blueoakcouncil.org/license/1.0.0.
     3  
     4  package btc
     5  
     6  import (
     7  	"bytes"
     8  	"context"
     9  	"encoding/json"
    10  	"fmt"
    11  	"math"
    12  	"math/rand"
    13  	"sort"
    14  
    15  	"decred.org/dcrdex/dex"
    16  	"github.com/btcsuite/btcd/btcjson"
    17  	"github.com/btcsuite/btcd/chaincfg/chainhash"
    18  	"github.com/btcsuite/btcd/wire"
    19  )
    20  
    21  const (
    22  	methodGetBestBlockHash  = "getbestblockhash"
    23  	methodGetBlockchainInfo = "getblockchaininfo"
    24  	methodEstimateSmartFee  = "estimatesmartfee"
    25  	methodEstimateFee       = "estimatefee"
    26  	methodGetTxOut          = "gettxout"
    27  	methodGetRawTransaction = "getrawtransaction"
    28  	methodGetBlock          = "getblock"
    29  	methodGetIndexInfo      = "getindexinfo"
    30  	methodGetBlockHeader    = "getblockheader"
    31  	methodGetBlockStats     = "getblockstats"
    32  	methodGetBlockHash      = "getblockhash"
    33  
    34  	errNoCompetition = dex.ErrorKind("no competition")
    35  	errNoFeeRate     = dex.ErrorKind("fee rate could not be estimated")
    36  )
    37  
    38  // RawRequester is for sending context-aware RPC requests, and has methods for
    39  // shutting down the underlying connection. The returned error should be of type
    40  // dcrjson.RPCError if non-nil.
    41  type RawRequester interface {
    42  	RawRequest(context.Context, string, []json.RawMessage) (json.RawMessage, error)
    43  	Shutdown()
    44  	WaitForShutdown()
    45  }
    46  
    47  // BlockFeeTransactions is a function that fetches a set of FeeTx, used to
    48  // calculate median-fees manually.
    49  type BlockFeeTransactions func(rc *RPCClient, blockHash *chainhash.Hash) (feeTxs []FeeTx, prevBlock chainhash.Hash, err error)
    50  
    51  // RPCClient is a bitcoind wallet RPC client that uses rpcclient.Client's
    52  // RawRequest for wallet-related calls.
    53  type RPCClient struct {
    54  	ctx                  context.Context
    55  	requester            RawRequester
    56  	booleanGetBlockRPC   bool
    57  	maxFeeBlocks         int
    58  	arglessFeeEstimates  bool
    59  	numericGetRawRPC     bool
    60  	blockDeserializer    func([]byte) (*wire.MsgBlock, error)
    61  	deserializeTx        func([]byte) (*wire.MsgTx, error)
    62  	blockFeeTransactions BlockFeeTransactions
    63  }
    64  
    65  func (rc *RPCClient) callHashGetter(method string, args anylist) (*chainhash.Hash, error) {
    66  	var txid string
    67  	err := rc.call(method, args, &txid)
    68  	if err != nil {
    69  		return nil, err
    70  	}
    71  	return chainhash.NewHashFromStr(txid)
    72  }
    73  
    74  // GetBestBlockHash returns the hash of the best block in the longest block
    75  // chain.
    76  func (rc *RPCClient) GetBestBlockHash() (*chainhash.Hash, error) {
    77  	return rc.callHashGetter(methodGetBestBlockHash, nil)
    78  }
    79  
    80  // GetBlockchainInfoResult models the data returned from the getblockchaininfo
    81  // command.
    82  type GetBlockchainInfoResult struct {
    83  	Blocks               int64  `json:"blocks"`
    84  	Headers              int64  `json:"headers"`
    85  	BestBlockHash        string `json:"bestblockhash"`
    86  	InitialBlockDownload bool   `json:"initialblockdownload"`
    87  }
    88  
    89  // GetBlockChainInfo returns information related to the processing state of
    90  // various chain-specific details.
    91  func (rc *RPCClient) GetBlockChainInfo() (*GetBlockchainInfoResult, error) {
    92  	chainInfo := new(GetBlockchainInfoResult)
    93  	err := rc.call(methodGetBlockchainInfo, nil, chainInfo)
    94  	if err != nil {
    95  		return nil, err
    96  	}
    97  	return chainInfo, nil
    98  }
    99  
   100  // txIndexResult models the data returned from the getindexinfo command for
   101  // txindex.
   102  // txIndexResult.Txindex is nil if the returned data is an empty json object.
   103  type txIndexResult struct {
   104  	TxIndex *struct{} `json:"txindex"`
   105  }
   106  
   107  // checkTxIndex checks if bitcoind transaction index is enabled.
   108  func (rc *RPCClient) checkTxIndex() (bool, error) {
   109  	res := new(txIndexResult)
   110  	err := rc.call(methodGetIndexInfo, anylist{"txindex"}, res)
   111  	if err == nil {
   112  		// Return early if there is no error. bitcoind returns an empty json
   113  		// object if txindex is not enabled. It is safe to conclude txindex is
   114  		// enabled if res.Txindex is not nil.
   115  		return res.TxIndex != nil, nil
   116  	}
   117  
   118  	if !isMethodNotFoundErr(err) {
   119  		return false, err
   120  	}
   121  
   122  	// Using block at index 5 to retrieve a coinbase transaction and ensure
   123  	// txindex is enabled for pre 0.21 versions of bitcoind.
   124  	const blockIndex = 5
   125  	blockHash, err := rc.getBlockHash(blockIndex)
   126  	if err != nil {
   127  		return false, err
   128  	}
   129  
   130  	blockInfo, err := rc.GetBlockVerbose(blockHash)
   131  	if err != nil {
   132  		return false, err
   133  	}
   134  
   135  	if len(blockInfo.Tx) == 0 {
   136  		return false, fmt.Errorf("block %d does not have a coinbase transaction", blockIndex)
   137  	}
   138  
   139  	txHash, err := chainhash.NewHashFromStr(blockInfo.Tx[0])
   140  	if err != nil {
   141  		return false, err
   142  	}
   143  
   144  	// Retrieve coinbase transaction information.
   145  	txBytes, err := rc.GetRawTransaction(txHash)
   146  	if err != nil {
   147  		return false, err
   148  	}
   149  
   150  	return len(txBytes) != 0, nil
   151  }
   152  
   153  // getBlockHash fetches the block hash for the block at the given index.
   154  func (rc *RPCClient) getBlockHash(index int64) (*chainhash.Hash, error) {
   155  	var blockHashStr string
   156  	if err := rc.call(methodGetBlockHash, anylist{index}, &blockHashStr); err != nil {
   157  		return nil, err
   158  	}
   159  	return chainhash.NewHashFromStr(blockHashStr)
   160  }
   161  
   162  // EstimateSmartFee requests the server to estimate a fee level.
   163  func (rc *RPCClient) EstimateSmartFee(confTarget int64, mode *btcjson.EstimateSmartFeeMode) (uint64, error) {
   164  	res := new(btcjson.EstimateSmartFeeResult)
   165  	if err := rc.call(methodEstimateSmartFee, anylist{confTarget, mode}, res); err != nil {
   166  		return 0, err
   167  	}
   168  	if res.FeeRate == nil || *res.FeeRate <= 0 {
   169  		return 0, errNoFeeRate
   170  	}
   171  	return uint64(math.Round(*res.FeeRate * 1e5)), nil
   172  }
   173  
   174  // EstimateFee requests the server to estimate a fee level.
   175  func (rc *RPCClient) EstimateFee(confTarget int64) (uint64, error) {
   176  	var feeRate float64
   177  	var args anylist
   178  	if !rc.arglessFeeEstimates {
   179  		args = anylist{confTarget}
   180  	}
   181  	if err := rc.call(methodEstimateFee, args, &feeRate); err != nil {
   182  		return 0, err
   183  	}
   184  	if feeRate <= 0 {
   185  		return 0, errNoFeeRate
   186  	}
   187  	return uint64(math.Round(feeRate * 1e5)), nil
   188  }
   189  
   190  // GetTxOut returns the transaction output info if it's unspent and
   191  // nil, otherwise.
   192  func (rc *RPCClient) GetTxOut(txHash *chainhash.Hash, index uint32, mempool bool) (*btcjson.GetTxOutResult, error) {
   193  	// Note that we pass to call pointer to a pointer (&res) so that
   194  	// json.Unmarshal can nil the pointer if the method returns the JSON null.
   195  	var res *btcjson.GetTxOutResult
   196  	return res, rc.call(methodGetTxOut, anylist{txHash.String(), index, mempool},
   197  		&res)
   198  }
   199  
   200  // GetRawTransaction retrieves tx's information.
   201  func (rc *RPCClient) GetRawTransaction(txHash *chainhash.Hash) ([]byte, error) {
   202  	var txB dex.Bytes
   203  	args := anylist{txHash.String(), false}
   204  	if rc.numericGetRawRPC {
   205  		args[1] = 0
   206  	}
   207  	err := rc.call(methodGetRawTransaction, args, &txB)
   208  	if err != nil {
   209  		return nil, err
   210  	}
   211  	return txB, nil
   212  }
   213  
   214  // GetRawTransactionVerbose retrieves the verbose tx information.
   215  func (rc *RPCClient) GetRawTransactionVerbose(txHash *chainhash.Hash) (*VerboseTxExtended, error) {
   216  	args := anylist{txHash.String(), true}
   217  	if rc.numericGetRawRPC {
   218  		args[1] = 1
   219  	}
   220  	res := new(VerboseTxExtended)
   221  	return res, rc.call(methodGetRawTransaction, args, res)
   222  }
   223  
   224  // GetBlockVerboseResult is a subset of *btcjson.GetBlockVerboseResult.
   225  type GetBlockVerboseResult struct {
   226  	Hash          string   `json:"hash"`
   227  	Confirmations int64    `json:"confirmations"`
   228  	Height        int64    `json:"height"`
   229  	Tx            []string `json:"tx,omitempty"`
   230  	PreviousHash  string   `json:"previousblockhash"`
   231  }
   232  
   233  func (rc *RPCClient) GetRawBlock(blockHash *chainhash.Hash) ([]byte, error) {
   234  	arg := any(0)
   235  	if rc.booleanGetBlockRPC {
   236  		arg = false
   237  	}
   238  	var blockB dex.Bytes // UnmarshalJSON hex -> bytes
   239  	err := rc.call(methodGetBlock, anylist{blockHash.String(), arg}, &blockB)
   240  	if err != nil {
   241  		return nil, err
   242  	}
   243  	return blockB, nil
   244  }
   245  
   246  func (rc *RPCClient) GetMsgBlock(blockHash *chainhash.Hash) (*wire.MsgBlock, error) {
   247  	blockB, err := rc.GetRawBlock(blockHash)
   248  	if err != nil {
   249  		return nil, err
   250  	}
   251  
   252  	var msgBlock *wire.MsgBlock
   253  	if rc.blockDeserializer == nil {
   254  		msgBlock = &wire.MsgBlock{}
   255  		if err := msgBlock.Deserialize(bytes.NewReader(blockB)); err != nil {
   256  			return nil, err
   257  		}
   258  	} else {
   259  		msgBlock, err = rc.blockDeserializer(blockB)
   260  		if err != nil {
   261  			return nil, err
   262  		}
   263  	}
   264  	return msgBlock, nil
   265  }
   266  
   267  type VerboseHeader struct {
   268  	Hash          string `json:"hash"`
   269  	Confirmations int64  `json:"confirmations"`
   270  	Height        int32  `json:"height"`
   271  	Version       int32  `json:"version"`
   272  	VersionHex    string `json:"versionHex"`
   273  	MerkleRoot    string `json:"merkleroot"`
   274  	Time          int64  `json:"time"`
   275  	// Nonce         uint64  `json:"nonce"`
   276  	Bits         string  `json:"bits"`
   277  	Difficulty   float64 `json:"difficulty"`
   278  	PreviousHash string  `json:"previousblockhash,omitempty"`
   279  	NextHash     string  `json:"nextblockhash,omitempty"`
   280  }
   281  
   282  // getBlockWithVerboseHeader fetches raw block data, and the "verbose" block
   283  // header, for the block with the given hash. The verbose block header return is
   284  // separate because it contains other useful info like the height and median
   285  // time that the wire type does not contain.
   286  func (rc *RPCClient) getBlockWithVerboseHeader(blockHash *chainhash.Hash) (*wire.MsgBlock, *VerboseHeader, error) {
   287  	msgBlock, err := rc.GetMsgBlock(blockHash)
   288  	if err != nil {
   289  		return nil, nil, err
   290  	}
   291  
   292  	var verboseHeader *VerboseHeader
   293  	err = rc.call(methodGetBlockHeader, anylist{blockHash.String(), true}, &verboseHeader)
   294  	if err != nil {
   295  		return nil, nil, err
   296  	}
   297  
   298  	return msgBlock, verboseHeader, nil
   299  }
   300  
   301  // GetBlockVerbose fetches verbose block data for the block with the given hash.
   302  func (rc *RPCClient) GetBlockVerbose(blockHash *chainhash.Hash) (*GetBlockVerboseResult, error) {
   303  	arg := any(1)
   304  	if rc.booleanGetBlockRPC {
   305  		arg = true
   306  	}
   307  	res := new(GetBlockVerboseResult)
   308  	return res, rc.call(methodGetBlock, anylist{blockHash.String(), arg}, res)
   309  }
   310  
   311  // MedianFeeRate returns the median rate from the specified block.
   312  func (rc *RPCClient) medianFeeRate() (uint64, error) {
   313  	blockHash, err := rc.GetBestBlockHash()
   314  	if err != nil {
   315  		return 0, err
   316  	}
   317  
   318  	res := struct {
   319  		FeeRatePercentiles []uint64 `json:"feerate_percentiles"` // 10th, 25th, 50th, 75th, and 90th percentiles
   320  		TxCount            int      `json:"txs"`
   321  	}{}
   322  
   323  	categories := []string{"feerate_percentiles", "txs"}
   324  
   325  	// We need at least a few transactions, but there's nothing stopping a miner
   326  	// from publishing empty blocks, regardless of the current mempool state,
   327  	// and we would want to reduce the impact of a particularly choosy node as
   328  	// well, I think. So we'll check > 100 transaction in up to 10 blocks,
   329  	// taking a weighted average of the fees. Consider two cases.
   330  	//
   331  	// 1) When the first block has > 100 transactions, it probably indicates the
   332  	// the miner is operating as expected and the blockchain is busy. In this
   333  	// case, the most recent block is the best estimate, since fees from older
   334  	// blocks could become quickly outdated on a busy chain.
   335  	//
   336  	// 2) If the first block has fewer transactions, it may be safe to say that
   337  	// either a) the miner is not stuffing blocks as expected or b) the
   338  	// blockchain does not have 100 txs/block worth of traffic. Because we have
   339  	// no historical view of mempool, it's impossible to say which one it is,
   340  	// though. In this case, taking a weighted average over a few recent blocks
   341  	// would provide a better estimate.
   342  
   343  	var blocksChecked, txCount int
   344  	var weight uint64
   345  	for txCount < 101 {
   346  		if blocksChecked >= rc.maxFeeBlocks {
   347  			return 0, errNoCompetition
   348  		}
   349  
   350  		if err := rc.call(methodGetBlockStats, anylist{blockHash.String(), categories}, &res); err != nil {
   351  			return 0, err
   352  		}
   353  		if len(res.FeeRatePercentiles) != 5 {
   354  			return 0, fmt.Errorf("unexpected feerate_percentiles response. %d entries", len(res.FeeRatePercentiles))
   355  		}
   356  
   357  		feeRate := res.FeeRatePercentiles[2]
   358  
   359  		weight += uint64(res.TxCount) * feeRate
   360  
   361  		txCount += res.TxCount
   362  		blocksChecked++
   363  
   364  		if txCount >= 101 {
   365  			break
   366  		}
   367  
   368  		// Not enough transactions to count yet.
   369  		verboseBlock, err := rc.GetBlockVerbose(blockHash)
   370  		if err != nil {
   371  			return 0, err
   372  		}
   373  
   374  		blockHash, err = chainhash.NewHashFromStr(verboseBlock.PreviousHash)
   375  		if err != nil {
   376  			return 0, err
   377  		}
   378  	}
   379  
   380  	// rounded average
   381  	return uint64(math.Round(float64(weight) / float64(txCount))), nil
   382  }
   383  
   384  // FeeTx is a representation of a transaction that 1) has zero or more previous
   385  // outpoints to fetch, and 2) given the requested outpoints, can report its tx
   386  // fee rate, in Sats/byte.
   387  type FeeTx interface {
   388  	PrevOuts() []wire.OutPoint
   389  	FeeRate(map[chainhash.Hash]map[int]int64) (uint64, error)
   390  }
   391  
   392  // BTCFeeTx is the FeeTx for a standard Bitcoin MsgTx.
   393  type BTCFeeTx struct {
   394  	*wire.MsgTx
   395  }
   396  
   397  var _ FeeTx = (*BTCFeeTx)(nil)
   398  
   399  // PrevOuts returns a list of previous outpoints for this tx.
   400  func (tx *BTCFeeTx) PrevOuts() []wire.OutPoint {
   401  	ops := make([]wire.OutPoint, len(tx.TxIn))
   402  	for i, txIn := range tx.TxIn {
   403  		ops[i] = txIn.PreviousOutPoint
   404  	}
   405  	return ops
   406  }
   407  
   408  // FeeRate calculates this tx's fee rate.
   409  func (tx *BTCFeeTx) FeeRate(prevOuts map[chainhash.Hash]map[int]int64) (uint64, error) {
   410  	var in, out int64
   411  	for i, vin := range tx.TxIn {
   412  		prevOut := vin.PreviousOutPoint
   413  		outs, found := prevOuts[prevOut.Hash]
   414  		if !found {
   415  			return 0, fmt.Errorf("no prevout tx %s for %s:%d", prevOut.Hash, tx.TxHash(), i)
   416  		}
   417  		v, found := outs[int(prevOut.Index)]
   418  		if !found {
   419  			return 0, fmt.Errorf("no prevout vout %s:%d for %s:%d", prevOut.Hash, prevOut.Index, tx.TxHash(), i)
   420  		}
   421  		in += v
   422  	}
   423  	for _, vout := range tx.TxOut {
   424  		out += vout.Value
   425  	}
   426  	fees := in - out
   427  	if fees < 0 {
   428  		return 0, fmt.Errorf("fees < 0 for tx %s", tx.TxHash())
   429  	}
   430  	sz := tx.SerializeSize()
   431  	if sz == 0 {
   432  		return 0, fmt.Errorf("size 0 tx %s", tx.TxHash())
   433  	}
   434  	return uint64(math.Round(float64(fees) / float64(sz))), nil
   435  }
   436  
   437  func btcBlockFeeTransactions(rc *RPCClient, blockHash *chainhash.Hash) (feeTxs []FeeTx, prevBlock chainhash.Hash, err error) {
   438  	blk, err := rc.GetMsgBlock(blockHash)
   439  	if err != nil {
   440  		return nil, chainhash.Hash{}, err
   441  	}
   442  
   443  	if len(blk.Transactions) == 0 {
   444  		return nil, chainhash.Hash{}, fmt.Errorf("no transactions?")
   445  	}
   446  
   447  	feeTxs = make([]FeeTx, len(blk.Transactions)-1)
   448  	for i, msgTx := range blk.Transactions[1:] { // skip coinbase
   449  		feeTxs[i] = &BTCFeeTx{msgTx}
   450  	}
   451  	return feeTxs, blk.Header.PrevBlock, nil
   452  }
   453  
   454  // medianFeesTheHardWay calculates the median fees from the previous block(s).
   455  // medianFeesTheHardWay is used for assets that don't have a getblockstats RPC,
   456  // and is only useful for non-segwit assets.
   457  func (rc *RPCClient) medianFeesTheHardWay(ctx context.Context) (uint64, error) {
   458  	const numTxs = 101
   459  
   460  	iHash, err := rc.GetBestBlockHash()
   461  	if err != nil {
   462  		return 0, err
   463  	}
   464  
   465  	txs := make([]FeeTx, 0, numTxs)
   466  
   467  	// prev_out_tx_hash -> prev_out_index -> value
   468  	prevOuts := make(map[chainhash.Hash]map[int]int64, numTxs)
   469  
   470  	var blocksChecked int
   471  
   472  out:
   473  	for len(txs) < numTxs {
   474  		if ctx.Err() != nil {
   475  			return 0, context.Canceled
   476  		}
   477  
   478  		blocksChecked++
   479  		if blocksChecked > rc.maxFeeBlocks {
   480  			return 0, errNoCompetition
   481  		}
   482  
   483  		feeTxs, prevBlock, err := rc.blockFeeTransactions(rc, iHash)
   484  		if err != nil {
   485  			return 0, err
   486  		}
   487  
   488  		rand.Shuffle(len(feeTxs), func(i, j int) { feeTxs[i], feeTxs[j] = feeTxs[j], feeTxs[i] })
   489  
   490  		for _, tx := range feeTxs {
   491  			for _, prevOut := range tx.PrevOuts() {
   492  				prevs := prevOuts[prevOut.Hash]
   493  				if len(prevs) == 0 {
   494  					prevs = make(map[int]int64, 1)
   495  					prevOuts[prevOut.Hash] = prevs
   496  				}
   497  				// Create a placeholder. Value will be set after all previous
   498  				// outpoints for the tx are recorded.
   499  				prevs[int(prevOut.Index)] = 0
   500  			}
   501  			txs = append(txs, tx)
   502  
   503  			if len(txs) >= numTxs {
   504  				break out
   505  			}
   506  		}
   507  
   508  		iHash = &prevBlock
   509  	}
   510  
   511  	// Fetch all the previous outpoints and log the values.
   512  	for txHash, prevs := range prevOuts {
   513  		if ctx.Err() != nil {
   514  			return 0, context.Canceled
   515  		}
   516  
   517  		txB, err := rc.GetRawTransaction(&txHash)
   518  		if err != nil {
   519  			return 0, fmt.Errorf("GetRawTransaction error: %v", err)
   520  		}
   521  
   522  		tx, err := rc.deserializeTx(txB)
   523  		if err != nil {
   524  			return 0, fmt.Errorf("error deserializing tx: %v", err)
   525  		}
   526  
   527  		for vout := range prevs {
   528  			if len(tx.TxOut) < vout+1 {
   529  				return 0, fmt.Errorf("too few outputs")
   530  			}
   531  			prevs[vout] = tx.TxOut[vout].Value
   532  		}
   533  	}
   534  
   535  	// Do math.
   536  	rates := make([]uint64, numTxs)
   537  	for i, tx := range txs {
   538  		r, err := tx.FeeRate(prevOuts)
   539  		if err != nil {
   540  			return 0, err
   541  		}
   542  		rates[i] = r
   543  	}
   544  
   545  	sort.Slice(rates, func(i, j int) bool { return rates[i] < rates[j] })
   546  	return rates[len(rates)/2], nil
   547  }
   548  
   549  // RawRequest is a wrapper func for callers that are not context-enabled.
   550  func (rc *RPCClient) RawRequest(method string, params []json.RawMessage) (json.RawMessage, error) {
   551  	return rc.requester.RawRequest(rc.ctx, method, params)
   552  }
   553  
   554  // Call is used to marshal parameters and send requests to the RPC server via
   555  // (*rpcclient.Client).RawRequest. If `thing` is non-nil, the result will be
   556  // marshaled into `thing`.
   557  func (rc *RPCClient) Call(method string, args []any, thing any) error {
   558  	return rc.call(method, args, thing)
   559  }
   560  
   561  // anylist is a list of RPC parameters to be converted to []json.RawMessage and
   562  // sent via RawRequest.
   563  type anylist []any
   564  
   565  // call is used internally to marshal parameters and send requests to the RPC
   566  // server via (*rpcclient.Client).RawRequest. If `thing` is non-nil, the result
   567  // will be marshaled into `thing`.
   568  func (rc *RPCClient) call(method string, args anylist, thing any) error {
   569  	params := make([]json.RawMessage, 0, len(args))
   570  	for i := range args {
   571  		p, err := json.Marshal(args[i])
   572  		if err != nil {
   573  			return err
   574  		}
   575  		params = append(params, p)
   576  	}
   577  	b, err := rc.requester.RawRequest(rc.ctx, method, params)
   578  	if err != nil {
   579  		return fmt.Errorf("rawrequest error: %w", err)
   580  	}
   581  
   582  	if thing != nil {
   583  		return json.Unmarshal(b, thing)
   584  	}
   585  	return nil
   586  }