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