decred.org/dcrdex@v1.0.5/server/asset/btc/btc.go (about)

     1  // This code is available on the terms of the project LICENSE.md file,
     2  // also available online at https://blueoakcouncil.org/license/1.0.0.
     3  
     4  package btc
     5  
     6  import (
     7  	"bytes"
     8  	"context"
     9  	"crypto/sha256"
    10  	"encoding/binary"
    11  	"encoding/hex"
    12  	"encoding/json"
    13  	"errors"
    14  	"fmt"
    15  	"math"
    16  	"net/http"
    17  	"os"
    18  	"strings"
    19  	"sync"
    20  	"time"
    21  
    22  	"decred.org/dcrdex/dex"
    23  	"decred.org/dcrdex/dex/calc"
    24  	"decred.org/dcrdex/dex/config"
    25  	"decred.org/dcrdex/dex/dexnet"
    26  	dexbtc "decred.org/dcrdex/dex/networks/btc"
    27  	dexzec "decred.org/dcrdex/dex/networks/zec"
    28  	"decred.org/dcrdex/dex/txfee"
    29  	"decred.org/dcrdex/server/account"
    30  	"decred.org/dcrdex/server/asset"
    31  	srvdex "decred.org/dcrdex/server/dex"
    32  	"github.com/btcsuite/btcd/blockchain"
    33  	"github.com/btcsuite/btcd/btcjson"
    34  	"github.com/btcsuite/btcd/btcutil"
    35  	"github.com/btcsuite/btcd/chaincfg"
    36  	"github.com/btcsuite/btcd/chaincfg/chainhash"
    37  	"github.com/btcsuite/btcd/wire"
    38  	"github.com/decred/dcrd/dcrjson/v4" // for dcrjson.RPCError returns from rpcclient
    39  	"github.com/decred/dcrd/rpcclient/v8"
    40  )
    41  
    42  const defaultNoCompetitionRate = 10
    43  
    44  type v1Config struct {
    45  	ConfigPath     string `json:"configPath"`
    46  	DisableAPIFees bool   `json:"disableApiFees"`
    47  	TatumKey       string `json:"tatumKey"`
    48  	BlockdaemonKey string `json:"blockdaemonKey"`
    49  }
    50  
    51  // Driver implements asset.Driver.
    52  type Driver struct{}
    53  
    54  // Setup creates the BTC backend. Start the backend with its Run method.
    55  func (d *Driver) Setup(cfg *asset.BackendConfig) (asset.Backend, error) {
    56  	return NewBackend(cfg)
    57  }
    58  
    59  // DecodeCoinID creates a human-readable representation of a coin ID for
    60  // Bitcoin.
    61  func (d *Driver) DecodeCoinID(coinID []byte) (string, error) {
    62  	txid, vout, err := decodeCoinID(coinID)
    63  	if err != nil {
    64  		return "", err
    65  	}
    66  	return fmt.Sprintf("%v:%d", txid, vout), err
    67  }
    68  
    69  // Version returns the Backend implementation's version number.
    70  func (d *Driver) Version() uint32 {
    71  	return version
    72  }
    73  
    74  // UnitInfo returns the dex.UnitInfo for the asset.
    75  func (d *Driver) UnitInfo() dex.UnitInfo {
    76  	return dexbtc.UnitInfo
    77  }
    78  
    79  // MinBondSize calculates the minimum bond size for a given fee rate that avoids
    80  // dust outputs on the bond and refund txs, assuming the maxFeeRate doesn't
    81  // change.
    82  func (d *Driver) MinBondSize(maxFeeRate uint64) uint64 {
    83  	return dexbtc.MinBondSize(maxFeeRate, true)
    84  }
    85  
    86  // MinLotSize calculates the minimum bond size for a given fee rate that avoids
    87  // dust outputs on the swap and refund txs, assuming the maxFeeRate doesn't
    88  // change.
    89  func (d *Driver) MinLotSize(maxFeeRate uint64) uint64 {
    90  	return dexbtc.MinLotSize(maxFeeRate, true)
    91  }
    92  
    93  // Name is the asset's name.
    94  func (d *Driver) Name() string {
    95  	return "Bitcoin"
    96  }
    97  
    98  func init() {
    99  	asset.Register(BipID, &Driver{})
   100  
   101  	if blockPollIntervalStr != "" {
   102  		blockPollInterval, _ = time.ParseDuration(blockPollIntervalStr)
   103  		if blockPollInterval < time.Second {
   104  			panic(fmt.Sprintf("invalid value for blockPollIntervalStr: %q", blockPollIntervalStr))
   105  		}
   106  	}
   107  }
   108  
   109  var (
   110  	zeroHash chainhash.Hash
   111  	// The blockPollInterval is the delay between calls to GetBestBlockHash to
   112  	// check for new blocks. Modify at compile time via blockPollIntervalStr:
   113  	// go build -ldflags "-X 'decred.org/dcrdex/server/asset/btc.blockPollIntervalStr=4s'"
   114  	blockPollInterval            time.Duration
   115  	blockPollIntervalStr         string
   116  	conventionalConversionFactor = float64(dexbtc.UnitInfo.Conventional.ConversionFactor)
   117  	defaultMaxFeeBlocks          = 3
   118  )
   119  
   120  const (
   121  	version                  = 0
   122  	BipID                    = 0
   123  	assetName                = "btc"
   124  	immatureTransactionError = dex.ErrorKind("immature output")
   125  	BondVersion              = 0
   126  )
   127  
   128  func netParams(network dex.Network) (*chaincfg.Params, error) {
   129  	var params *chaincfg.Params
   130  	switch network {
   131  	case dex.Simnet:
   132  		params = &chaincfg.RegressionNetParams
   133  	case dex.Testnet:
   134  		params = &chaincfg.TestNet3Params
   135  	case dex.Mainnet:
   136  		params = &chaincfg.MainNetParams
   137  	default:
   138  		return nil, fmt.Errorf("unknown network ID: %d", uint8(network))
   139  	}
   140  	return params, nil
   141  }
   142  
   143  // Backend is a dex backend for Bitcoin or a Bitcoin clone. It has methods for
   144  // fetching UTXO information and subscribing to block updates. It maintains a
   145  // cache of block data for quick lookups. Backend implements asset.Backend, so
   146  // provides exported methods for DEX-related blockchain info.
   147  type Backend struct {
   148  	rpcCfg *dexbtc.RPCConfig
   149  	cfg    *BackendCloneConfig
   150  	// The asset name (e.g. btc), primarily for logging purposes.
   151  	name string
   152  	// segwit should be set to true for blockchains that support segregated
   153  	// witness.
   154  	segwit                     bool
   155  	initTxSizeBase, initTxSize uint64
   156  	// node is used throughout for RPC calls. For testing, it can be set to a stub.
   157  	node *RPCClient
   158  	// The block cache stores just enough info about the blocks to shortcut future
   159  	// calls to GetBlockVerbose.
   160  	blockCache *blockCache
   161  	// The backend provides block notification channels through it BlockChannel
   162  	// method. signalMtx locks the blockChans array.
   163  	signalMtx   sync.RWMutex
   164  	blockChans  map[chan *asset.BlockUpdate]struct{}
   165  	chainParams *chaincfg.Params
   166  	// A logger will be provided by the dex for this backend. All logging should
   167  	// use the provided logger.
   168  	log        dex.Logger
   169  	decodeAddr dexbtc.AddressDecoder
   170  	// booleanGetBlockRPC corresponds to BackendCloneConfig.BooleanGetBlockRPC
   171  	// field and is used by RPCClient, which is constructed on Connect.
   172  	booleanGetBlockRPC bool
   173  	blockDeserializer  func([]byte) (*wire.MsgBlock, error) // may be nil
   174  	txDeserializer     func([]byte) (*wire.MsgTx, error)    // must not be nil
   175  	txHasher           func(*wire.MsgTx) *chainhash.Hash
   176  	numericGetRawRPC   bool
   177  	// fee estimation configuration
   178  	feeConfs          int64
   179  	noCompetitionRate uint64
   180  
   181  	// The feeCache prevents repeated calculations of the median fee rate
   182  	// between block changes when estimate(smart)fee is unprimed.
   183  	feeCache struct {
   184  		sync.Mutex
   185  		fee  uint64
   186  		hash chainhash.Hash
   187  	}
   188  
   189  	feeRateCache struct {
   190  		sync.RWMutex
   191  		feeRate uint64
   192  		stamp   time.Time
   193  	}
   194  }
   195  
   196  // Check that Backend satisfies the Backend interface.
   197  var _ asset.Backend = (*Backend)(nil)
   198  var _ srvdex.Bonder = (*Backend)(nil)
   199  
   200  // NewBackend is the exported constructor by which the DEX will import the
   201  // backend. The configPath can be an empty string, in which case the standard
   202  // system location of the bitcoind config file is assumed.
   203  func NewBackend(cfg *asset.BackendConfig) (asset.Backend, error) {
   204  	params, err := netParams(cfg.Net)
   205  	if err != nil {
   206  		return nil, err
   207  	}
   208  
   209  	configPath := cfg.ConfigPath
   210  	if configPath == "" {
   211  		configPath = dexbtc.SystemConfigPath("bitcoin")
   212  	}
   213  
   214  	feeSources := make([]*txfee.SourceConfig, len(freeFeeSources), len(freeFeeSources)+1)
   215  	copy(feeSources, freeFeeSources)
   216  	b, err := os.ReadFile(configPath)
   217  	if err != nil {
   218  		return nil, fmt.Errorf("error reading config file: %w", err)
   219  	}
   220  	var cfgV1 v1Config
   221  	if err = json.Unmarshal(b, &cfgV1); err == nil {
   222  		if cfgV1.ConfigPath == "" {
   223  			return nil, errors.New("no config path defined in v1 config file")
   224  		}
   225  		configPath = cfgV1.ConfigPath
   226  
   227  		if cfgV1.TatumKey != "" {
   228  			feeSources = append(feeSources, tatumFeeFetcher(cfgV1.TatumKey))
   229  		}
   230  		if cfgV1.BlockdaemonKey != "" {
   231  			feeSources = append(feeSources, blockDaemonFeeFetcher(cfgV1.BlockdaemonKey))
   232  		}
   233  	}
   234  	var feeFetcher *txfee.FeeFetcher
   235  	if !cfgV1.DisableAPIFees {
   236  		feeFetcher = txfee.NewFeeFetcher(feeSources, cfg.Logger)
   237  	}
   238  	return NewBTCClone(&BackendCloneConfig{
   239  		Name:        assetName,
   240  		Segwit:      true,
   241  		ConfigPath:  configPath,
   242  		Logger:      cfg.Logger,
   243  		Net:         cfg.Net,
   244  		ChainParams: params,
   245  		Ports:       dexbtc.RPCPorts,
   246  		RelayAddr:   cfg.RelayAddr,
   247  		FeeFetcher:  feeFetcher,
   248  	})
   249  }
   250  
   251  func newBTC(cloneCfg *BackendCloneConfig, rpcCfg *dexbtc.RPCConfig) *Backend {
   252  	addrDecoder := btcutil.DecodeAddress
   253  	if cloneCfg.AddressDecoder != nil {
   254  		addrDecoder = cloneCfg.AddressDecoder
   255  	}
   256  
   257  	noCompetitionRate := cloneCfg.NoCompetitionFeeRate
   258  	if noCompetitionRate == 0 {
   259  		noCompetitionRate = defaultNoCompetitionRate
   260  	}
   261  
   262  	feeConfs := cloneCfg.FeeConfs
   263  	if feeConfs == 0 {
   264  		feeConfs = 1
   265  	}
   266  
   267  	txDeserializer := cloneCfg.TxDeserializer
   268  	if txDeserializer == nil {
   269  		txDeserializer = msgTxFromBytes
   270  	}
   271  
   272  	txHasher := cloneCfg.TxHasher
   273  	if txHasher == nil {
   274  		txHasher = hashTx
   275  	}
   276  
   277  	initTxSize, initTxSizeBase := uint64(dexbtc.InitTxSize), uint64(dexbtc.InitTxSizeBase)
   278  	switch {
   279  	case cloneCfg.Segwit:
   280  		initTxSize, initTxSizeBase = dexbtc.InitTxSizeSegwit, dexbtc.InitTxSizeBaseSegwit
   281  	case cloneCfg.Name == "zcl":
   282  		initTxSize, initTxSizeBase = dexzec.InitTxSize, dexzec.InitTxSizeBase
   283  	}
   284  
   285  	return &Backend{
   286  		rpcCfg:             rpcCfg,
   287  		cfg:                cloneCfg,
   288  		name:               cloneCfg.Name,
   289  		blockCache:         newBlockCache(),
   290  		blockChans:         make(map[chan *asset.BlockUpdate]struct{}),
   291  		chainParams:        cloneCfg.ChainParams,
   292  		log:                cloneCfg.Logger,
   293  		segwit:             cloneCfg.Segwit,
   294  		initTxSizeBase:     initTxSizeBase,
   295  		initTxSize:         initTxSize,
   296  		decodeAddr:         addrDecoder,
   297  		noCompetitionRate:  noCompetitionRate,
   298  		feeConfs:           feeConfs,
   299  		booleanGetBlockRPC: cloneCfg.BooleanGetBlockRPC,
   300  		blockDeserializer:  cloneCfg.BlockDeserializer,
   301  		txDeserializer:     txDeserializer,
   302  		txHasher:           txHasher,
   303  		numericGetRawRPC:   cloneCfg.NumericGetRawRPC,
   304  	}
   305  }
   306  
   307  // BackendCloneConfig captures the arguments necessary to configure a BTC clone
   308  // backend.
   309  type BackendCloneConfig struct {
   310  	Name           string
   311  	Segwit         bool
   312  	ConfigPath     string
   313  	AddressDecoder dexbtc.AddressDecoder
   314  	Logger         dex.Logger
   315  	Net            dex.Network
   316  	ChainParams    *chaincfg.Params
   317  	Ports          dexbtc.NetPorts
   318  	// ManualFeeScan specifies that median block fees should be calculated by
   319  	// scanning transactions since the getblockstats rpc is not available.
   320  	// Median block fees are used to estimate fee rates when the cache is not
   321  	// primed.
   322  	ManualMedianFee bool
   323  	// NoCompetitionFeeRate specifies a fee rate to use if estimatesmartfee
   324  	// or estimatefee aren't ready and the median fee is finding relatively
   325  	// empty blocks.
   326  	NoCompetitionFeeRate uint64
   327  	// DumbFeeEstimates is for asset's whose RPC is estimatefee instead of
   328  	// estimatesmartfee.
   329  	DumbFeeEstimates bool
   330  	// Argsless fee estimates are for assets who don't take an argument for
   331  	// number of blocks to estimatefee.
   332  	ArglessFeeEstimates bool
   333  	// FeeConfs specifies the target number of confirmations to use for
   334  	// estimate(smart)fee. If not set, default value is 1,
   335  	FeeConfs int64
   336  	// MaxFeeBlocks is the maximum number of blocks that can be evaluated for
   337  	// median fee calculations. If > 100 txs are not seen in the last
   338  	// MaxFeeBlocks, then the NoCompetitionRate will be returned as the median
   339  	// fee.
   340  	MaxFeeBlocks int
   341  	// BooleanGetBlockRPC will pass true instead of 2 as the getblock argument.
   342  	BooleanGetBlockRPC bool
   343  	// BlockDeserializer can be used in place of (*wire.MsgBlock).Deserialize.
   344  	BlockDeserializer func(blk []byte) (*wire.MsgBlock, error)
   345  	// TxDeserializer is an optional function used to deserialize a transaction.
   346  	// TxDeserializer is only used if ManualMedianFee is true.
   347  	TxDeserializer func([]byte) (*wire.MsgTx, error)
   348  	// TxHasher is a function that generates a tx hash from a MsgTx.
   349  	TxHasher func(*wire.MsgTx) *chainhash.Hash
   350  	// BlockFeeTransactions is a function to fetch a set of FeeTx and a previous
   351  	// block hash for a specific block.
   352  	BlockFeeTransactions BlockFeeTransactions
   353  	// NumericGetRawRPC uses a numeric boolean indicator for the
   354  	// getrawtransaction RPC.
   355  	NumericGetRawRPC bool
   356  	// ShieldedIO is a function to read a transaction and calculate the shielded
   357  	// input and output amounts. This is a temporary measure until zcashd
   358  	// encodes valueBalanceOrchard in their getrawtransaction RPC results.
   359  	ShieldedIO func(tx *VerboseTxExtended) (in, out uint64, err error)
   360  	// RelayAddr is an address for a NodeRelay.
   361  	RelayAddr  string
   362  	FeeFetcher *txfee.FeeFetcher
   363  }
   364  
   365  // NewBTCClone creates a BTC backend for a set of network parameters and default
   366  // network ports. A BTC clone can use this method, possibly in conjunction with
   367  // ReadCloneParams, to create a Backend for other assets with minimal coding.
   368  // See ReadCloneParams and CompatibilityCheck for more info.
   369  func NewBTCClone(cloneCfg *BackendCloneConfig) (*Backend, error) {
   370  	// Read the configuration parameters
   371  	rpcConfig := new(dexbtc.RPCConfig)
   372  	err := config.ParseInto(cloneCfg.ConfigPath, rpcConfig)
   373  	if err != nil {
   374  		return nil, err
   375  	}
   376  	if cloneCfg.RelayAddr != "" {
   377  		rpcConfig.RPCBind = cloneCfg.RelayAddr
   378  	}
   379  
   380  	err = dexbtc.CheckRPCConfig(rpcConfig, cloneCfg.Name, cloneCfg.Net, cloneCfg.Ports)
   381  	if err != nil {
   382  		return nil, err
   383  	}
   384  	return newBTC(cloneCfg, rpcConfig), nil
   385  }
   386  
   387  func (btc *Backend) shutdown() {
   388  	if btc.node != nil {
   389  		btc.node.requester.Shutdown()
   390  		btc.node.requester.WaitForShutdown()
   391  	}
   392  }
   393  
   394  // Connect connects to the node RPC server. A dex.Connector.
   395  func (btc *Backend) Connect(ctx context.Context) (*sync.WaitGroup, error) {
   396  	client, err := rpcclient.New(&rpcclient.ConnConfig{
   397  		HTTPPostMode: true,
   398  		DisableTLS:   !btc.rpcCfg.IsPublicProvider,
   399  		Host:         btc.rpcCfg.RPCBind,
   400  		User:         btc.rpcCfg.RPCUser,
   401  		Pass:         btc.rpcCfg.RPCPass,
   402  	}, nil)
   403  	if err != nil {
   404  		return nil, fmt.Errorf("error creating %q RPC client: %w", btc.name, err)
   405  	}
   406  
   407  	maxFeeBlocks := btc.cfg.MaxFeeBlocks
   408  	if maxFeeBlocks == 0 {
   409  		maxFeeBlocks = defaultMaxFeeBlocks
   410  	}
   411  
   412  	blockFeeTransactions := btc.cfg.BlockFeeTransactions
   413  	if blockFeeTransactions == nil {
   414  		blockFeeTransactions = btcBlockFeeTransactions
   415  	}
   416  
   417  	btc.node = &RPCClient{
   418  		ctx:                  ctx,
   419  		requester:            client,
   420  		booleanGetBlockRPC:   btc.booleanGetBlockRPC,
   421  		maxFeeBlocks:         maxFeeBlocks,
   422  		arglessFeeEstimates:  btc.cfg.ArglessFeeEstimates,
   423  		blockDeserializer:    btc.blockDeserializer,
   424  		numericGetRawRPC:     btc.numericGetRawRPC,
   425  		deserializeTx:        btc.txDeserializer,
   426  		blockFeeTransactions: blockFeeTransactions,
   427  	}
   428  
   429  	// Prime the cache
   430  	bestHash, err := btc.node.GetBestBlockHash()
   431  	if err != nil {
   432  		btc.shutdown()
   433  		return nil, fmt.Errorf("error getting best block from rpc: %w", err)
   434  	}
   435  	if bestHash != nil {
   436  		if _, err = btc.getBtcBlock(bestHash); err != nil {
   437  			btc.shutdown()
   438  			return nil, fmt.Errorf("error priming the cache: %w", err)
   439  		}
   440  	}
   441  
   442  	// Assume public RPC providers have txindex, or maybe want to check an old
   443  	// transaction or something, but the getindexinfo method may not be
   444  	// available for public providers.
   445  	txindex := btc.rpcCfg.IsPublicProvider
   446  	if !txindex {
   447  		txindex, err = btc.node.checkTxIndex()
   448  		if err != nil {
   449  			btc.log.Warnf(`Please ensure txindex is enabled in the node config and you might need to re-index if txindex was not previously enabled for %s`, btc.name)
   450  			btc.shutdown()
   451  			return nil, fmt.Errorf("error checking txindex for %s: %w", btc.name, err)
   452  		}
   453  	}
   454  	if !txindex {
   455  		btc.shutdown()
   456  		return nil, fmt.Errorf("%s transaction index is not enabled. Please enable txindex in the node config and you might need to re-index when you enable txindex", btc.name)
   457  	}
   458  
   459  	var wg sync.WaitGroup
   460  
   461  	if fetcher := btc.cfg.FeeFetcher; fetcher != nil {
   462  		cm := dex.NewConnectionMaster(btc.cfg.FeeFetcher)
   463  		if err := cm.ConnectOnce(ctx); err != nil {
   464  			btc.shutdown()
   465  			return nil, fmt.Errorf("error starting fee fetcher: %w", err)
   466  		}
   467  		wg.Add(1)
   468  		go func() {
   469  			defer wg.Done()
   470  			defer cm.Disconnect()
   471  			for {
   472  				select {
   473  				case r := <-fetcher.Next():
   474  					btc.log.Tracef("New fee reported: %d", r)
   475  					btc.feeRateCache.Lock()
   476  					btc.feeRateCache.stamp = time.Now()
   477  					btc.feeRateCache.feeRate = r
   478  					btc.feeRateCache.Unlock()
   479  				case <-ctx.Done():
   480  					return
   481  				}
   482  			}
   483  		}()
   484  	}
   485  
   486  	if _, err = btc.estimateFee(ctx); err != nil {
   487  		btc.log.Warnf("Backend started without fee estimation available: %v", err)
   488  	}
   489  
   490  	wg.Add(1)
   491  	go func() {
   492  		defer wg.Done()
   493  		btc.run(ctx)
   494  	}()
   495  	return &wg, nil
   496  }
   497  
   498  // Net returns the *chaincfg.Params. This is not part of the asset.Backend
   499  // interface, and is exported as a convenience for embedding types.
   500  func (btc *Backend) Net() *chaincfg.Params {
   501  	return btc.chainParams
   502  }
   503  
   504  // Contract is part of the asset.Backend interface. An asset.Contract is an
   505  // output that has been validated as a swap contract for the passed redeem
   506  // script. A spendable output is one that can be spent in the next block. Every
   507  // output from a non-coinbase transaction is spendable immediately. Coinbase
   508  // outputs are only spendable after CoinbaseMaturity confirmations. Pubkey
   509  // scripts can be P2PKH or P2SH. Multi-sig P2SH redeem scripts are supported.
   510  func (btc *Backend) Contract(coinID []byte, redeemScript []byte) (*asset.Contract, error) {
   511  	txHash, vout, err := decodeCoinID(coinID)
   512  	if err != nil {
   513  		return nil, fmt.Errorf("error decoding coin ID %x: %w", coinID, err)
   514  	}
   515  	output, err := btc.output(txHash, vout, redeemScript)
   516  	if err != nil {
   517  		return nil, err
   518  	}
   519  	// Verify contract and set refundAddress and swapAddress.
   520  	return btc.auditContract(output)
   521  }
   522  
   523  // ValidateSecret checks that the secret satisfies the contract.
   524  func (btc *Backend) ValidateSecret(secret, contract []byte) bool {
   525  	_, _, _, secretHash, err := dexbtc.ExtractSwapDetails(contract, btc.segwit, btc.chainParams)
   526  	if err != nil {
   527  		btc.log.Errorf("ValidateSecret->ExtractSwapDetails error: %v\n", err)
   528  		return false
   529  	}
   530  	h := sha256.Sum256(secret)
   531  	return bytes.Equal(h[:], secretHash)
   532  }
   533  
   534  // Synced is true if the blockchain is ready for action.
   535  func (btc *Backend) Synced() (bool, error) {
   536  	chainInfo, err := btc.node.GetBlockChainInfo()
   537  	if err != nil {
   538  		return false, fmt.Errorf("GetBlockChainInfo error: %w", err)
   539  	}
   540  	return !chainInfo.InitialBlockDownload && chainInfo.Headers-chainInfo.Blocks <= 1, nil
   541  }
   542  
   543  // Redemption is an input that redeems a swap contract.
   544  func (btc *Backend) Redemption(redemptionID, contractID, _ []byte) (asset.Coin, error) {
   545  	txHash, vin, err := decodeCoinID(redemptionID)
   546  	if err != nil {
   547  		return nil, fmt.Errorf("error decoding redemption coin ID %x: %w", txHash, err)
   548  	}
   549  	input, err := btc.input(txHash, vin)
   550  	if err != nil {
   551  		return nil, err
   552  	}
   553  	spends, err := input.spendsCoin(contractID)
   554  	if err != nil {
   555  		return nil, err
   556  	}
   557  	if !spends {
   558  		return nil, fmt.Errorf("%x does not spend %x", redemptionID, contractID)
   559  	}
   560  	return input, nil
   561  }
   562  
   563  // FundingCoin is an unspent output.
   564  func (btc *Backend) FundingCoin(_ context.Context, coinID []byte, redeemScript []byte) (asset.FundingCoin, error) {
   565  	txHash, vout, err := decodeCoinID(coinID)
   566  	if err != nil {
   567  		return nil, fmt.Errorf("error decoding coin ID %x: %w", coinID, err)
   568  	}
   569  
   570  	utxo, err := btc.utxo(txHash, vout, redeemScript)
   571  	if err != nil {
   572  		return nil, err
   573  	}
   574  
   575  	if utxo.nonStandardScript {
   576  		return nil, fmt.Errorf("non-standard script")
   577  	}
   578  	return utxo, nil
   579  }
   580  
   581  func (btc *Backend) ValidateOrderFunding(swapVal, valSum, _, inputsSize, maxSwaps uint64, nfo *dex.Asset) bool {
   582  	reqVal := calc.RequiredOrderFunds(swapVal, inputsSize, maxSwaps, btc.initTxSizeBase, btc.initTxSize, nfo.MaxFeeRate)
   583  	return valSum >= reqVal
   584  }
   585  
   586  // ValidateCoinID attempts to decode the coinID.
   587  func (btc *Backend) ValidateCoinID(coinID []byte) (string, error) {
   588  	txid, vout, err := decodeCoinID(coinID)
   589  	if err != nil {
   590  		return "", err
   591  	}
   592  	return fmt.Sprintf("%v:%d", txid, vout), err
   593  }
   594  
   595  // ValidateContract ensures that the swap contract is constructed properly, and
   596  // contains valid sender and receiver addresses.
   597  func (btc *Backend) ValidateContract(contract []byte) error {
   598  	_, _, _, _, err := dexbtc.ExtractSwapDetails(contract, btc.segwit, btc.chainParams)
   599  	return err
   600  }
   601  
   602  // VerifyUnspentCoin attempts to verify a coin ID by decoding the coin ID and
   603  // retrieving the corresponding UTXO. If the coin is not found or no longer
   604  // unspent, an asset.CoinNotFoundError is returned.
   605  func (btc *Backend) VerifyUnspentCoin(_ context.Context, coinID []byte) error {
   606  	txHash, vout, err := decodeCoinID(coinID)
   607  	if err != nil {
   608  		return fmt.Errorf("error decoding coin ID %x: %w", coinID, err)
   609  	}
   610  	txOut, err := btc.node.GetTxOut(txHash, vout, true)
   611  	if err != nil {
   612  		return fmt.Errorf("GetTxOut (%s:%d): %w", txHash.String(), vout, err)
   613  	}
   614  	if txOut == nil {
   615  		return asset.CoinNotFoundError
   616  	}
   617  	return nil
   618  }
   619  
   620  // ParseBondTx performs basic validation of a serialized time-locked fidelity
   621  // bond transaction given the bond's P2SH or P2WSH redeem script.
   622  //
   623  // The transaction must have at least two outputs: out 0 pays to a P2SH address
   624  // (the bond), and out 1 is a nulldata output that commits to an account ID.
   625  // There may also be a change output.
   626  //
   627  // Returned: The bond's coin ID (i.e. encoded UTXO) of the bond output. The bond
   628  // output's amount and P2SH/P2WSH address. The lockTime and pubkey hash data pushes
   629  // from the script. The account ID from the second output is also returned.
   630  //
   631  // Properly formed transactions:
   632  //
   633  //  1. The bond output (vout 0) must be a P2SH/P2WSH output.
   634  //  2. The bond's redeem script must be of the form:
   635  //     <lockTime[4]> OP_CHECKLOCKTIMEVERIFY OP_DROP OP_DUP OP_HASH160 <pubkeyhash[20]> OP_EQUALVERIFY OP_CHECKSIG
   636  //  3. The null data output (vout 1) must have a 58-byte data push (ver | account ID | lockTime | pubkeyHash).
   637  //  4. The transaction must have a zero locktime and expiry.
   638  //  5. All inputs must have the max sequence num set (finalized).
   639  //  6. The transaction must pass the checks in the
   640  //     blockchain.CheckTransactionSanity function.
   641  func ParseBondTx(ver uint16, msgTx *wire.MsgTx, chainParams *chaincfg.Params, segwit bool) (amt int64, bondAddr string,
   642  	bondPubKeyHash []byte, lockTime int64, acct account.AccountID, err error) {
   643  	if ver != BondVersion {
   644  		err = errors.New("only version 0 bonds supported")
   645  		return
   646  	}
   647  
   648  	if msgTx.LockTime != 0 {
   649  		err = errors.New("transaction locktime not zero")
   650  		return
   651  	}
   652  	if err = blockchain.CheckTransactionSanity(btcutil.NewTx(msgTx)); err != nil {
   653  		return
   654  	}
   655  
   656  	if len(msgTx.TxOut) < 2 {
   657  		err = fmt.Errorf("expected at least 2 outputs, found %d", len(msgTx.TxOut))
   658  		return
   659  	}
   660  
   661  	for _, txIn := range msgTx.TxIn {
   662  		if txIn.Sequence != wire.MaxTxInSequenceNum {
   663  			err = errors.New("input has non-max sequence number")
   664  			return
   665  		}
   666  	}
   667  
   668  	// Fidelity bond (output 0)
   669  	bondOut := msgTx.TxOut[0]
   670  	scriptHash := dexbtc.ExtractScriptHash(bondOut.PkScript)
   671  	if scriptHash == nil {
   672  		err = fmt.Errorf("bad bond pkScript")
   673  		return
   674  	}
   675  	switch len(scriptHash) {
   676  	case 32:
   677  		if !segwit {
   678  			err = fmt.Errorf("%s backend does not support segwit bonds", chainParams.Name)
   679  			return
   680  		}
   681  	case 20:
   682  		if segwit {
   683  			err = fmt.Errorf("%s backend requires segwit bonds", chainParams.Name)
   684  			return
   685  		}
   686  	default:
   687  		err = fmt.Errorf("unexpected script hash length %d", len(scriptHash))
   688  		return
   689  	}
   690  
   691  	acctCommitOut := msgTx.TxOut[1]
   692  	acct, lock, pkh, err := dexbtc.ExtractBondCommitDataV0(0, acctCommitOut.PkScript)
   693  	if err != nil {
   694  		err = fmt.Errorf("invalid bond commitment output: %w", err)
   695  		return
   696  	}
   697  
   698  	// Reconstruct and check the bond redeem script.
   699  	bondScript, err := dexbtc.MakeBondScript(ver, lock, pkh[:])
   700  	if err != nil {
   701  		err = fmt.Errorf("failed to build bond output redeem script: %w", err)
   702  		return
   703  	}
   704  
   705  	// Check that the script hash extracted from output 0 is what is expected
   706  	// based on the information in the account commitment.
   707  	// P2WSH uses sha256, while P2SH uses ripemd160(sha256).
   708  	var expectedScriptHash []byte
   709  	if len(scriptHash) == 32 {
   710  		hash := sha256.Sum256(bondScript)
   711  		expectedScriptHash = hash[:]
   712  	} else {
   713  		expectedScriptHash = btcutil.Hash160(bondScript)
   714  	}
   715  	if !bytes.Equal(expectedScriptHash, scriptHash) {
   716  		err = fmt.Errorf("script hash check failed for output 0")
   717  		return
   718  	}
   719  
   720  	_, addrs, _, err := dexbtc.ExtractScriptData(bondOut.PkScript, chainParams)
   721  	if err != nil {
   722  		err = fmt.Errorf("error extracting addresses from bond output: %w", err)
   723  		return
   724  	}
   725  	amt = bondOut.Value
   726  	bondAddr = addrs[0] // don't convert address, must match type we specified
   727  	lockTime = int64(lock)
   728  	bondPubKeyHash = pkh[:]
   729  
   730  	return
   731  }
   732  
   733  // BondVer returns the latest supported bond version.
   734  func (dcr *Backend) BondVer() uint16 {
   735  	return BondVersion
   736  }
   737  
   738  // ParseBondTx makes the package-level ParseBondTx pure function accessible via
   739  // a Backend instance. This performs basic validation of a serialized
   740  // time-locked fidelity bond transaction given the bond's P2SH redeem script.
   741  func (btc *Backend) ParseBondTx(ver uint16, rawTx []byte) (bondCoinID []byte, amt int64, bondAddr string,
   742  	bondPubKeyHash []byte, lockTime int64, acct account.AccountID, err error) {
   743  	var msgTx *wire.MsgTx
   744  	msgTx, err = btc.txDeserializer(rawTx)
   745  	if err != nil {
   746  		return
   747  	}
   748  	bondCoinID = toCoinID(btc.txHasher(msgTx), 0)
   749  	amt, bondAddr, bondPubKeyHash, lockTime, acct, err = ParseBondTx(ver, msgTx, btc.chainParams, btc.segwit)
   750  	return
   751  }
   752  
   753  // BondCoin locates a bond transaction output, validates the entire transaction,
   754  // and returns the amount, encoded lockTime and account ID, and the
   755  // confirmations of the transaction. It is a CoinNotFoundError if the
   756  // transaction output is spent.
   757  func (btc *Backend) BondCoin(ctx context.Context, ver uint16, coinID []byte) (amt, lockTime, confs int64, acct account.AccountID, err error) {
   758  	txHash, vout, errCoin := decodeCoinID(coinID)
   759  	if errCoin != nil {
   760  		err = fmt.Errorf("error decoding coin ID %x: %w", coinID, errCoin)
   761  		return
   762  	}
   763  
   764  	verboseTx, err := btc.node.GetRawTransactionVerbose(txHash)
   765  	if err != nil {
   766  		if isTxNotFoundErr(err) {
   767  			err = asset.CoinNotFoundError
   768  		}
   769  		return
   770  	}
   771  
   772  	if int(vout) > len(verboseTx.Vout)-1 {
   773  		err = fmt.Errorf("invalid output index for tx with %d outputs", len(verboseTx.Vout))
   774  		return
   775  	}
   776  
   777  	confs = int64(verboseTx.Confirmations)
   778  
   779  	var msgTx *wire.MsgTx
   780  	msgTx, err = btc.txDeserializer(verboseTx.Raw)
   781  	if err != nil {
   782  		return
   783  	}
   784  
   785  	txOut, err := btc.node.GetTxOut(txHash, vout, true) // check regular tree first
   786  	if err != nil {
   787  		if isTxNotFoundErr(err) { // should be txOut==nil, but checking anyway
   788  			err = asset.CoinNotFoundError
   789  			return
   790  		}
   791  		return
   792  	}
   793  	if txOut == nil { // spent == invalid bond
   794  		err = asset.CoinNotFoundError
   795  		return
   796  	}
   797  
   798  	amt, _, _, lockTime, acct, err = ParseBondTx(ver, msgTx, btc.chainParams, btc.segwit)
   799  	return
   800  }
   801  
   802  // txOutData is transaction output data, including recipient addresses, value,
   803  // script type, and number of required signatures.
   804  type txOutData struct {
   805  	value        uint64
   806  	addresses    []string
   807  	sigsRequired int
   808  	scriptType   dexbtc.BTCScriptType
   809  }
   810  
   811  // outputSummary gets transaction output data, including recipient addresses,
   812  // value, script type, and number of required signatures, plus the current
   813  // confirmations of a transaction output. If the output does not exist, an error
   814  // will be returned. Non-standard scripts are not an error.
   815  func (btc *Backend) outputSummary(txHash *chainhash.Hash, vout uint32) (txOut *txOutData, confs int64, err error) {
   816  	var verboseTx *VerboseTxExtended
   817  	verboseTx, err = btc.node.GetRawTransactionVerbose(txHash)
   818  	if err != nil {
   819  		if isTxNotFoundErr(err) {
   820  			err = asset.CoinNotFoundError
   821  		}
   822  		return
   823  	}
   824  
   825  	if int(vout) > len(verboseTx.Vout)-1 {
   826  		err = asset.CoinNotFoundError // should be something fatal?
   827  		return
   828  	}
   829  
   830  	out := verboseTx.Vout[vout]
   831  
   832  	script, err := hex.DecodeString(out.ScriptPubKey.Hex)
   833  	if err != nil {
   834  		return nil, -1, dex.UnsupportedScriptError
   835  	}
   836  	scriptType, addrs, numRequired, err := dexbtc.ExtractScriptData(script, btc.chainParams)
   837  	if err != nil {
   838  		return nil, -1, dex.UnsupportedScriptError
   839  	}
   840  
   841  	txOut = &txOutData{
   842  		value:        toSat(out.Value),
   843  		addresses:    addrs,       // out.ScriptPubKey.Addresses
   844  		sigsRequired: numRequired, // out.ScriptPubKey.ReqSigs
   845  		scriptType:   scriptType,  // integer representation of the string in out.ScriptPubKey.Type
   846  	}
   847  
   848  	confs = int64(verboseTx.Confirmations)
   849  	return
   850  }
   851  
   852  // BlockChannel creates and returns a new channel on which to receive block
   853  // updates. If the returned channel is ever blocking, there will be no error
   854  // logged from the btc package. Part of the asset.Backend interface.
   855  func (btc *Backend) BlockChannel(size int) <-chan *asset.BlockUpdate {
   856  	c := make(chan *asset.BlockUpdate, size)
   857  	btc.signalMtx.Lock()
   858  	defer btc.signalMtx.Unlock()
   859  	btc.blockChans[c] = struct{}{}
   860  	return c
   861  }
   862  
   863  // FeeRate returns the current optimal fee rate in sat / byte.
   864  func (btc *Backend) FeeRate(ctx context.Context) (uint64, error) {
   865  	return btc.estimateFee(ctx)
   866  }
   867  
   868  // Info provides some general information about the backend.
   869  func (*Backend) Info() *asset.BackendInfo {
   870  	return &asset.BackendInfo{}
   871  }
   872  
   873  // ValidateFeeRate checks that the transaction fees used to initiate the
   874  // contract are sufficient.
   875  func (btc *Backend) ValidateFeeRate(c asset.Coin, reqFeeRate uint64) bool {
   876  	return c.FeeRate() >= reqFeeRate
   877  }
   878  
   879  // CheckSwapAddress checks that the given address is parseable, and suitable as
   880  // a redeem address in a swap contract script.
   881  func (btc *Backend) CheckSwapAddress(addr string) bool {
   882  	btcAddr, err := btc.decodeAddr(addr, btc.chainParams)
   883  	if err != nil {
   884  		btc.log.Errorf("CheckSwapAddress for %s failed: %v", addr, err)
   885  		return false
   886  	}
   887  	if btc.segwit {
   888  		if _, ok := btcAddr.(*btcutil.AddressWitnessPubKeyHash); !ok {
   889  			btc.log.Errorf("CheckSwapAddress for %s failed: not a witness-pubkey-hash address (%T)",
   890  				btcAddr.String(), btcAddr)
   891  			return false
   892  		}
   893  	} else {
   894  		if _, ok := btcAddr.(*btcutil.AddressPubKeyHash); !ok {
   895  			btc.log.Errorf("CheckSwapAddress for %s failed: not a pubkey-hash address (%T)",
   896  				btcAddr.String(), btcAddr)
   897  			return false
   898  		}
   899  	}
   900  	return true
   901  }
   902  
   903  // TxData is the raw transaction bytes. SPV clients rebroadcast the transaction
   904  // bytes to get around not having a mempool to check.
   905  func (btc *Backend) TxData(coinID []byte) ([]byte, error) {
   906  	txHash, _, err := decodeCoinID(coinID)
   907  	if err != nil {
   908  		return nil, err
   909  	}
   910  	txB, err := btc.node.GetRawTransaction(txHash)
   911  	if err != nil {
   912  		if isTxNotFoundErr(err) {
   913  			return nil, asset.CoinNotFoundError
   914  		}
   915  		return nil, fmt.Errorf("GetRawTransaction for txid %s: %w", txHash, err)
   916  	}
   917  	return txB, nil
   918  }
   919  
   920  // blockInfo returns block information for the verbose transaction data. The
   921  // current tip hash is also returned as a convenience.
   922  func (btc *Backend) blockInfo(verboseTx *VerboseTxExtended) (blockHeight uint32, blockHash chainhash.Hash, tipHash *chainhash.Hash, err error) {
   923  	h := btc.blockCache.tipHash()
   924  	if h != zeroHash {
   925  		tipHash = &h
   926  	}
   927  	if verboseTx.Confirmations > 0 {
   928  		var blk *cachedBlock
   929  		blk, err = btc.getBlockInfo(verboseTx.BlockHash)
   930  		if err != nil {
   931  			return
   932  		}
   933  		blockHeight = blk.height
   934  		blockHash = blk.hash
   935  	}
   936  	return
   937  }
   938  
   939  // Get the UTXO data and perform some checks for script support.
   940  func (btc *Backend) utxo(txHash *chainhash.Hash, vout uint32, redeemScript []byte) (*UTXO, error) {
   941  	txOut, verboseTx, pkScript, err := btc.getTxOutInfo(txHash, vout)
   942  	if err != nil {
   943  		return nil, err
   944  	}
   945  
   946  	inputNfo, err := dexbtc.InputInfo(pkScript, redeemScript, btc.chainParams)
   947  	if err != nil {
   948  		return nil, err
   949  	}
   950  	scriptType := inputNfo.ScriptType
   951  
   952  	// If it's a pay-to-script-hash, extract the script hash and check it against
   953  	// the hash of the user-supplied redeem script.
   954  	if scriptType.IsP2SH() || scriptType.IsP2WSH() {
   955  		scriptHash := dexbtc.ExtractScriptHash(pkScript)
   956  		if scriptType.IsSegwit() {
   957  			shash := sha256.Sum256(redeemScript)
   958  			if !bytes.Equal(shash[:], scriptHash) {
   959  				return nil, fmt.Errorf("(utxo:segwit) script hash check failed for utxo %s,%d", txHash, vout)
   960  			}
   961  		} else {
   962  			if !bytes.Equal(btcutil.Hash160(redeemScript), scriptHash) {
   963  				return nil, fmt.Errorf("(utxo:non-segwit) script hash check failed for utxo %s,%d", txHash, vout)
   964  			}
   965  		}
   966  	}
   967  
   968  	// Get block information.
   969  	blockHeight, blockHash, lastLookup, err := btc.blockInfo(verboseTx)
   970  	if err != nil {
   971  		return nil, err
   972  	}
   973  
   974  	// Coinbase transactions must mature before spending.
   975  	var maturity int64
   976  	if txOut.Coinbase {
   977  		maturity = int64(btc.chainParams.CoinbaseMaturity)
   978  	}
   979  	if txOut.Confirmations < maturity {
   980  		return nil, immatureTransactionError
   981  	}
   982  
   983  	tx, err := btc.transaction(txHash, verboseTx)
   984  	if err != nil {
   985  		return nil, fmt.Errorf("error fetching verbose transaction data: %w", err)
   986  	}
   987  
   988  	out := &Output{
   989  		TXIO: TXIO{
   990  			btc:        btc,
   991  			tx:         tx,
   992  			height:     blockHeight,
   993  			blockHash:  blockHash,
   994  			maturity:   int32(maturity),
   995  			lastLookup: lastLookup,
   996  		},
   997  		vout:              vout,
   998  		scriptType:        scriptType,
   999  		nonStandardScript: inputNfo.NonStandardScript,
  1000  		pkScript:          pkScript,
  1001  		redeemScript:      redeemScript,
  1002  		numSigs:           inputNfo.ScriptAddrs.NRequired,
  1003  		spendSize:         inputNfo.VBytes(),
  1004  		value:             toSat(txOut.Value),
  1005  	}
  1006  	return &UTXO{out}, nil
  1007  }
  1008  
  1009  // newTXIO creates a TXIO for a transaction, spent or unspent.
  1010  func (btc *Backend) newTXIO(txHash *chainhash.Hash) (*TXIO, int64, error) {
  1011  	verboseTx, err := btc.node.GetRawTransactionVerbose(txHash)
  1012  	if err != nil {
  1013  		if isTxNotFoundErr(err) {
  1014  			return nil, 0, asset.CoinNotFoundError
  1015  		}
  1016  		return nil, 0, fmt.Errorf("GetRawTransactionVerbose for txid %s: %w", txHash, err)
  1017  	}
  1018  	tx, err := btc.transaction(txHash, verboseTx)
  1019  	if err != nil {
  1020  		return nil, 0, fmt.Errorf("error fetching verbose transaction data: %w", err)
  1021  	}
  1022  	blockHeight, blockHash, lastLookup, err := btc.blockInfo(verboseTx)
  1023  	if err != nil {
  1024  		return nil, 0, err
  1025  	}
  1026  	var maturity int32
  1027  	if tx.isCoinbase {
  1028  		maturity = int32(btc.chainParams.CoinbaseMaturity)
  1029  	}
  1030  	return &TXIO{
  1031  		btc:        btc,
  1032  		tx:         tx,
  1033  		height:     blockHeight,
  1034  		blockHash:  blockHash,
  1035  		maturity:   maturity,
  1036  		lastLookup: lastLookup,
  1037  	}, int64(verboseTx.Confirmations), nil
  1038  }
  1039  
  1040  // input gets the transaction input.
  1041  func (btc *Backend) input(txHash *chainhash.Hash, vin uint32) (*Input, error) {
  1042  	txio, _, err := btc.newTXIO(txHash)
  1043  	if err != nil {
  1044  		return nil, err
  1045  	}
  1046  	if int(vin) >= len(txio.tx.ins) {
  1047  		return nil, fmt.Errorf("tx %v has %d outputs (no vin %d)", txHash, len(txio.tx.ins), vin)
  1048  	}
  1049  	return &Input{
  1050  		TXIO: *txio,
  1051  		vin:  vin,
  1052  	}, nil
  1053  }
  1054  
  1055  // output gets the transaction output.
  1056  func (btc *Backend) output(txHash *chainhash.Hash, vout uint32, redeemScript []byte) (*Output, error) {
  1057  	txio, confs, err := btc.newTXIO(txHash)
  1058  	if err != nil {
  1059  		return nil, err
  1060  	}
  1061  	if int(vout) >= len(txio.tx.outs) {
  1062  		return nil, fmt.Errorf("tx %v has %d outputs (no vout %d)", txHash, len(txio.tx.outs), vout)
  1063  	}
  1064  
  1065  	txOut := txio.tx.outs[vout]
  1066  	pkScript := txOut.pkScript
  1067  	inputNfo, err := dexbtc.InputInfo(pkScript, redeemScript, btc.chainParams)
  1068  	if err != nil {
  1069  		return nil, err
  1070  	}
  1071  	scriptType := inputNfo.ScriptType
  1072  
  1073  	// If it's a pay-to-script-hash, extract the script hash and check it against
  1074  	// the hash of the user-supplied redeem script.
  1075  	if scriptType.IsP2SH() || scriptType.IsP2WSH() {
  1076  		scriptHash := dexbtc.ExtractScriptHash(pkScript)
  1077  		if scriptType.IsSegwit() {
  1078  			shash := sha256.Sum256(redeemScript)
  1079  			if !bytes.Equal(shash[:], scriptHash) {
  1080  				return nil, fmt.Errorf("(output:segwit) script hash check failed for utxo %s,%d", txHash, vout)
  1081  			}
  1082  		} else {
  1083  			if !bytes.Equal(btcutil.Hash160(redeemScript), scriptHash) {
  1084  				return nil, fmt.Errorf("(output:non-segwit) script hash check failed for utxo %s,%d", txHash, vout)
  1085  			}
  1086  		}
  1087  	}
  1088  
  1089  	scrAddrs := inputNfo.ScriptAddrs
  1090  	addresses := make([]string, scrAddrs.NumPK+scrAddrs.NumPKH)
  1091  	for i, addr := range append(scrAddrs.PkHashes, scrAddrs.PubKeys...) {
  1092  		addresses[i] = addr.String() // unconverted
  1093  	}
  1094  
  1095  	// Coinbase transactions must mature before spending.
  1096  	if confs < int64(txio.maturity) {
  1097  		return nil, immatureTransactionError
  1098  	}
  1099  
  1100  	return &Output{
  1101  		TXIO:              *txio,
  1102  		vout:              vout,
  1103  		value:             txOut.value,
  1104  		addresses:         addresses,
  1105  		scriptType:        scriptType,
  1106  		nonStandardScript: inputNfo.NonStandardScript,
  1107  		pkScript:          pkScript,
  1108  		redeemScript:      redeemScript,
  1109  		numSigs:           scrAddrs.NRequired,
  1110  		// The total size associated with the wire.TxIn.
  1111  		spendSize: inputNfo.VBytes(),
  1112  	}, nil
  1113  }
  1114  
  1115  // Get the value of the previous outpoint.
  1116  func (btc *Backend) prevOutputValue(txid string, vout int) (uint64, error) {
  1117  	txHash, err := chainhash.NewHashFromStr(txid)
  1118  	if err != nil {
  1119  		return 0, fmt.Errorf("error decoding tx hash %s: %w", txid, err)
  1120  	}
  1121  	verboseTx, err := btc.node.GetRawTransactionVerbose(txHash)
  1122  	if err != nil {
  1123  		return 0, err
  1124  	}
  1125  	if vout > len(verboseTx.Vout)-1 {
  1126  		return 0, fmt.Errorf("prevOutput: vout index out of range")
  1127  	}
  1128  	output := verboseTx.Vout[vout]
  1129  	return toSat(output.Value), nil
  1130  }
  1131  
  1132  // Get the Tx. Transaction info is not cached, so every call will result in a
  1133  // GetRawTransactionVerbose RPC call.
  1134  func (btc *Backend) transaction(txHash *chainhash.Hash, verboseTx *VerboseTxExtended) (*Tx, error) {
  1135  	// If it's not a mempool transaction, get and cache the block data.
  1136  	var blockHash *chainhash.Hash
  1137  	var lastLookup *chainhash.Hash
  1138  	var blockHeight int64
  1139  	if verboseTx.BlockHash == "" {
  1140  		tipHash := btc.blockCache.tipHash()
  1141  		if tipHash != zeroHash {
  1142  			lastLookup = &tipHash
  1143  		}
  1144  	} else {
  1145  		var err error
  1146  		blockHash, err = chainhash.NewHashFromStr(verboseTx.BlockHash)
  1147  		if err != nil {
  1148  			return nil, fmt.Errorf("error decoding block hash %s for tx %s: %w", verboseTx.BlockHash, txHash, err)
  1149  		}
  1150  		// Make sure the block info is cached.
  1151  		blk, err := btc.getBtcBlock(blockHash)
  1152  		if err != nil {
  1153  			return nil, fmt.Errorf("error caching the block data for transaction %s", txHash)
  1154  		}
  1155  		blockHeight = int64(blk.height)
  1156  	}
  1157  
  1158  	// Parse inputs and outputs, storing only what's needed.
  1159  	inputs := make([]txIn, 0, len(verboseTx.Vin))
  1160  
  1161  	// sumIn, sumOut := verboseTx.ShieldedIO()
  1162  	var sumIn, sumOut uint64
  1163  	if btc.cfg.ShieldedIO != nil {
  1164  		var err error
  1165  		sumIn, sumOut, err = btc.cfg.ShieldedIO(verboseTx)
  1166  		if err != nil {
  1167  			return nil, fmt.Errorf("ShieldedIO error: %w", err)
  1168  		}
  1169  	}
  1170  
  1171  	var isCoinbase bool
  1172  	for vin, input := range verboseTx.Vin {
  1173  		isCoinbase = input.Coinbase != ""
  1174  		var valIn uint64
  1175  		if isCoinbase {
  1176  			valIn = toSat(verboseTx.Vout[0].Value)
  1177  		} else {
  1178  			var err error
  1179  			valIn, err = btc.prevOutputValue(input.Txid, int(input.Vout))
  1180  			if err != nil {
  1181  				return nil, fmt.Errorf("error fetching previous output value for %s:%d: %w", txHash, vin, err)
  1182  			}
  1183  		}
  1184  		sumIn += valIn
  1185  		if input.Txid == "" {
  1186  			inputs = append(inputs, txIn{
  1187  				vout:  input.Vout,
  1188  				value: valIn,
  1189  			})
  1190  			continue
  1191  		}
  1192  		hash, err := chainhash.NewHashFromStr(input.Txid)
  1193  		if err != nil {
  1194  			return nil, fmt.Errorf("error decoding previous tx hash %s for tx %s: %w", input.Txid, txHash, err)
  1195  		}
  1196  		inputs = append(inputs, txIn{
  1197  			prevTx: *hash,
  1198  			vout:   input.Vout,
  1199  			value:  valIn,
  1200  		})
  1201  	}
  1202  
  1203  	outputs := make([]txOut, 0, len(verboseTx.Vout))
  1204  	for vout, output := range verboseTx.Vout {
  1205  		pkScript, err := hex.DecodeString(output.ScriptPubKey.Hex)
  1206  		if err != nil {
  1207  			return nil, fmt.Errorf("error decoding pubkey script from %s for transaction %d:%d: %w",
  1208  				output.ScriptPubKey.Hex, txHash, vout, err)
  1209  		}
  1210  		vOut := toSat(output.Value)
  1211  		sumOut += vOut
  1212  		outputs = append(outputs, txOut{
  1213  			value:    vOut,
  1214  			pkScript: pkScript,
  1215  		})
  1216  	}
  1217  
  1218  	// TODO: Unneeded after https://github.com/ZclassicCommunity/zclassic/pull/82
  1219  	if verboseTx.Size == 0 {
  1220  		verboseTx.Size = int32(len(verboseTx.Raw))
  1221  	}
  1222  
  1223  	var feeRate uint64
  1224  	if btc.segwit {
  1225  		if verboseTx.Vsize > 0 {
  1226  			feeRate = (sumIn - sumOut) / uint64(verboseTx.Vsize)
  1227  		}
  1228  	} else if verboseTx.Size > 0 && sumIn > sumOut {
  1229  		// For non-segwit transactions, Size = Vsize anyway, so use Size to
  1230  		// cover assets that won't set Vsize in their RPC response.
  1231  		feeRate = (sumIn - sumOut) / uint64(verboseTx.Size)
  1232  	}
  1233  	hash := blockHash
  1234  	if hash == nil {
  1235  		hash = &zeroHash
  1236  	}
  1237  	return &Tx{
  1238  		btc:        btc,
  1239  		blockHash:  *hash,
  1240  		height:     blockHeight,
  1241  		hash:       *txHash,
  1242  		ins:        inputs,
  1243  		outs:       outputs,
  1244  		isCoinbase: isCoinbase,
  1245  		lastLookup: lastLookup,
  1246  		inputSum:   sumIn,
  1247  		feeRate:    feeRate,
  1248  		raw:        verboseTx.Raw,
  1249  	}, nil
  1250  }
  1251  
  1252  // Get information for an unspent transaction output and it's transaction.
  1253  func (btc *Backend) getTxOutInfo(txHash *chainhash.Hash, vout uint32) (*btcjson.GetTxOutResult, *VerboseTxExtended, []byte, error) {
  1254  	txOut, err := btc.node.GetTxOut(txHash, vout, true)
  1255  	if err != nil {
  1256  		if isTxNotFoundErr(err) { // should be txOut==nil, but checking anyway
  1257  			return nil, nil, nil, asset.CoinNotFoundError
  1258  		}
  1259  		return nil, nil, nil, fmt.Errorf("GetTxOut error for output %s:%d: %w", txHash, vout, err)
  1260  	}
  1261  	if txOut == nil {
  1262  		return nil, nil, nil, asset.CoinNotFoundError
  1263  	}
  1264  	pkScript, err := hex.DecodeString(txOut.ScriptPubKey.Hex)
  1265  	if err != nil {
  1266  		return nil, nil, nil, fmt.Errorf("failed to decode pubkey from '%s' for output %s:%d", txOut.ScriptPubKey.Hex, txHash, vout)
  1267  	}
  1268  	verboseTx, err := btc.node.GetRawTransactionVerbose(txHash)
  1269  	if err != nil {
  1270  		if isTxNotFoundErr(err) {
  1271  			return nil, nil, nil, asset.CoinNotFoundError // shouldn't happen if gettxout found it
  1272  		}
  1273  		return nil, nil, nil, fmt.Errorf("GetRawTransactionVerbose for txid %s: %w", txHash, err)
  1274  	}
  1275  	return txOut, verboseTx, pkScript, nil
  1276  }
  1277  
  1278  // Get the block information, checking the cache first. Same as
  1279  // getBtcBlock, but takes a string argument.
  1280  func (btc *Backend) getBlockInfo(blockid string) (*cachedBlock, error) {
  1281  	blockHash, err := chainhash.NewHashFromStr(blockid)
  1282  	if err != nil {
  1283  		return nil, fmt.Errorf("unable to decode block hash from %s", blockid)
  1284  	}
  1285  	return btc.getBtcBlock(blockHash)
  1286  }
  1287  
  1288  // Get the block information, checking the cache first.
  1289  func (btc *Backend) getBtcBlock(blockHash *chainhash.Hash) (*cachedBlock, error) {
  1290  	cachedBlk, found := btc.blockCache.block(blockHash)
  1291  	if found {
  1292  		return cachedBlk, nil
  1293  	}
  1294  	blockVerbose, err := btc.node.GetBlockVerbose(blockHash)
  1295  	if err != nil {
  1296  		return nil, fmt.Errorf("error retrieving block %s: %w", blockHash, err)
  1297  	}
  1298  	return btc.blockCache.add(blockVerbose)
  1299  }
  1300  
  1301  // auditContract checks that output is a swap contract and extracts the
  1302  // receiving address and contract value on success.
  1303  func (btc *Backend) auditContract(contract *Output) (*asset.Contract, error) {
  1304  	tx := contract.tx
  1305  	if len(tx.outs) <= int(contract.vout) {
  1306  		return nil, fmt.Errorf("invalid index %d for transaction %s", contract.vout, tx.hash)
  1307  	}
  1308  	output := tx.outs[int(contract.vout)]
  1309  
  1310  	// If it's a pay-to-script-hash, extract the script hash and check it against
  1311  	// the hash of the user-supplied redeem script.
  1312  	scriptType := dexbtc.ParseScriptType(output.pkScript, contract.redeemScript)
  1313  	if scriptType == dexbtc.ScriptUnsupported {
  1314  		return nil, fmt.Errorf("specified output %s:%d is not P2SH", tx.hash, contract.vout)
  1315  	}
  1316  	var scriptHash, hashed []byte
  1317  	if scriptType.IsP2SH() || scriptType.IsP2WSH() {
  1318  		scriptHash = dexbtc.ExtractScriptHash(output.pkScript)
  1319  		if scriptType.IsSegwit() {
  1320  			if !btc.segwit {
  1321  				return nil, fmt.Errorf("segwit contract, but %s is not configured for segwit", btc.name)
  1322  			}
  1323  			shash := sha256.Sum256(contract.redeemScript)
  1324  			hashed = shash[:]
  1325  		} else {
  1326  			if btc.segwit {
  1327  				return nil, fmt.Errorf("non-segwit contract, but %s is configured for segwit", btc.name)
  1328  			}
  1329  			hashed = btcutil.Hash160(contract.redeemScript)
  1330  		}
  1331  	}
  1332  	if scriptHash == nil {
  1333  		return nil, fmt.Errorf("specified output %s:%d is not P2SH or P2WSH", tx.hash, contract.vout)
  1334  	}
  1335  	if !bytes.Equal(hashed, scriptHash) {
  1336  		return nil, fmt.Errorf("swap contract hash mismatch for %s:%d", tx.hash, contract.vout)
  1337  	}
  1338  	_, receiver, lockTime, secretHash, err := dexbtc.ExtractSwapDetails(contract.redeemScript, contract.btc.segwit, contract.btc.chainParams)
  1339  	if err != nil {
  1340  		return nil, fmt.Errorf("error parsing swap contract for %s:%d: %w", tx.hash, contract.vout, err)
  1341  	}
  1342  	return &asset.Contract{
  1343  		Coin:         contract,
  1344  		SwapAddress:  receiver.String(),
  1345  		ContractData: contract.redeemScript,
  1346  		SecretHash:   secretHash,
  1347  		LockTime:     time.Unix(int64(lockTime), 0),
  1348  		TxData:       contract.tx.raw,
  1349  	}, nil
  1350  }
  1351  
  1352  // run is responsible for best block polling and checking the application
  1353  // context to trigger a clean shutdown.
  1354  func (btc *Backend) run(ctx context.Context) {
  1355  	defer btc.shutdown()
  1356  
  1357  	if blockPollInterval == 0 {
  1358  		blockPollInterval = time.Second
  1359  		if btc.rpcCfg.IsPublicProvider {
  1360  			blockPollInterval = time.Second * 10
  1361  		}
  1362  	}
  1363  
  1364  	btc.log.Infof("Starting %v block polling with interval of %v",
  1365  		strings.ToUpper(btc.name), blockPollInterval)
  1366  	blockPoll := time.NewTicker(blockPollInterval)
  1367  	defer blockPoll.Stop()
  1368  	addBlock := func(block *GetBlockVerboseResult, reorg bool) {
  1369  		_, err := btc.blockCache.add(block)
  1370  		if err != nil {
  1371  			btc.log.Errorf("error adding new best block to cache: %v", err)
  1372  		}
  1373  		btc.signalMtx.RLock()
  1374  		btc.log.Tracef("Notifying %d %s asset consumers of new block at height %d",
  1375  			len(btc.blockChans), btc.name, block.Height)
  1376  		for c := range btc.blockChans {
  1377  			select {
  1378  			case c <- &asset.BlockUpdate{
  1379  				Err:   nil,
  1380  				Reorg: reorg,
  1381  			}:
  1382  			default:
  1383  				btc.log.Errorf("failed to send block update on blocking channel")
  1384  				// Commented to try sends on future blocks.
  1385  				// close(c)
  1386  				// delete(btc.blockChans, c)
  1387  				//
  1388  				// TODO: Allow the receiver (e.g. Swapper.Run) to inform done
  1389  				// status so the channels can be retired cleanly rather than
  1390  				// trying them forever.
  1391  			}
  1392  		}
  1393  		btc.signalMtx.RUnlock()
  1394  	}
  1395  
  1396  	sendErr := func(err error) {
  1397  		btc.log.Error(err)
  1398  		for c := range btc.blockChans {
  1399  			select {
  1400  			case c <- &asset.BlockUpdate{
  1401  				Err: err,
  1402  			}:
  1403  			default:
  1404  				btc.log.Errorf("failed to send sending block update on blocking channel")
  1405  				// close(c)
  1406  				// delete(btc.blockChans, c)
  1407  			}
  1408  		}
  1409  	}
  1410  
  1411  	sendErrFmt := func(s string, a ...any) {
  1412  		sendErr(fmt.Errorf(s, a...))
  1413  	}
  1414  
  1415  out:
  1416  	for {
  1417  		select {
  1418  		case <-blockPoll.C:
  1419  			tip := btc.blockCache.tip()
  1420  			bestHash, err := btc.node.GetBestBlockHash()
  1421  			if err != nil {
  1422  				sendErr(asset.NewConnectionError("error retrieving best block: %v", err))
  1423  				continue
  1424  			}
  1425  			if *bestHash == tip.hash {
  1426  				continue
  1427  			}
  1428  			best := bestHash.String()
  1429  			block, err := btc.node.GetBlockVerbose(bestHash)
  1430  			if err != nil {
  1431  				sendErrFmt("error retrieving block %s: %v", best, err)
  1432  				continue
  1433  			}
  1434  			// If this doesn't build on the best known block, look for a reorg.
  1435  			prevHash, err := chainhash.NewHashFromStr(block.PreviousHash)
  1436  			if err != nil {
  1437  				sendErrFmt("error parsing previous hash %s: %v", block.PreviousHash, err)
  1438  				continue
  1439  			}
  1440  			// If it builds on the best block or the cache is empty, it's good to add.
  1441  			if *prevHash == tip.hash || tip.height == 0 {
  1442  				btc.log.Debugf("New block %s (%d)", bestHash, block.Height)
  1443  				addBlock(block, false)
  1444  				continue
  1445  			}
  1446  			// It is either a reorg, or the previous block is not the cached
  1447  			// best block. Crawl blocks backwards until finding a mainchain
  1448  			// block, flagging blocks from the cache as orphans along the way.
  1449  			iHash := &tip.hash
  1450  			reorgHeight := int64(0)
  1451  			for {
  1452  				if *iHash == zeroHash {
  1453  					break
  1454  				}
  1455  				iBlock, err := btc.node.GetBlockVerbose(iHash)
  1456  				if err != nil {
  1457  					sendErrFmt("error retrieving block %s: %v", iHash, err)
  1458  					break
  1459  				}
  1460  				if iBlock.Confirmations > -1 {
  1461  					// This is a mainchain block, nothing to do.
  1462  					break
  1463  				}
  1464  				if iBlock.Height == 0 {
  1465  					break
  1466  				}
  1467  				reorgHeight = iBlock.Height
  1468  				iHash, err = chainhash.NewHashFromStr(iBlock.PreviousHash)
  1469  				if err != nil {
  1470  					sendErrFmt("error decoding previous hash %s for block %s: %v",
  1471  						iBlock.PreviousHash, iHash.String(), err)
  1472  					// Some blocks on the side chain may not be flagged as
  1473  					// orphaned, but still proceed, flagging the ones we have
  1474  					// identified and adding the new best block to the cache and
  1475  					// setting it to the best block in the cache.
  1476  					break
  1477  				}
  1478  			}
  1479  			var reorg bool
  1480  			if reorgHeight > 0 {
  1481  				reorg = true
  1482  				btc.log.Infof("Reorg from %s (%d) to %s (%d) detected.",
  1483  					tip.hash, tip.height, bestHash, block.Height)
  1484  				btc.blockCache.reorg(reorgHeight)
  1485  			}
  1486  			// Now add the new block.
  1487  			addBlock(block, reorg)
  1488  		case <-ctx.Done():
  1489  			break out
  1490  		}
  1491  	}
  1492  }
  1493  
  1494  // estimateFee attempts to get a reasonable tx fee rates (units: atomic/(v)byte)
  1495  // to use for the asset by checking estimate(smart)fee. That call can fail or
  1496  // otherwise be useless on an otherwise perfectly functioning node. In that
  1497  // case, an estimate is calculated from the median fees of the previous
  1498  // block(s).
  1499  func (btc *Backend) estimateFee(ctx context.Context) (satsPerB uint64, err error) {
  1500  	if btc.cfg.FeeFetcher != nil {
  1501  		const feeRateExpiry = time.Minute * 10
  1502  		btc.feeRateCache.RLock()
  1503  		stamp, feeRate := btc.feeRateCache.stamp, btc.feeRateCache.feeRate
  1504  		btc.feeRateCache.RUnlock()
  1505  		if time.Since(stamp) < feeRateExpiry {
  1506  			return feeRate, nil
  1507  		} else {
  1508  			btc.log.Warnf("external btc fee rate is expired. falling back to estimatesmartfee")
  1509  		}
  1510  	}
  1511  
  1512  	if btc.cfg.DumbFeeEstimates {
  1513  		satsPerB, err = btc.node.EstimateFee(btc.feeConfs)
  1514  	} else {
  1515  		satsPerB, err = btc.node.EstimateSmartFee(btc.feeConfs, &btcjson.EstimateModeConservative)
  1516  	}
  1517  	if err == nil && satsPerB > 0 {
  1518  		return satsPerB, nil
  1519  	} else if err != nil && !errors.Is(err, errNoFeeRate) {
  1520  		btc.log.Debugf("Estimate fee failure: %v", err)
  1521  	}
  1522  	btc.log.Debugf("No fee estimate from node. Computing median fee rate from blocks...")
  1523  
  1524  	tip := btc.blockCache.tipHash()
  1525  
  1526  	btc.feeCache.Lock()
  1527  	defer btc.feeCache.Unlock()
  1528  
  1529  	// If the current block hasn't changed, no need to recalc.
  1530  	if btc.feeCache.hash == tip {
  1531  		return btc.feeCache.fee, nil
  1532  	}
  1533  
  1534  	// Need to revert to the median fee calculation.
  1535  	if btc.cfg.ManualMedianFee {
  1536  		satsPerB, err = btc.node.medianFeesTheHardWay(ctx)
  1537  	} else {
  1538  		satsPerB, err = btc.node.medianFeeRate()
  1539  	}
  1540  	if err != nil {
  1541  		if errors.Is(err, errNoCompetition) {
  1542  			btc.log.Debugf("Blocks are too empty to calculate median fees. "+
  1543  				"Using no-competition rate (%d).", btc.noCompetitionRate)
  1544  			btc.feeCache.fee = btc.noCompetitionRate
  1545  			btc.feeCache.hash = tip
  1546  			return btc.noCompetitionRate, nil
  1547  		}
  1548  		return 0, err
  1549  	}
  1550  	if satsPerB < btc.noCompetitionRate {
  1551  		btc.log.Debugf("Calculated median fees %d are lower than the no-competition rate %d. Using the latter.",
  1552  			satsPerB, btc.noCompetitionRate)
  1553  		satsPerB = btc.noCompetitionRate
  1554  	}
  1555  	btc.feeCache.fee = satsPerB
  1556  	btc.feeCache.hash = tip
  1557  	return satsPerB, nil
  1558  }
  1559  
  1560  // decodeCoinID decodes the coin ID into a tx hash and a vout.
  1561  func decodeCoinID(coinID []byte) (*chainhash.Hash, uint32, error) {
  1562  	if len(coinID) != 36 {
  1563  		return nil, 0, fmt.Errorf("coin ID wrong length. expected 36, got %d", len(coinID))
  1564  	}
  1565  	var txHash chainhash.Hash
  1566  	copy(txHash[:], coinID[:32])
  1567  	return &txHash, binary.BigEndian.Uint32(coinID[32:]), nil
  1568  }
  1569  
  1570  // toCoinID converts the outpoint to a coin ID.
  1571  func toCoinID(txHash *chainhash.Hash, vout uint32) []byte {
  1572  	hashLen := len(txHash)
  1573  	b := make([]byte, hashLen+4)
  1574  	copy(b[:hashLen], txHash[:])
  1575  	binary.BigEndian.PutUint32(b[hashLen:], vout)
  1576  	return b
  1577  }
  1578  
  1579  // Convert the BTC value to satoshis.
  1580  func toSat(v float64) uint64 {
  1581  	return uint64(math.Round(v * conventionalConversionFactor))
  1582  }
  1583  
  1584  // isTxNotFoundErr will return true if the error indicates that the requested
  1585  // transaction is not known.
  1586  func isTxNotFoundErr(err error) bool {
  1587  	// We are using dcrd's client with Bitcoin Core, so errors will be of type
  1588  	// dcrjson.RPCError, but numeric codes should come from btcjson.
  1589  	const errRPCNoTxInfo = int(btcjson.ErrRPCNoTxInfo)
  1590  	var rpcErr *dcrjson.RPCError
  1591  	return errors.As(err, &rpcErr) && int(rpcErr.Code) == errRPCNoTxInfo
  1592  }
  1593  
  1594  // isMethodNotFoundErr will return true if the error indicates that the RPC
  1595  // method was not found by the RPC server. The error must be dcrjson.RPCError
  1596  // with a numeric code equal to btcjson.ErrRPCMethodNotFound.Code or a message
  1597  // containing "method not found".
  1598  func isMethodNotFoundErr(err error) bool {
  1599  	var errRPCMethodNotFound = int(btcjson.ErrRPCMethodNotFound.Code)
  1600  	var rpcErr *dcrjson.RPCError
  1601  	return errors.As(err, &rpcErr) &&
  1602  		(int(rpcErr.Code) == errRPCMethodNotFound ||
  1603  			strings.Contains(strings.ToLower(rpcErr.Message), "method not found"))
  1604  }
  1605  
  1606  // msgTxFromBytes creates a wire.MsgTx by deserializing the transaction.
  1607  // WARNING: You probably want to use a deserializer that is appropriate for the
  1608  // asset instead. This does not work for all assets, like Zcash.
  1609  func msgTxFromBytes(txB []byte) (*wire.MsgTx, error) {
  1610  	msgTx := new(wire.MsgTx)
  1611  	if err := msgTx.Deserialize(bytes.NewReader(txB)); err != nil {
  1612  		return nil, err
  1613  	}
  1614  	return msgTx, nil
  1615  }
  1616  
  1617  // hashTx just calls the tx's TxHash method.
  1618  // WARNING: You probably want to use a hasher that is appropriate for the
  1619  // asset instead. This does not work for all assets, like Zcash.
  1620  func hashTx(tx *wire.MsgTx) *chainhash.Hash {
  1621  	h := tx.TxHash()
  1622  	return &h
  1623  }
  1624  
  1625  var freeFeeSources = []*txfee.SourceConfig{
  1626  	{ // https://mempool.space/docs/api/rest#get-recommended-fees
  1627  		Name:   "mempool.space",
  1628  		Rank:   1,
  1629  		Period: time.Minute * 2, // Rate limit might be 1 per 10 seconds.
  1630  		F: func(ctx context.Context) (rate uint64, errDelay time.Duration, err error) {
  1631  			const uri = "https://mempool.space/api/v1/fees/recommended"
  1632  			var res struct {
  1633  				FastestFee uint64 `json:"fastestFee"`
  1634  			}
  1635  			var code int
  1636  			if err := dexnet.Get(ctx, uri, &res, dexnet.WithStatusFunc(func(respCode int) {
  1637  				code = respCode
  1638  			})); err != nil {
  1639  				if code == http.StatusTooManyRequests { // 429 per docs
  1640  					return 0, time.Minute * 30, errors.New("exceeded request limit")
  1641  				}
  1642  				return 0, time.Minute * 2, err
  1643  			}
  1644  			return res.FastestFee, 0, nil
  1645  		},
  1646  	},
  1647  	{ // https://bitcoiner.live/doc/api
  1648  		Name:   "bitcoiner.live",
  1649  		Rank:   1,
  1650  		Period: time.Minute * 5, // Data is refreshed every 5 minutes
  1651  		F: func(ctx context.Context) (rate uint64, errDelay time.Duration, err error) {
  1652  			const uri = "https://bitcoiner.live/api/fees/estimates/latest"
  1653  			var res struct {
  1654  				Estimates map[string]struct {
  1655  					SatsPerVB float64 `json:"sat_per_vbyte"`
  1656  				} `json:"estimates"`
  1657  			}
  1658  			if err := dexnet.Get(ctx, uri, &res); err != nil {
  1659  				return 0, time.Minute * 10, err
  1660  			}
  1661  			if res.Estimates == nil {
  1662  				return 0, time.Minute * 10, errors.New("no estimates returned")
  1663  			}
  1664  			// Using 30 minutes estimate. There is also a 60, 120, and higher
  1665  			r, found := res.Estimates["30"]
  1666  			if !found {
  1667  				return 0, time.Minute * 10, errors.New("no 30-minute estimate returned")
  1668  			}
  1669  			return uint64(math.Round(r.SatsPerVB)), 0, nil
  1670  		},
  1671  	},
  1672  	{
  1673  		// https://api.blockcypher.com/v1/btc/main
  1674  		// Also have ltc, dash, doge
  1675  		Name:   "blockcypher.com",
  1676  		Rank:   1,
  1677  		Period: time.Minute * 3, // 100 requests/hr => 0.6 minutes
  1678  		F: func(ctx context.Context) (rate uint64, errDelay time.Duration, err error) {
  1679  			const uri = "https://api.blockcypher.com/v1/btc/main"
  1680  			var res struct {
  1681  				MediumPerKB uint64 `json:"medium_fee_per_kb"`
  1682  			}
  1683  			var code int
  1684  			if err := dexnet.Get(ctx, uri, &res, dexnet.WithStatusFunc(func(respCode int) {
  1685  				code = respCode
  1686  			})); err != nil {
  1687  				if code == http.StatusTooManyRequests { // 429 per docs
  1688  					// There's a X-Ratelimit-Remaining response header that
  1689  					// could potentially be used to caculate a proper delay here.
  1690  					return 0, time.Minute * 30, errors.New("exceeded request limit")
  1691  				}
  1692  				return 0, time.Minute * 10, err
  1693  			}
  1694  			return uint64(math.Round(float64(res.MediumPerKB) / 1e3)), 0, nil
  1695  		},
  1696  	},
  1697  	{ // undocumented
  1698  		Name:   "btc.com",
  1699  		Rank:   2,
  1700  		Period: time.Minute * 5,
  1701  		F: func(ctx context.Context) (rate uint64, errDelay time.Duration, err error) {
  1702  			const uri = "https://btc.com/service/fees/distribution"
  1703  			var res struct {
  1704  				RecommendedFees struct {
  1705  					OneBlockFee uint64 `json:"one_block_fee"`
  1706  				} `json:"fees_recommended"`
  1707  			}
  1708  			if err := dexnet.Get(ctx, uri, &res); err != nil {
  1709  				return 0, time.Minute * 10, err
  1710  			}
  1711  			return res.RecommendedFees.OneBlockFee, 0, nil
  1712  		},
  1713  	},
  1714  	{ // undocumented. source is somehow related to blockchain.com
  1715  		Name:   "blockchain.info",
  1716  		Rank:   2,
  1717  		Period: time.Minute * 3, // Rate limit might be 1 per 10 seconds.
  1718  		F: func(ctx context.Context) (rate uint64, errDelay time.Duration, err error) {
  1719  			const uri = "https://api.blockchain.info/mempool/fees"
  1720  			var res struct {
  1721  				Regular  uint64 `json:"regular"` // Might be a little low
  1722  				Priority uint64 `json:"priority"`
  1723  			}
  1724  			if err := dexnet.Get(ctx, uri, &res); err != nil {
  1725  				return 0, time.Minute * 10, err
  1726  			}
  1727  			return res.Priority, 0, nil
  1728  		},
  1729  	},
  1730  	{
  1731  		// undocumented. Probably just estimatesmartfee underneath
  1732  		Name:   "bitcoinfees.net",
  1733  		Rank:   2,
  1734  		Period: time.Minute * 3,
  1735  		F: func(ctx context.Context) (rate uint64, errDelay time.Duration, err error) {
  1736  			const uri = "https://bitcoinfees.net/api.json"
  1737  			var res struct {
  1738  				FeePerKBByBlockTarget map[string]uint64 `json:"fee_by_block_target"`
  1739  			}
  1740  			if err := dexnet.Get(ctx, uri, &res); err != nil {
  1741  				return 0, time.Minute * 10, err
  1742  			}
  1743  			if res.FeePerKBByBlockTarget == nil {
  1744  				return 0, time.Minute * 10, errors.New("no estimates returned")
  1745  			}
  1746  			// Using 30 minutes estimate. There is also a 60, 120, and higher
  1747  			r, found := res.FeePerKBByBlockTarget["1"]
  1748  			if !found {
  1749  				return 0, time.Minute * 10, errors.New("no 1-block estimate returned")
  1750  			}
  1751  			return uint64(math.Round(float64(r) / 1e3)), 0, nil
  1752  		},
  1753  	},
  1754  	{
  1755  		// https://blockchair.com/api/docs#link_M0
  1756  		Name:   "blockchair.com",
  1757  		Rank:   3,               // blockchair sometimes returns zero. Use only as a last resort.
  1758  		Period: time.Minute * 3, // 1440 per day => 1 request / minute
  1759  		F: func(ctx context.Context) (rate uint64, errDelay time.Duration, err error) {
  1760  			const uri = "https://api.blockchair.com/bitcoin/stats"
  1761  			var res struct {
  1762  				Data struct {
  1763  					SatsPerByte uint64 `json:"suggested_transaction_fee_per_byte_sat"`
  1764  				} `json:"data"`
  1765  			}
  1766  			var code int
  1767  			if err := dexnet.Get(ctx, uri, &res, dexnet.WithStatusFunc(func(respCode int) {
  1768  				code = respCode
  1769  			})); err != nil {
  1770  				switch code {
  1771  				case http.StatusTooManyRequests, http.StatusPaymentRequired:
  1772  					return 0, time.Minute * 30, errors.New("exceeded request limit")
  1773  				case http.StatusServiceUnavailable, 430, 434:
  1774  					return 0, time.Hour * 24, errors.New("banned from api")
  1775  				}
  1776  				return 0, time.Minute * 10, err
  1777  			}
  1778  			return res.Data.SatsPerByte, 0, nil
  1779  		},
  1780  	},
  1781  }
  1782  
  1783  func tatumFeeFetcher(apiKey string) *txfee.SourceConfig {
  1784  	return &txfee.SourceConfig{
  1785  		Name:   "tatum",
  1786  		Rank:   1,
  1787  		Period: time.Minute * 1, // 1M credit / mo => 3 req / sec
  1788  		F: func(ctx context.Context) (rate uint64, errDelay time.Duration, err error) {
  1789  			const uri = "https://api.tatum.io/v3/blockchain/fee/BTC"
  1790  			var res struct {
  1791  				Fast   float64 `json:"fast"` // Might be a little high
  1792  				Medium float64 `json:"medium"`
  1793  			}
  1794  			var code int
  1795  			withCode := dexnet.WithStatusFunc(func(respCode int) {
  1796  				code = respCode
  1797  			})
  1798  			withApiKey := dexnet.WithRequestHeader("x-api-key", apiKey)
  1799  			if err := dexnet.Get(ctx, uri, &res, withCode, withApiKey); err != nil {
  1800  				if code == http.StatusForbidden {
  1801  					return 0, time.Minute * 30, errors.New("exceeded request limit")
  1802  				}
  1803  				return 0, time.Minute * 10, err
  1804  			}
  1805  			return uint64(math.Round(res.Medium)), 0, nil
  1806  		},
  1807  	}
  1808  }
  1809  
  1810  func blockDaemonFeeFetcher(apiKey string) *txfee.SourceConfig {
  1811  	// https://docs.blockdaemon.com/reference/getfeeestimate
  1812  	return &txfee.SourceConfig{
  1813  		Name:   "blockdaemon",
  1814  		Rank:   1,
  1815  		Period: time.Minute * 2, // 25 reqs/second, 3M compute units, request is 50 compute units => 1 req / 43 secs
  1816  		F: func(ctx context.Context) (rate uint64, errDelay time.Duration, err error) {
  1817  			const uri = "https://svc.blockdaemon.com/universal/v1/bitcoin/mainnet/tx/estimate_fee"
  1818  			var res struct {
  1819  				Fees struct {
  1820  					Fast   uint64 `json:"fast"` // a little high
  1821  					Medium uint64 `json:"medium"`
  1822  				} `json:"estimated_fees"`
  1823  			}
  1824  			var code int
  1825  			withCode := dexnet.WithStatusFunc(func(respCode int) {
  1826  				code = respCode
  1827  			})
  1828  			withApiKey := dexnet.WithRequestHeader("X-API-Key", apiKey)
  1829  			if err := dexnet.Get(ctx, uri, &res, withCode, withApiKey); err != nil {
  1830  				if code == http.StatusTooManyRequests {
  1831  					return 0, time.Minute * 30, errors.New("exceeded request limit")
  1832  				}
  1833  				return 0, time.Minute * 10, err
  1834  			}
  1835  			return res.Fees.Medium, 0, nil
  1836  		},
  1837  	}
  1838  }