func (b *Client) getBlockHash(
	ctx context.Context,
	identifier *types.PartialBlockIdentifier,
) (string, error) {
	if identifier == nil || (identifier.Hash == nil && identifier.Index == nil) {
		info, err := b.getBlockchainInfo(ctx)
		if err != nil {
			return "", fmt.Errorf("%w: unable to get blockchain info", err)
		}
		return info.BestBlockHash, nil
	}
	if identifier.Hash != nil {
		return *identifier.Hash, nil
	}
	return b.getHashFromIndex(ctx, *identifier.Index)
}
getBlockHash returns the hash for a specified block identifier.
If the identifier includes a hash it will return that hash.
If the identifier only includes an index, if will fetch the hash that corresponds to
that block height from the node.
package bitcoin
import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"strconv"
	"time"
	bitcoinUtils "github.com/coinbase/rosetta-bitcoin/utils"
	"github.com/btcsuite/btcutil"
	"github.com/coinbase/rosetta-sdk-go/types"
	"github.com/coinbase/rosetta-sdk-go/utils"
)
const (
	genesisBlockIndex = 0
	requestID = 1
	jSONRPCVersion = "2.0"
	blockVerbosity = 2
)
type requestMethod string
const (
	requestMethodGetBlock requestMethod = "getblock"
	requestMethodGetBlockHash requestMethod = "getblockhash"
	requestMethodGetBlockchainInfo requestMethod = "getblockchaininfo"
	requestMethodGetPeerInfo requestMethod = "getpeerinfo"
	requestMethodPruneBlockchain requestMethod = "pruneblockchain"
	requestMethodSendRawTransaction requestMethod = "sendrawtransaction"
	requestMethodEstimateSmartFee requestMethod = "estimatesmartfee"
	requestMethodRawMempool requestMethod = "getrawmempool"
	blockNotFoundErrCode = -5
)
const (
	defaultTimeout = 100 * time.Second
	dialTimeout    = 5 * time.Second
	timeMultiplier = 1000
	rpcUsername = "rosetta"
	rpcPassword = "rosetta"
)
var (
	ErrBlockNotFound = errors.New("unable to find block")
	ErrJSONRPCError = errors.New("JSON-RPC error")
)
type Client struct {
	baseURL string
	genesisBlockIdentifier *types.BlockIdentifier
	currency               *types.Currency
	httpClient *http.Client
}
func LocalhostURL(rpcPort int) string {
	return fmt.Sprintf("http://localhost:%d", rpcPort)
}
func NewClient(
	baseURL string,
	genesisBlockIdentifier *types.BlockIdentifier,
	currency *types.Currency,
) *Client {
	return &Client{
		baseURL:                baseURL,
		genesisBlockIdentifier: genesisBlockIdentifier,
		currency:               currency,
		httpClient:             newHTTPClient(defaultTimeout),
	}
}
func newHTTPClient(timeout time.Duration) *http.Client {
	var netTransport = &http.Transport{
		Dial: (&net.Dialer{
			Timeout: dialTimeout,
		}).Dial,
	}
	httpClient := &http.Client{
		Timeout:   timeout,
		Transport: netTransport,
	}
	return httpClient
}
func (b *Client) NetworkStatus(ctx context.Context) (*types.NetworkStatusResponse, error) {
	rawBlock, err := b.getBlock(ctx, nil)
	if err != nil {
		return nil, fmt.Errorf("%w: unable to get current block", err)
	}
	currentBlock, err := b.parseBlockData(rawBlock)
	if err != nil {
		return nil, fmt.Errorf("%w: unable to parse current block", err)
	}
	peers, err := b.GetPeers(ctx)
	if err != nil {
		return nil, err
	}
	return &types.NetworkStatusResponse{
		CurrentBlockIdentifier: currentBlock.BlockIdentifier,
		CurrentBlockTimestamp:  currentBlock.Timestamp,
		GenesisBlockIdentifier: b.genesisBlockIdentifier,
		Peers:                  peers,
	}, nil
}
func (b *Client) GetPeers(ctx context.Context) ([]*types.Peer, error) {
	info, err := b.getPeerInfo(ctx)
	if err != nil {
		return nil, err
	}
	peers := make([]*types.Peer, len(info))
	for i, peerInfo := range info {
		metadata, err := types.MarshalMap(peerInfo)
		if err != nil {
			return nil, fmt.Errorf("%w: unable to marshal peer info", err)
		}
		peers[i] = &types.Peer{
			PeerID:   peerInfo.Addr,
			Metadata: metadata,
		}
	}
	return peers, nil
}
func (b *Client) GetRawBlock(
	ctx context.Context,
	identifier *types.PartialBlockIdentifier,
) (*Block, []string, error) {
	block, err := b.getBlock(ctx, identifier)
	if err != nil {
		return nil, nil, err
	}
	coins := []string{}
	blockTxHashes := []string{}
	for txIndex, tx := range block.Txs {
		blockTxHashes = append(blockTxHashes, tx.Hash)
		for inputIndex, input := range tx.Inputs {
			txHash, vout, ok := b.getInputTxHash(input, txIndex, inputIndex)
			if !ok {
				continue
			}
			if !utils.ContainsString(blockTxHashes, txHash) {
				coins = append(coins, CoinIdentifier(txHash, vout))
			}
		}
	}
	return block, coins, nil
}
func (b *Client) ParseBlock(
	ctx context.Context,
	block *Block,
	coins map[string]*types.AccountCoin,
) (*types.Block, error) {
	rblock, err := b.parseBlockData(block)
	if err != nil {
		return nil, err
	}
	txs, err := b.parseTransactions(ctx, block, coins)
	if err != nil {
		return nil, err
	}
	rblock.Transactions = txs
	return rblock, nil
}
func (b *Client) SendRawTransaction(
	ctx context.Context,
	serializedTx string,
) (string, error) {
	params := []interface{}{serializedTx, 0}
	response := &sendRawTransactionResponse{}
	if err := b.post(ctx, requestMethodSendRawTransaction, params, response); err != nil {
		return "", fmt.Errorf("%w: error submitting raw transaction", err)
	}
	return response.Result, nil
}
func (b *Client) SuggestedFeeRate(
	ctx context.Context,
	confTarget int64,
) (float64, error) {
	params := []interface{}{confTarget}
	response := &suggestedFeeRateResponse{}
	if err := b.post(ctx, requestMethodEstimateSmartFee, params, response); err != nil {
		return -1, fmt.Errorf("%w: error getting fee estimate", err)
	}
	return response.Result.FeeRate, nil
}
func (b *Client) PruneBlockchain(
	ctx context.Context,
	height int64,
) (int64, error) {
	params := []interface{}{height}
	response := &pruneBlockchainResponse{}
	if err := b.post(ctx, requestMethodPruneBlockchain, params, response); err != nil {
		return -1, fmt.Errorf("%w: error pruning blockchain", err)
	}
	return response.Result, nil
}
func (b *Client) RawMempool(
	ctx context.Context,
) ([]string, error) {
	params := []interface{}{false}
	response := &rawMempoolResponse{}
	if err := b.post(ctx, requestMethodRawMempool, params, response); err != nil {
		return nil, fmt.Errorf("%w: error getting raw mempool", err)
	}
	return response.Result, nil
}
func (b *Client) getPeerInfo(
	ctx context.Context,
) ([]*PeerInfo, error) {
	params := []interface{}{}
	response := &peerInfoResponse{}
	if err := b.post(ctx, requestMethodGetPeerInfo, params, response); err != nil {
		return nil, fmt.Errorf("%w: error posting to JSON-RPC", err)
	}
	return response.Result, nil
}
func (b *Client) getBlock(
	ctx context.Context,
	identifier *types.PartialBlockIdentifier,
) (*Block, error) {
	hash, err := b.getBlockHash(ctx, identifier)
	if err != nil {
		return nil, fmt.Errorf("%w: error getting block hash by identifier", err)
	}
	params := []interface{}{hash, blockVerbosity}
	response := &blockResponse{}
	if err := b.post(ctx, requestMethodGetBlock, params, response); err != nil {
		return nil, fmt.Errorf("%w: error fetching block by hash %s", err, hash)
	}
	return response.Result, nil
}
func (b *Client) getBlockchainInfo(
	ctx context.Context,
) (*BlockchainInfo, error) {
	params := []interface{}{}
	response := &blockchainInfoResponse{}
	if err := b.post(ctx, requestMethodGetBlockchainInfo, params, response); err != nil {
		return nil, fmt.Errorf("%w: unbale to get blockchain info", err)
	}
	return response.Result, nil
}