decred.org/dcrdex@v1.0.5/server/asset/btc/utxo.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  	"errors"
    10  	"fmt"
    11  
    12  	"decred.org/dcrdex/dex"
    13  	dexbtc "decred.org/dcrdex/dex/networks/btc"
    14  	"decred.org/dcrdex/server/asset"
    15  	"github.com/btcsuite/btcd/btcutil"
    16  	"github.com/btcsuite/btcd/chaincfg/chainhash"
    17  )
    18  
    19  const ErrReorgDetected = dex.ErrorKind("reorg detected")
    20  
    21  // TXIO is common information stored with an Input or Output.
    22  type TXIO struct {
    23  	// Because a TXIO's validity and block info can change after creation, keep a
    24  	// Backend around to query the state of the tx and update the block info.
    25  	btc *Backend
    26  	tx  *Tx
    27  	// The height and hash of the transaction's best known block.
    28  	height    uint32
    29  	blockHash chainhash.Hash
    30  	// The number of confirmations needed for maturity. For outputs of coinbase
    31  	// transactions, this will be set to chaincfg.Params.CoinbaseMaturity (256 for
    32  	// mainchain). For other supported script types, this will be zero.
    33  	maturity int32
    34  	// While the TXIO's tx is still in mempool, the tip hash will be stored.
    35  	// This enables an optimization in the Confirmations method to return zero
    36  	// without extraneous RPC calls.
    37  	lastLookup *chainhash.Hash
    38  }
    39  
    40  // confirmations returns the number of confirmations for a TXIO's transaction.
    41  // Because a tx can become invalid after once being considered valid, validity
    42  // should be verified again on every call. An error will be returned if this
    43  // TXIO is no longer ready to spend. An unmined transaction should have zero
    44  // confirmations. A transaction in the current best block should have one
    45  // confirmation. The value -1 will be returned with any error.
    46  func (txio *TXIO) confirmations() (int64, error) {
    47  	btc := txio.btc
    48  	tipHash := btc.blockCache.tipHash()
    49  	// If the tx was a mempool transaction, check if it has been confirmed.
    50  	if txio.height == 0 {
    51  		// If the tip hasn't changed, don't do anything here.
    52  		if txio.lastLookup == nil || *txio.lastLookup != tipHash {
    53  			txio.lastLookup = &tipHash
    54  			verboseTx, err := txio.btc.node.GetRawTransactionVerbose(&txio.tx.hash)
    55  			if err != nil {
    56  				return -1, fmt.Errorf("GetRawTransactionVerbose for txid %s: %w", txio.tx.hash, err)
    57  			}
    58  			// More than zero confirmations would indicate that the transaction has
    59  			// been mined. Collect the block info and update the tx fields.
    60  			if verboseTx.Confirmations > 0 {
    61  				blk, err := txio.btc.getBlockInfo(verboseTx.BlockHash)
    62  				if err != nil {
    63  					return -1, err
    64  				}
    65  				txio.height = blk.height
    66  				txio.blockHash = blk.hash
    67  			}
    68  			return int64(verboseTx.Confirmations), nil
    69  		}
    70  	} else {
    71  		// The tx was included in a block, but make sure that the tx's block has
    72  		// not been orphaned.
    73  		mainchainBlock, found := btc.blockCache.atHeight(txio.height)
    74  		if !found || mainchainBlock.hash != txio.blockHash {
    75  			return -1, ErrReorgDetected
    76  		}
    77  	}
    78  	// If the height is still 0, this is a mempool transaction.
    79  	if txio.height == 0 {
    80  		return 0, nil
    81  	}
    82  	// Otherwise just check that there hasn't been a reorg which would render the
    83  	// output immature. This would be exceedingly rare (impossible?).
    84  	confs := int32(btc.blockCache.tipHeight()) - int32(txio.height) + 1
    85  	if confs < txio.maturity {
    86  		return -1, fmt.Errorf("transaction %s became immature", txio.tx.hash)
    87  	}
    88  	return int64(confs), nil
    89  }
    90  
    91  // TxID is a string identifier for the transaction, typically a hexadecimal
    92  // representation of the byte-reversed transaction hash.
    93  func (txio *TXIO) TxID() string {
    94  	return txio.tx.hash.String()
    95  }
    96  
    97  // FeeRate returns the transaction fee rate, in satoshi/vbyte.
    98  func (txio *TXIO) FeeRate() uint64 {
    99  	return txio.tx.feeRate
   100  }
   101  
   102  func (txio *TXIO) InputsValue() uint64 {
   103  	return txio.tx.inputSum
   104  }
   105  
   106  func (txio *TXIO) RawTx() []byte {
   107  	return txio.tx.raw
   108  }
   109  
   110  // Input is a transaction input.
   111  type Input struct {
   112  	TXIO
   113  	vin uint32
   114  }
   115  
   116  var _ asset.Coin = (*Input)(nil)
   117  
   118  // Value is the value of the previous output spent by the input.
   119  func (input *Input) Value() uint64 {
   120  	return input.TXIO.tx.ins[input.vin].value
   121  }
   122  
   123  // String creates a human-readable representation of a Bitcoin transaction input
   124  // in the format "{txid = [transaction hash], vin = [input index]}".
   125  func (input *Input) String() string {
   126  	return fmt.Sprintf("{txid = %s, vin = %d}", input.TxID(), input.vin)
   127  }
   128  
   129  // Confirmations returns the number of confirmations on this input's
   130  // transaction.
   131  func (input *Input) Confirmations(context.Context) (int64, error) {
   132  	confs, err := input.confirmations()
   133  	if errors.Is(err, ErrReorgDetected) {
   134  		newInput, err := input.btc.input(&input.tx.hash, input.vin)
   135  		if err != nil {
   136  			return -1, fmt.Errorf("input block is not mainchain")
   137  		}
   138  		*input = *newInput
   139  		return input.Confirmations(context.Background())
   140  	}
   141  	return confs, err
   142  }
   143  
   144  // ID returns the coin ID.
   145  func (input *Input) ID() []byte {
   146  	return toCoinID(&input.tx.hash, input.vin)
   147  }
   148  
   149  // spendsCoin checks whether a particular coin is spent in this coin's tx.
   150  func (input *Input) spendsCoin(coinID []byte) (bool, error) {
   151  	txHash, vout, err := decodeCoinID(coinID)
   152  	if err != nil {
   153  		return false, fmt.Errorf("error decoding coin ID %x: %w", coinID, err)
   154  	}
   155  	if uint32(len(input.tx.ins)) < input.vin+1 {
   156  		return false, nil
   157  	}
   158  	txIn := input.tx.ins[input.vin]
   159  	return txIn.prevTx == *txHash && txIn.vout == vout, nil
   160  }
   161  
   162  // Output represents a transaction output.
   163  type Output struct {
   164  	TXIO
   165  	vout uint32
   166  	// The output value.
   167  	value     uint64
   168  	addresses []string
   169  	// A bitmask for script type information.
   170  	scriptType dexbtc.BTCScriptType
   171  	// If the pkScript, or redeemScript in the case of a P2SH/P2WSH pkScript, is
   172  	// non-standard according to txscript.
   173  	nonStandardScript bool
   174  	// The output's scriptPubkey.
   175  	pkScript []byte
   176  	// If the pubkey script is P2SH or P2WSH, the Output will only be generated
   177  	// if the redeem script is supplied and the script-hash validated. For P2PKH
   178  	// and P2WPKH pubkey scripts, the redeem script should be nil.
   179  	redeemScript []byte
   180  	// numSigs is the number of signatures required to spend this output.
   181  	numSigs int
   182  	// spendSize stores the best estimate of the size (bytes) of the serialized
   183  	// transaction input that spends this Output.
   184  	spendSize uint32
   185  }
   186  
   187  // Confirmations returns the number of confirmations on this output's
   188  // transaction.
   189  func (output *Output) Confirmations(context.Context) (int64, error) {
   190  	confs, err := output.confirmations()
   191  	if errors.Is(err, ErrReorgDetected) {
   192  		newOut, err := output.btc.output(&output.tx.hash, output.vout, output.redeemScript)
   193  		if err != nil {
   194  			return -1, fmt.Errorf("output block is not mainchain")
   195  		}
   196  		*output = *newOut
   197  		return output.Confirmations(context.Background())
   198  	}
   199  	return confs, err
   200  }
   201  
   202  var _ asset.Coin = (*Output)(nil)
   203  
   204  // SpendSize returns the maximum size of the serialized TxIn that spends this
   205  // output, in bytes. This is a method of the asset.Output interface.
   206  func (output *Output) SpendSize() uint32 {
   207  	return output.spendSize
   208  }
   209  
   210  // ID returns the coin ID.
   211  func (output *Output) ID() []byte {
   212  	return toCoinID(&output.tx.hash, output.vout)
   213  }
   214  
   215  // Value is the output value, in satoshis.
   216  func (output *Output) Value() uint64 {
   217  	return output.value // == output.TXIO.tx.outs[output.vout].value
   218  }
   219  
   220  func (output *Output) Addresses() []string {
   221  	return output.addresses
   222  }
   223  
   224  // String creates a human-readable representation of a Bitcoin transaction output
   225  // in the format "{txid = [transaction hash], vout = [output index]}".
   226  func (output *Output) String() string {
   227  	return fmt.Sprintf("{txid = %s, vout = %d}", output.TxID(), output.vout)
   228  }
   229  
   230  // Auth verifies that the output pays to the supplied public key(s). This is an
   231  // asset.Backend method.
   232  func (output *Output) Auth(pubkeys, sigs [][]byte, msg []byte) error {
   233  	// If there are not enough pubkeys, no reason to check anything.
   234  	if len(pubkeys) < output.numSigs {
   235  		return fmt.Errorf("not enough signatures for output %s:%d. expected %d, got %d",
   236  			output.tx.hash, output.vout, output.numSigs, len(pubkeys))
   237  	}
   238  	// Extract the addresses from the pubkey scripts and redeem scripts.
   239  	evalScript := output.pkScript
   240  	if output.scriptType.IsP2SH() || output.scriptType.IsP2WSH() {
   241  		evalScript = output.redeemScript
   242  	}
   243  	scriptAddrs, nonStandard, err := dexbtc.ExtractScriptAddrs(evalScript, output.btc.chainParams)
   244  	if err != nil {
   245  		return err
   246  	}
   247  	if nonStandard {
   248  		return fmt.Errorf("non-standard script")
   249  	}
   250  	// Ensure that at least 1 signature is required to spend this output.
   251  	// Non-standard scripts are already be caught, but check again here in case
   252  	// this can happen another way. Note that Auth may be called via an
   253  	// interface, where this requirement may not fit into a generic spendability
   254  	// check.
   255  	if scriptAddrs.NRequired == 0 {
   256  		return fmt.Errorf("script requires no signatures to spend")
   257  	}
   258  	// Sanity check that the required signature count matches the count parsed
   259  	// during output initialization.
   260  	if scriptAddrs.NRequired != output.numSigs {
   261  		return fmt.Errorf("signature requirement mismatch. required: %d, matched: %d",
   262  			scriptAddrs.NRequired, output.numSigs)
   263  	}
   264  	matches := append(pkMatches(pubkeys, scriptAddrs.PubKeys, nil),
   265  		pkMatches(pubkeys, scriptAddrs.PkHashes, btcutil.Hash160)...)
   266  	if len(matches) < output.numSigs {
   267  		return fmt.Errorf("not enough pubkey matches to satisfy the script for output %s:%d. expected %d, got %d",
   268  			output.tx.hash, output.vout, output.numSigs, len(matches))
   269  	}
   270  	for _, match := range matches {
   271  		err := checkSig(msg, match.pubkey, sigs[match.idx])
   272  		if err != nil {
   273  			return err
   274  		}
   275  	}
   276  	return nil
   277  }
   278  
   279  // TODO: Eliminate the UTXO type. Instead use Output (asset.Coin) and check for
   280  // spendability in the consumer as needed. This is left as is to retain current
   281  // behavior with respect to the unspent requirements.
   282  
   283  // A UTXO is information regarding an unspent transaction output.
   284  type UTXO struct {
   285  	*Output
   286  }
   287  
   288  func (utxo *UTXO) Coin() asset.Coin {
   289  	return utxo
   290  }
   291  
   292  // Confirmations returns the number of confirmations on this output's
   293  // transaction. See also (*Output).Confirmations. This function differs from the
   294  // Output method in that it is necessary to relocate the utxo after a reorg, it
   295  // may error if the output is spent.
   296  func (utxo *UTXO) Confirmations(context.Context) (int64, error) {
   297  	confs, err := utxo.confirmations()
   298  	if errors.Is(err, ErrReorgDetected) {
   299  		// See if we can find the utxo in another block.
   300  		newUtxo, err := utxo.btc.utxo(&utxo.tx.hash, utxo.vout, utxo.redeemScript)
   301  		if err != nil {
   302  			return -1, fmt.Errorf("utxo error: %w", err)
   303  		}
   304  		*utxo = *newUtxo
   305  		return utxo.Confirmations(context.Background())
   306  	}
   307  	return confs, err
   308  }
   309  
   310  var _ asset.FundingCoin = (*UTXO)(nil)
   311  
   312  type pkMatch struct {
   313  	pubkey []byte
   314  	idx    int
   315  }
   316  
   317  // pkMatches looks through a set of addresses and a returns a set of match
   318  // structs with details about the match.
   319  func pkMatches(pubkeys [][]byte, addrs []btcutil.Address, hasher func([]byte) []byte) []pkMatch {
   320  	matches := make([]pkMatch, 0, len(pubkeys))
   321  	if hasher == nil {
   322  		hasher = func(a []byte) []byte { return a }
   323  	}
   324  	matchIndex := make(map[string]struct{})
   325  	for _, addr := range addrs {
   326  		for i, pubkey := range pubkeys {
   327  			if bytes.Equal(addr.ScriptAddress(), hasher(pubkey)) {
   328  				addrStr := addr.String()
   329  				_, alreadyFound := matchIndex[addrStr]
   330  				if alreadyFound {
   331  					continue
   332  				}
   333  				matchIndex[addrStr] = struct{}{}
   334  				matches = append(matches, pkMatch{
   335  					pubkey: pubkey,
   336  					idx:    i,
   337  				})
   338  				break
   339  			}
   340  		}
   341  	}
   342  	return matches
   343  }