Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

server: use decred's rpcclient for BTC #975

Merged
merged 12 commits into from
May 4, 2021
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,9 @@ const (
methodGetRawTransaction = "getrawtransaction"
)

// RawRequester defines dcred's rpcclient RawRequest func where all RPC
// requests sent through. For testing, it can be satisfied by a stub.
// RawRequester is for sending context-aware RPC requests, and has methods for
// shutting down the underlying connection. For testing, it can be satisfied
// by a stub.
type RawRequester interface {
RawRequest(context.Context, string, []json.RawMessage) (json.RawMessage, error)
}
Expand Down Expand Up @@ -83,8 +84,7 @@ func newWalletClient(requester RawRequester, segwit bool, addrDecoder dexbtc.Add
// sent via RawRequest.
type anylist []interface{}

// EstimateSmartFee requests the server to estimate a fee level based on the
// given parameters.
// EstimateSmartFee requests the server to estimate a fee level.
func (wc *rpcClient) EstimateSmartFee(confTarget int64, mode *btcjson.EstimateSmartFeeMode) (*btcjson.EstimateSmartFeeResult, error) {
res := new(btcjson.EstimateSmartFeeResult)
return res, wc.call(methodEstimateSmartFee, anylist{confTarget, mode}, res)
Expand Down Expand Up @@ -151,8 +151,7 @@ func (wc *rpcClient) GetRawMempool() ([]*chainhash.Hash, error) {
return hashes, nil
}

// GetRawTransactionVerbose retrieves tx's information with verbose flag set
// to true.
// GetRawTransactionVerbose retrieves the verbose tx information.
func (wc *rpcClient) GetRawTransactionVerbose(txHash *chainhash.Hash) (*btcjson.TxRawResult, error) {
res := new(btcjson.TxRawResult)
return res, wc.call(methodGetRawTransaction, anylist{txHash.String(),
Expand Down
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,13 @@ github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufo
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
github.com/btcsuite/btcutil v1.0.2 h1:9iZ1Terx9fMIOtq1VrwdqfsATL9MC2l8ZrUY6YZ2uts=
github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts=
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd h1:R/opQEbFEy9JGkIguV40SvRY1uliPX8ifOvi6ICsFCw=
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg=
github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY=
github.com/btcsuite/goleveldb v1.0.0 h1:Tvd0BfvqX9o823q1j2UZ/epQo09eJh6dTcRp79ilIN4=
github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I=
github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
github.com/btcsuite/snappy-go v1.0.0 h1:ZxaA6lo2EpxGddsA8JwWOcxlzRybb444sgmeJQMJGQE=
github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 h1:R8vQdOQdZ9Y3SkEwmHoWBmX1DNXhXZqlTpq6s4tyJGc=
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
Expand Down
2 changes: 1 addition & 1 deletion server/asset/bch/bch.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ func (bch *BCHBackend) Contract(coinID []byte, redeemScript []byte) (*asset.Cont

// estimateFee estimates the network transaction fee rate using the estimatefee
// RPC.
func estimateFee(node btc.BTCNode) (uint64, error) {
func estimateFee(node *btc.RPCClient) (uint64, error) {
resp, err := node.RawRequest("estimatefee", nil)
if err != nil {
return 0, err
Expand Down
96 changes: 15 additions & 81 deletions server/asset/btc/btc.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"crypto/sha256"
"encoding/binary"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"math"
Expand All @@ -23,13 +22,11 @@ import (
"github.com/btcsuite/btcd/btcjson"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/rpcclient"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/decred/dcrd/rpcclient/v6"
)

const methodGetBlockchainInfo = "getblockchaininfo"

// Driver implements asset.Driver.
type Driver struct{}

Expand Down Expand Up @@ -64,20 +61,6 @@ const (
immatureTransactionError = dex.ErrorKind("immature output")
)

// BTCNode represents a blockchain information fetcher. In practice, it is
// satisfied by rpcclient.Client, and all methods are matches for Client
// methods. For testing, it can be satisfied by a stub.
type BTCNode interface {
EstimateSmartFee(confTarget int64, mode *btcjson.EstimateSmartFeeMode) (*btcjson.EstimateSmartFeeResult, error)
GetTxOut(txHash *chainhash.Hash, index uint32, mempool bool) (*btcjson.GetTxOutResult, error)
GetRawTransactionVerbose(txHash *chainhash.Hash) (*btcjson.TxRawResult, error)
GetBlockVerbose(blockHash *chainhash.Hash) (*btcjson.GetBlockVerboseResult, error)
GetBlockHash(blockHeight int64) (*chainhash.Hash, error)
GetBestBlockHash() (*chainhash.Hash, error)
RawRequest(method string, params []json.RawMessage) (json.RawMessage, error)
GetRawTransaction(txHash *chainhash.Hash) (*btcutil.Tx, error)
}

// Backend is a dex backend for Bitcoin or a Bitcoin clone. It has methods for
// fetching UTXO information and subscribing to block updates. It maintains a
// cache of block data for quick lookups. Backend implements asset.Backend, so
Expand All @@ -89,12 +72,8 @@ type Backend struct {
// segwit should be set to true for blockchains that support segregated
// witness.
segwit bool
// If an rpcclient.Client is used for the node, keeping a reference at client
// will result the (Client).Shutdown() being called on context cancellation.
client *rpcclient.Client
// node is used throughout for RPC calls, and in typical use will be the same
// as client. For testing, it can be set to a stub.
node BTCNode
// node is used throughout for RPC calls. For testing, it can be set to a stub.
node *RPCClient
// The block cache stores just enough info about the blocks to shortcut future
// calls to GetBlockVerbose.
blockCache *blockCache
Expand All @@ -107,7 +86,7 @@ type Backend struct {
// use the provided logger.
log dex.Logger
decodeAddr dexbtc.AddressDecoder
estimateFee func(BTCNode) (uint64, error)
estimateFee func(*RPCClient) (uint64, error)
}

// Check that Backend satisfies the Backend interface.
Expand Down Expand Up @@ -145,7 +124,6 @@ func NewBackend(configPath string, logger dex.Logger, network dex.Network) (asse
}

func newBTC(cloneCfg *BackendCloneConfig, cfg *dexbtc.Config) *Backend {

feeEstimator := feeRate
if cloneCfg.FeeEstimator != nil {
feeEstimator = cloneCfg.FeeEstimator
Expand Down Expand Up @@ -182,7 +160,7 @@ type BackendCloneConfig struct {
Ports dexbtc.NetPorts
// FeeEstimator provides a way to get fees given an RawRequest-enabled
// client and a confirmation target.
FeeEstimator func(BTCNode) (uint64, error)
FeeEstimator func(*RPCClient) (uint64, error)
}

// NewBTCClone creates a BTC backend for a set of network parameters and default
Expand All @@ -200,9 +178,9 @@ func NewBTCClone(cloneCfg *BackendCloneConfig) (*Backend, error) {
}

func (btc *Backend) shutdown() {
if btc.client != nil {
btc.client.Shutdown()
btc.client.WaitForShutdown()
if btc.node != nil {
btc.node.requester.Shutdown()
btc.node.requester.WaitForShutdown()
}
}

Expand All @@ -219,12 +197,13 @@ func (btc *Backend) Connect(ctx context.Context) (*sync.WaitGroup, error) {
return nil, fmt.Errorf("error creating %q RPC client: %w", btc.name, err)
}

// Setting the client field will enable shutdown
btc.client = client
btc.node = client
btc.node = &RPCClient{
ctx: ctx,
requester: client,
}

// Prime the cache
bestHash, err := btc.client.GetBestBlockHash()
bestHash, err := btc.node.GetBestBlockHash()
if err != nil {
btc.shutdown()
return nil, fmt.Errorf("error getting best block from rpc: %w", err)
Expand Down Expand Up @@ -287,7 +266,7 @@ func (btc *Backend) ValidateSecret(secret, contract []byte) bool {

// Synced is true if the blockchain is ready for action.
func (btc *Backend) Synced() (bool, error) {
chainInfo, err := btc.getBlockchainInfo()
chainInfo, err := btc.node.GetBlockChainInfo()
if err != nil {
return false, fmt.Errorf("GetBlockChainInfo error: %w", err)
}
Expand Down Expand Up @@ -444,51 +423,6 @@ func (btc *Backend) blockInfo(verboseTx *btcjson.TxRawResult) (blockHeight uint3
return
}

// anylist is a list of RPC parameters to be converted to []json.RawMessage and
// sent via RawRequest.
type anylist []interface{}

// call is used internally to marshal parmeters and send requests to the RPC
// server via (*rpcclient.Client).RawRequest. If `thing` is non-nil, the result
// will be marshaled into `thing`.
func (btc *Backend) call(method string, args anylist, thing interface{}) error {
params := make([]json.RawMessage, 0, len(args))
for i := range args {
p, err := json.Marshal(args[i])
if err != nil {
return err
}
params = append(params, p)
}
b, err := btc.node.RawRequest(method, params)
if err != nil {
return fmt.Errorf("rawrequest error: %w", err)
}
if thing != nil {
return json.Unmarshal(b, thing)
}
return nil
}

// getBlockchainInfoResult models the data returned from the getblockchaininfo
// command.
type getBlockchainInfoResult struct {
Blocks int64 `json:"blocks"`
Headers int64 `json:"headers"`
BestBlockHash string `json:"bestblockhash"`
InitialBlockDownload bool `json:"initialblockdownload"`
}

// getBlockchainInfo sends the getblockchaininfo request and returns the result.
func (btc *Backend) getBlockchainInfo() (*getBlockchainInfoResult, error) {
chainInfo := new(getBlockchainInfoResult)
err := btc.call(methodGetBlockchainInfo, nil, chainInfo)
if err != nil {
return nil, err
}
return chainInfo, nil
}

// Get the UTXO data and perform some checks for script support.
func (btc *Backend) utxo(txHash *chainhash.Hash, vout uint32, redeemScript []byte) (*UTXO, error) {
txOut, verboseTx, pkScript, err := btc.getTxOutInfo(txHash, vout)
Expand Down Expand Up @@ -1040,7 +974,7 @@ func isTxNotFoundErr(err error) bool {

// feeRate returns the current optimal fee rate in sat / byte using the
// estimatesmartfee RPC.
func feeRate(node BTCNode) (uint64, error) {
func feeRate(node *RPCClient) (uint64, error) {
feeResult, err := node.EstimateSmartFee(1, &btcjson.EstimateModeConservative)
if err != nil {
return 0, err
Expand Down
Loading