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