blockbook/bchain/coins/dcr/decredrpc.go

877 lines
25 KiB
Go

package dcr
import (
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"math/big"
"net"
"net/http"
"runtime/debug"
"strconv"
"strings"
"sync"
"time"
"github.com/decred/dcrd/dcrjson/v3"
"github.com/golang/glog"
"github.com/juju/errors"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
"spacecruft.org/spacecruft/blockbook/common"
)
// voteBitYes defines the vote bit set when a given block validates the previous
// block
const voteBitYes = 0x0001
type DecredRPC struct {
*btc.BitcoinRPC
mtx sync.Mutex
client http.Client
rpcURL string
rpcUser string
bestBlock uint32
rpcPassword string
}
// NewDecredRPC returns new DecredRPC instance.
func NewDecredRPC(config json.RawMessage, pushHandler func(bchain.NotificationType)) (bchain.BlockChain, error) {
b, err := btc.NewBitcoinRPC(config, pushHandler)
if err != nil {
return nil, err
}
var c btc.Configuration
if err = json.Unmarshal(config, &c); err != nil {
return nil, errors.Annotate(err, "Invalid configuration file")
}
transport := &http.Transport{
Dial: (&net.Dialer{KeepAlive: 600 * time.Second}).Dial,
MaxIdleConns: 100,
MaxIdleConnsPerHost: 100, // necessary to not to deplete ports
}
d := &DecredRPC{
BitcoinRPC: b.(*btc.BitcoinRPC),
client: http.Client{Timeout: time.Duration(c.RPCTimeout) * time.Second, Transport: transport},
rpcURL: c.RPCURL,
rpcUser: c.RPCUser,
rpcPassword: c.RPCPass,
}
d.BitcoinRPC.RPCMarshaler = btc.JSONMarshalerV1{}
d.BitcoinRPC.ChainConfig.SupportsEstimateSmartFee = false
return d, nil
}
// Initialize initializes DecredRPC instance.
func (d *DecredRPC) Initialize() error {
chainInfo, err := d.GetChainInfo()
if err != nil {
return err
}
chainName := chainInfo.Chain
glog.Info("Chain name ", chainName)
params := GetChainParams(chainName)
// always create parser
d.BitcoinRPC.Parser = NewDecredParser(params, d.BitcoinRPC.ChainConfig)
// parameters for getInfo request
if params.Net == MainnetMagic {
d.BitcoinRPC.Testnet = false
d.BitcoinRPC.Network = "livenet"
} else {
d.BitcoinRPC.Testnet = true
d.BitcoinRPC.Network = "testnet"
}
glog.Info("rpc: block chain ", params.Name)
return nil
}
type Error struct {
Code int `json:"code"`
Message string `json:"message"`
}
type GenericCmd struct {
ID int `json:"id"`
Method string `json:"method"`
Params []interface{} `json:"params,omitempty"`
}
type GetBlockChainInfoResult struct {
Error Error `json:"error"`
Result struct {
Chain string `json:"chain"`
Blocks int64 `json:"blocks"`
Headers int64 `json:"headers"`
SyncHeight int64 `json:"syncheight"`
BestBlockHash string `json:"bestblockhash"`
Difficulty uint32 `json:"difficulty"`
VerificationProgress float64 `json:"verificationprogress"`
ChainWork string `json:"chainwork"`
InitialBlockDownload bool `json:"initialblockdownload"`
MaxBlockSize int64 `json:"maxblocksize"`
} `json:"result"`
}
type GetNetworkInfoResult struct {
Error Error `json:"error"`
Result struct {
Version int32 `json:"version"`
ProtocolVersion int32 `json:"protocolversion"`
TimeOffset int64 `json:"timeoffset"`
Connections int32 `json:"connections"`
RelayFee float64 `json:"relayfee"`
} `json:"result"`
}
type GetInfoChainResult struct {
Error Error `json:"error"`
Result struct {
Version int32 `json:"version"`
ProtocolVersion int32 `json:"protocolversion"`
Blocks int64 `json:"blocks"`
TimeOffset int64 `json:"timeoffset"`
Connections int32 `json:"connections"`
Proxy string `json:"proxy"`
Difficulty float64 `json:"difficulty"`
TestNet bool `json:"testnet"`
RelayFee float64 `json:"relayfee"`
Errors string `json:"errors"`
}
}
type GetBestBlockResult struct {
Error Error `json:"error"`
Result struct {
Hash string `json:"hash"`
Height uint32 `json:"height"`
} `json:"result"`
}
type GetBlockHashResult struct {
Error Error `json:"error"`
Result string `json:"result"`
}
type GetBlockResult struct {
Error Error `json:"error"`
Result struct {
Hash string `json:"hash"`
Confirmations int64 `json:"confirmations"`
Size int32 `json:"size"`
Height uint32 `json:"height"`
Version common.JSONNumber `json:"version"`
MerkleRoot string `json:"merkleroot"`
StakeRoot string `json:"stakeroot"`
RawTx []RawTx `json:"rawtx"`
Tx []string `json:"tx,omitempty"`
STx []string `json:"stx,omitempty"`
Time int64 `json:"time"`
Nonce common.JSONNumber `json:"nonce"`
VoteBits uint16 `json:"votebits"`
FinalState string `json:"finalstate"`
Voters uint16 `json:"voters"`
FreshStake uint8 `json:"freshstake"`
Revocations uint8 `json:"revocations"`
PoolSize uint32 `json:"poolsize"`
Bits string `json:"bits"`
SBits float64 `json:"sbits"`
ExtraData string `json:"extradata"`
StakeVersion uint32 `json:"stakeversion"`
Difficulty float64 `json:"difficulty"`
ChainWork string `json:"chainwork"`
PreviousHash string `json:"previousblockhash"`
NextHash string `json:"nextblockhash,omitempty"`
} `json:"result"`
}
type GetBlockHeaderResult struct {
Error Error `json:"error"`
Result struct {
Hash string `json:"hash"`
Confirmations int64 `json:"confirmations"`
Version common.JSONNumber `json:"version"`
MerkleRoot string `json:"merkleroot"`
StakeRoot string `json:"stakeroot"`
VoteBits uint16 `json:"votebits"`
FinalState string `json:"finalstate"`
Voters uint16 `json:"voters"`
FreshStake uint8 `json:"freshstake"`
Revocations uint8 `json:"revocations"`
PoolSize uint32 `json:"poolsize"`
Bits string `json:"bits"`
SBits float64 `json:"sbits"`
Height uint32 `json:"height"`
Size uint32 `json:"size"`
Time int64 `json:"time"`
Nonce uint32 `json:"nonce"`
ExtraData string `json:"extradata"`
StakeVersion uint32 `json:"stakeversion"`
Difficulty float64 `json:"difficulty"`
ChainWork string `json:"chainwork"`
PreviousHash string `json:"previousblockhash,omitempty"`
NextHash string `json:"nextblockhash,omitempty"`
} `json:"result"`
}
type ScriptSig struct {
Asm string `json:"asm"`
Hex string `json:"hex"`
}
type Vin struct {
Coinbase string `json:"coinbase"`
Stakebase string `json:"stakebase"`
Txid string `json:"txid"`
Vout uint32 `json:"vout"`
Tree int8 `json:"tree"`
Sequence uint32 `json:"sequence"`
AmountIn float64 `json:"amountin"`
BlockHeight uint32 `json:"blockheight"`
BlockIndex uint32 `json:"blockindex"`
ScriptSig *ScriptSig `json:"scriptsig"`
}
type ScriptPubKeyResult struct {
Asm string `json:"asm"`
Hex string `json:"hex,omitempty"`
ReqSigs int32 `json:"reqSigs,omitempty"`
Type string `json:"type"`
Addresses []string `json:"addresses,omitempty"`
CommitAmt *float64 `json:"commitamt,omitempty"`
}
type Vout struct {
Value float64 `json:"value"`
N uint32 `json:"n"`
Version uint16 `json:"version"`
ScriptPubKey ScriptPubKeyResult `json:"scriptPubKey"`
}
type RawTx struct {
Hex string `json:"hex"`
Txid string `json:"txid"`
Version int32 `json:"version"`
LockTime uint32 `json:"locktime"`
Vin []Vin `json:"vin"`
Vout []Vout `json:"vout"`
Expiry uint32 `json:"expiry"`
BlockIndex uint32 `json:"blockindex,omitempty"`
Confirmations int64 `json:"confirmations,omitempty"`
Time int64 `json:"time,omitempty"`
Blocktime int64 `json:"blocktime,omitempty"`
TxExtraInfo
}
type GetTransactionResult struct {
Error Error `json:"error"`
Result struct {
RawTx
} `json:"result"`
}
type MempoolTxsResult struct {
Error Error `json:"error"`
Result []string `json:"result"`
}
type EstimateSmartFeeResult struct {
Error Error `json:"error"`
Result struct {
FeeRate float64 `json:"feerate"`
Errors []string `json:"errors"`
Blocks int64 `json:"blocks"`
} `json:"result"`
}
type EstimateFeeResult struct {
Error Error `json:"error"`
Result common.JSONNumber `json:"result"`
}
type SendRawTransactionResult struct {
Error Error `json:"error"`
Result string `json:"result"`
}
type DecodeRawTransactionResult struct {
Error Error `json:"error"`
Result struct {
Txid string `json:"txid"`
Version int32 `json:"version"`
Locktime uint32 `json:"locktime"`
Expiry uint32 `json:"expiry"`
Vin []Vin `json:"vin"`
Vout []Vout `json:"vout"`
TxExtraInfo
} `json:"result"`
}
type TxExtraInfo struct {
BlockHeight uint32 `json:"blockheight,omitempty"`
BlockHash string `json:"blockhash,omitempty"`
}
func (d *DecredRPC) GetChainInfo() (*bchain.ChainInfo, error) {
blockchainInfoRequest := GenericCmd{
ID: 1,
Method: "getblockchaininfo",
}
var blockchainInfoResult GetBlockChainInfoResult
if err := d.Call(blockchainInfoRequest, &blockchainInfoResult); err != nil {
return nil, err
}
if blockchainInfoResult.Error.Message != "" {
return nil, mapToStandardErr("Error fetching blockchain info: %s", blockchainInfoResult.Error)
}
infoChainRequest := GenericCmd{
ID: 2,
Method: "getinfo",
}
var infoChainResult GetInfoChainResult
if err := d.Call(infoChainRequest, &infoChainResult); err != nil {
return nil, err
}
if infoChainResult.Error.Message != "" {
return nil, mapToStandardErr("Error fetching network info: %s", infoChainResult.Error)
}
chainInfo := &bchain.ChainInfo{
Chain: blockchainInfoResult.Result.Chain,
Blocks: int(blockchainInfoResult.Result.Blocks),
Headers: int(blockchainInfoResult.Result.Headers),
Bestblockhash: blockchainInfoResult.Result.BestBlockHash,
Difficulty: strconv.Itoa(int(blockchainInfoResult.Result.Difficulty)),
SizeOnDisk: blockchainInfoResult.Result.SyncHeight,
Version: strconv.Itoa(int(infoChainResult.Result.Version)),
Subversion: "",
ProtocolVersion: strconv.Itoa(int(infoChainResult.Result.ProtocolVersion)),
Timeoffset: float64(infoChainResult.Result.TimeOffset),
Warnings: "",
}
return chainInfo, nil
}
// getChainBestBlock returns the best block according to dcrd chain. This block
// has no atleast one confirming block.
func (d *DecredRPC) getChainBestBlock() (*GetBestBlockResult, error) {
bestBlockRequest := GenericCmd{
ID: 1,
Method: "getbestblock",
}
var bestBlockResult GetBestBlockResult
if err := d.Call(bestBlockRequest, &bestBlockResult); err != nil {
return nil, err
}
if bestBlockResult.Error.Message != "" {
return nil, mapToStandardErr("Error fetching best block: %s", bestBlockResult.Error)
}
return &bestBlockResult, nil
}
// getBestBlock returns details for the block mined immediately before the
// official dcrd chain's bestblock i.e. it has a minimum of 1 confirmation.
// The chain's best block is not returned as its block validity is not guarranteed.
func (d *DecredRPC) getBestBlock() (*GetBestBlockResult, error) {
bestBlockResult, err := d.getChainBestBlock()
if err != nil {
return nil, err
}
// remove the block with less than 1 confirming block
bestBlockResult.Result.Height--
validBlockHash, err := d.getBlockHashByHeight(bestBlockResult.Result.Height)
if err != nil {
return nil, err
}
bestBlockResult.Result.Hash = validBlockHash.Result
return bestBlockResult, nil
}
// GetBestBlockHash returns the block hash of the most recent block to be mined
// and has a minimum of 1 confirming block.
func (d *DecredRPC) GetBestBlockHash() (string, error) {
bestBlock, err := d.getBestBlock()
if err != nil {
return "", err
}
return bestBlock.Result.Hash, nil
}
// GetBestBlockHeight returns the block height of the most recent block to be mined
// and has a minimum of 1 confirming block.
func (d *DecredRPC) GetBestBlockHeight() (uint32, error) {
bestBlock, err := d.getBestBlock()
if err != nil {
return 0, err
}
return uint32(bestBlock.Result.Height), err
}
// GetBlockHash returns the block hash of the block at the provided height.
func (d *DecredRPC) GetBlockHash(height uint32) (string, error) {
blockHashResult, err := d.getBlockHashByHeight(height)
if err != nil {
return "", err
}
return blockHashResult.Result, nil
}
func (d *DecredRPC) getBlockHashByHeight(height uint32) (*GetBlockHashResult, error) {
blockHashRequest := GenericCmd{
ID: 1,
Method: "getblockhash",
Params: []interface{}{height},
}
var blockHashResult GetBlockHashResult
if err := d.Call(blockHashRequest, &blockHashResult); err != nil {
return nil, err
}
if blockHashResult.Error.Message != "" {
return nil, mapToStandardErr("Error fetching block hash: %s", blockHashResult.Error)
}
return &blockHashResult, nil
}
// GetBlockHeader returns the block header of the block the provided block hash.
func (d *DecredRPC) GetBlockHeader(hash string) (*bchain.BlockHeader, error) {
blockHeaderRequest := GenericCmd{
ID: 1,
Method: "getblockheader",
Params: []interface{}{hash},
}
var blockHeader GetBlockHeaderResult
if err := d.Call(blockHeaderRequest, &blockHeader); err != nil {
return nil, err
}
if blockHeader.Error.Message != "" {
return nil, mapToStandardErr("Error fetching block info: %s", blockHeader.Error)
}
header := &bchain.BlockHeader{
Hash: blockHeader.Result.Hash,
Prev: blockHeader.Result.PreviousHash,
Next: blockHeader.Result.NextHash,
Height: blockHeader.Result.Height,
Confirmations: int(blockHeader.Result.Confirmations),
Size: int(blockHeader.Result.Size),
Time: blockHeader.Result.Time,
}
return header, nil
}
// GetBlock returns the block retrieved using the provided block hash by default
// or using the block height if an empty hash string was provided. If the
// requested block has less than 2 confirmation bchain.ErrBlockNotFound error
// is returned. This rule is in places to guarrantee that only validated block
// details (txs) are saved to the db. Access to the bestBlock height is threadsafe.
func (d *DecredRPC) GetBlock(hash string, height uint32) (*bchain.Block, error) {
// Confirm if the block at provided height has at least 2 confirming blocks.
d.mtx.Lock()
if height > d.bestBlock {
bestBlock, err := d.getBestBlock()
if err != nil || height > bestBlock.Result.Height {
// If an error occurred or the current height doesn't have a minimum
// of two confirming blocks (greater than best block), quit.
d.mtx.Unlock()
return nil, bchain.ErrBlockNotFound
}
d.bestBlock = bestBlock.Result.Height
}
d.mtx.Unlock() // Releases the lock soonest possible
if hash == "" {
getHashResult, err := d.getBlockHashByHeight(height)
if err != nil {
return nil, err
}
hash = getHashResult.Result
}
block, err := d.getBlock(hash)
if err != nil {
return nil, err
}
header := bchain.BlockHeader{
Hash: block.Result.Hash,
Prev: block.Result.PreviousHash,
Next: block.Result.NextHash,
Height: block.Result.Height,
Confirmations: int(block.Result.Confirmations),
Size: int(block.Result.Size),
Time: block.Result.Time,
}
bchainBlock := &bchain.Block{BlockHeader: header}
// Check the current block validity by fetch the next block
nextBlockHashResult, err := d.getBlockHashByHeight(height + 1)
if err != nil {
return nil, err
}
nextBlock, err := d.getBlock(nextBlockHashResult.Result)
if err != nil {
return nil, err
}
// If the Votesbits set equals to voteBitYes append the regular transactions.
if nextBlock.Result.VoteBits == voteBitYes {
for _, txID := range block.Result.Tx {
if block.Result.Height == 0 {
continue
}
tx, err := d.GetTransaction(txID)
if err != nil {
return nil, err
}
bchainBlock.Txs = append(bchainBlock.Txs, *tx)
}
}
return bchainBlock, nil
}
func (d *DecredRPC) getBlock(hash string) (*GetBlockResult, error) {
blockRequest := GenericCmd{
ID: 1,
Method: "getblock",
Params: []interface{}{hash},
}
var block GetBlockResult
if err := d.Call(blockRequest, &block); err != nil {
return nil, err
}
if block.Error.Message != "" {
return nil, mapToStandardErr("Error fetching block info: %s", block.Error)
}
return &block, nil
}
func (d *DecredRPC) decodeRawTransaction(txHex string) (*bchain.Tx, error) {
decodeRawTxRequest := GenericCmd{
ID: 1,
Method: "decoderawtransaction",
Params: []interface{}{txHex},
}
var decodeRawTxResult DecodeRawTransactionResult
if err := d.Call(decodeRawTxRequest, &decodeRawTxResult); err != nil {
return nil, err
}
if decodeRawTxResult.Error.Message != "" {
return nil, mapToStandardErr("Error decoding raw tx: %s", decodeRawTxResult.Error)
}
tx := &bchain.Tx{
Hex: txHex,
Txid: decodeRawTxResult.Result.Txid,
Version: decodeRawTxResult.Result.Version,
LockTime: decodeRawTxResult.Result.Locktime,
}
// Add block height and block hash info
tx.CoinSpecificData = decodeRawTxResult.Result.TxExtraInfo
return tx, nil
}
func (d *DecredRPC) GetBlockInfo(hash string) (*bchain.BlockInfo, error) {
block, err := d.getBlock(hash)
if err != nil {
return nil, err
}
header := bchain.BlockHeader{
Hash: block.Result.Hash,
Prev: block.Result.PreviousHash,
Next: block.Result.NextHash,
Height: block.Result.Height,
Confirmations: int(block.Result.Confirmations),
Size: int(block.Result.Size),
Time: int64(block.Result.Time),
}
bInfo := &bchain.BlockInfo{
BlockHeader: header,
MerkleRoot: block.Result.MerkleRoot,
Version: block.Result.Version,
Nonce: block.Result.Nonce,
Bits: block.Result.Bits,
Difficulty: common.JSONNumber(strconv.FormatFloat(block.Result.Difficulty, 'e', -1, 64)),
Txids: block.Result.Tx,
}
return bInfo, nil
}
// GetTransaction returns a transaction by the transaction ID
func (d *DecredRPC) GetTransaction(txid string) (*bchain.Tx, error) {
r, err := d.getRawTransaction(txid)
if err != nil {
return nil, err
}
tx, err := d.Parser.ParseTxFromJson(r)
if err != nil {
return nil, errors.Annotatef(err, "txid %v", txid)
}
return tx, nil
}
// getRawTransaction returns json as returned by backend, with all coin specific data
func (d *DecredRPC) getRawTransaction(txid string) (json.RawMessage, error) {
if txid == "" {
return nil, bchain.ErrTxidMissing
}
verbose := 1
getTxRequest := GenericCmd{
ID: 1,
Method: "getrawtransaction",
Params: []interface{}{txid, &verbose},
}
var getTxResult GetTransactionResult
if err := d.Call(getTxRequest, &getTxResult); err != nil {
return nil, err
}
if getTxResult.Error.Message != "" {
return nil, mapToStandardErr("Error fetching transaction: %s", getTxResult.Error)
}
bytes, err := json.Marshal(getTxResult.Result)
if err != nil {
return nil, errors.Annotatef(err, "txid %v", txid)
}
return json.RawMessage(bytes), nil
}
// GetTransactionForMempool returns the full tx information identified by the
// provided txid.
func (d *DecredRPC) GetTransactionForMempool(txid string) (*bchain.Tx, error) {
return d.GetTransaction(txid)
}
// GetMempoolTransactions returns a slice of regular transactions currently in
// the mempool. The block whose validation is still undecided will have its txs,
// listed like they are still in the mempool till the block is confirmed.
func (d *DecredRPC) GetMempoolTransactions() ([]string, error) {
verbose := false
txType := "regular"
mempoolRequest := GenericCmd{
ID: 1,
Method: "getrawmempool",
Params: []interface{}{&verbose, &txType},
}
var mempool MempoolTxsResult
if err := d.Call(mempoolRequest, &mempool); err != nil {
return []string{}, err
}
if mempool.Error.Message != "" {
return nil, mapToStandardErr("Error fetching mempool data: %s", mempool.Error)
}
unvalidatedBlockResult, err := d.getChainBestBlock()
if err != nil {
return nil, err
}
unvalidatedBlock, err := d.getBlock(unvalidatedBlockResult.Result.Hash)
if err != nil {
return nil, err
}
mempool.Result = append(mempool.Result, unvalidatedBlock.Result.Tx...)
return mempool.Result, nil
}
// GetTransactionSpecific returns the json raw message for the tx identified by
// the provided txid.
func (d *DecredRPC) GetTransactionSpecific(tx *bchain.Tx) (json.RawMessage, error) {
return d.getRawTransaction(tx.Txid)
}
// EstimateSmartFee returns fee estimation
func (d *DecredRPC) EstimateSmartFee(blocks int, conservative bool) (big.Int, error) {
estimateSmartFeeRequest := GenericCmd{
ID: 1,
Method: "estimatesmartfee",
Params: []interface{}{blocks},
}
var smartFeeEstimate EstimateSmartFeeResult
if err := d.Call(estimateSmartFeeRequest, &smartFeeEstimate); err != nil {
return *big.NewInt(0), err
}
if smartFeeEstimate.Error.Message != "" {
return *big.NewInt(0), mapToStandardErr("Error fetching smart fee estimate: %s", smartFeeEstimate.Error)
}
return *big.NewInt(int64(smartFeeEstimate.Result.FeeRate)), nil
}
// EstimateFee returns fee estimation.
func (d *DecredRPC) EstimateFee(blocks int) (big.Int, error) {
estimateFeeRequest := GenericCmd{
ID: 1,
Method: "estimatefee",
Params: []interface{}{blocks},
}
var feeEstimate EstimateFeeResult
if err := d.Call(estimateFeeRequest, &feeEstimate); err != nil {
return *big.NewInt(0), err
}
if feeEstimate.Error.Message != "" {
return *big.NewInt(0), mapToStandardErr("Error fetching fee estimate: %s", feeEstimate.Error)
}
r, err := d.Parser.AmountToBigInt(feeEstimate.Result)
if err != nil {
return r, err
}
return r, nil
}
func (d *DecredRPC) SendRawTransaction(tx string) (string, error) {
sendRawTxRequest := &GenericCmd{
ID: 1,
Method: "sendrawtransaction",
Params: []interface{}{tx},
}
var sendRawTxResult SendRawTransactionResult
err := d.Call(sendRawTxRequest, &sendRawTxResult)
if err != nil {
return "", err
}
if sendRawTxResult.Error.Message != "" {
return "", mapToStandardErr("error sending raw transaction: %s", sendRawTxResult.Error)
}
return sendRawTxResult.Result, nil
}
// Call calls Backend RPC interface, using RPCMarshaler interface to marshall the request
func (d *DecredRPC) Call(req interface{}, res interface{}) error {
httpData, err := json.Marshal(req)
if err != nil {
return err
}
httpReq, err := http.NewRequest("POST", d.rpcURL, bytes.NewBuffer(httpData))
if err != nil {
return err
}
httpReq.SetBasicAuth(d.rpcUser, d.rpcPassword)
httpRes, err := d.client.Do(httpReq)
// in some cases the httpRes can contain data even if it returns error
// see http://devs.cloudimmunity.com/gotchas-and-common-mistakes-in-go-golang/
if httpRes != nil {
defer httpRes.Body.Close()
}
if err != nil {
return err
}
// if server returns HTTP error code it might not return json with response
// handle both cases
if httpRes.StatusCode != 200 {
if err = safeDecodeResponse(httpRes.Body, &res); err != nil {
return errors.Errorf("%v %v", httpRes.Status, err)
}
return nil
}
return safeDecodeResponse(httpRes.Body, &res)
}
func safeDecodeResponse(body io.ReadCloser, res *interface{}) (err error) {
var data []byte
defer func() {
if r := recover(); r != nil {
glog.Error("unmarshal json recovered from panic: ", r, "; data: ", string(data))
debug.PrintStack()
if len(data) > 0 && len(data) < 2048 {
err = errors.Errorf("Error: %v", string(data))
} else {
err = errors.New("Internal error")
}
}
}()
data, err = ioutil.ReadAll(body)
if err != nil {
return err
}
error := json.Unmarshal(data, res)
return error
}
// mapToStandardErr map the dcrd API Message errors to the standard error messages
// supported by trezor. Dcrd errors to be mapped are listed here:
// https://github.com/decred/dcrd/blob/2f5e47371263b996bb99e8dc3484f659309bd83a/dcrjson/jsonerr.go
func mapToStandardErr(customPrefix string, err Error) error {
switch {
case strings.Contains(err.Message, dcrjson.ErrBlockNotFound.Message) || // Block not found
strings.Contains(err.Message, dcrjson.ErrOutOfRange.Message) || // Block number out of range
strings.Contains(err.Message, dcrjson.ErrBestBlockHash.Message): // Error getting best block hash
return bchain.ErrBlockNotFound
case strings.Contains(err.Message, dcrjson.ErrNoTxInfo.Message): // No information available about transaction
return bchain.ErrTxNotFound
case strings.Contains(err.Message, dcrjson.ErrInvalidTxVout.Message): // Output index number (vout) does not exist for transaction
return bchain.ErrTxidMissing
default:
return fmt.Errorf(customPrefix, err.Message)
}
}