github.com/trezor/blockbook@v0.4.1-0.20240328132726-e9a08582ee2c/bchain/coins/btc/bitcoinrpc.go (about) 1 package btc 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/hex" 7 "encoding/json" 8 "io" 9 "math/big" 10 "net" 11 "net/http" 12 "runtime/debug" 13 "time" 14 15 "github.com/golang/glog" 16 "github.com/juju/errors" 17 "github.com/martinboehm/btcd/wire" 18 "github.com/trezor/blockbook/bchain" 19 "github.com/trezor/blockbook/common" 20 ) 21 22 // BitcoinRPC is an interface to JSON-RPC bitcoind service. 23 type BitcoinRPC struct { 24 *bchain.BaseChain 25 client http.Client 26 rpcURL string 27 user string 28 password string 29 Mempool *bchain.MempoolBitcoinType 30 ParseBlocks bool 31 pushHandler func(bchain.NotificationType) 32 mq *bchain.MQ 33 ChainConfig *Configuration 34 RPCMarshaler RPCMarshaler 35 mempoolGolombFilterP uint8 36 mempoolFilterScripts string 37 mempoolUseZeroedKey bool 38 alternativeFeeProvider alternativeFeeProviderInterface 39 } 40 41 // Configuration represents json config file 42 type Configuration struct { 43 CoinName string `json:"coin_name"` 44 CoinShortcut string `json:"coin_shortcut"` 45 RPCURL string `json:"rpc_url"` 46 RPCUser string `json:"rpc_user"` 47 RPCPass string `json:"rpc_pass"` 48 RPCTimeout int `json:"rpc_timeout"` 49 AddressAliases bool `json:"address_aliases,omitempty"` 50 Parse bool `json:"parse"` 51 MessageQueueBinding string `json:"message_queue_binding"` 52 Subversion string `json:"subversion"` 53 BlockAddressesToKeep int `json:"block_addresses_to_keep"` 54 MempoolWorkers int `json:"mempool_workers"` 55 MempoolSubWorkers int `json:"mempool_sub_workers"` 56 AddressFormat string `json:"address_format"` 57 SupportsEstimateFee bool `json:"supports_estimate_fee"` 58 SupportsEstimateSmartFee bool `json:"supports_estimate_smart_fee"` 59 XPubMagic uint32 `json:"xpub_magic,omitempty"` 60 XPubMagicSegwitP2sh uint32 `json:"xpub_magic_segwit_p2sh,omitempty"` 61 XPubMagicSegwitNative uint32 `json:"xpub_magic_segwit_native,omitempty"` 62 Slip44 uint32 `json:"slip44,omitempty"` 63 AlternativeEstimateFee string `json:"alternative_estimate_fee,omitempty"` 64 AlternativeEstimateFeeParams string `json:"alternative_estimate_fee_params,omitempty"` 65 MinimumCoinbaseConfirmations int `json:"minimumCoinbaseConfirmations,omitempty"` 66 MempoolGolombFilterP uint8 `json:"mempool_golomb_filter_p,omitempty"` 67 MempoolFilterScripts string `json:"mempool_filter_scripts,omitempty"` 68 MempoolFilterUseZeroedKey bool `json:"mempool_filter_use_zeroed_key,omitempty"` 69 } 70 71 // NewBitcoinRPC returns new BitcoinRPC instance. 72 func NewBitcoinRPC(config json.RawMessage, pushHandler func(bchain.NotificationType)) (bchain.BlockChain, error) { 73 var err error 74 var c Configuration 75 err = json.Unmarshal(config, &c) 76 if err != nil { 77 return nil, errors.Annotatef(err, "Invalid configuration file") 78 } 79 // keep at least 100 mappings block->addresses to allow rollback 80 if c.BlockAddressesToKeep < 100 { 81 c.BlockAddressesToKeep = 100 82 } 83 // default MinimumCoinbaseConfirmations is 100 84 if c.MinimumCoinbaseConfirmations == 0 { 85 c.MinimumCoinbaseConfirmations = 100 86 } 87 // at least 1 mempool worker/subworker for synchronous mempool synchronization 88 if c.MempoolWorkers < 1 { 89 c.MempoolWorkers = 1 90 } 91 if c.MempoolSubWorkers < 1 { 92 c.MempoolSubWorkers = 1 93 } 94 // btc supports both calls, other coins overriding BitcoinRPC can change this 95 c.SupportsEstimateFee = true 96 c.SupportsEstimateSmartFee = true 97 98 transport := &http.Transport{ 99 Dial: (&net.Dialer{KeepAlive: 600 * time.Second}).Dial, 100 MaxIdleConns: 100, 101 MaxIdleConnsPerHost: 100, // necessary to not to deplete ports 102 } 103 104 s := &BitcoinRPC{ 105 BaseChain: &bchain.BaseChain{}, 106 client: http.Client{Timeout: time.Duration(c.RPCTimeout) * time.Second, Transport: transport}, 107 rpcURL: c.RPCURL, 108 user: c.RPCUser, 109 password: c.RPCPass, 110 ParseBlocks: c.Parse, 111 ChainConfig: &c, 112 pushHandler: pushHandler, 113 RPCMarshaler: JSONMarshalerV2{}, 114 mempoolGolombFilterP: c.MempoolGolombFilterP, 115 mempoolFilterScripts: c.MempoolFilterScripts, 116 mempoolUseZeroedKey: c.MempoolFilterUseZeroedKey, 117 } 118 119 return s, nil 120 } 121 122 // Initialize initializes BitcoinRPC instance. 123 func (b *BitcoinRPC) Initialize() error { 124 b.ChainConfig.SupportsEstimateFee = false 125 126 ci, err := b.GetChainInfo() 127 if err != nil { 128 return err 129 } 130 chainName := ci.Chain 131 132 params := GetChainParams(chainName) 133 134 // always create parser 135 b.Parser = NewBitcoinParser(params, b.ChainConfig) 136 137 // parameters for getInfo request 138 if params.Net == wire.MainNet { 139 b.Testnet = false 140 b.Network = "livenet" 141 } else { 142 b.Testnet = true 143 b.Network = "testnet" 144 } 145 146 glog.Info("rpc: block chain ", params.Name) 147 148 if b.ChainConfig.AlternativeEstimateFee == "whatthefee" { 149 if b.alternativeFeeProvider, err = NewWhatTheFee(b, b.ChainConfig.AlternativeEstimateFeeParams); err != nil { 150 glog.Error("NewWhatTheFee error ", err, " Reverting to default estimateFee functionality") 151 // disable AlternativeEstimateFee logic 152 b.alternativeFeeProvider = nil 153 } 154 } else if b.ChainConfig.AlternativeEstimateFee == "mempoolspace" { 155 if b.alternativeFeeProvider, err = NewMempoolSpaceFee(b, b.ChainConfig.AlternativeEstimateFeeParams); err != nil { 156 glog.Error("MempoolSpaceFee error ", err, " Reverting to default estimateFee functionality") 157 // disable AlternativeEstimateFee logic 158 b.alternativeFeeProvider = nil 159 } 160 } 161 162 return nil 163 } 164 165 // CreateMempool creates mempool if not already created, however does not initialize it 166 func (b *BitcoinRPC) CreateMempool(chain bchain.BlockChain) (bchain.Mempool, error) { 167 if b.Mempool == nil { 168 b.Mempool = bchain.NewMempoolBitcoinType(chain, b.ChainConfig.MempoolWorkers, b.ChainConfig.MempoolSubWorkers, b.mempoolGolombFilterP, b.mempoolFilterScripts, b.mempoolUseZeroedKey) 169 } 170 return b.Mempool, nil 171 } 172 173 // InitializeMempool creates ZeroMQ subscription and sets AddrDescForOutpointFunc to the Mempool 174 func (b *BitcoinRPC) InitializeMempool(addrDescForOutpoint bchain.AddrDescForOutpointFunc, onNewTxAddr bchain.OnNewTxAddrFunc, onNewTx bchain.OnNewTxFunc) error { 175 if b.Mempool == nil { 176 return errors.New("Mempool not created") 177 } 178 b.Mempool.AddrDescForOutpoint = addrDescForOutpoint 179 b.Mempool.OnNewTxAddr = onNewTxAddr 180 b.Mempool.OnNewTx = onNewTx 181 if b.mq == nil { 182 mq, err := bchain.NewMQ(b.ChainConfig.MessageQueueBinding, b.pushHandler) 183 if err != nil { 184 glog.Error("mq: ", err) 185 return err 186 } 187 b.mq = mq 188 } 189 return nil 190 } 191 192 // Shutdown ZeroMQ and other resources 193 func (b *BitcoinRPC) Shutdown(ctx context.Context) error { 194 if b.mq != nil { 195 if err := b.mq.Shutdown(ctx); err != nil { 196 glog.Error("MQ.Shutdown error: ", err) 197 return err 198 } 199 } 200 return nil 201 } 202 203 // GetCoinName returns the coin name 204 func (b *BitcoinRPC) GetCoinName() string { 205 return b.ChainConfig.CoinName 206 } 207 208 // GetSubversion returns the backend subversion 209 func (b *BitcoinRPC) GetSubversion() string { 210 return b.ChainConfig.Subversion 211 } 212 213 // getblockhash 214 215 type CmdGetBlockHash struct { 216 Method string `json:"method"` 217 Params struct { 218 Height uint32 `json:"height"` 219 } `json:"params"` 220 } 221 222 type ResGetBlockHash struct { 223 Error *bchain.RPCError `json:"error"` 224 Result string `json:"result"` 225 } 226 227 // getbestblockhash 228 229 type CmdGetBestBlockHash struct { 230 Method string `json:"method"` 231 } 232 233 type ResGetBestBlockHash struct { 234 Error *bchain.RPCError `json:"error"` 235 Result string `json:"result"` 236 } 237 238 // getblockcount 239 240 type CmdGetBlockCount struct { 241 Method string `json:"method"` 242 } 243 244 type ResGetBlockCount struct { 245 Error *bchain.RPCError `json:"error"` 246 Result uint32 `json:"result"` 247 } 248 249 // getblockchaininfo 250 251 type CmdGetBlockChainInfo struct { 252 Method string `json:"method"` 253 } 254 255 type ResGetBlockChainInfo struct { 256 Error *bchain.RPCError `json:"error"` 257 Result struct { 258 Chain string `json:"chain"` 259 Blocks int `json:"blocks"` 260 Headers int `json:"headers"` 261 Bestblockhash string `json:"bestblockhash"` 262 Difficulty common.JSONNumber `json:"difficulty"` 263 SizeOnDisk int64 `json:"size_on_disk"` 264 Warnings string `json:"warnings"` 265 } `json:"result"` 266 } 267 268 // getnetworkinfo 269 270 type CmdGetNetworkInfo struct { 271 Method string `json:"method"` 272 } 273 274 type ResGetNetworkInfo struct { 275 Error *bchain.RPCError `json:"error"` 276 Result struct { 277 Version common.JSONNumber `json:"version"` 278 Subversion common.JSONNumber `json:"subversion"` 279 ProtocolVersion common.JSONNumber `json:"protocolversion"` 280 Timeoffset float64 `json:"timeoffset"` 281 Warnings string `json:"warnings"` 282 } `json:"result"` 283 } 284 285 // getrawmempool 286 287 type CmdGetMempool struct { 288 Method string `json:"method"` 289 } 290 291 type ResGetMempool struct { 292 Error *bchain.RPCError `json:"error"` 293 Result []string `json:"result"` 294 } 295 296 // getblockheader 297 298 type CmdGetBlockHeader struct { 299 Method string `json:"method"` 300 Params struct { 301 BlockHash string `json:"blockhash"` 302 Verbose bool `json:"verbose"` 303 } `json:"params"` 304 } 305 306 type ResGetBlockHeader struct { 307 Error *bchain.RPCError `json:"error"` 308 Result bchain.BlockHeader `json:"result"` 309 } 310 311 // getblock 312 313 type CmdGetBlock struct { 314 Method string `json:"method"` 315 Params struct { 316 BlockHash string `json:"blockhash"` 317 Verbosity int `json:"verbosity"` 318 } `json:"params"` 319 } 320 321 type ResGetBlockRaw struct { 322 Error *bchain.RPCError `json:"error"` 323 Result string `json:"result"` 324 } 325 326 type BlockThin struct { 327 bchain.BlockHeader 328 Txids []string `json:"tx"` 329 } 330 331 type ResGetBlockThin struct { 332 Error *bchain.RPCError `json:"error"` 333 Result BlockThin `json:"result"` 334 } 335 336 type ResGetBlockFull struct { 337 Error *bchain.RPCError `json:"error"` 338 Result bchain.Block `json:"result"` 339 } 340 341 type ResGetBlockInfo struct { 342 Error *bchain.RPCError `json:"error"` 343 Result bchain.BlockInfo `json:"result"` 344 } 345 346 // getrawtransaction 347 348 type CmdGetRawTransaction struct { 349 Method string `json:"method"` 350 Params struct { 351 Txid string `json:"txid"` 352 Verbose bool `json:"verbose"` 353 } `json:"params"` 354 } 355 356 type ResGetRawTransaction struct { 357 Error *bchain.RPCError `json:"error"` 358 Result json.RawMessage `json:"result"` 359 } 360 361 type ResGetRawTransactionNonverbose struct { 362 Error *bchain.RPCError `json:"error"` 363 Result string `json:"result"` 364 } 365 366 // estimatesmartfee 367 368 type CmdEstimateSmartFee struct { 369 Method string `json:"method"` 370 Params struct { 371 ConfTarget int `json:"conf_target"` 372 EstimateMode string `json:"estimate_mode"` 373 } `json:"params"` 374 } 375 376 type ResEstimateSmartFee struct { 377 Error *bchain.RPCError `json:"error"` 378 Result struct { 379 Feerate common.JSONNumber `json:"feerate"` 380 Blocks int `json:"blocks"` 381 } `json:"result"` 382 } 383 384 // estimatefee 385 386 type CmdEstimateFee struct { 387 Method string `json:"method"` 388 Params struct { 389 Blocks int `json:"nblocks"` 390 } `json:"params"` 391 } 392 393 type ResEstimateFee struct { 394 Error *bchain.RPCError `json:"error"` 395 Result common.JSONNumber `json:"result"` 396 } 397 398 // sendrawtransaction 399 400 type CmdSendRawTransaction struct { 401 Method string `json:"method"` 402 Params []string `json:"params"` 403 } 404 405 type ResSendRawTransaction struct { 406 Error *bchain.RPCError `json:"error"` 407 Result string `json:"result"` 408 } 409 410 // getmempoolentry 411 412 type CmdGetMempoolEntry struct { 413 Method string `json:"method"` 414 Params []string `json:"params"` 415 } 416 417 type ResGetMempoolEntry struct { 418 Error *bchain.RPCError `json:"error"` 419 Result *bchain.MempoolEntry `json:"result"` 420 } 421 422 // GetBestBlockHash returns hash of the tip of the best-block-chain. 423 func (b *BitcoinRPC) GetBestBlockHash() (string, error) { 424 425 glog.V(1).Info("rpc: getbestblockhash") 426 427 res := ResGetBestBlockHash{} 428 req := CmdGetBestBlockHash{Method: "getbestblockhash"} 429 err := b.Call(&req, &res) 430 431 if err != nil { 432 return "", err 433 } 434 if res.Error != nil { 435 return "", res.Error 436 } 437 return res.Result, nil 438 } 439 440 // GetBestBlockHeight returns height of the tip of the best-block-chain. 441 func (b *BitcoinRPC) GetBestBlockHeight() (uint32, error) { 442 glog.V(1).Info("rpc: getblockcount") 443 444 res := ResGetBlockCount{} 445 req := CmdGetBlockCount{Method: "getblockcount"} 446 err := b.Call(&req, &res) 447 448 if err != nil { 449 return 0, err 450 } 451 if res.Error != nil { 452 return 0, res.Error 453 } 454 return res.Result, nil 455 } 456 457 // GetChainInfo returns information about the connected backend 458 func (b *BitcoinRPC) GetChainInfo() (*bchain.ChainInfo, error) { 459 glog.V(1).Info("rpc: getblockchaininfo") 460 461 resCi := ResGetBlockChainInfo{} 462 err := b.Call(&CmdGetBlockChainInfo{Method: "getblockchaininfo"}, &resCi) 463 if err != nil { 464 return nil, err 465 } 466 if resCi.Error != nil { 467 return nil, resCi.Error 468 } 469 470 glog.V(1).Info("rpc: getnetworkinfo") 471 resNi := ResGetNetworkInfo{} 472 err = b.Call(&CmdGetNetworkInfo{Method: "getnetworkinfo"}, &resNi) 473 if err != nil { 474 return nil, err 475 } 476 if resNi.Error != nil { 477 return nil, resNi.Error 478 } 479 480 rv := &bchain.ChainInfo{ 481 Bestblockhash: resCi.Result.Bestblockhash, 482 Blocks: resCi.Result.Blocks, 483 Chain: resCi.Result.Chain, 484 Difficulty: string(resCi.Result.Difficulty), 485 Headers: resCi.Result.Headers, 486 SizeOnDisk: resCi.Result.SizeOnDisk, 487 Subversion: string(resNi.Result.Subversion), 488 Timeoffset: resNi.Result.Timeoffset, 489 } 490 rv.Version = string(resNi.Result.Version) 491 rv.ProtocolVersion = string(resNi.Result.ProtocolVersion) 492 if len(resCi.Result.Warnings) > 0 { 493 rv.Warnings = resCi.Result.Warnings + " " 494 } 495 if resCi.Result.Warnings != resNi.Result.Warnings { 496 rv.Warnings += resNi.Result.Warnings 497 } 498 return rv, nil 499 } 500 501 // IsErrBlockNotFound returns true if error means block was not found 502 func IsErrBlockNotFound(err *bchain.RPCError) bool { 503 return err.Message == "Block not found" || 504 err.Message == "Block height out of range" 505 } 506 507 // GetBlockHash returns hash of block in best-block-chain at given height. 508 func (b *BitcoinRPC) GetBlockHash(height uint32) (string, error) { 509 glog.V(1).Info("rpc: getblockhash ", height) 510 511 res := ResGetBlockHash{} 512 req := CmdGetBlockHash{Method: "getblockhash"} 513 req.Params.Height = height 514 err := b.Call(&req, &res) 515 516 if err != nil { 517 return "", errors.Annotatef(err, "height %v", height) 518 } 519 if res.Error != nil { 520 if IsErrBlockNotFound(res.Error) { 521 return "", bchain.ErrBlockNotFound 522 } 523 return "", errors.Annotatef(res.Error, "height %v", height) 524 } 525 return res.Result, nil 526 } 527 528 // GetBlockHeader returns header of block with given hash. 529 func (b *BitcoinRPC) GetBlockHeader(hash string) (*bchain.BlockHeader, error) { 530 glog.V(1).Info("rpc: getblockheader") 531 532 res := ResGetBlockHeader{} 533 req := CmdGetBlockHeader{Method: "getblockheader"} 534 req.Params.BlockHash = hash 535 req.Params.Verbose = true 536 err := b.Call(&req, &res) 537 538 if err != nil { 539 return nil, errors.Annotatef(err, "hash %v", hash) 540 } 541 if res.Error != nil { 542 if IsErrBlockNotFound(res.Error) { 543 return nil, bchain.ErrBlockNotFound 544 } 545 return nil, errors.Annotatef(res.Error, "hash %v", hash) 546 } 547 return &res.Result, nil 548 } 549 550 // GetBlock returns block with given hash. 551 func (b *BitcoinRPC) GetBlock(hash string, height uint32) (*bchain.Block, error) { 552 var err error 553 if hash == "" { 554 hash, err = b.GetBlockHash(height) 555 if err != nil { 556 return nil, err 557 } 558 } 559 if !b.ParseBlocks { 560 return b.GetBlockFull(hash) 561 } 562 // optimization 563 if height > 0 { 564 return b.GetBlockWithoutHeader(hash, height) 565 } 566 header, err := b.GetBlockHeader(hash) 567 if err != nil { 568 return nil, err 569 } 570 data, err := b.GetBlockBytes(hash) 571 if err != nil { 572 return nil, err 573 } 574 block, err := b.Parser.ParseBlock(data) 575 if err != nil { 576 return nil, errors.Annotatef(err, "hash %v", hash) 577 } 578 block.BlockHeader = *header 579 return block, nil 580 } 581 582 // GetBlockInfo returns extended header (more info than in bchain.BlockHeader) with a list of txids 583 func (b *BitcoinRPC) GetBlockInfo(hash string) (*bchain.BlockInfo, error) { 584 glog.V(1).Info("rpc: getblock (verbosity=1) ", hash) 585 586 res := ResGetBlockInfo{} 587 req := CmdGetBlock{Method: "getblock"} 588 req.Params.BlockHash = hash 589 req.Params.Verbosity = 1 590 err := b.Call(&req, &res) 591 592 if err != nil { 593 return nil, errors.Annotatef(err, "hash %v", hash) 594 } 595 if res.Error != nil { 596 if IsErrBlockNotFound(res.Error) { 597 return nil, bchain.ErrBlockNotFound 598 } 599 return nil, errors.Annotatef(res.Error, "hash %v", hash) 600 } 601 return &res.Result, nil 602 } 603 604 // GetBlockWithoutHeader is an optimization - it does not call GetBlockHeader to get prev, next hashes 605 // instead it sets to header only block hash and height passed in parameters 606 func (b *BitcoinRPC) GetBlockWithoutHeader(hash string, height uint32) (*bchain.Block, error) { 607 data, err := b.GetBlockBytes(hash) 608 if err != nil { 609 return nil, err 610 } 611 block, err := b.Parser.ParseBlock(data) 612 if err != nil { 613 return nil, errors.Annotatef(err, "%v %v", height, hash) 614 } 615 block.BlockHeader.Hash = hash 616 block.BlockHeader.Height = height 617 return block, nil 618 } 619 620 // GetBlockRaw returns block with given hash as hex string 621 func (b *BitcoinRPC) GetBlockRaw(hash string) (string, error) { 622 glog.V(1).Info("rpc: getblock (verbosity=0) ", hash) 623 624 res := ResGetBlockRaw{} 625 req := CmdGetBlock{Method: "getblock"} 626 req.Params.BlockHash = hash 627 req.Params.Verbosity = 0 628 err := b.Call(&req, &res) 629 630 if err != nil { 631 return "", errors.Annotatef(err, "hash %v", hash) 632 } 633 if res.Error != nil { 634 if IsErrBlockNotFound(res.Error) { 635 return "", bchain.ErrBlockNotFound 636 } 637 return "", errors.Annotatef(res.Error, "hash %v", hash) 638 } 639 return res.Result, nil 640 } 641 642 // GetBlockBytes returns block with given hash as bytes 643 func (b *BitcoinRPC) GetBlockBytes(hash string) ([]byte, error) { 644 block, err := b.GetBlockRaw(hash) 645 if err != nil { 646 return nil, err 647 } 648 return hex.DecodeString(block) 649 } 650 651 // GetBlockFull returns block with given hash 652 func (b *BitcoinRPC) GetBlockFull(hash string) (*bchain.Block, error) { 653 glog.V(1).Info("rpc: getblock (verbosity=2) ", hash) 654 655 res := ResGetBlockFull{} 656 req := CmdGetBlock{Method: "getblock"} 657 req.Params.BlockHash = hash 658 req.Params.Verbosity = 2 659 err := b.Call(&req, &res) 660 661 if err != nil { 662 return nil, errors.Annotatef(err, "hash %v", hash) 663 } 664 if res.Error != nil { 665 if IsErrBlockNotFound(res.Error) { 666 return nil, bchain.ErrBlockNotFound 667 } 668 return nil, errors.Annotatef(res.Error, "hash %v", hash) 669 } 670 671 for i := range res.Result.Txs { 672 tx := &res.Result.Txs[i] 673 for j := range tx.Vout { 674 vout := &tx.Vout[j] 675 // convert vout.JsonValue to big.Int and clear it, it is only temporary value used for unmarshal 676 vout.ValueSat, err = b.Parser.AmountToBigInt(vout.JsonValue) 677 if err != nil { 678 return nil, err 679 } 680 vout.JsonValue = "" 681 } 682 } 683 684 return &res.Result, nil 685 } 686 687 // GetMempoolTransactions returns transactions in mempool 688 func (b *BitcoinRPC) GetMempoolTransactions() ([]string, error) { 689 glog.V(1).Info("rpc: getrawmempool") 690 691 res := ResGetMempool{} 692 req := CmdGetMempool{Method: "getrawmempool"} 693 err := b.Call(&req, &res) 694 695 if err != nil { 696 return nil, err 697 } 698 if res.Error != nil { 699 return nil, res.Error 700 } 701 return res.Result, nil 702 } 703 704 // IsMissingTx return true if error means missing tx 705 func IsMissingTx(err *bchain.RPCError) bool { 706 // err.Code == -5 "No such mempool or blockchain transaction" 707 return err.Code == -5 708 } 709 710 // GetTransactionForMempool returns a transaction by the transaction ID 711 // It could be optimized for mempool, i.e. without block time and confirmations 712 func (b *BitcoinRPC) GetTransactionForMempool(txid string) (*bchain.Tx, error) { 713 glog.V(1).Info("rpc: getrawtransaction nonverbose ", txid) 714 715 res := ResGetRawTransactionNonverbose{} 716 req := CmdGetRawTransaction{Method: "getrawtransaction"} 717 req.Params.Txid = txid 718 req.Params.Verbose = false 719 err := b.Call(&req, &res) 720 if err != nil { 721 return nil, errors.Annotatef(err, "txid %v", txid) 722 } 723 if res.Error != nil { 724 if IsMissingTx(res.Error) { 725 return nil, bchain.ErrTxNotFound 726 } 727 return nil, errors.Annotatef(res.Error, "txid %v", txid) 728 } 729 data, err := hex.DecodeString(res.Result) 730 if err != nil { 731 return nil, errors.Annotatef(err, "txid %v", txid) 732 } 733 tx, err := b.Parser.ParseTx(data) 734 if err != nil { 735 return nil, errors.Annotatef(err, "txid %v", txid) 736 } 737 return tx, nil 738 } 739 740 // GetTransaction returns a transaction by the transaction ID 741 func (b *BitcoinRPC) GetTransaction(txid string) (*bchain.Tx, error) { 742 r, err := b.getRawTransaction(txid) 743 if err != nil { 744 return nil, err 745 } 746 tx, err := b.Parser.ParseTxFromJson(r) 747 if err != nil { 748 return nil, errors.Annotatef(err, "txid %v", txid) 749 } 750 tx.CoinSpecificData = r 751 return tx, nil 752 } 753 754 // GetTransactionSpecific returns json as returned by backend, with all coin specific data 755 func (b *BitcoinRPC) GetTransactionSpecific(tx *bchain.Tx) (json.RawMessage, error) { 756 if csd, ok := tx.CoinSpecificData.(json.RawMessage); ok { 757 return csd, nil 758 } 759 return b.getRawTransaction(tx.Txid) 760 } 761 762 // getRawTransaction returns json as returned by backend, with all coin specific data 763 func (b *BitcoinRPC) getRawTransaction(txid string) (json.RawMessage, error) { 764 glog.V(1).Info("rpc: getrawtransaction ", txid) 765 766 res := ResGetRawTransaction{} 767 req := CmdGetRawTransaction{Method: "getrawtransaction"} 768 req.Params.Txid = txid 769 req.Params.Verbose = true 770 err := b.Call(&req, &res) 771 772 if err != nil { 773 return nil, errors.Annotatef(err, "txid %v", txid) 774 } 775 if res.Error != nil { 776 if IsMissingTx(res.Error) { 777 return nil, bchain.ErrTxNotFound 778 } 779 return nil, errors.Annotatef(res.Error, "txid %v", txid) 780 } 781 return res.Result, nil 782 } 783 784 func (b *BitcoinRPC) blockchainEstimateSmartFee(blocks int, conservative bool) (big.Int, error) { 785 // use EstimateFee if EstimateSmartFee is not supported 786 if !b.ChainConfig.SupportsEstimateSmartFee && b.ChainConfig.SupportsEstimateFee { 787 return b.EstimateFee(blocks) 788 } 789 790 glog.V(1).Info("rpc: estimatesmartfee ", blocks) 791 792 res := ResEstimateSmartFee{} 793 req := CmdEstimateSmartFee{Method: "estimatesmartfee"} 794 req.Params.ConfTarget = blocks 795 if conservative { 796 req.Params.EstimateMode = "CONSERVATIVE" 797 } else { 798 req.Params.EstimateMode = "ECONOMICAL" 799 } 800 err := b.Call(&req, &res) 801 var r big.Int 802 if err != nil { 803 return r, err 804 } 805 if res.Error != nil { 806 return r, res.Error 807 } 808 r, err = b.Parser.AmountToBigInt(res.Result.Feerate) 809 if err != nil { 810 return r, err 811 } 812 return r, nil 813 } 814 815 // EstimateSmartFee returns fee estimation 816 func (b *BitcoinRPC) EstimateSmartFee(blocks int, conservative bool) (big.Int, error) { 817 // use alternative estimator if enabled 818 if b.alternativeFeeProvider != nil { 819 r, err := b.alternativeFeeProvider.estimateFee(blocks) 820 // in case of error, fallback to default estimator 821 if err == nil { 822 return r, nil 823 } 824 } 825 return b.blockchainEstimateSmartFee(blocks, conservative) 826 } 827 828 // EstimateFee returns fee estimation. 829 func (b *BitcoinRPC) EstimateFee(blocks int) (big.Int, error) { 830 var r big.Int 831 var err error 832 // use alternative estimator if enabled 833 if b.alternativeFeeProvider != nil { 834 r, err = b.alternativeFeeProvider.estimateFee(blocks) 835 // in case of error, fallback to default estimator 836 if err == nil { 837 return r, nil 838 } 839 } 840 // use EstimateSmartFee if EstimateFee is not supported 841 if !b.ChainConfig.SupportsEstimateFee && b.ChainConfig.SupportsEstimateSmartFee { 842 return b.EstimateSmartFee(blocks, true) 843 } 844 845 glog.V(1).Info("rpc: estimatefee ", blocks) 846 847 res := ResEstimateFee{} 848 req := CmdEstimateFee{Method: "estimatefee"} 849 req.Params.Blocks = blocks 850 err = b.Call(&req, &res) 851 852 if err != nil { 853 return r, err 854 } 855 if res.Error != nil { 856 return r, res.Error 857 } 858 r, err = b.Parser.AmountToBigInt(res.Result) 859 if err != nil { 860 return r, err 861 } 862 return r, nil 863 } 864 865 // SendRawTransaction sends raw transaction 866 func (b *BitcoinRPC) SendRawTransaction(tx string) (string, error) { 867 glog.V(1).Info("rpc: sendrawtransaction") 868 869 res := ResSendRawTransaction{} 870 req := CmdSendRawTransaction{Method: "sendrawtransaction"} 871 req.Params = []string{tx} 872 err := b.Call(&req, &res) 873 874 if err != nil { 875 return "", err 876 } 877 if res.Error != nil { 878 return "", res.Error 879 } 880 return res.Result, nil 881 } 882 883 // GetMempoolEntry returns mempool data for given transaction 884 func (b *BitcoinRPC) GetMempoolEntry(txid string) (*bchain.MempoolEntry, error) { 885 glog.V(1).Info("rpc: getmempoolentry") 886 887 res := ResGetMempoolEntry{} 888 req := CmdGetMempoolEntry{ 889 Method: "getmempoolentry", 890 Params: []string{txid}, 891 } 892 err := b.Call(&req, &res) 893 if err != nil { 894 return nil, err 895 } 896 if res.Error != nil { 897 return nil, res.Error 898 } 899 res.Result.FeeSat, err = b.Parser.AmountToBigInt(res.Result.Fee) 900 if err != nil { 901 return nil, err 902 } 903 res.Result.ModifiedFeeSat, err = b.Parser.AmountToBigInt(res.Result.ModifiedFee) 904 if err != nil { 905 return nil, err 906 } 907 return res.Result, nil 908 } 909 910 func safeDecodeResponse(body io.ReadCloser, res interface{}) (err error) { 911 var data []byte 912 defer func() { 913 if r := recover(); r != nil { 914 glog.Error("unmarshal json recovered from panic: ", r, "; data: ", string(data)) 915 debug.PrintStack() 916 if len(data) > 0 && len(data) < 2048 { 917 err = errors.Errorf("Error: %v", string(data)) 918 } else { 919 err = errors.New("Internal error") 920 } 921 } 922 }() 923 data, err = io.ReadAll(body) 924 if err != nil { 925 return err 926 } 927 return json.Unmarshal(data, &res) 928 } 929 930 // Call calls Backend RPC interface, using RPCMarshaler interface to marshall the request 931 func (b *BitcoinRPC) Call(req interface{}, res interface{}) error { 932 httpData, err := b.RPCMarshaler.Marshal(req) 933 if err != nil { 934 return err 935 } 936 httpReq, err := http.NewRequest("POST", b.rpcURL, bytes.NewBuffer(httpData)) 937 if err != nil { 938 return err 939 } 940 httpReq.SetBasicAuth(b.user, b.password) 941 httpRes, err := b.client.Do(httpReq) 942 // in some cases the httpRes can contain data even if it returns error 943 // see http://devs.cloudimmunity.com/gotchas-and-common-mistakes-in-go-golang/ 944 if httpRes != nil { 945 defer httpRes.Body.Close() 946 } 947 if err != nil { 948 return err 949 } 950 // if server returns HTTP error code it might not return json with response 951 // handle both cases 952 if httpRes.StatusCode != 200 { 953 err = safeDecodeResponse(httpRes.Body, &res) 954 if err != nil { 955 return errors.Errorf("%v %v", httpRes.Status, err) 956 } 957 return nil 958 } 959 return safeDecodeResponse(httpRes.Body, &res) 960 }