decred.org/dcrdex@v1.0.3/client/asset/btc/rpcclient.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  	"encoding/hex"
    10  	"encoding/json"
    11  	"errors"
    12  	"fmt"
    13  	"strings"
    14  	"sync"
    15  	"sync/atomic"
    16  	"time"
    17  
    18  	"decred.org/dcrdex/client/asset"
    19  	"decred.org/dcrdex/dex"
    20  	"decred.org/dcrdex/dex/config"
    21  	dexbtc "decred.org/dcrdex/dex/networks/btc"
    22  	"github.com/btcsuite/btcd/btcec/v2"
    23  	"github.com/btcsuite/btcd/btcjson"
    24  	"github.com/btcsuite/btcd/btcutil"
    25  	"github.com/btcsuite/btcd/chaincfg"
    26  	"github.com/btcsuite/btcd/chaincfg/chainhash"
    27  	"github.com/btcsuite/btcd/wire"
    28  	"github.com/decred/dcrd/dcrjson/v4" // for dcrjson.RPCError returns from rpcclient
    29  )
    30  
    31  const (
    32  	methodGetBalances        = "getbalances"
    33  	methodGetBalance         = "getbalance"
    34  	methodListUnspent        = "listunspent"
    35  	methodLockUnspent        = "lockunspent"
    36  	methodListLockUnspent    = "listlockunspent"
    37  	methodChangeAddress      = "getrawchangeaddress"
    38  	methodNewAddress         = "getnewaddress"
    39  	methodSignTx             = "signrawtransactionwithwallet"
    40  	methodSignTxLegacy       = "signrawtransaction"
    41  	methodUnlock             = "walletpassphrase"
    42  	methodLock               = "walletlock"
    43  	methodPrivKeyForAddress  = "dumpprivkey"
    44  	methodGetTransaction     = "gettransaction"
    45  	methodSendToAddress      = "sendtoaddress"
    46  	methodSetTxFee           = "settxfee"
    47  	methodGetWalletInfo      = "getwalletinfo"
    48  	methodGetAddressInfo     = "getaddressinfo"
    49  	methodListDescriptors    = "listdescriptors"
    50  	methodValidateAddress    = "validateaddress"
    51  	methodEstimateSmartFee   = "estimatesmartfee"
    52  	methodSendRawTransaction = "sendrawtransaction"
    53  	methodGetTxOut           = "gettxout"
    54  	methodGetBlock           = "getblock"
    55  	methodGetBlockHash       = "getblockhash"
    56  	methodGetBestBlockHash   = "getbestblockhash"
    57  	methodGetRawMempool      = "getrawmempool"
    58  	methodGetRawTransaction  = "getrawtransaction"
    59  	methodGetBlockHeader     = "getblockheader"
    60  	methodGetNetworkInfo     = "getnetworkinfo"
    61  	methodGetBlockchainInfo  = "getblockchaininfo"
    62  	methodFundRawTransaction = "fundrawtransaction"
    63  	methodListSinceBlock     = "listsinceblock"
    64  )
    65  
    66  // IsTxNotFoundErr will return true if the error indicates that the requested
    67  // transaction is not known. The error must be dcrjson.RPCError with a numeric
    68  // code equal to btcjson.ErrRPCNoTxInfo. WARNING: This is specific to errors
    69  // from an RPC to a bitcoind (or clone) using dcrd's rpcclient!
    70  func IsTxNotFoundErr(err error) bool {
    71  	// We are using dcrd's client with Bitcoin Core, so errors will be of type
    72  	// dcrjson.RPCError, but numeric codes should come from btcjson.
    73  	const errRPCNoTxInfo = int(btcjson.ErrRPCNoTxInfo)
    74  	var rpcErr *dcrjson.RPCError
    75  	return errors.As(err, &rpcErr) && int(rpcErr.Code) == errRPCNoTxInfo
    76  }
    77  
    78  // isMethodNotFoundErr will return true if the error indicates that the RPC
    79  // method was not found by the RPC server. The error must be dcrjson.RPCError
    80  // with a numeric code equal to btcjson.ErrRPCMethodNotFound.Code or a message
    81  // containing "method not found".
    82  func isMethodNotFoundErr(err error) bool {
    83  	var errRPCMethodNotFound = int(btcjson.ErrRPCMethodNotFound.Code)
    84  	var rpcErr *dcrjson.RPCError
    85  	return errors.As(err, &rpcErr) &&
    86  		(int(rpcErr.Code) == errRPCMethodNotFound ||
    87  			strings.Contains(strings.ToLower(rpcErr.Message), "method not found"))
    88  }
    89  
    90  // RawRequester defines decred's rpcclient RawRequest func where all RPC
    91  // requests sent through. For testing, it can be satisfied by a stub.
    92  type RawRequester interface {
    93  	RawRequest(context.Context, string, []json.RawMessage) (json.RawMessage, error)
    94  }
    95  
    96  // anylist is a list of RPC parameters to be converted to []json.RawMessage and
    97  // sent via RawRequest.
    98  type anylist []any
    99  
   100  type rpcCore struct {
   101  	rpcConfig         *RPCConfig
   102  	cloneParams       *BTCCloneCFG
   103  	requesterV        atomic.Value // RawRequester
   104  	segwit            bool
   105  	decodeAddr        dexbtc.AddressDecoder
   106  	stringAddr        dexbtc.AddressStringer
   107  	legacyRawSends    bool
   108  	minNetworkVersion uint64
   109  	log               dex.Logger
   110  	chainParams       *chaincfg.Params
   111  	omitAddressType   bool
   112  	legacySignTx      bool
   113  	booleanGetBlock   bool
   114  	unlockSpends      bool
   115  
   116  	deserializeTx      func([]byte) (*wire.MsgTx, error)
   117  	serializeTx        func(*wire.MsgTx) ([]byte, error)
   118  	hashTx             func(*wire.MsgTx) *chainhash.Hash
   119  	numericGetRawTxRPC bool
   120  	manualMedianTime   bool
   121  	addrFunc           func() (btcutil.Address, error)
   122  
   123  	deserializeBlock         func([]byte) (*wire.MsgBlock, error)
   124  	legacyValidateAddressRPC bool
   125  	omitRPCOptionsArg        bool
   126  	privKeyFunc              func(addr string) (*btcec.PrivateKey, error)
   127  }
   128  
   129  func (c *rpcCore) requester() RawRequester {
   130  	return c.requesterV.Load().(RawRequester)
   131  }
   132  
   133  // rpcClient is a bitcoind JSON RPC client that uses rpcclient.Client's
   134  // RawRequest for wallet-related calls.
   135  type rpcClient struct {
   136  	*rpcCore
   137  	ctx         context.Context
   138  	descriptors bool // set on connect like ctx
   139  }
   140  
   141  var _ Wallet = (*rpcClient)(nil)
   142  
   143  // newRPCClient is the constructor for a rpcClient.
   144  func newRPCClient(cfg *rpcCore) *rpcClient {
   145  	return &rpcClient{rpcCore: cfg}
   146  }
   147  
   148  // ChainOK is for screening the chain field of the getblockchaininfo result.
   149  func ChainOK(net dex.Network, str string) bool {
   150  	var chainStr string
   151  	switch net {
   152  	case dex.Mainnet:
   153  		chainStr = "main"
   154  	case dex.Testnet:
   155  		chainStr = "test"
   156  	case dex.Regtest:
   157  		chainStr = "reg"
   158  	}
   159  	return strings.Contains(str, chainStr)
   160  }
   161  
   162  func (wc *rpcClient) connect(ctx context.Context, _ *sync.WaitGroup) error {
   163  	wc.ctx = ctx
   164  	// Check the version. Do it here, so we can also diagnose a bad connection.
   165  	netVer, codeVer, err := wc.getVersion()
   166  	if err != nil {
   167  		return fmt.Errorf("error getting version: %w", err)
   168  	}
   169  	if netVer < wc.minNetworkVersion {
   170  		return fmt.Errorf("reported node version %d is less than minimum %d", netVer, wc.minNetworkVersion)
   171  	}
   172  	// TODO: codeVer is actually asset-dependent. Zcash, for example, is at
   173  	// 170100. So we're just lucking out here, really.
   174  	if codeVer < minProtocolVersion {
   175  		return fmt.Errorf("node software out of date. version %d is less than minimum %d", codeVer, minProtocolVersion)
   176  	}
   177  	chainInfo, err := wc.getBlockchainInfo()
   178  	if err != nil {
   179  		return fmt.Errorf("getblockchaininfo error: %w", err)
   180  	}
   181  	if !ChainOK(wc.cloneParams.Network, chainInfo.Chain) {
   182  		return errors.New("wrong net")
   183  	}
   184  	wiRes, err := wc.GetWalletInfo()
   185  	if err != nil {
   186  		return fmt.Errorf("getwalletinfo failure: %w", err)
   187  	}
   188  	wc.descriptors = wiRes.Descriptors
   189  	if wc.descriptors {
   190  		if netVer < minDescriptorVersion {
   191  			return fmt.Errorf("reported node version %d is less than minimum %d"+
   192  				" for descriptor wallets", netVer, minDescriptorVersion)
   193  		}
   194  		wc.log.Debug("Using a descriptor wallet.")
   195  	}
   196  	return nil
   197  }
   198  
   199  // reconfigure attempts to reconfigure the rpcClient for the new settings. Live
   200  // reconfiguration is only attempted if the new wallet type is walletTypeRPC. If
   201  // the special_activelyUsed flag is set, reconfigure will fail if we can't
   202  // validate ownership of the current deposit address.
   203  func (wc *rpcClient) reconfigure(cfg *asset.WalletConfig, currentAddress string) (restartRequired bool, err error) {
   204  	if cfg.Type != wc.cloneParams.WalletCFG.Type {
   205  		restartRequired = true
   206  		return
   207  	}
   208  	if wc.ctx == nil || wc.ctx.Err() != nil {
   209  		return true, nil // not connected, ok to reconfigure, but restart required
   210  	}
   211  
   212  	parsedCfg := new(RPCWalletConfig)
   213  	if err = config.Unmapify(cfg.Settings, parsedCfg); err != nil {
   214  		return
   215  	}
   216  
   217  	// Check the RPC configuration.
   218  	newCfg := &parsedCfg.RPCConfig
   219  	if err = dexbtc.CheckRPCConfig(&newCfg.RPCConfig, wc.cloneParams.WalletInfo.Name,
   220  		wc.cloneParams.Network, wc.cloneParams.Ports); err != nil {
   221  		return
   222  	}
   223  
   224  	// If the RPC configuration has changed, try to update the client.
   225  	oldCfg := wc.rpcConfig
   226  	if *newCfg != *oldCfg {
   227  		cl, err := newRPCConnection(parsedCfg, wc.cloneParams.SingularWallet)
   228  		if err != nil {
   229  			return false, fmt.Errorf("error creating RPC client with new credentials: %v", err)
   230  		}
   231  
   232  		// Require restart if the wallet does not own or understand our current
   233  		// address. We can't use wc.ownsAddress because the rpcClient still has
   234  		// the old requester stored, so we'll call directly.
   235  		method := methodGetAddressInfo
   236  		if wc.legacyValidateAddressRPC {
   237  			method = methodValidateAddress
   238  		}
   239  		ai := new(GetAddressInfoResult)
   240  		if err := Call(wc.ctx, cl, method, anylist{currentAddress}, ai); err != nil {
   241  			return false, fmt.Errorf("error getting address info with new RPC client: %w", err)
   242  		} else if !ai.IsMine {
   243  			// If the wallet is in active use, check the supplied address.
   244  			if parsedCfg.ActivelyUsed { // deny reconfigure
   245  				return false, errors.New("cannot reconfigure to a new RPC wallet during active use")
   246  			}
   247  			// Allow reconfigure, but restart to trigger dep address refresh and
   248  			// full connect checks, which include the getblockchaininfo check.
   249  			return true, nil
   250  		} // else same wallet, skip full reconnect
   251  
   252  		chainInfo := new(GetBlockchainInfoResult)
   253  		if err := Call(wc.ctx, cl, methodGetBlockchainInfo, nil, chainInfo); err != nil {
   254  			return false, fmt.Errorf("%s: %w", methodGetBlockchainInfo, err)
   255  		}
   256  		if !ChainOK(wc.cloneParams.Network, chainInfo.Chain) {
   257  			return false, errors.New("wrong net")
   258  		}
   259  
   260  		wc.requesterV.Store(cl)
   261  		wc.rpcConfig = newCfg
   262  
   263  		// No restart required
   264  	}
   265  	return
   266  }
   267  
   268  // RawRequest passes the request to the wallet's RawRequester.
   269  func (wc *rpcClient) RawRequest(ctx context.Context, method string, params []json.RawMessage) (json.RawMessage, error) {
   270  	return wc.requester().RawRequest(ctx, method, params)
   271  }
   272  
   273  // estimateSmartFee requests the server to estimate a fee level based on the
   274  // given parameters.
   275  func estimateSmartFee(ctx context.Context, rr RawRequester, confTarget uint64, mode *btcjson.EstimateSmartFeeMode) (*btcjson.EstimateSmartFeeResult, error) {
   276  	res := new(btcjson.EstimateSmartFeeResult)
   277  	return res, Call(ctx, rr, methodEstimateSmartFee, anylist{confTarget, mode}, res)
   278  }
   279  
   280  // SendRawTransactionLegacy broadcasts the transaction with an additional legacy
   281  // boolean `allowhighfees` argument set to false.
   282  func (wc *rpcClient) SendRawTransactionLegacy(tx *wire.MsgTx) (*chainhash.Hash, error) {
   283  	txBytes, err := wc.serializeTx(tx)
   284  	if err != nil {
   285  		return nil, err
   286  	}
   287  	return wc.callHashGetter(methodSendRawTransaction, anylist{
   288  		hex.EncodeToString(txBytes), false})
   289  }
   290  
   291  // SendRawTransaction broadcasts the transaction.
   292  func (wc *rpcClient) SendRawTransaction(tx *wire.MsgTx) (*chainhash.Hash, error) {
   293  	b, err := wc.serializeTx(tx)
   294  	if err != nil {
   295  		return nil, err
   296  	}
   297  	var txid string
   298  	err = wc.call(methodSendRawTransaction, anylist{hex.EncodeToString(b)}, &txid)
   299  	if err != nil {
   300  		return nil, err
   301  	}
   302  	return chainhash.NewHashFromStr(txid)
   303  }
   304  
   305  // sendRawTransaction sends the MsgTx.
   306  func (wc *rpcClient) sendRawTransaction(tx *wire.MsgTx) (txHash *chainhash.Hash, err error) {
   307  	if wc.legacyRawSends {
   308  		txHash, err = wc.SendRawTransactionLegacy(tx)
   309  	} else {
   310  		txHash, err = wc.SendRawTransaction(tx)
   311  	}
   312  	if err != nil {
   313  		return nil, err
   314  	}
   315  	if !wc.unlockSpends {
   316  		return txHash, nil
   317  	}
   318  
   319  	// TODO: lockUnspent should really just take a []*OutPoint, since it doesn't
   320  	// need the value.
   321  	ops := make([]*Output, 0, len(tx.TxIn))
   322  	for _, txIn := range tx.TxIn {
   323  		prevOut := &txIn.PreviousOutPoint
   324  		ops = append(ops, &Output{Pt: NewOutPoint(&prevOut.Hash, prevOut.Index)})
   325  	}
   326  	if err := wc.lockUnspent(true, ops); err != nil {
   327  		wc.log.Warnf("error unlocking spent outputs: %v", err)
   328  	}
   329  	return txHash, nil
   330  }
   331  
   332  // getTxOut returns the transaction output info if it's unspent and
   333  // nil, otherwise.
   334  func (wc *rpcClient) getTxOut(txHash *chainhash.Hash, index uint32, _ []byte, _ time.Time) (*wire.TxOut, uint32, error) {
   335  	txOut, err := wc.getTxOutput(txHash, index)
   336  	if err != nil {
   337  		return nil, 0, fmt.Errorf("getTxOut error: %w", err)
   338  	}
   339  	if txOut == nil {
   340  		return nil, 0, nil
   341  	}
   342  	outputScript, _ := hex.DecodeString(txOut.ScriptPubKey.Hex)
   343  	// Check equivalence of pkScript and outputScript?
   344  	return wire.NewTxOut(int64(toSatoshi(txOut.Value)), outputScript), uint32(txOut.Confirmations), nil
   345  }
   346  
   347  // getTxOut returns the transaction output info if it's unspent and
   348  // nil, otherwise.
   349  func (wc *rpcClient) getTxOutput(txHash *chainhash.Hash, index uint32) (*btcjson.GetTxOutResult, error) {
   350  	// Note that we pass to call pointer to a pointer (&res) so that
   351  	// json.Unmarshal can nil the pointer if the method returns the JSON null.
   352  	var res *btcjson.GetTxOutResult
   353  	return res, wc.call(methodGetTxOut, anylist{txHash.String(), index, true},
   354  		&res)
   355  }
   356  
   357  func (wc *rpcClient) callHashGetter(method string, args anylist) (*chainhash.Hash, error) {
   358  	var txid string
   359  	err := wc.call(method, args, &txid)
   360  	if err != nil {
   361  		return nil, err
   362  	}
   363  	return chainhash.NewHashFromStr(txid)
   364  }
   365  
   366  // getBlock fetches the MsgBlock.
   367  func (wc *rpcClient) getBlock(h chainhash.Hash) (*wire.MsgBlock, error) {
   368  	var blkB dex.Bytes
   369  	args := anylist{h.String()}
   370  	if wc.booleanGetBlock {
   371  		args = append(args, false)
   372  	} else {
   373  		args = append(args, 0)
   374  	}
   375  	err := wc.call(methodGetBlock, args, &blkB)
   376  	if err != nil {
   377  		return nil, err
   378  	}
   379  
   380  	return wc.deserializeBlock(blkB)
   381  }
   382  
   383  // getBlockHash returns the hash of the block in the best block chain at the
   384  // given height.
   385  func (wc *rpcClient) getBlockHash(blockHeight int64) (*chainhash.Hash, error) {
   386  	return wc.callHashGetter(methodGetBlockHash, anylist{blockHeight})
   387  }
   388  
   389  // getBestBlockHash returns the hash of the best block in the longest block
   390  // chain (aka mainchain).
   391  func (wc *rpcClient) getBestBlockHash() (*chainhash.Hash, error) {
   392  	return wc.callHashGetter(methodGetBestBlockHash, nil)
   393  }
   394  
   395  // getBestBlockHeight returns the height of the top mainchain block.
   396  func (wc *rpcClient) getBestBlockHeader() (*BlockHeader, error) {
   397  	tipHash, err := wc.getBestBlockHash()
   398  	if err != nil {
   399  		return nil, err
   400  	}
   401  	hdr, _, err := wc.getBlockHeader(tipHash)
   402  	return hdr, err
   403  }
   404  
   405  // getBestBlockHeight returns the height of the top mainchain block.
   406  func (wc *rpcClient) getBestBlockHeight() (int32, error) {
   407  	header, err := wc.getBestBlockHeader()
   408  	if err != nil {
   409  		return -1, err
   410  	}
   411  	return int32(header.Height), nil
   412  }
   413  
   414  // getChainStamp satisfies chainStamper for manual median time calculations.
   415  func (wc *rpcClient) getChainStamp(blockHash *chainhash.Hash) (stamp time.Time, prevHash *chainhash.Hash, err error) {
   416  	hdr, _, err := wc.getBlockHeader(blockHash)
   417  	if err != nil {
   418  		return
   419  	}
   420  	prevHash, err = chainhash.NewHashFromStr(hdr.PreviousBlockHash)
   421  	if err != nil {
   422  		return
   423  	}
   424  	return time.Unix(hdr.Time, 0).UTC(), prevHash, nil
   425  }
   426  
   427  // medianTime is the median time for the current best block.
   428  func (wc *rpcClient) medianTime() (stamp time.Time, err error) {
   429  	tipHash, err := wc.getBestBlockHash()
   430  	if err != nil {
   431  		return
   432  	}
   433  	if wc.manualMedianTime {
   434  		return CalcMedianTime(func(blockHash *chainhash.Hash) (stamp time.Time, prevHash *chainhash.Hash, err error) {
   435  			hdr, _, err := wc.getBlockHeader(blockHash)
   436  			if err != nil {
   437  				return
   438  			}
   439  			prevHash, err = chainhash.NewHashFromStr(hdr.PreviousBlockHash)
   440  			if err != nil {
   441  				return
   442  			}
   443  			return time.Unix(hdr.Time, 0), prevHash, nil
   444  		}, tipHash)
   445  	}
   446  	hdr, err := wc.getRPCBlockHeader(tipHash)
   447  	if err != nil {
   448  		return
   449  	}
   450  	return time.Unix(hdr.MedianTime, 0).UTC(), nil
   451  }
   452  
   453  // GetRawMempool returns the hashes of all transactions in the memory pool.
   454  func (wc *rpcClient) GetRawMempool() ([]*chainhash.Hash, error) {
   455  	var mempool []string
   456  	err := wc.call(methodGetRawMempool, nil, &mempool)
   457  	if err != nil {
   458  		return nil, err
   459  	}
   460  
   461  	// Convert received hex hashes to chainhash.Hash
   462  	hashes := make([]*chainhash.Hash, 0, len(mempool))
   463  	for _, h := range mempool {
   464  		hash, err := chainhash.NewHashFromStr(h)
   465  		if err != nil {
   466  			return nil, err
   467  		}
   468  		hashes = append(hashes, hash)
   469  	}
   470  	return hashes, nil
   471  }
   472  
   473  // GetRawTransaction retrieves the MsgTx.
   474  func (wc *rpcClient) GetRawTransaction(txHash *chainhash.Hash) (*wire.MsgTx, error) {
   475  	var txB dex.Bytes
   476  	args := anylist{txHash.String(), false}
   477  	if wc.numericGetRawTxRPC {
   478  		args[1] = 0
   479  	}
   480  	err := wc.call(methodGetRawTransaction, args, &txB)
   481  	if err != nil {
   482  		return nil, err
   483  	}
   484  
   485  	return wc.deserializeTx(txB)
   486  }
   487  
   488  // balances retrieves a wallet's balance details.
   489  func (wc *rpcClient) balances() (*GetBalancesResult, error) {
   490  	var balances GetBalancesResult
   491  	return &balances, wc.call(methodGetBalances, nil, &balances)
   492  }
   493  
   494  // listUnspent retrieves a list of the wallet's UTXOs.
   495  func (wc *rpcClient) listUnspent() ([]*ListUnspentResult, error) {
   496  	unspents := make([]*ListUnspentResult, 0)
   497  	// TODO: listunspent 0 9999999 []string{}, include_unsafe=false
   498  	return unspents, wc.call(methodListUnspent, anylist{uint8(0)}, &unspents)
   499  }
   500  
   501  // lockUnspent locks and unlocks outputs for spending. An output that is part of
   502  // an order, but not yet spent, should be locked until spent or until the order
   503  // is canceled or fails.
   504  func (wc *rpcClient) lockUnspent(unlock bool, ops []*Output) error {
   505  	var rpcops []*RPCOutpoint // To clear all, this must be nil->null, not empty slice.
   506  	for _, op := range ops {
   507  		rpcops = append(rpcops, &RPCOutpoint{
   508  			TxID: op.txHash().String(),
   509  			Vout: op.vout(),
   510  		})
   511  	}
   512  	var success bool
   513  	err := wc.call(methodLockUnspent, anylist{unlock, rpcops}, &success)
   514  	if err == nil && !success {
   515  		return fmt.Errorf("lockunspent unsuccessful")
   516  	}
   517  	return err
   518  }
   519  
   520  // listLockUnspent returns a slice of outpoints for all unspent outputs marked
   521  // as locked by a wallet.
   522  func (wc *rpcClient) listLockUnspent() ([]*RPCOutpoint, error) {
   523  	var unspents []*RPCOutpoint
   524  	err := wc.call(methodListLockUnspent, nil, &unspents)
   525  	if err != nil {
   526  		return nil, err
   527  	}
   528  	if !wc.unlockSpends {
   529  		return unspents, nil
   530  	}
   531  	// This is quirky wallet software that does not unlock spent outputs, so
   532  	// we'll verify that each output is actually unspent.
   533  	var i int // for in-place filter
   534  	for _, utxo := range unspents {
   535  		var gtxo *btcjson.GetTxOutResult
   536  		err = wc.call(methodGetTxOut, anylist{utxo.TxID, utxo.Vout, true}, &gtxo)
   537  		if err != nil {
   538  			wc.log.Warnf("gettxout(%v:%d): %v", utxo.TxID, utxo.Vout, err)
   539  			continue
   540  		}
   541  		if gtxo != nil {
   542  			unspents[i] = utxo // unspent, keep it
   543  			i++
   544  			continue
   545  		}
   546  		// actually spent, unlock
   547  		var success bool
   548  		op := []*RPCOutpoint{{
   549  			TxID: utxo.TxID,
   550  			Vout: utxo.Vout,
   551  		}}
   552  		err = wc.call(methodLockUnspent, anylist{true, op}, &success)
   553  		if err != nil || !success {
   554  			wc.log.Warnf("lockunspent(unlocking %v:%d): success = %v, err = %v",
   555  				utxo.TxID, utxo.Vout, success, err)
   556  			continue
   557  		}
   558  		wc.log.Debugf("Unlocked spent outpoint %v:%d", utxo.TxID, utxo.Vout)
   559  	}
   560  	unspents = unspents[:i]
   561  	return unspents, nil
   562  }
   563  
   564  // changeAddress gets a new internal address from the wallet. The address will
   565  // be bech32-encoded (P2WPKH).
   566  func (wc *rpcClient) changeAddress() (btcutil.Address, error) {
   567  	var addrStr string
   568  	var err error
   569  	switch {
   570  	case wc.omitAddressType:
   571  		err = wc.call(methodChangeAddress, nil, &addrStr)
   572  	case wc.segwit:
   573  		err = wc.call(methodChangeAddress, anylist{"bech32"}, &addrStr)
   574  	default:
   575  		err = wc.call(methodChangeAddress, anylist{"legacy"}, &addrStr)
   576  	}
   577  	if err != nil {
   578  		return nil, err
   579  	}
   580  	return wc.decodeAddr(addrStr, wc.chainParams)
   581  }
   582  
   583  func (wc *rpcClient) externalAddress() (btcutil.Address, error) {
   584  	if wc.segwit {
   585  		return wc.address("bech32")
   586  	}
   587  	return wc.address("legacy")
   588  }
   589  
   590  // address is used internally for fetching addresses of various types from the
   591  // wallet.
   592  func (wc *rpcClient) address(aType string) (btcutil.Address, error) {
   593  	var addrStr string
   594  	args := anylist{""}
   595  	if !wc.omitAddressType {
   596  		args = append(args, aType)
   597  	}
   598  	err := wc.call(methodNewAddress, args, &addrStr)
   599  	if err != nil {
   600  		return nil, err
   601  	}
   602  	return wc.decodeAddr(addrStr, wc.chainParams) // we should consider returning a string
   603  }
   604  
   605  // signTx attempts to have the wallet sign the transaction inputs.
   606  func (wc *rpcClient) signTx(inTx *wire.MsgTx) (*wire.MsgTx, error) {
   607  	txBytes, err := wc.serializeTx(inTx)
   608  	if err != nil {
   609  		return nil, fmt.Errorf("tx serialization error: %w", err)
   610  	}
   611  	res := new(SignTxResult)
   612  	method := methodSignTx
   613  	if wc.legacySignTx {
   614  		method = methodSignTxLegacy
   615  	}
   616  
   617  	err = wc.call(method, anylist{hex.EncodeToString(txBytes)}, res)
   618  	if err != nil {
   619  		return nil, fmt.Errorf("tx signing error: %w", err)
   620  	}
   621  	if !res.Complete {
   622  		sep := ""
   623  		errMsg := ""
   624  		for _, e := range res.Errors {
   625  			errMsg += e.Error + sep
   626  			sep = ";"
   627  		}
   628  		return nil, fmt.Errorf("signing incomplete. %d signing errors encountered: %s", len(res.Errors), errMsg)
   629  	}
   630  	outTx, err := wc.deserializeTx(res.Hex)
   631  	if err != nil {
   632  		return nil, fmt.Errorf("error deserializing transaction response: %w", err)
   633  	}
   634  	return outTx, nil
   635  }
   636  
   637  func (wc *rpcClient) listDescriptors(private bool) (*listDescriptorsResult, error) {
   638  	descriptors := new(listDescriptorsResult)
   639  	return descriptors, wc.call(methodListDescriptors, anylist{private}, descriptors)
   640  }
   641  
   642  func (wc *rpcClient) listTransactionsSinceBlock(blockHeight int32) ([]*ListTransactionsResult, error) {
   643  	blockHash, err := wc.getBlockHash(int64(blockHeight))
   644  	if err != nil {
   645  		return nil, fmt.Errorf("getBlockHash error: %w", err)
   646  	}
   647  	result := new(struct {
   648  		Transactions []btcjson.ListTransactionsResult `json:"transactions"`
   649  	})
   650  	err = wc.call(methodListSinceBlock, anylist{blockHash.String()}, result)
   651  	if err != nil {
   652  		return nil, fmt.Errorf("listtransactions error: %w", err)
   653  	}
   654  
   655  	txs := make([]*ListTransactionsResult, 0, len(result.Transactions))
   656  	for _, tx := range result.Transactions {
   657  		var blockHeight uint32
   658  		if tx.BlockHeight != nil {
   659  			blockHeight = uint32(*tx.BlockHeight)
   660  		}
   661  		txs = append(txs, &ListTransactionsResult{
   662  			TxID:        tx.TxID,
   663  			BlockHeight: blockHeight,
   664  			BlockTime:   uint64(tx.BlockTime),
   665  			Fee:         tx.Fee,
   666  			Send:        tx.Category == "send",
   667  		})
   668  	}
   669  
   670  	return txs, nil
   671  }
   672  
   673  // privKeyForAddress retrieves the private key associated with the specified
   674  // address.
   675  func (wc *rpcClient) privKeyForAddress(addr string) (*btcec.PrivateKey, error) {
   676  	// Use a specialized client's privKey function
   677  	if wc.privKeyFunc != nil {
   678  		return wc.privKeyFunc(addr)
   679  	}
   680  	// Descriptor wallets do not have dumpprivkey.
   681  	if !wc.descriptors {
   682  		var keyHex string
   683  		err := wc.call(methodPrivKeyForAddress, anylist{addr}, &keyHex)
   684  		if err != nil {
   685  			return nil, err
   686  		}
   687  		wif, err := btcutil.DecodeWIF(keyHex)
   688  		if err != nil {
   689  			return nil, err
   690  		}
   691  		return wif.PrivKey, nil
   692  	}
   693  
   694  	// With descriptor wallets, we have to get the address' descriptor from
   695  	// getaddressinfo, parse out its key origin (fingerprint of the master
   696  	// private key followed by derivation path to the address) and the pubkey of
   697  	// the address itself. Then we get the private key using listdescriptors
   698  	// private=true, which returns a set of master private keys and derivation
   699  	// paths, one of which corresponds to the fingerprint and path from
   700  	// getaddressinfo. When the parent master private key is identified, we
   701  	// derive the private key for the address.
   702  	ai := new(GetAddressInfoResult)
   703  	if err := wc.call(methodGetAddressInfo, anylist{addr}, ai); err != nil {
   704  		return nil, fmt.Errorf("getaddressinfo RPC failure: %w", err)
   705  	}
   706  	wc.log.Tracef("Address %v descriptor: %v", addr, ai.Descriptor)
   707  	desc, err := dexbtc.ParseDescriptor(ai.Descriptor)
   708  	if err != nil {
   709  		return nil, fmt.Errorf("failed to parse descriptor %q: %w", ai.Descriptor, err)
   710  	}
   711  	if desc.KeyOrigin == nil {
   712  		return nil, errors.New("address descriptor has no key origin")
   713  	}
   714  	// For addresses from imported private keys that have no derivation path in
   715  	// the key origin, we inspect private keys of type KeyWIFPriv. For addresses
   716  	// with a derivation path, we match KeyExtended private keys based on the
   717  	// master key fingerprint and derivation path.
   718  	fp, addrPath := desc.KeyOrigin.Fingerprint, desc.KeyOrigin.Steps
   719  	// Should match:
   720  	//   fp, path = ai.HDMasterFingerprint, ai.HDKeyPath
   721  	//   addrPath, _, err = dexbtc.ParsePath(path)
   722  	bareKey := len(addrPath) == 0
   723  
   724  	if desc.KeyFmt != dexbtc.KeyHexPub {
   725  		return nil, fmt.Errorf("not a hexadecimal pubkey: %v", desc.Key)
   726  	}
   727  	// The key was validated by ParseDescriptor, but check again.
   728  	addrPubKeyB, err := hex.DecodeString(desc.Key)
   729  	if err != nil {
   730  		return nil, fmt.Errorf("address pubkey not hexadecimal: %w", err)
   731  	}
   732  	addrPubKey, err := btcec.ParsePubKey(addrPubKeyB)
   733  	if err != nil {
   734  		return nil, fmt.Errorf("invalid pubkey for address: %w", err)
   735  	}
   736  	addrPubKeyC := addrPubKey.SerializeCompressed() // may or may not equal addrPubKeyB
   737  
   738  	// Get the private key descriptors.
   739  	masterDescs, err := wc.listDescriptors(true)
   740  	if err != nil {
   741  		return nil, fmt.Errorf("listdescriptors RPC failure: %w", err)
   742  	}
   743  
   744  	// We're going to decode a number of private keys that we need to zero.
   745  	var toClear []interface{ Zero() }
   746  	defer func() {
   747  		for _, k := range toClear {
   748  			k.Zero()
   749  		}
   750  	}() // surprisingly, much cleaner than making the loop body below into a function
   751  	deferZero := func(z interface{ Zero() }) { toClear = append(toClear, z) }
   752  
   753  masters:
   754  	for _, d := range masterDescs.Descriptors {
   755  		masterDesc, err := dexbtc.ParseDescriptor(d.Descriptor)
   756  		if err != nil {
   757  			wc.log.Errorf("Failed to parse descriptor %q: %v", d.Descriptor, err)
   758  			continue // unexpected, but check the others
   759  		}
   760  		if bareKey { // match KeyHexPub -> KeyWIFPriv
   761  			if masterDesc.KeyFmt != dexbtc.KeyWIFPriv {
   762  				continue
   763  			}
   764  			wif, err := btcutil.DecodeWIF(masterDesc.Key)
   765  			if err != nil {
   766  				wc.log.Errorf("Invalid WIF private key: %v", err)
   767  				continue // ParseDescriptor already validated it, so shouldn't happen
   768  			}
   769  			if !bytes.Equal(addrPubKeyC, wif.PrivKey.PubKey().SerializeCompressed()) {
   770  				continue // not the one
   771  			}
   772  			return wif.PrivKey, nil
   773  		}
   774  
   775  		// match KeyHexPub -> [fingerprint/path]KeyExtended
   776  		if masterDesc.KeyFmt != dexbtc.KeyExtended {
   777  			continue
   778  		}
   779  		// Break the key into its parts and compute the fingerprint of the
   780  		// master private key.
   781  		xPriv, fingerprint, pathStr, isRange, err := dexbtc.ParseKeyExtended(masterDesc.Key)
   782  		if err != nil {
   783  			wc.log.Debugf("Failed to parse descriptor extended key: %v", err)
   784  			continue
   785  		}
   786  		deferZero(xPriv)
   787  		if fingerprint != fp {
   788  			continue
   789  		}
   790  		if !xPriv.IsPrivate() { // imported xpub with no private key?
   791  			wc.log.Debugf("Not an extended private key. Fingerprint: %v", fingerprint)
   792  			continue
   793  		}
   794  		// NOTE: After finding the xprv with the matching fingerprint, we could
   795  		// skip to checking the private key for a match instead of first
   796  		// matching the path. Let's just check the path too since fingerprint
   797  		// collision are possible, and the different address types are allowed
   798  		// to use descriptors with different fingerprints.
   799  		if !isRange {
   800  			continue // imported?
   801  		}
   802  		path, _, err := dexbtc.ParsePath(pathStr)
   803  		if err != nil {
   804  			wc.log.Debugf("Failed to parse descriptor extended key path %q: %v", pathStr, err)
   805  			continue
   806  		}
   807  		if len(addrPath) != len(path)+1 { // addrPath includes index of self
   808  			continue
   809  		}
   810  		for i := range path {
   811  			if addrPath[i] != path[i] {
   812  				continue masters // different path
   813  			}
   814  		}
   815  
   816  		// NOTE: We could conceivably cache the extended private key for this
   817  		// address range/branch, but it could be a security risk:
   818  		// childIdx := addrPath[len(addrPath)-1]
   819  		// branch, err := dexbtc.DeepChild(xPriv, path)
   820  		// child, err := branch.Derive(childIdx)
   821  		child, err := dexbtc.DeepChild(xPriv, addrPath)
   822  		if err != nil {
   823  			return nil, fmt.Errorf("address key derivation failed: %v", err) // any point in checking the rest?
   824  		}
   825  		deferZero(child)
   826  		privkey, err := child.ECPrivKey()
   827  		if err != nil { // only errors if the extended key is not private
   828  			return nil, err // hdkeychain.ErrNotPrivExtKey
   829  		}
   830  		// That's the private key, but do a final check that the pubkey matches
   831  		// the "pubkey" field of the getaddressinfo response.
   832  		pubkey := privkey.PubKey().SerializeCompressed()
   833  		if !bytes.Equal(pubkey, addrPubKeyC) {
   834  			wc.log.Warnf("Derived wrong pubkey for address %v from matching descriptor %v: %x != %x",
   835  				addr, d.Descriptor, pubkey, addrPubKey)
   836  			continue // theoretically could be a fingerprint collision (see KeyOrigin docs)
   837  		}
   838  		return privkey, nil
   839  	}
   840  
   841  	return nil, errors.New("no private key found for address")
   842  }
   843  
   844  // getWalletTransaction retrieves the JSON-RPC gettransaction result.
   845  func (wc *rpcClient) getWalletTransaction(txHash *chainhash.Hash) (*GetTransactionResult, error) {
   846  	tx := new(GetTransactionResult)
   847  	err := wc.call(methodGetTransaction, anylist{txHash.String()}, tx)
   848  	if err != nil {
   849  		if IsTxNotFoundErr(err) {
   850  			return nil, asset.CoinNotFoundError
   851  		}
   852  		return nil, err
   853  	}
   854  	return tx, nil
   855  }
   856  
   857  // walletUnlock unlocks the wallet.
   858  func (wc *rpcClient) walletUnlock(pw []byte) error {
   859  	// 100000000 comes from bitcoin-cli help walletpassphrase
   860  	return wc.call(methodUnlock, anylist{string(pw), 100000000}, nil)
   861  }
   862  
   863  // walletLock locks the wallet.
   864  func (wc *rpcClient) walletLock() error {
   865  	return wc.call(methodLock, nil, nil)
   866  }
   867  
   868  // locked returns the wallet's lock state.
   869  func (wc *rpcClient) locked() bool {
   870  	walletInfo, err := wc.GetWalletInfo()
   871  	if err != nil {
   872  		wc.log.Errorf("GetWalletInfo error: %w", err)
   873  		return false
   874  	}
   875  	if walletInfo.UnlockedUntil == nil {
   876  		// This wallet is not encrypted.
   877  		return false
   878  	}
   879  
   880  	return time.Unix(*walletInfo.UnlockedUntil, 0).Before(time.Now())
   881  }
   882  
   883  // sendTxFeeEstimator returns the fee required to send tx using the provided
   884  // feeRate.
   885  func (wc *rpcClient) estimateSendTxFee(tx *wire.MsgTx, feeRate uint64, subtract bool) (txfee uint64, err error) {
   886  	txBytes, err := wc.serializeTx(tx)
   887  	if err != nil {
   888  		return 0, fmt.Errorf("tx serialization error: %w", err)
   889  	}
   890  	args := anylist{hex.EncodeToString(txBytes)}
   891  
   892  	// 1e-5 = 1e-8 for satoshis * 1000 for kB.
   893  	feeRateOption := float64(feeRate) / 1e5
   894  	options := &btcjson.FundRawTransactionOpts{
   895  		FeeRate: &feeRateOption,
   896  	}
   897  	if !wc.omitAddressType {
   898  		if wc.segwit {
   899  			options.ChangeType = &btcjson.ChangeTypeBech32
   900  		} else {
   901  			options.ChangeType = &btcjson.ChangeTypeLegacy
   902  		}
   903  	}
   904  	if subtract {
   905  		options.SubtractFeeFromOutputs = []int{0}
   906  	}
   907  	args = append(args, options)
   908  
   909  	var res struct {
   910  		TxBytes dex.Bytes `json:"hex"`
   911  		Fees    float64   `json:"fee"`
   912  	}
   913  	err = wc.call(methodFundRawTransaction, args, &res)
   914  	if err != nil {
   915  		wc.log.Debugf("%s fundrawtranasaction error for args %+v: %v \n", wc.cloneParams.WalletInfo.Name, args, err)
   916  		// This is a work around for ZEC wallet, which does not support options
   917  		// argument for fundrawtransaction.
   918  		if wc.omitRPCOptionsArg {
   919  			var sendAmount uint64
   920  			for _, txOut := range tx.TxOut {
   921  				sendAmount += uint64(txOut.Value)
   922  			}
   923  			var bal float64
   924  			// args: "(dummy)" minconf includeWatchonly inZat
   925  			// Using default inZat = false for compatibility with ZCL.
   926  			if err := wc.call(methodGetBalance, anylist{"", 0, false}, &bal); err != nil {
   927  				return 0, err
   928  			}
   929  			if subtract && sendAmount <= toSatoshi(bal) {
   930  				return 0, errors.New("wallet does not support options")
   931  			}
   932  		}
   933  		return 0, fmt.Errorf("error calculating transaction fee: %w", err)
   934  	}
   935  	return toSatoshi(res.Fees), nil
   936  }
   937  
   938  // GetWalletInfo gets the getwalletinfo RPC result.
   939  func (wc *rpcClient) GetWalletInfo() (*GetWalletInfoResult, error) {
   940  	wi := new(GetWalletInfoResult)
   941  	return wi, wc.call(methodGetWalletInfo, nil, wi)
   942  }
   943  
   944  // fingerprint returns an identifier for this wallet. Only HD wallets will have
   945  // an identifier. Descriptor wallets will not.
   946  func (wc *rpcClient) fingerprint() (string, error) {
   947  	walletInfo, err := wc.GetWalletInfo()
   948  	if err != nil {
   949  		return "", err
   950  	}
   951  
   952  	if walletInfo.HdSeedID == "" {
   953  		return "", fmt.Errorf("fingerprint not availble")
   954  	}
   955  
   956  	return walletInfo.HdSeedID, nil
   957  }
   958  
   959  // GetAddressInfo gets information about the given address by calling
   960  // getaddressinfo RPC command.
   961  func (wc *rpcClient) getAddressInfo(addr btcutil.Address, method string) (*GetAddressInfoResult, error) {
   962  	ai := new(GetAddressInfoResult)
   963  	addrStr, err := wc.stringAddr(addr, wc.chainParams)
   964  	if err != nil {
   965  		return nil, err
   966  	}
   967  	return ai, wc.call(method, anylist{addrStr}, ai)
   968  }
   969  
   970  // ownsAddress indicates if an address belongs to the wallet.
   971  func (wc *rpcClient) ownsAddress(addr btcutil.Address) (bool, error) {
   972  	method := methodGetAddressInfo
   973  	if wc.legacyValidateAddressRPC {
   974  		method = methodValidateAddress
   975  	}
   976  	ai, err := wc.getAddressInfo(addr, method)
   977  	if err != nil {
   978  		return false, err
   979  	}
   980  	return ai.IsMine, nil
   981  }
   982  
   983  // syncStatus is information about the blockchain sync status.
   984  func (wc *rpcClient) syncStatus() (*asset.SyncStatus, error) {
   985  	chainInfo, err := wc.getBlockchainInfo()
   986  	if err != nil {
   987  		return nil, fmt.Errorf("getblockchaininfo error: %w", err)
   988  	}
   989  	synced := !chainInfo.Syncing()
   990  	return &asset.SyncStatus{
   991  		Synced:       synced,
   992  		TargetHeight: uint64(chainInfo.Headers),
   993  		Blocks:       uint64(chainInfo.Blocks),
   994  	}, nil
   995  }
   996  
   997  // swapConfirmations gets the number of confirmations for the specified coin ID
   998  // by first checking for a unspent output, and if not found, searching indexed
   999  // wallet transactions.
  1000  func (wc *rpcClient) swapConfirmations(txHash *chainhash.Hash, vout uint32, _ []byte, _ time.Time) (confs uint32, spent bool, err error) {
  1001  	// Check for an unspent output.
  1002  	txOut, err := wc.getTxOutput(txHash, vout)
  1003  	if err == nil && txOut != nil {
  1004  		return uint32(txOut.Confirmations), false, nil
  1005  	}
  1006  	// Check wallet transactions.
  1007  	tx, err := wc.getWalletTransaction(txHash)
  1008  	if err != nil {
  1009  		if IsTxNotFoundErr(err) {
  1010  			return 0, false, asset.CoinNotFoundError
  1011  		}
  1012  		return 0, false, err
  1013  	}
  1014  	return uint32(tx.Confirmations), true, nil
  1015  }
  1016  
  1017  // getBlockHeader gets the *rpcBlockHeader for the specified block hash.
  1018  func (wc *rpcClient) getRPCBlockHeader(blockHash *chainhash.Hash) (*BlockHeader, error) {
  1019  	blkHeader := new(BlockHeader)
  1020  	err := wc.call(methodGetBlockHeader,
  1021  		anylist{blockHash.String(), true}, blkHeader)
  1022  	if err != nil {
  1023  		return nil, err
  1024  	}
  1025  
  1026  	return blkHeader, nil
  1027  }
  1028  
  1029  // getBlockHeader gets the *blockHeader for the specified block hash. It also
  1030  // returns a bool value to indicate whether this block is a part of main chain.
  1031  // For orphaned blocks header.Confirmations is negative (typically -1).
  1032  func (wc *rpcClient) getBlockHeader(blockHash *chainhash.Hash) (header *BlockHeader, mainchain bool, err error) {
  1033  	hdr, err := wc.getRPCBlockHeader(blockHash)
  1034  	if err != nil {
  1035  		return nil, false, err
  1036  	}
  1037  	// RPC wallet must return negative confirmations number for orphaned blocks.
  1038  	mainchain = hdr.Confirmations >= 0
  1039  	return hdr, mainchain, nil
  1040  }
  1041  
  1042  // getBlockHeight gets the mainchain height for the specified block. Returns
  1043  // error for orphaned blocks.
  1044  func (wc *rpcClient) getBlockHeight(blockHash *chainhash.Hash) (int32, error) {
  1045  	hdr, _, err := wc.getBlockHeader(blockHash)
  1046  	if err != nil {
  1047  		return -1, err
  1048  	}
  1049  	if hdr.Height < 0 {
  1050  		return -1, fmt.Errorf("block is not a mainchain block")
  1051  	}
  1052  	return int32(hdr.Height), nil
  1053  }
  1054  
  1055  func (wc *rpcClient) peerCount() (uint32, error) {
  1056  	var r struct {
  1057  		Connections uint32 `json:"connections"`
  1058  	}
  1059  	err := wc.call(methodGetNetworkInfo, nil, &r)
  1060  	if err != nil {
  1061  		return 0, err
  1062  	}
  1063  	return r.Connections, nil
  1064  }
  1065  
  1066  // getBlockchainInfo sends the getblockchaininfo request and returns the result.
  1067  func (wc *rpcClient) getBlockchainInfo() (*GetBlockchainInfoResult, error) {
  1068  	chainInfo := new(GetBlockchainInfoResult)
  1069  	err := wc.call(methodGetBlockchainInfo, nil, chainInfo)
  1070  	if err != nil {
  1071  		return nil, err
  1072  	}
  1073  	return chainInfo, nil
  1074  }
  1075  
  1076  // getVersion gets the current BTC network and protocol versions.
  1077  func (wc *rpcClient) getVersion() (uint64, uint64, error) {
  1078  	r := &struct {
  1079  		Version         uint64 `json:"version"`
  1080  		SubVersion      string `json:"subversion"`
  1081  		ProtocolVersion uint64 `json:"protocolversion"`
  1082  	}{}
  1083  	err := wc.call(methodGetNetworkInfo, nil, r)
  1084  	if err != nil {
  1085  		return 0, 0, err
  1086  	}
  1087  	// TODO: We might consider checking getnetworkinfo's "subversion" field,
  1088  	// which is something like "/Satoshi:24.0.1/".
  1089  	wc.log.Debugf("Node at %v reports subversion \"%v\"", wc.rpcConfig.RPCBind, r.SubVersion)
  1090  	return r.Version, r.ProtocolVersion, nil
  1091  }
  1092  
  1093  // findRedemptionsInMempool attempts to find spending info for the specified
  1094  // contracts by searching every input of all txs in the mempool.
  1095  func (wc *rpcClient) findRedemptionsInMempool(ctx context.Context, reqs map[OutPoint]*FindRedemptionReq) (discovered map[OutPoint]*FindRedemptionResult) {
  1096  	return FindRedemptionsInMempool(ctx, wc.log, reqs, wc.GetRawMempool, wc.GetRawTransaction, wc.segwit, wc.hashTx, wc.chainParams)
  1097  }
  1098  
  1099  func FindRedemptionsInMempool(
  1100  	ctx context.Context,
  1101  	log dex.Logger,
  1102  	reqs map[OutPoint]*FindRedemptionReq,
  1103  	getMempool func() ([]*chainhash.Hash, error),
  1104  	getTx func(txHash *chainhash.Hash) (*wire.MsgTx, error),
  1105  	segwit bool,
  1106  	hashTx func(*wire.MsgTx) *chainhash.Hash,
  1107  	chainParams *chaincfg.Params,
  1108  
  1109  ) (discovered map[OutPoint]*FindRedemptionResult) {
  1110  	contractsCount := len(reqs)
  1111  	log.Debugf("finding redemptions for %d contracts in mempool", contractsCount)
  1112  
  1113  	discovered = make(map[OutPoint]*FindRedemptionResult, len(reqs))
  1114  
  1115  	var totalFound, totalCanceled int
  1116  	logAbandon := func(reason string) {
  1117  		// Do not remove the contracts from the findRedemptionQueue
  1118  		// as they could be subsequently redeemed in some mined tx(s),
  1119  		// which would be captured when a new tip is reported.
  1120  		if totalFound+totalCanceled > 0 {
  1121  			log.Debugf("%d redemptions found, %d canceled out of %d contracts in mempool",
  1122  				totalFound, totalCanceled, contractsCount)
  1123  		}
  1124  		log.Errorf("abandoning mempool redemption search for %d contracts because of %s",
  1125  			contractsCount-totalFound-totalCanceled, reason)
  1126  	}
  1127  
  1128  	mempoolTxs, err := getMempool()
  1129  	if err != nil {
  1130  		logAbandon(fmt.Sprintf("error retrieving transactions: %v", err))
  1131  		return
  1132  	}
  1133  
  1134  	for _, txHash := range mempoolTxs {
  1135  		if ctx.Err() != nil {
  1136  			return nil
  1137  		}
  1138  		tx, err := getTx(txHash)
  1139  		if err != nil {
  1140  			logAbandon(fmt.Sprintf("getrawtransaction error for tx hash %v: %v", txHash, err))
  1141  			return
  1142  		}
  1143  		newlyDiscovered := findRedemptionsInTxWithHasher(ctx, segwit, reqs, tx, chainParams, hashTx)
  1144  		for outPt, res := range newlyDiscovered {
  1145  			discovered[outPt] = res
  1146  		}
  1147  
  1148  	}
  1149  	return
  1150  }
  1151  
  1152  // searchBlockForRedemptions attempts to find spending info for the specified
  1153  // contracts by searching every input of all txs in the provided block range.
  1154  func (wc *rpcClient) searchBlockForRedemptions(ctx context.Context, reqs map[OutPoint]*FindRedemptionReq, blockHash chainhash.Hash) (discovered map[OutPoint]*FindRedemptionResult) {
  1155  	msgBlock, err := wc.getBlock(blockHash)
  1156  	if err != nil {
  1157  		wc.log.Errorf("RPC GetBlock error: %v", err)
  1158  		return
  1159  	}
  1160  	return SearchBlockForRedemptions(ctx, reqs, msgBlock, wc.segwit, wc.hashTx, wc.chainParams)
  1161  }
  1162  
  1163  func SearchBlockForRedemptions(
  1164  	ctx context.Context,
  1165  	reqs map[OutPoint]*FindRedemptionReq,
  1166  	msgBlock *wire.MsgBlock,
  1167  	segwit bool,
  1168  	hashTx func(*wire.MsgTx) *chainhash.Hash,
  1169  	chainParams *chaincfg.Params,
  1170  ) (discovered map[OutPoint]*FindRedemptionResult) {
  1171  
  1172  	discovered = make(map[OutPoint]*FindRedemptionResult, len(reqs))
  1173  
  1174  	for _, msgTx := range msgBlock.Transactions {
  1175  		newlyDiscovered := findRedemptionsInTxWithHasher(ctx, segwit, reqs, msgTx, chainParams, hashTx)
  1176  		for outPt, res := range newlyDiscovered {
  1177  			discovered[outPt] = res
  1178  		}
  1179  	}
  1180  	return
  1181  }
  1182  
  1183  // call is used internally to marshal parameters and send requests to the RPC
  1184  // server via (*rpcclient.Client).RawRequest. If thing is non-nil, the result
  1185  // will be marshaled into thing.
  1186  func (wc *rpcClient) call(method string, args anylist, thing any) error {
  1187  	return Call(wc.ctx, wc.requester(), method, args, thing)
  1188  }
  1189  
  1190  func Call(ctx context.Context, r RawRequester, method string, args anylist, thing any) error {
  1191  	params := make([]json.RawMessage, 0, len(args))
  1192  	for i := range args {
  1193  		p, err := json.Marshal(args[i])
  1194  		if err != nil {
  1195  			return err
  1196  		}
  1197  		params = append(params, p)
  1198  	}
  1199  
  1200  	b, err := r.RawRequest(ctx, method, params)
  1201  	if err != nil {
  1202  		return fmt.Errorf("rawrequest (%v) error: %w", method, err)
  1203  	}
  1204  	if thing != nil {
  1205  		return json.Unmarshal(b, thing)
  1206  	}
  1207  	return nil
  1208  }