decred.org/dcrwallet/v3@v3.1.0/wallet/txrules/rules.go (about)

     1  // Copyright (c) 2016 The btcsuite developers
     2  // Copyright (c) 2016-2021 The Decred developers
     3  // Use of this source code is governed by an ISC
     4  // license that can be found in the LICENSE file.
     5  
     6  package txrules
     7  
     8  import (
     9  	"decred.org/dcrwallet/v3/errors"
    10  	"github.com/decred/dcrd/dcrutil/v4"
    11  	"github.com/decred/dcrd/txscript/v4"
    12  	"github.com/decred/dcrd/txscript/v4/stdscript"
    13  	"github.com/decred/dcrd/wire"
    14  )
    15  
    16  // StakeSubScriptType potentially transforms the provided script type by
    17  // converting the various stake-specific script types to their associated sub
    18  // type.  It will be returned unmodified otherwise.
    19  func StakeSubScriptType(scriptType stdscript.ScriptType) (stdscript.ScriptType, bool) {
    20  	switch scriptType {
    21  	case stdscript.STStakeSubmissionPubKeyHash, stdscript.STStakeChangePubKeyHash,
    22  		stdscript.STStakeGenPubKeyHash, stdscript.STStakeRevocationPubKeyHash,
    23  		stdscript.STTreasuryGenPubKeyHash:
    24  
    25  		return stdscript.STPubKeyHashEcdsaSecp256k1, true
    26  
    27  	case stdscript.STStakeSubmissionScriptHash, stdscript.STStakeChangeScriptHash,
    28  		stdscript.STStakeGenScriptHash, stdscript.STStakeRevocationScriptHash,
    29  		stdscript.STTreasuryGenScriptHash:
    30  
    31  		return stdscript.STScriptHash, true
    32  	}
    33  
    34  	return scriptType, false
    35  }
    36  
    37  // DefaultRelayFeePerKb is the default minimum relay fee policy for a mempool.
    38  const DefaultRelayFeePerKb dcrutil.Amount = 0.0001 * 1e8
    39  
    40  // IsDustAmount determines whether a transaction output value and script length would
    41  // cause the output to be considered dust.  Transactions with dust outputs are
    42  // not standard and are rejected by mempools with default policies.
    43  func IsDustAmount(amount dcrutil.Amount, scriptSize int, relayFeePerKb dcrutil.Amount) bool {
    44  	// Calculate the total (estimated) cost to the network.  This is
    45  	// calculated using the serialize size of the output plus the serial
    46  	// size of a transaction input which redeems it.  The output is assumed
    47  	// to be compressed P2PKH as this is the most common script type.  Use
    48  	// the average size of a compressed P2PKH redeem input (165) rather than
    49  	// the largest possible (txsizes.RedeemP2PKHInputSize).
    50  	totalSize := 8 + 2 + wire.VarIntSerializeSize(uint64(scriptSize)) +
    51  		scriptSize + 165
    52  
    53  	// Dust is defined as an output value where the total cost to the network
    54  	// (output size + input size) is greater than 1/3 of the relay fee.
    55  	return int64(amount)*1000/(3*int64(totalSize)) < int64(relayFeePerKb)
    56  }
    57  
    58  // IsDustOutput determines whether a transaction output is considered dust.
    59  // Transactions with dust outputs are not standard and are rejected by mempools
    60  // with default policies.
    61  func IsDustOutput(output *wire.TxOut, relayFeePerKb dcrutil.Amount) bool {
    62  	// Unspendable outputs which solely carry data are not checked for dust.
    63  	if stdscript.IsNullDataScript(output.Version, output.PkScript) {
    64  		return false
    65  	}
    66  
    67  	// All other unspendable outputs are considered dust.
    68  	if txscript.IsUnspendable(output.Value, output.PkScript) {
    69  		return true
    70  	}
    71  
    72  	return IsDustAmount(dcrutil.Amount(output.Value), len(output.PkScript),
    73  		relayFeePerKb)
    74  }
    75  
    76  // CheckOutput performs simple consensus and policy tests on a transaction
    77  // output.  Returns with errors.Invalid if output violates consensus rules, and
    78  // errors.Policy if the output violates a non-consensus policy.
    79  func CheckOutput(output *wire.TxOut, relayFeePerKb dcrutil.Amount) error {
    80  	if output.Value < 0 {
    81  		return errors.E(errors.Invalid, "transaction output amount is negative")
    82  	}
    83  	if output.Value > dcrutil.MaxAmount {
    84  		return errors.E(errors.Invalid, "transaction output amount exceeds maximum value")
    85  	}
    86  	if IsDustOutput(output, relayFeePerKb) {
    87  		return errors.E(errors.Policy, "transaction output is dust")
    88  	}
    89  	return nil
    90  }
    91  
    92  // FeeForSerializeSize calculates the required fee for a transaction of some
    93  // arbitrary size given a mempool's relay fee policy.
    94  func FeeForSerializeSize(relayFeePerKb dcrutil.Amount, txSerializeSize int) dcrutil.Amount {
    95  	fee := relayFeePerKb * dcrutil.Amount(txSerializeSize) / 1000
    96  
    97  	if fee == 0 && relayFeePerKb > 0 {
    98  		fee = relayFeePerKb
    99  	}
   100  
   101  	if fee < 0 || fee > dcrutil.MaxAmount {
   102  		fee = dcrutil.MaxAmount
   103  	}
   104  
   105  	return fee
   106  }
   107  
   108  func sumOutputValues(outputs []*wire.TxOut) (totalOutput dcrutil.Amount) {
   109  	for _, txOut := range outputs {
   110  		totalOutput += dcrutil.Amount(txOut.Value)
   111  	}
   112  	return totalOutput
   113  }
   114  
   115  // PaysHighFees checks whether the signed transaction pays insanely high fees.
   116  // Transactons are defined to have a high fee if they have pay a fee rate that
   117  // is 1000 time higher than the default fee.
   118  func PaysHighFees(totalInput dcrutil.Amount, tx *wire.MsgTx) bool {
   119  	fee := totalInput - sumOutputValues(tx.TxOut)
   120  	if fee <= 0 {
   121  		// Impossible to determine
   122  		return false
   123  	}
   124  
   125  	maxFee := FeeForSerializeSize(1000*DefaultRelayFeePerKb, tx.SerializeSize())
   126  	return fee > maxFee
   127  }
   128  
   129  // TxPaysHighFees checks whether the signed transaction pays insanely high fees.
   130  // Transactons are defined to have a high fee if they have pay a fee rate that
   131  // is 1000 time higher than the default fee.  Total transaction input value is
   132  // determined by summing the ValueIn fields of each input, and an error is returned
   133  // if any input values were the null value.
   134  func TxPaysHighFees(tx *wire.MsgTx) (bool, error) {
   135  	var input dcrutil.Amount
   136  	for i, in := range tx.TxIn {
   137  		if in.ValueIn < 0 {
   138  			err := errors.Errorf("transaction input %d does not "+
   139  				"specify the input value", i)
   140  			return false, errors.E(errors.Invalid, err)
   141  		}
   142  		input += dcrutil.Amount(in.ValueIn)
   143  	}
   144  	return PaysHighFees(input, tx), nil
   145  }