decred.org/dcrdex@v1.0.5/server/asset/dcr/dcr.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 dcr
     5  
     6  import (
     7  	"bytes"
     8  	"context"
     9  	"crypto/sha256"
    10  	"encoding/binary"
    11  	"encoding/hex"
    12  	"errors"
    13  	"fmt"
    14  	"math"
    15  	"os"
    16  	"strings"
    17  	"sync"
    18  	"time"
    19  
    20  	"decred.org/dcrdex/dex"
    21  	"decred.org/dcrdex/dex/calc"
    22  	dexdcr "decred.org/dcrdex/dex/networks/dcr"
    23  	"decred.org/dcrdex/server/account"
    24  	"decred.org/dcrdex/server/asset"
    25  	"github.com/decred/dcrd/blockchain/stake/v5"
    26  	"github.com/decred/dcrd/blockchain/standalone/v2"
    27  	"github.com/decred/dcrd/chaincfg/chainhash"
    28  	"github.com/decred/dcrd/dcrjson/v4"
    29  	"github.com/decred/dcrd/dcrutil/v4"
    30  	"github.com/decred/dcrd/hdkeychain/v3"
    31  	chainjson "github.com/decred/dcrd/rpc/jsonrpc/types/v4"
    32  	"github.com/decred/dcrd/rpcclient/v8"
    33  	"github.com/decred/dcrd/txscript/v4"
    34  	"github.com/decred/dcrd/txscript/v4/stdaddr"
    35  	"github.com/decred/dcrd/txscript/v4/stdscript"
    36  	"github.com/decred/dcrd/wire"
    37  )
    38  
    39  // Driver implements asset.Driver.
    40  type Driver struct{}
    41  
    42  var _ asset.Driver = (*Driver)(nil)
    43  
    44  // Setup creates the DCR backend. Start the backend with its Run method.
    45  func (d *Driver) Setup(cfg *asset.BackendConfig) (asset.Backend, error) {
    46  	// With a websocket RPC client with auto-reconnect, setup a logging
    47  	// subsystem for the rpcclient.
    48  	rpcLogger := cfg.Logger.SubLogger("RPC")
    49  	if rpcLogger.Level() == dex.LevelTrace {
    50  		rpcLogger.SetLevel(dex.LevelDebug)
    51  	}
    52  	rpcclient.UseLogger(rpcLogger)
    53  	return NewBackend(cfg)
    54  }
    55  
    56  // DecodeCoinID creates a human-readable representation of a coin ID for Decred.
    57  func (d *Driver) DecodeCoinID(coinID []byte) (string, error) {
    58  	txid, vout, err := decodeCoinID(coinID)
    59  	if err != nil {
    60  		return "", err
    61  	}
    62  	return fmt.Sprintf("%v:%d", txid, vout), err
    63  }
    64  
    65  // UnitInfo returns the dex.UnitInfo for the asset.
    66  func (d *Driver) UnitInfo() dex.UnitInfo {
    67  	return dexdcr.UnitInfo
    68  }
    69  
    70  // Version returns the Backend implementation's version number.
    71  func (d *Driver) Version() uint32 {
    72  	return version
    73  }
    74  
    75  // MinBondSize calculates the minimum bond size for a given fee rate that avoids
    76  // dust outputs on the bond and refund txs, assuming the maxFeeRate doesn't
    77  // change.
    78  func (d *Driver) MinBondSize(maxFeeRate uint64) uint64 {
    79  	return dexdcr.MinBondSize(maxFeeRate)
    80  }
    81  
    82  // MinLotSize calculates the minimum bond size for a given fee rate that avoids
    83  // dust outputs on the swap and refund txs, assuming the maxFeeRate doesn't
    84  // change.
    85  func (d *Driver) MinLotSize(maxFeeRate uint64) uint64 {
    86  	return dexdcr.MinLotSize(maxFeeRate)
    87  }
    88  
    89  // Name is the asset's name.
    90  func (d *Driver) Name() string {
    91  	return "Decred"
    92  }
    93  
    94  func init() {
    95  	asset.Register(BipID, &Driver{})
    96  }
    97  
    98  var (
    99  	zeroHash chainhash.Hash
   100  	// The blockPollInterval is the delay between calls to GetBestBlockHash to
   101  	// check for new blocks.
   102  	blockPollInterval = time.Second
   103  
   104  	compatibleNodeRPCVersions = []dex.Semver{
   105  		{Major: 8, Minor: 0, Patch: 0}, // 1.8-pre, just dropped unused ticket RPCs
   106  		{Major: 7, Minor: 0, Patch: 0}, // 1.7 release, new gettxout args
   107  	}
   108  
   109  	conventionalConversionFactor = float64(dexdcr.UnitInfo.Conventional.ConversionFactor)
   110  )
   111  
   112  const (
   113  	version                  = 0
   114  	BipID                    = 42
   115  	assetName                = "dcr"
   116  	immatureTransactionError = dex.ErrorKind("immature output")
   117  	BondVersion              = 0
   118  )
   119  
   120  // dcrNode represents a blockchain information fetcher. In practice, it is
   121  // satisfied by rpcclient.Client, and all methods are matches for Client
   122  // methods. For testing, it can be satisfied by a stub.
   123  type dcrNode interface {
   124  	EstimateSmartFee(ctx context.Context, confirmations int64, mode chainjson.EstimateSmartFeeMode) (*chainjson.EstimateSmartFeeResult, error)
   125  	GetTxOut(ctx context.Context, txHash *chainhash.Hash, index uint32, tree int8, mempool bool) (*chainjson.GetTxOutResult, error)
   126  	GetRawTransactionVerbose(ctx context.Context, txHash *chainhash.Hash) (*chainjson.TxRawResult, error)
   127  	GetBlockVerbose(ctx context.Context, blockHash *chainhash.Hash, verboseTx bool) (*chainjson.GetBlockVerboseResult, error)
   128  	GetBlockHash(ctx context.Context, blockHeight int64) (*chainhash.Hash, error)
   129  	GetBestBlockHash(ctx context.Context) (*chainhash.Hash, error)
   130  	GetBlockChainInfo(ctx context.Context) (*chainjson.GetBlockChainInfoResult, error)
   131  	GetRawTransaction(ctx context.Context, txHash *chainhash.Hash) (*dcrutil.Tx, error)
   132  	SendRawTransaction(ctx context.Context, tx *wire.MsgTx, allowHighFees bool) (*chainhash.Hash, error)
   133  }
   134  
   135  // The rpcclient package functions will return a rpcclient.ErrRequestCanceled
   136  // error if the context is canceled. Translate these to asset.ErrRequestTimeout.
   137  func translateRPCCancelErr(err error) error {
   138  	if errors.Is(err, rpcclient.ErrRequestCanceled) {
   139  		err = asset.ErrRequestTimeout
   140  	}
   141  	return err
   142  }
   143  
   144  // ParseBondTx performs basic validation of a serialized time-locked fidelity
   145  // bond transaction given the bond's P2SH redeem script.
   146  //
   147  // The transaction must have at least two outputs: out 0 pays to a P2SH address
   148  // (the bond), and out 1 is a nulldata output that commits to an account ID.
   149  // There may also be a change output.
   150  //
   151  // Returned: The bond's coin ID (i.e. encoded UTXO) of the bond output. The bond
   152  // output's amount and P2SH address. The lockTime and pubkey hash data pushes
   153  // from the script. The account ID from the second output is also returned.
   154  //
   155  // Properly formed transactions:
   156  //
   157  //  1. The bond output (vout 0) must be a P2SH output.
   158  //  2. The bond's redeem script must be of the form:
   159  //     <lockTime[4]> OP_CHECKLOCKTIMEVERIFY OP_DROP OP_DUP OP_HASH160 <pubkeyhash[20]> OP_EQUALVERIFY OP_CHECKSIG
   160  //  3. The null data output (vout 1) must have a 58-byte data push (ver | account ID | lockTime | pubkeyHash).
   161  //  4. The transaction must have a zero locktime and expiry.
   162  //  5. All inputs must have the max sequence num set (finalized).
   163  //  6. The transaction must pass the checks in the
   164  //     blockchain.CheckTransactionSanity function.
   165  //
   166  // For DCR, and possibly all assets, the bond script is reconstructed from the
   167  // null data output, and it is verified that the bond output pays to this
   168  // script.
   169  func ParseBondTx(ver uint16, rawTx []byte) (bondCoinID []byte, amt int64, bondAddr string,
   170  	bondPubKeyHash []byte, lockTime int64, acct account.AccountID, err error) {
   171  	if ver != BondVersion {
   172  		err = errors.New("only version 0 bonds supported")
   173  		return
   174  	}
   175  	// While the dcr package uses a package-level chainParams variable, ensure
   176  	// that a backend has been instantiated first. Alternatively, we can add a
   177  	// dex.Network argument to this function, or make it a Backend method.
   178  	if chainParams == nil {
   179  		err = errors.New("dcr asset package config not yet loaded")
   180  		return
   181  	}
   182  	msgTx := wire.NewMsgTx()
   183  	if err = msgTx.Deserialize(bytes.NewReader(rawTx)); err != nil {
   184  		return
   185  	}
   186  
   187  	if msgTx.LockTime != 0 {
   188  		err = errors.New("transaction locktime not zero")
   189  		return
   190  	}
   191  	if msgTx.Expiry != wire.NoExpiryValue {
   192  		err = errors.New("transaction has an expiration")
   193  		return
   194  	}
   195  
   196  	if err = standalone.CheckTransactionSanity(msgTx, uint64(chainParams.MaxTxSize)); err != nil {
   197  		return
   198  	}
   199  
   200  	if len(msgTx.TxOut) < 2 {
   201  		err = fmt.Errorf("expected at least 2 outputs, found %d", len(msgTx.TxOut))
   202  		return
   203  	}
   204  
   205  	for _, txIn := range msgTx.TxIn {
   206  		if txIn.Sequence != wire.MaxTxInSequenceNum {
   207  			err = errors.New("input has non-max sequence number")
   208  			return
   209  		}
   210  	}
   211  
   212  	// Fidelity bond (output 0)
   213  	bondOut := msgTx.TxOut[0]
   214  	class, addrs := stdscript.ExtractAddrs(bondOut.Version, bondOut.PkScript, chainParams)
   215  	if class != stdscript.STScriptHash || len(addrs) != 1 { // addrs check is redundant for p2sh
   216  		err = fmt.Errorf("bad bond pkScript (class = %v)", class)
   217  		return
   218  	}
   219  	scriptHash := txscript.ExtractScriptHash(bondOut.PkScript)
   220  
   221  	// Bond account commitment (output 1)
   222  	acctCommitOut := msgTx.TxOut[1]
   223  	acct, lock, pkh, err := dexdcr.ExtractBondCommitDataV0(acctCommitOut.Version, acctCommitOut.PkScript)
   224  	if err != nil {
   225  		err = fmt.Errorf("invalid bond commitment output: %w", err)
   226  		return
   227  	}
   228  
   229  	// Reconstruct and check the bond redeem script.
   230  	bondScript, err := dexdcr.MakeBondScript(ver, lock, pkh[:])
   231  	if err != nil {
   232  		err = fmt.Errorf("failed to build bond output redeem script: %w", err)
   233  		return
   234  	}
   235  	if !bytes.Equal(dcrutil.Hash160(bondScript), scriptHash) {
   236  		err = fmt.Errorf("script hash check failed for output 0 of %s", msgTx.TxHash())
   237  		return
   238  	}
   239  	// lock, pkh, _ := dexdcr.ExtractBondDetailsV0(bondOut.Version, bondScript)
   240  
   241  	txid := msgTx.TxHash()
   242  	bondCoinID = toCoinID(&txid, 0)
   243  	amt = bondOut.Value
   244  	bondAddr = addrs[0].String() // don't convert address, must match type we specified
   245  	lockTime = int64(lock)
   246  	bondPubKeyHash = pkh[:]
   247  
   248  	return
   249  }
   250  
   251  // Backend is an asset backend for Decred. It has methods for fetching output
   252  // information and subscribing to block updates. It maintains a cache of block
   253  // data for quick lookups. Backend implements asset.Backend, so provides
   254  // exported methods for DEX-related blockchain info.
   255  type Backend struct {
   256  	ctx context.Context
   257  	cfg *config
   258  	// If an rpcclient.Client is used for the node, keeping a reference at client
   259  	// will result in (Client).Shutdown() being called on context cancellation.
   260  	client *rpcclient.Client
   261  	// node is used throughout for RPC calls, and in typical use will be the same
   262  	// as client. For testing, it can be set to a stub.
   263  	node dcrNode
   264  	// The backend provides block notification channels through it BlockChannel
   265  	// method. signalMtx locks the blockChans array.
   266  	signalMtx  sync.RWMutex
   267  	blockChans map[chan *asset.BlockUpdate]struct{}
   268  	// The block cache stores just enough info about the blocks to prevent future
   269  	// calls to GetBlockVerbose.
   270  	blockCache *blockCache
   271  	// A logger will be provided by the DEX. All logging should use the provided
   272  	// logger.
   273  	log dex.Logger
   274  	// nodeRelay is the NodeRelay address.
   275  	nodeRelay string
   276  }
   277  
   278  // Check that Backend satisfies the Backend interface.
   279  var _ asset.Backend = (*Backend)(nil)
   280  
   281  // unconnectedDCR returns a Backend without a node. The node should be set
   282  // before use.
   283  func unconnectedDCR(cfg *asset.BackendConfig, dcrConfig *config) *Backend {
   284  	return &Backend{
   285  		cfg:        dcrConfig,
   286  		blockCache: newBlockCache(cfg.Logger),
   287  		log:        cfg.Logger,
   288  		blockChans: make(map[chan *asset.BlockUpdate]struct{}),
   289  		nodeRelay:  cfg.RelayAddr,
   290  	}
   291  }
   292  
   293  // NewBackend is the exported constructor by which the DEX will import the
   294  // Backend. If configPath is an empty string, the backend will attempt to read
   295  // the settings directly from the dcrd config file in its default system
   296  // location.
   297  func NewBackend(cfg *asset.BackendConfig) (*Backend, error) {
   298  	// loadConfig will set fields if defaults are used and set the chainParams
   299  	// package variable.
   300  	dcrConfig, err := loadConfig(cfg.ConfigPath, cfg.Net)
   301  	if err != nil {
   302  		return nil, err
   303  	}
   304  	return unconnectedDCR(cfg, dcrConfig), nil
   305  }
   306  
   307  func (dcr *Backend) shutdown() {
   308  	if dcr.client != nil {
   309  		dcr.client.Shutdown()
   310  		dcr.client.WaitForShutdown()
   311  	}
   312  }
   313  
   314  // Connect connects to the node RPC server. A dex.Connector.
   315  func (dcr *Backend) Connect(ctx context.Context) (_ *sync.WaitGroup, err error) {
   316  	var client *rpcclient.Client
   317  	if dcr.nodeRelay == "" {
   318  		client, err = connectNodeRPC(dcr.cfg.RPCListen, dcr.cfg.RPCUser, dcr.cfg.RPCPass, dcr.cfg.RPCCert)
   319  	} else {
   320  		client, err = connectNodeRelay(dcr.nodeRelay, dcr.cfg.RPCUser, dcr.cfg.RPCPass)
   321  	}
   322  	if err != nil {
   323  		return nil, err
   324  	}
   325  	dcr.client = client
   326  
   327  	// Ensure the network of the connected node is correct for the expected
   328  	// dex.Network.
   329  	net, err := dcr.client.GetCurrentNet(ctx)
   330  	if err != nil {
   331  		dcr.shutdown()
   332  		return nil, fmt.Errorf("getcurrentnet failure: %w", err)
   333  	}
   334  	var wantCurrencyNet wire.CurrencyNet
   335  	switch dcr.cfg.Network {
   336  	case dex.Testnet:
   337  		wantCurrencyNet = wire.TestNet3
   338  	case dex.Mainnet:
   339  		wantCurrencyNet = wire.MainNet
   340  	case dex.Regtest: // dex.Simnet
   341  		wantCurrencyNet = wire.SimNet
   342  	}
   343  	if net != wantCurrencyNet {
   344  		dcr.shutdown()
   345  		return nil, fmt.Errorf("wrong net %v", net.String())
   346  	}
   347  
   348  	// Check the required API versions.
   349  	versions, err := dcr.client.Version(ctx)
   350  	if err != nil {
   351  		dcr.shutdown()
   352  		return nil, fmt.Errorf("DCR node version fetch error: %w", err)
   353  	}
   354  
   355  	ver, exists := versions["dcrdjsonrpcapi"]
   356  	if !exists {
   357  		dcr.shutdown()
   358  		return nil, fmt.Errorf("dcrd.Version response missing 'dcrdjsonrpcapi'")
   359  	}
   360  	nodeSemver := dex.NewSemver(ver.Major, ver.Minor, ver.Patch)
   361  	if !dex.SemverCompatibleAny(compatibleNodeRPCVersions, nodeSemver) {
   362  		dcr.shutdown()
   363  		return nil, fmt.Errorf("dcrd has an incompatible JSON-RPC version %s, require one of %s",
   364  			nodeSemver, compatibleNodeRPCVersions)
   365  	}
   366  
   367  	// Verify dcrd has tx index enabled (required for getrawtransaction).
   368  	info, err := dcr.client.GetInfo(ctx)
   369  	if err != nil {
   370  		dcr.shutdown()
   371  		return nil, fmt.Errorf("dcrd getinfo check failed: %w", err)
   372  	}
   373  	if !info.TxIndex {
   374  		dcr.shutdown()
   375  		return nil, errors.New("dcrd does not have transaction index enabled (specify --txindex)")
   376  	}
   377  
   378  	dcr.log.Infof("Connected to dcrd (JSON-RPC API v%s) on %v", nodeSemver, net)
   379  
   380  	dcr.node = dcr.client
   381  	dcr.ctx = ctx
   382  
   383  	// Prime the cache with the best block.
   384  	bestHash, _, err := dcr.client.GetBestBlock(ctx)
   385  	if err != nil {
   386  		dcr.shutdown()
   387  		return nil, fmt.Errorf("error getting best block from dcrd: %w", err)
   388  	}
   389  	if bestHash != nil {
   390  		_, err := dcr.getDcrBlock(ctx, bestHash)
   391  		if err != nil {
   392  			dcr.shutdown()
   393  			return nil, fmt.Errorf("error priming the cache: %w", err)
   394  		}
   395  	}
   396  
   397  	if _, err = dcr.FeeRate(ctx); err != nil {
   398  		dcr.log.Warnf("Decred backend started without fee estimation available: %v", err)
   399  	}
   400  
   401  	var wg sync.WaitGroup
   402  	wg.Add(1)
   403  	go func() {
   404  		defer wg.Done()
   405  		dcr.run(ctx)
   406  	}()
   407  	return &wg, nil
   408  }
   409  
   410  // FeeRate returns the current optimal fee rate in atoms / byte.
   411  func (dcr *Backend) FeeRate(ctx context.Context) (uint64, error) {
   412  	// estimatesmartfee 1 returns extremely high rates on DCR.
   413  	estimateFeeResult, err := dcr.node.EstimateSmartFee(ctx, 2, chainjson.EstimateSmartFeeConservative)
   414  	if err != nil {
   415  		return 0, translateRPCCancelErr(err)
   416  	}
   417  	atomsPerKB, err := dcrutil.NewAmount(estimateFeeResult.FeeRate)
   418  	if err != nil {
   419  		return 0, err
   420  	}
   421  	atomsPerB := uint64(math.Round(float64(atomsPerKB) / 1000))
   422  	if atomsPerB == 0 {
   423  		atomsPerB = 1
   424  	}
   425  	return atomsPerB, nil
   426  }
   427  
   428  // Info provides some general information about the backend.
   429  func (*Backend) Info() *asset.BackendInfo {
   430  	return &asset.BackendInfo{}
   431  }
   432  
   433  // ValidateFeeRate checks that the transaction fees used to initiate the
   434  // contract are sufficient.
   435  func (dcr *Backend) ValidateFeeRate(c asset.Coin, reqFeeRate uint64) bool {
   436  	return c.FeeRate() >= reqFeeRate
   437  }
   438  
   439  // BlockChannel creates and returns a new channel on which to receive block
   440  // updates. If the returned channel is ever blocking, there will be no error
   441  // logged from the dcr package. Part of the asset.Backend interface.
   442  func (dcr *Backend) BlockChannel(size int) <-chan *asset.BlockUpdate {
   443  	c := make(chan *asset.BlockUpdate, size)
   444  	dcr.signalMtx.Lock()
   445  	defer dcr.signalMtx.Unlock()
   446  	dcr.blockChans[c] = struct{}{}
   447  	return c
   448  }
   449  
   450  // SendRawTransaction broadcasts a raw transaction, returning a coin ID.
   451  func (dcr *Backend) SendRawTransaction(rawtx []byte) (coinID []byte, err error) {
   452  	msgTx := wire.NewMsgTx()
   453  	if err = msgTx.Deserialize(bytes.NewReader(rawtx)); err != nil {
   454  		return nil, err
   455  	}
   456  
   457  	var hash *chainhash.Hash
   458  	hash, err = dcr.node.SendRawTransaction(dcr.ctx, msgTx, false) // or allow high fees?
   459  	if err != nil {
   460  		return
   461  	}
   462  	coinID = toCoinID(hash, 0)
   463  	return
   464  }
   465  
   466  // Contract is part of the asset.Backend interface. An asset.Contract is an
   467  // output that has been validated as a swap contract for the passed redeem
   468  // script. A spendable output is one that can be spent in the next block. Every
   469  // regular-tree output from a non-coinbase transaction is spendable immediately.
   470  // Coinbase and stake tree outputs are only spendable after CoinbaseMaturity
   471  // confirmations. Pubkey scripts can be P2PKH or P2SH in either regular- or
   472  // stake-tree flavor. P2PKH supports two alternative signatures, Schnorr and
   473  // Edwards. Multi-sig P2SH redeem scripts are supported as well.
   474  func (dcr *Backend) Contract(coinID []byte, redeemScript []byte) (*asset.Contract, error) {
   475  	txHash, vout, err := decodeCoinID(coinID)
   476  	if err != nil {
   477  		return nil, fmt.Errorf("error decoding coin ID %x: %w", coinID, err)
   478  	}
   479  
   480  	op, err := dcr.output(txHash, vout, redeemScript)
   481  	if err != nil {
   482  		return nil, err
   483  	}
   484  
   485  	return auditContract(op)
   486  }
   487  
   488  // ValidateSecret checks that the secret satisfies the contract.
   489  func (dcr *Backend) ValidateSecret(secret, contract []byte) bool {
   490  	_, _, _, secretHash, err := dexdcr.ExtractSwapDetails(contract, chainParams)
   491  	if err != nil {
   492  		dcr.log.Errorf("ValidateSecret->ExtractSwapDetails error: %v\n", err)
   493  		return false
   494  	}
   495  	h := sha256.Sum256(secret)
   496  	return bytes.Equal(h[:], secretHash)
   497  }
   498  
   499  // Synced is true if the blockchain is ready for action.
   500  func (dcr *Backend) Synced() (bool, error) {
   501  	// With ws autoreconnect enabled, requests hang when backend is
   502  	// disconnected.
   503  	ctx, cancel := context.WithTimeout(dcr.ctx, 2*time.Second)
   504  	defer cancel()
   505  	chainInfo, err := dcr.node.GetBlockChainInfo(ctx)
   506  	if err != nil {
   507  		return false, fmt.Errorf("GetBlockChainInfo error: %w", translateRPCCancelErr(err))
   508  	}
   509  	return !chainInfo.InitialBlockDownload && chainInfo.Headers-chainInfo.Blocks <= 1, nil
   510  }
   511  
   512  // Redemption is an input that redeems a swap contract.
   513  func (dcr *Backend) Redemption(redemptionID, contractID, _ []byte) (asset.Coin, error) {
   514  	txHash, vin, err := decodeCoinID(redemptionID)
   515  	if err != nil {
   516  		return nil, fmt.Errorf("error decoding redemption coin ID %x: %w", txHash, err)
   517  	}
   518  	input, err := dcr.input(txHash, vin)
   519  	if err != nil {
   520  		return nil, err
   521  	}
   522  	spends, err := input.spendsCoin(contractID)
   523  	if err != nil {
   524  		return nil, err
   525  	}
   526  	if !spends {
   527  		return nil, fmt.Errorf("%x does not spend %x", redemptionID, contractID)
   528  	}
   529  	return input, nil
   530  }
   531  
   532  // FundingCoin is an unspent output.
   533  func (dcr *Backend) FundingCoin(ctx context.Context, coinID []byte, redeemScript []byte) (asset.FundingCoin, error) {
   534  	txHash, vout, err := decodeCoinID(coinID)
   535  	if err != nil {
   536  		return nil, fmt.Errorf("error decoding coin ID %x: %w", coinID, err)
   537  	}
   538  	utxo, err := dcr.utxo(ctx, txHash, vout, redeemScript)
   539  	if err != nil {
   540  		if isTxNotFoundErr(err) {
   541  			return nil, asset.CoinNotFoundError
   542  		}
   543  		return nil, err
   544  	}
   545  	if utxo.nonStandardScript {
   546  		return nil, fmt.Errorf("non-standard script")
   547  	}
   548  	return utxo, nil
   549  }
   550  
   551  // ValidateXPub validates the base-58 encoded extended key, and ensures that it
   552  // is an extended public, not private, key.
   553  func ValidateXPub(xpub string) error {
   554  	xp, err := hdkeychain.NewKeyFromString(xpub, chainParams)
   555  	if err != nil {
   556  		return err
   557  	}
   558  	if xp.IsPrivate() {
   559  		xp.Zero()
   560  		return fmt.Errorf("extended key is a private key")
   561  	}
   562  	return nil
   563  }
   564  
   565  func (*Backend) ValidateOrderFunding(swapVal, valSum, _, inputsSize, maxSwaps uint64, nfo *dex.Asset) bool {
   566  	reqVal := calc.RequiredOrderFunds(swapVal, inputsSize, maxSwaps, dexdcr.InitTxSizeBase, dexdcr.InitTxSize, nfo.MaxFeeRate)
   567  	return valSum >= reqVal
   568  }
   569  
   570  // ValidateCoinID attempts to decode the coinID.
   571  func (dcr *Backend) ValidateCoinID(coinID []byte) (string, error) {
   572  	txid, vout, err := decodeCoinID(coinID)
   573  	if err != nil {
   574  		return "", err
   575  	}
   576  	return fmt.Sprintf("%v:%d", txid, vout), err
   577  }
   578  
   579  // ValidateContract ensures that the swap contract is constructed properly, and
   580  // contains valid sender and receiver addresses.
   581  func (dcr *Backend) ValidateContract(contract []byte) error {
   582  	_, _, _, _, err := dexdcr.ExtractSwapDetails(contract, chainParams)
   583  	return err
   584  }
   585  
   586  // CheckSwapAddress checks that the given address is parseable and of the
   587  // required type for a swap contract script (p2pkh).
   588  func (dcr *Backend) CheckSwapAddress(addr string) bool {
   589  	dcrAddr, err := stdaddr.DecodeAddress(addr, chainParams)
   590  	if err != nil {
   591  		dcr.log.Errorf("DecodeAddress error for %s: %v", addr, err)
   592  		return false
   593  	}
   594  	if _, ok := dcrAddr.(*stdaddr.AddressPubKeyHashEcdsaSecp256k1V0); !ok {
   595  		dcr.log.Errorf("CheckSwapAddress for %s failed: not a pubkey-hash-ecdsa-secp256k1 address (%T)",
   596  			dcrAddr.String(), dcrAddr)
   597  		return false
   598  	}
   599  	return true
   600  }
   601  
   602  // TxData is the raw transaction bytes. SPV clients rebroadcast the transaction
   603  // bytes to get around not having a mempool to check.
   604  func (dcr *Backend) TxData(coinID []byte) ([]byte, error) {
   605  	txHash, _, err := decodeCoinID(coinID)
   606  	if err != nil {
   607  		return nil, err
   608  	}
   609  	stdaddrTx, err := dcr.node.GetRawTransaction(dcr.ctx, txHash)
   610  	if err != nil {
   611  		if isTxNotFoundErr(err) {
   612  			return nil, asset.CoinNotFoundError
   613  		}
   614  		return nil, fmt.Errorf("TxData: GetRawTransactionVerbose for txid %s: %w", txHash, err)
   615  	}
   616  	return stdaddrTx.MsgTx().Bytes()
   617  }
   618  
   619  // VerifyUnspentCoin attempts to verify a coin ID by decoding the coin ID and
   620  // retrieving the corresponding UTXO. If the coin is not found or no longer
   621  // unspent, an asset.CoinNotFoundError is returned.
   622  func (dcr *Backend) VerifyUnspentCoin(ctx context.Context, coinID []byte) error {
   623  	txHash, vout, err := decodeCoinID(coinID)
   624  	if err != nil {
   625  		return fmt.Errorf("error decoding coin ID %x: %w", coinID, err)
   626  	}
   627  	txOut, err := dcr.node.GetTxOut(ctx, txHash, vout, wire.TxTreeRegular, true) // check regular tree first
   628  	if err == nil && txOut == nil {
   629  		txOut, err = dcr.node.GetTxOut(ctx, txHash, vout, wire.TxTreeStake, true) // check stake tree
   630  	}
   631  	if err != nil {
   632  		return fmt.Errorf("GetTxOut (%s:%d): %w", txHash.String(), vout, translateRPCCancelErr(err))
   633  	}
   634  	if txOut == nil {
   635  		return asset.CoinNotFoundError
   636  	}
   637  	return nil
   638  }
   639  
   640  // BondVer returns the latest supported bond version.
   641  func (dcr *Backend) BondVer() uint16 {
   642  	return BondVersion
   643  }
   644  
   645  // ParseBondTx makes the package-level ParseBondTx pure function accessible via
   646  // a Backend instance. This performs basic validation of a serialized
   647  // time-locked fidelity bond transaction given the bond's P2SH redeem script.
   648  func (*Backend) ParseBondTx(ver uint16, rawTx []byte) (bondCoinID []byte, amt int64, bondAddr string,
   649  	bondPubKeyHash []byte, lockTime int64, acct account.AccountID, err error) {
   650  	return ParseBondTx(ver, rawTx)
   651  }
   652  
   653  // BondCoin locates a bond transaction output, validates the entire transaction,
   654  // and returns the amount, encoded lockTime and account ID, and the
   655  // confirmations of the transaction. It is a CoinNotFoundError if the
   656  // transaction output is spent.
   657  func (dcr *Backend) BondCoin(ctx context.Context, ver uint16, coinID []byte) (amt, lockTime, confs int64, acct account.AccountID, err error) {
   658  	txHash, vout, errCoin := decodeCoinID(coinID)
   659  	if errCoin != nil {
   660  		err = fmt.Errorf("error decoding coin ID %x: %w", coinID, errCoin)
   661  		return
   662  	}
   663  
   664  	verboseTx, err := dcr.node.GetRawTransactionVerbose(dcr.ctx, txHash)
   665  	if err != nil {
   666  		if isTxNotFoundErr(err) {
   667  			err = asset.CoinNotFoundError
   668  		} else {
   669  			err = translateRPCCancelErr(err)
   670  		}
   671  		return
   672  	}
   673  
   674  	if int(vout) > len(verboseTx.Vout)-1 {
   675  		err = fmt.Errorf("invalid output index for tx with %d outputs", len(verboseTx.Vout))
   676  		return
   677  	}
   678  
   679  	confs = verboseTx.Confirmations
   680  
   681  	// msgTx, err := msgTxFromHex(verboseTx.Hex)
   682  	rawTx, err := hex.DecodeString(verboseTx.Hex) // ParseBondTx will deserialize to msgTx, so just get the bytes
   683  	if err != nil {
   684  		err = fmt.Errorf("failed to decode transaction %s: %w", txHash, err)
   685  		return
   686  	}
   687  	// rawTx, _ := msgTx.Bytes()
   688  
   689  	// tree := determineTxTree(msgTx)
   690  	txOut, err := dcr.node.GetTxOut(ctx, txHash, vout, wire.TxTreeRegular, true) // check regular tree first
   691  	if err == nil && txOut == nil {
   692  		txOut, err = dcr.node.GetTxOut(ctx, txHash, vout, wire.TxTreeStake, true) // check stake tree
   693  	}
   694  	if err != nil {
   695  		err = fmt.Errorf("GetTxOut error for output %s:%d: %w",
   696  			txHash, vout, translateRPCCancelErr(err))
   697  		return
   698  	}
   699  	if txOut == nil { // spent == invalid bond
   700  		err = asset.CoinNotFoundError
   701  		return
   702  	}
   703  
   704  	_, amt, _, _, lockTime, acct, err = ParseBondTx(ver, rawTx)
   705  	return
   706  }
   707  
   708  // FeeCoin gets the recipient address, value, and confirmations of a transaction
   709  // output encoded by the given coinID. A non-nil error is returned if the
   710  // output's pubkey script is not a non-stake P2PKH requiring a single
   711  // ECDSA-secp256k1 signature.
   712  func (dcr *Backend) FeeCoin(coinID []byte) (addr string, val uint64, confs int64, err error) {
   713  	txHash, vout, errCoin := decodeCoinID(coinID)
   714  	if errCoin != nil {
   715  		err = fmt.Errorf("error decoding coin ID %x: %w", coinID, errCoin)
   716  		return
   717  	}
   718  
   719  	var txOut *txOutData
   720  	txOut, confs, err = dcr.outputSummary(txHash, vout)
   721  	if err != nil {
   722  		return
   723  	}
   724  
   725  	// No stake outputs, and no multisig.
   726  	if len(txOut.addresses) != 1 || txOut.sigsRequired != 1 ||
   727  		txOut.scriptType&dexdcr.ScriptStake != 0 {
   728  		return "", 0, -1, dex.UnsupportedScriptError
   729  	}
   730  
   731  	// Needs to work for legacy fee and new bond txns.
   732  	switch txOut.scriptType {
   733  	case dexdcr.ScriptP2SH, dexdcr.ScriptP2PKH:
   734  	default:
   735  		return "", 0, -1, dex.UnsupportedScriptError
   736  	}
   737  
   738  	addr = txOut.addresses[0]
   739  	val = txOut.value
   740  
   741  	return
   742  }
   743  
   744  // txOutData is transaction output data, including recipient addresses, value,
   745  // script type, and number of required signatures.
   746  type txOutData struct {
   747  	value        uint64
   748  	addresses    []string
   749  	sigsRequired int
   750  	scriptType   dexdcr.ScriptType
   751  }
   752  
   753  // outputSummary gets transaction output data, including recipient addresses,
   754  // value, script type, and number of required signatures, plus the current
   755  // confirmations of a transaction output. If the output does not exist, an error
   756  // will be returned. Non-standard scripts are not an error.
   757  func (dcr *Backend) outputSummary(txHash *chainhash.Hash, vout uint32) (txOut *txOutData, confs int64, err error) {
   758  	var verboseTx *chainjson.TxRawResult
   759  	verboseTx, err = dcr.node.GetRawTransactionVerbose(dcr.ctx, txHash)
   760  	if err != nil {
   761  		if isTxNotFoundErr(err) {
   762  			err = asset.CoinNotFoundError
   763  		} else {
   764  			err = translateRPCCancelErr(err)
   765  		}
   766  		return
   767  	}
   768  
   769  	if int(vout) > len(verboseTx.Vout)-1 {
   770  		err = fmt.Errorf("invalid output index for tx with %d outputs", len(verboseTx.Vout))
   771  		return
   772  	}
   773  
   774  	out := verboseTx.Vout[vout]
   775  
   776  	script, err := hex.DecodeString(out.ScriptPubKey.Hex)
   777  	if err != nil {
   778  		return nil, -1, dex.UnsupportedScriptError
   779  	}
   780  	scriptType, addrs, numRequired := dexdcr.ExtractScriptData(out.ScriptPubKey.Version, script, chainParams)
   781  	txOut = &txOutData{
   782  		value:        toAtoms(out.Value),
   783  		addresses:    addrs,       // out.ScriptPubKey.Addresses
   784  		sigsRequired: numRequired, // out.ScriptPubKey.ReqSigs
   785  		scriptType:   scriptType,  // integer representation of the string in out.ScriptPubKey.Type
   786  	}
   787  
   788  	confs = verboseTx.Confirmations
   789  	return
   790  }
   791  
   792  // Get the Tx. Transaction info is not cached, so every call will result in a
   793  // GetRawTransactionVerbose RPC call.
   794  func (dcr *Backend) transaction(txHash *chainhash.Hash, verboseTx *chainjson.TxRawResult) (*Tx, error) {
   795  	// Figure out if it's a stake transaction
   796  	msgTx, err := msgTxFromHex(verboseTx.Hex)
   797  	if err != nil {
   798  		return nil, fmt.Errorf("failed to decode MsgTx from hex for transaction %s: %w", txHash, err)
   799  	}
   800  	isStake := determineTxTree(msgTx) == wire.TxTreeStake
   801  
   802  	// If it's not a mempool transaction, get and cache the block data.
   803  	var blockHash *chainhash.Hash
   804  	var lastLookup *chainhash.Hash
   805  	if verboseTx.BlockHash == "" {
   806  		tipHash := dcr.blockCache.tipHash()
   807  		if tipHash != zeroHash {
   808  			lastLookup = &tipHash
   809  		}
   810  	} else {
   811  		blockHash, err = chainhash.NewHashFromStr(verboseTx.BlockHash)
   812  		if err != nil {
   813  			return nil, fmt.Errorf("error decoding block hash %s for tx %s: %w", verboseTx.BlockHash, txHash, err)
   814  		}
   815  		// Make sure the block info is cached.
   816  		_, err := dcr.getDcrBlock(dcr.ctx, blockHash)
   817  		if err != nil {
   818  			return nil, fmt.Errorf("error caching the block data for transaction %s", txHash)
   819  		}
   820  	}
   821  
   822  	var sumIn, sumOut uint64
   823  	// Parse inputs and outputs, grabbing only what's needed.
   824  	inputs := make([]txIn, 0, len(verboseTx.Vin))
   825  	var isCoinbase bool
   826  	for _, input := range verboseTx.Vin {
   827  		isCoinbase = input.Coinbase != ""
   828  		value := toAtoms(input.AmountIn)
   829  		sumIn += value
   830  		hash, err := chainhash.NewHashFromStr(input.Txid)
   831  		if err != nil {
   832  			return nil, fmt.Errorf("error decoding previous tx hash %s for tx %s: %w", input.Txid, txHash, err)
   833  		}
   834  		inputs = append(inputs, txIn{prevTx: *hash, vout: input.Vout, value: value})
   835  	}
   836  
   837  	outputs := make([]txOut, 0, len(verboseTx.Vout))
   838  	for vout, output := range verboseTx.Vout {
   839  		pkScript, err := hex.DecodeString(output.ScriptPubKey.Hex)
   840  		if err != nil {
   841  			return nil, fmt.Errorf("error decoding pubkey script from %s for transaction %d:%d: %w",
   842  				output.ScriptPubKey.Hex, txHash, vout, err)
   843  		}
   844  		sumOut += toAtoms(output.Value)
   845  		outputs = append(outputs, txOut{
   846  			value:    toAtoms(output.Value),
   847  			version:  output.ScriptPubKey.Version,
   848  			pkScript: pkScript,
   849  		})
   850  	}
   851  	rawTx, err := hex.DecodeString(verboseTx.Hex)
   852  	if err != nil {
   853  		return nil, fmt.Errorf("error decoding tx hex: %w", err)
   854  	}
   855  
   856  	feeRate := (sumIn - sumOut) / uint64(len(verboseTx.Hex)/2)
   857  	if isCoinbase {
   858  		feeRate = 0
   859  	}
   860  	return newTransaction(txHash, blockHash, lastLookup, verboseTx.BlockHeight, isStake, isCoinbase, inputs, outputs, feeRate, rawTx), nil
   861  }
   862  
   863  // run processes the queue and monitors the application context.
   864  func (dcr *Backend) run(ctx context.Context) {
   865  	var wg sync.WaitGroup
   866  	wg.Add(1)
   867  	// Shut down the RPC client on ctx.Done().
   868  	go func() {
   869  		<-ctx.Done()
   870  		dcr.shutdown()
   871  		wg.Done()
   872  	}()
   873  
   874  	blockPoll := time.NewTicker(blockPollInterval)
   875  	defer blockPoll.Stop()
   876  	addBlock := func(block *chainjson.GetBlockVerboseResult, reorg bool) {
   877  		_, err := dcr.blockCache.add(block)
   878  		if err != nil {
   879  			dcr.log.Errorf("error adding new best block to cache: %v", err)
   880  		}
   881  		dcr.signalMtx.Lock()
   882  		dcr.log.Tracef("Notifying %d dcr asset consumers of new block at height %d",
   883  			len(dcr.blockChans), block.Height)
   884  		for c := range dcr.blockChans {
   885  			select {
   886  			case c <- &asset.BlockUpdate{
   887  				Err:   nil,
   888  				Reorg: reorg,
   889  			}:
   890  			default:
   891  				// Commented to try sends on future blocks.
   892  				// close(c)
   893  				// delete(dcr.blockChans, c)
   894  				//
   895  				// TODO: Allow the receiver (e.g. Swapper.Run) to inform done
   896  				// status so the channels can be retired cleanly rather than
   897  				// trying them forever.
   898  			}
   899  		}
   900  		dcr.signalMtx.Unlock()
   901  	}
   902  
   903  	sendErr := func(err error) {
   904  		dcr.log.Error(err)
   905  		dcr.signalMtx.Lock()
   906  		for c := range dcr.blockChans {
   907  			select {
   908  			case c <- &asset.BlockUpdate{
   909  				Err: err,
   910  			}:
   911  			default:
   912  				dcr.log.Errorf("failed to send sending block update on blocking channel")
   913  				// close(c)
   914  				// delete(dcr.blockChans, c)
   915  			}
   916  		}
   917  		dcr.signalMtx.Unlock()
   918  	}
   919  
   920  	sendErrFmt := func(s string, a ...any) {
   921  		sendErr(fmt.Errorf(s, a...))
   922  	}
   923  
   924  out:
   925  	for {
   926  		select {
   927  		case <-blockPoll.C:
   928  			tip := dcr.blockCache.tip()
   929  			bestHash, err := dcr.node.GetBestBlockHash(ctx)
   930  			if err != nil {
   931  				sendErr(asset.NewConnectionError("error retrieving best block: %w", translateRPCCancelErr(err)))
   932  				continue
   933  			}
   934  			if *bestHash == tip.hash {
   935  				continue
   936  			}
   937  
   938  			best := bestHash.String()
   939  			block, err := dcr.node.GetBlockVerbose(ctx, bestHash, false)
   940  			if err != nil {
   941  				sendErrFmt("error retrieving block %s: %w", best, translateRPCCancelErr(err))
   942  				continue
   943  			}
   944  			// If this doesn't build on the best known block, look for a reorg.
   945  			prevHash, err := chainhash.NewHashFromStr(block.PreviousHash)
   946  			if err != nil {
   947  				sendErrFmt("error parsing previous hash %s: %w", block.PreviousHash, err)
   948  				continue
   949  			}
   950  			// If it builds on the best block or the cache is empty, it's good to add.
   951  			if *prevHash == tip.hash || tip.height == 0 {
   952  				dcr.log.Debugf("New block %s (%d)", bestHash, block.Height)
   953  				addBlock(block, false)
   954  				continue
   955  			}
   956  
   957  			// It is either a reorg, or the previous block is not the cached
   958  			// best block. Crawl blocks backwards until finding a mainchain
   959  			// block, flagging blocks from the cache as orphans along the way.
   960  			iHash := &tip.hash
   961  			reorgHeight := int64(0)
   962  			for {
   963  				if *iHash == zeroHash {
   964  					break
   965  				}
   966  				iBlock, err := dcr.node.GetBlockVerbose(ctx, iHash, false)
   967  				if err != nil {
   968  					sendErrFmt("error retrieving block %s: %w", iHash, translateRPCCancelErr(err))
   969  					break
   970  				}
   971  				if iBlock.Confirmations > -1 {
   972  					// This is a mainchain block, nothing to do.
   973  					break
   974  				}
   975  				if iBlock.Height == 0 {
   976  					break
   977  				}
   978  				reorgHeight = iBlock.Height
   979  				iHash, err = chainhash.NewHashFromStr(iBlock.PreviousHash)
   980  				if err != nil {
   981  					sendErrFmt("error decoding previous hash %s for block %s: %w",
   982  						iBlock.PreviousHash, iHash.String(), translateRPCCancelErr(err))
   983  					// Some blocks on the side chain may not be flagged as
   984  					// orphaned, but still proceed, flagging the ones we have
   985  					// identified and adding the new best block to the cache and
   986  					// setting it to the best block in the cache.
   987  					break
   988  				}
   989  			}
   990  
   991  			var reorg bool
   992  			if reorgHeight > 0 {
   993  				reorg = true
   994  				dcr.log.Infof("Tip change from %s (%d) to %s (%d) detected (reorg or just fast blocks).",
   995  					tip.hash, tip.height, bestHash, block.Height)
   996  				dcr.blockCache.reorg(reorgHeight)
   997  			}
   998  
   999  			// Now add the new block.
  1000  			addBlock(block, reorg)
  1001  
  1002  		case <-ctx.Done():
  1003  			break out
  1004  		}
  1005  	}
  1006  	// Wait for the RPC client to shut down.
  1007  	wg.Wait()
  1008  }
  1009  
  1010  // blockInfo returns block information for the verbose transaction data. The
  1011  // current tip hash is also returned as a convenience.
  1012  func (dcr *Backend) blockInfo(ctx context.Context, verboseTx *chainjson.TxRawResult) (blockHeight uint32, blockHash chainhash.Hash, tipHash *chainhash.Hash, err error) {
  1013  	blockHeight = uint32(verboseTx.BlockHeight)
  1014  	tip := dcr.blockCache.tipHash()
  1015  	if tip != zeroHash {
  1016  		tipHash = &tip
  1017  	}
  1018  	// Assumed to be valid while in mempool, so skip the validity check.
  1019  	if verboseTx.Confirmations > 0 {
  1020  		if blockHeight == 0 {
  1021  			err = fmt.Errorf("zero block height for output with "+
  1022  				"non-zero confirmation count (%s has %d confirmations)", verboseTx.Txid, verboseTx.Confirmations)
  1023  			return
  1024  		}
  1025  		var blk *dcrBlock
  1026  		blk, err = dcr.getBlockInfo(ctx, verboseTx.BlockHash)
  1027  		if err != nil {
  1028  			return
  1029  		}
  1030  		blockHeight = blk.height
  1031  		blockHash = blk.hash
  1032  	}
  1033  	return
  1034  }
  1035  
  1036  // Get the UTXO, populating the block data along the way.
  1037  func (dcr *Backend) utxo(ctx context.Context, txHash *chainhash.Hash, vout uint32, redeemScript []byte) (*UTXO, error) {
  1038  	txOut, verboseTx, pkScript, err := dcr.getTxOutInfo(ctx, txHash, vout)
  1039  	if err != nil {
  1040  		return nil, err
  1041  	}
  1042  	if len(verboseTx.Vout) <= int(vout) { // shouldn't happen if gettxout worked
  1043  		return nil, fmt.Errorf("only %d outputs, requested index %d", len(verboseTx.Vout), vout)
  1044  	}
  1045  
  1046  	scriptVersion := txOut.ScriptPubKey.Version // or verboseTx.Vout[vout].Version
  1047  	inputNfo, err := dexdcr.InputInfo(scriptVersion, pkScript, redeemScript, chainParams)
  1048  	if err != nil {
  1049  		return nil, err
  1050  	}
  1051  	scriptType := inputNfo.ScriptType
  1052  
  1053  	// If it's a pay-to-script-hash, extract the script hash and check it against
  1054  	// the hash of the user-supplied redeem script.
  1055  	if scriptType.IsP2SH() {
  1056  		scriptHash := dexdcr.ExtractScriptHash(scriptVersion, pkScript)
  1057  		if !bytes.Equal(stdaddr.Hash160(redeemScript), scriptHash) {
  1058  			return nil, fmt.Errorf("script hash check failed for utxo %s,%d", txHash, vout)
  1059  		}
  1060  	}
  1061  
  1062  	blockHeight, blockHash, lastLookup, err := dcr.blockInfo(ctx, verboseTx)
  1063  	if err != nil {
  1064  		return nil, err
  1065  	}
  1066  
  1067  	// Coinbase, vote, and revocation transactions must mature before spending.
  1068  	var maturity int64
  1069  	if scriptType.IsStake() || txOut.Coinbase {
  1070  		// TODO: this is specific to the output with stake transactions. Must
  1071  		// check the output type.
  1072  		maturity = int64(chainParams.CoinbaseMaturity)
  1073  	}
  1074  	if txOut.Confirmations < maturity {
  1075  		return nil, immatureTransactionError
  1076  	}
  1077  
  1078  	tx, err := dcr.transaction(txHash, verboseTx)
  1079  	if err != nil {
  1080  		return nil, fmt.Errorf("error fetching verbose transaction data: %w", err)
  1081  	}
  1082  
  1083  	out := &Output{
  1084  		TXIO: TXIO{
  1085  			dcr:        dcr,
  1086  			tx:         tx,
  1087  			height:     blockHeight,
  1088  			blockHash:  blockHash,
  1089  			maturity:   int32(maturity),
  1090  			lastLookup: lastLookup,
  1091  		},
  1092  		vout:              vout,
  1093  		scriptType:        scriptType,
  1094  		scriptVersion:     scriptVersion,
  1095  		nonStandardScript: inputNfo.NonStandardScript,
  1096  		pkScript:          pkScript,
  1097  		redeemScript:      redeemScript,
  1098  		numSigs:           inputNfo.ScriptAddrs.NRequired,
  1099  		// The total size associated with the wire.TxIn.
  1100  		spendSize: inputNfo.Size(),
  1101  		value:     toAtoms(txOut.Value),
  1102  	}
  1103  	return &UTXO{out}, nil
  1104  }
  1105  
  1106  // newTXIO creates a TXIO for any transaction, spent or unspent. The caller must
  1107  // set the maturity field.
  1108  func (dcr *Backend) newTXIO(txHash *chainhash.Hash) (*TXIO, int64, error) {
  1109  	verboseTx, err := dcr.node.GetRawTransactionVerbose(dcr.ctx, txHash)
  1110  	if err != nil {
  1111  		if isTxNotFoundErr(err) {
  1112  			return nil, 0, asset.CoinNotFoundError
  1113  		}
  1114  		return nil, 0, fmt.Errorf("newTXIO: GetRawTransactionVerbose for txid %s: %w", txHash, translateRPCCancelErr(err))
  1115  	}
  1116  	tx, err := dcr.transaction(txHash, verboseTx)
  1117  	if err != nil {
  1118  		return nil, 0, fmt.Errorf("error fetching verbose transaction data: %w", err)
  1119  	}
  1120  	blockHeight, blockHash, lastLookup, err := dcr.blockInfo(dcr.ctx, verboseTx)
  1121  	if err != nil {
  1122  		return nil, 0, err
  1123  	}
  1124  	return &TXIO{
  1125  		dcr:       dcr,
  1126  		tx:        tx,
  1127  		height:    blockHeight,
  1128  		blockHash: blockHash,
  1129  		// maturity TODO: move this into an output specific type.
  1130  		lastLookup: lastLookup,
  1131  	}, verboseTx.Confirmations, nil
  1132  }
  1133  
  1134  // input gets the transaction input.
  1135  func (dcr *Backend) input(txHash *chainhash.Hash, vin uint32) (*Input, error) {
  1136  	txio, _, err := dcr.newTXIO(txHash)
  1137  	if err != nil {
  1138  		return nil, err
  1139  	}
  1140  	if int(vin) >= len(txio.tx.ins) {
  1141  		return nil, fmt.Errorf("tx %v has %d outputs (no vin %d)", txHash, len(txio.tx.ins), vin)
  1142  	}
  1143  	return &Input{
  1144  		TXIO: *txio,
  1145  		vin:  vin,
  1146  	}, nil
  1147  }
  1148  
  1149  // output gets the transaction output.
  1150  func (dcr *Backend) output(txHash *chainhash.Hash, vout uint32, redeemScript []byte) (*Output, error) {
  1151  	txio, confs, err := dcr.newTXIO(txHash)
  1152  	if err != nil {
  1153  		return nil, err
  1154  	}
  1155  	if int(vout) >= len(txio.tx.outs) {
  1156  		return nil, fmt.Errorf("tx %v has %d outputs (no vout %d)", txHash, len(txio.tx.outs), vout)
  1157  	}
  1158  
  1159  	txOut := txio.tx.outs[vout]
  1160  	pkScript := txOut.pkScript
  1161  	inputNfo, err := dexdcr.InputInfo(txOut.version, pkScript, redeemScript, chainParams)
  1162  	if err != nil {
  1163  		return nil, err
  1164  	}
  1165  	scriptType := inputNfo.ScriptType
  1166  
  1167  	// If it's a pay-to-script-hash, extract the script hash and check it against
  1168  	// the hash of the user-supplied redeem script.
  1169  	if scriptType.IsP2SH() {
  1170  		scriptHash := dexdcr.ExtractScriptHash(txOut.version, pkScript)
  1171  		if !bytes.Equal(stdaddr.Hash160(redeemScript), scriptHash) {
  1172  			return nil, fmt.Errorf("script hash check failed for output %s:%d", txHash, vout)
  1173  		}
  1174  	}
  1175  
  1176  	scrAddrs := inputNfo.ScriptAddrs
  1177  	addresses := make([]string, len(scrAddrs.PubKeys)+len(scrAddrs.PkHashes))
  1178  	for i, addr := range append(scrAddrs.PkHashes, scrAddrs.PubKeys...) {
  1179  		addresses[i] = addr.String()
  1180  	}
  1181  
  1182  	// Coinbase, vote, and revocation transactions must mature before spending.
  1183  	var maturity int64
  1184  	if scriptType.IsStake() || txio.tx.isCoinbase {
  1185  		maturity = int64(chainParams.CoinbaseMaturity)
  1186  	}
  1187  	if confs < maturity {
  1188  		return nil, immatureTransactionError
  1189  	}
  1190  	txio.maturity = int32(maturity)
  1191  
  1192  	return &Output{
  1193  		TXIO:              *txio,
  1194  		vout:              vout,
  1195  		value:             txOut.value,
  1196  		addresses:         addresses,
  1197  		scriptType:        scriptType,
  1198  		nonStandardScript: inputNfo.NonStandardScript,
  1199  		pkScript:          pkScript,
  1200  		redeemScript:      redeemScript,
  1201  		numSigs:           scrAddrs.NRequired,
  1202  		// The total size associated with the wire.TxIn.
  1203  		spendSize: inputNfo.Size(),
  1204  	}, nil
  1205  }
  1206  
  1207  // MsgTxFromHex creates a wire.MsgTx by deserializing the hex transaction.
  1208  func msgTxFromHex(txhex string) (*wire.MsgTx, error) {
  1209  	msgTx := wire.NewMsgTx()
  1210  	if err := msgTx.Deserialize(hex.NewDecoder(strings.NewReader(txhex))); err != nil {
  1211  		return nil, err
  1212  	}
  1213  	return msgTx, nil
  1214  }
  1215  
  1216  // Get information for an unspent transaction output.
  1217  func (dcr *Backend) getUnspentTxOut(ctx context.Context, txHash *chainhash.Hash, vout uint32, tree int8) (*chainjson.GetTxOutResult, []byte, error) {
  1218  	txOut, err := dcr.node.GetTxOut(ctx, txHash, vout, tree, true)
  1219  	if err != nil {
  1220  		return nil, nil, fmt.Errorf("GetTxOut error for output %s:%d: %w",
  1221  			txHash, vout, translateRPCCancelErr(err))
  1222  	}
  1223  	if txOut == nil {
  1224  		return nil, nil, asset.CoinNotFoundError
  1225  	}
  1226  	pkScript, err := hex.DecodeString(txOut.ScriptPubKey.Hex)
  1227  	if err != nil {
  1228  		return nil, nil, fmt.Errorf("failed to decode pubkey script from '%s' for output %s:%d", txOut.ScriptPubKey.Hex, txHash, vout)
  1229  	}
  1230  	return txOut, pkScript, nil
  1231  }
  1232  
  1233  // Get information for an unspent transaction output, plus the verbose
  1234  // transaction.
  1235  func (dcr *Backend) getTxOutInfo(ctx context.Context, txHash *chainhash.Hash, vout uint32) (*chainjson.GetTxOutResult, *chainjson.TxRawResult, []byte, error) {
  1236  	verboseTx, err := dcr.node.GetRawTransactionVerbose(ctx, txHash)
  1237  	if err != nil {
  1238  		if isTxNotFoundErr(err) { // since we're calling gettxout after this, check now
  1239  			return nil, nil, nil, asset.CoinNotFoundError
  1240  		}
  1241  		return nil, nil, nil, fmt.Errorf("getTxOutInfo: GetRawTransactionVerbose for txid %s: %w", txHash, translateRPCCancelErr(err))
  1242  	}
  1243  	msgTx, err := msgTxFromHex(verboseTx.Hex)
  1244  	if err != nil {
  1245  		return nil, nil, nil, fmt.Errorf("failed to decode MsgTx from hex for transaction %s: %w", txHash, err)
  1246  	}
  1247  	tree := determineTxTree(msgTx)
  1248  	txOut, pkScript, err := dcr.getUnspentTxOut(ctx, txHash, vout, tree)
  1249  	if err != nil {
  1250  		return nil, nil, nil, err
  1251  	}
  1252  	return txOut, verboseTx, pkScript, nil
  1253  }
  1254  
  1255  // determineTxTree determines if the transaction is in the regular transaction
  1256  // tree (wire.TxTreeRegular) or the stake tree (wire.TxTreeStake).
  1257  func determineTxTree(msgTx *wire.MsgTx) int8 {
  1258  	if stake.DetermineTxType(msgTx) != stake.TxTypeRegular {
  1259  		return wire.TxTreeStake
  1260  	}
  1261  	return wire.TxTreeRegular
  1262  }
  1263  
  1264  // Get the block information, checking the cache first. Same as
  1265  // getDcrBlock, but takes a string argument.
  1266  func (dcr *Backend) getBlockInfo(ctx context.Context, blockid string) (*dcrBlock, error) {
  1267  	blockHash, err := chainhash.NewHashFromStr(blockid)
  1268  	if err != nil {
  1269  		return nil, fmt.Errorf("unable to decode block hash from %s", blockid)
  1270  	}
  1271  	return dcr.getDcrBlock(ctx, blockHash)
  1272  }
  1273  
  1274  // Get the block information, checking the cache first.
  1275  func (dcr *Backend) getDcrBlock(ctx context.Context, blockHash *chainhash.Hash) (*dcrBlock, error) {
  1276  	cachedBlock, found := dcr.blockCache.block(blockHash)
  1277  	if found {
  1278  		return cachedBlock, nil
  1279  	}
  1280  	blockVerbose, err := dcr.node.GetBlockVerbose(ctx, blockHash, false)
  1281  	if err != nil {
  1282  		return nil, fmt.Errorf("error retrieving block %s: %w", blockHash, translateRPCCancelErr(err))
  1283  	}
  1284  	return dcr.blockCache.add(blockVerbose)
  1285  }
  1286  
  1287  // Get the mainchain block at the given height, checking the cache first.
  1288  func (dcr *Backend) getMainchainDcrBlock(ctx context.Context, height uint32) (*dcrBlock, error) {
  1289  	cachedBlock, found := dcr.blockCache.atHeight(height)
  1290  	if found {
  1291  		return cachedBlock, nil
  1292  	}
  1293  	hash, err := dcr.node.GetBlockHash(ctx, int64(height))
  1294  	if err != nil {
  1295  		// Likely not mined yet. Not an error.
  1296  		return nil, nil
  1297  	}
  1298  	return dcr.getDcrBlock(ctx, hash)
  1299  }
  1300  
  1301  // connectNodeRPC attempts to create a new websocket connection to a dcrd node
  1302  // with the given credentials and notification handlers.
  1303  func connectNodeRPC(host, user, pass, cert string) (*rpcclient.Client, error) {
  1304  
  1305  	dcrdCerts, err := os.ReadFile(cert)
  1306  	if err != nil {
  1307  		return nil, fmt.Errorf("TLS certificate read error: %w", err)
  1308  	}
  1309  
  1310  	config := &rpcclient.ConnConfig{
  1311  		Host:         host,
  1312  		Endpoint:     "ws", // websocket
  1313  		User:         user,
  1314  		Pass:         pass,
  1315  		Certificates: dcrdCerts,
  1316  	}
  1317  
  1318  	dcrdClient, err := rpcclient.New(config, nil)
  1319  	if err != nil {
  1320  		return nil, fmt.Errorf("Failed to start dcrd RPC client: %w", err)
  1321  	}
  1322  
  1323  	return dcrdClient, nil
  1324  }
  1325  
  1326  func connectNodeRelay(host, user, pass string) (*rpcclient.Client, error) {
  1327  	config := &rpcclient.ConnConfig{
  1328  		Host:         host,
  1329  		HTTPPostMode: true,
  1330  		DisableTLS:   true,
  1331  		User:         user,
  1332  		Pass:         pass,
  1333  	}
  1334  
  1335  	dcrdClient, err := rpcclient.New(config, nil)
  1336  	if err != nil {
  1337  		return nil, fmt.Errorf("failed to start dcrd RPC client: %w", err)
  1338  	}
  1339  
  1340  	return dcrdClient, nil
  1341  }
  1342  
  1343  // decodeCoinID decodes the coin ID into a tx hash and a vin/vout index.
  1344  func decodeCoinID(coinID []byte) (*chainhash.Hash, uint32, error) {
  1345  	if len(coinID) != 36 {
  1346  		return nil, 0, fmt.Errorf("coin ID wrong length. expected 36, got %d", len(coinID))
  1347  	}
  1348  	var txHash chainhash.Hash
  1349  	copy(txHash[:], coinID[:32])
  1350  	return &txHash, binary.BigEndian.Uint32(coinID[32:]), nil
  1351  }
  1352  
  1353  // toCoinID converts the outpoint to a coin ID.
  1354  func toCoinID(txHash *chainhash.Hash, vout uint32) []byte {
  1355  	hashLen := len(txHash)
  1356  	b := make([]byte, hashLen+4)
  1357  	copy(b[:hashLen], txHash[:])
  1358  	binary.BigEndian.PutUint32(b[hashLen:], vout)
  1359  	return b
  1360  }
  1361  
  1362  // Convert the DCR value to atoms.
  1363  func toAtoms(v float64) uint64 {
  1364  	return uint64(math.Round(v * conventionalConversionFactor))
  1365  }
  1366  
  1367  // isTxNotFoundErr will return true if the error indicates that the requested
  1368  // transaction is not known.
  1369  func isTxNotFoundErr(err error) bool {
  1370  	var rpcErr *dcrjson.RPCError
  1371  	return errors.As(err, &rpcErr) && rpcErr.Code == dcrjson.ErrRPCNoTxInfo
  1372  }