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