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 }