Add more mempool standard checks.

This commit adds a few more checks to restrict what transactions are
allowed into the transaction memory pool and therefore are candidates
to be mined and relayed.

In particular, the following changes were made to what is considered
standard:

- nulldata scripts are now supported and considered standard
- multi-signature transaction are now checked to ensure they only have a
  max of 3 pubkeys and the number of signatures doesn't exceed the number
  of pubkeys
- the number of inputs to a signature script must now match the expected
  number of inputs for the script type (includes support for additional
  pay-to-script-hash inputs)
- the number of inputs pushed onto the stack by a redeeming sig script
  must match the number of inputs consumed by the referenced pk script
- there can now only be a max of one nulldata output per transaction
This commit is contained in:
Dave Collins 2013-11-13 10:55:58 -06:00
parent e3eca752da
commit 50388bcf66

View file

@ -60,10 +60,10 @@ const (
// prosperity. 3*80 + 3*65 + 65 = 500 // prosperity. 3*80 + 3*65 + 65 = 500
maxStandardSigScriptSize = 500 maxStandardSigScriptSize = 500
// maxStandardMultiSigs is the maximum number of signatures // maxStandardMultiSigKeys is the maximum number of public keys allowed
// allowed in a multi-signature transaction output script for it to be // in a multi-signature transaction output script for it to be
// considered standard. // considered standard.
maxStandardMultiSigs = 3 maxStandardMultiSigKeys = 3
// minTxRelayFee is the minimum fee in satoshi that is required for a // minTxRelayFee is the minimum fee in satoshi that is required for a
// transaction to be treated as free for relay purposes. It is also // transaction to be treated as free for relay purposes. It is also
@ -157,22 +157,42 @@ func isDust(txOut *btcwire.TxOut) bool {
// checkPkScriptStandard performs a series of checks on a transaction ouput // checkPkScriptStandard performs a series of checks on a transaction ouput
// script (public key script) to ensure it is a "standard" public key script. // script (public key script) to ensure it is a "standard" public key script.
// A standard public key script is one that is a recognized form, and for // A standard public key script is one that is a recognized form, and for
// multi-signature scripts, only contains from 1 to 3 signatures. // multi-signature scripts, only contains from 1 to maxStandardMultiSigKeys
func checkPkScriptStandard(pkScript []byte) error { // public keys.
scriptClass := btcscript.GetScriptClass(pkScript) func checkPkScriptStandard(pkScript []byte, scriptClass btcscript.ScriptClass) error {
switch scriptClass { switch scriptClass {
case btcscript.MultiSigTy: case btcscript.MultiSigTy:
// TODO(davec): Need to get the actual number of signatures. numPubKeys, numSigs, err := btcscript.CalcMultiSigStats(pkScript)
numSigs := 1 if err != nil {
return err
}
// A standard multi-signature public key script must contain
// from 1 to maxStandardMultiSigKeys public keys.
if numPubKeys < 1 {
str := fmt.Sprintf("multi-signature script with no " +
"pubkeys")
return TxRuleError(str)
}
if numPubKeys > maxStandardMultiSigKeys {
str := fmt.Sprintf("multi-signature script with %d "+
"public keys which is more than the allowed "+
"max of %d", numPubKeys, maxStandardMultiSigKeys)
return TxRuleError(str)
}
// A standard multi-signature public key script must have at
// least 1 signature and no more signatures than available
// public keys.
if numSigs < 1 { if numSigs < 1 {
str := fmt.Sprintf("multi-signature script with no " + str := fmt.Sprintf("multi-signature script with no " +
"signatures") "signatures")
return TxRuleError(str) return TxRuleError(str)
} }
if numSigs > maxStandardMultiSigs { if numSigs > numPubKeys {
str := fmt.Sprintf("multi-signature script with %d "+ str := fmt.Sprintf("multi-signature script with %d "+
"signatures which is more than the allowed max "+ "signatures which is more than the available "+
"of %d", numSigs, maxStandardMultiSigs) "%d public keys", numSigs, numPubKeys)
return TxRuleError(str) return TxRuleError(str)
} }
@ -243,13 +263,20 @@ func checkTransactionStandard(tx *btcutil.Tx, height int64) error {
// None of the output public key scripts can be a non-standard script or // None of the output public key scripts can be a non-standard script or
// be "dust". // be "dust".
numNullDataOutputs := 0
for i, txOut := range msgTx.TxOut { for i, txOut := range msgTx.TxOut {
err := checkPkScriptStandard(txOut.PkScript) scriptClass := btcscript.GetScriptClass(txOut.PkScript)
err := checkPkScriptStandard(txOut.PkScript, scriptClass)
if err != nil { if err != nil {
str := fmt.Sprintf("transaction output %d: %v", i, err) str := fmt.Sprintf("transaction output %d: %v", i, err)
return TxRuleError(str) return TxRuleError(str)
} }
// Accumulate the number of outputs which only carry data.
if scriptClass == btcscript.NullDataTy {
numNullDataOutputs++
}
if isDust(txOut) { if isDust(txOut) {
str := fmt.Sprintf("transaction output %d: payment "+ str := fmt.Sprintf("transaction output %d: payment "+
"of %d is dust", i, txOut.Value) "of %d is dust", i, txOut.Value)
@ -257,17 +284,62 @@ func checkTransactionStandard(tx *btcutil.Tx, height int64) error {
} }
} }
// A standard transaction must not have more than one output script that
// only carries data.
if numNullDataOutputs > 1 {
return TxRuleError("more than one transaction output is a " +
"nulldata script")
}
return nil return nil
} }
// checkInputsStandard performs a series of checks on a transaction's inputs // checkInputsStandard performs a series of checks on a transaction's inputs
// to ensure they are "standard". A standard transaction input is one that // to ensure they are "standard". A standard transaction input is one that
// that consumes the same number of outputs from the stack as the output script // that consumes the expected number of elements from the stack and that number
// pushes. This help prevent resource exhaustion attacks by "creative" use of // is the same as the output script pushes. This help prevent resource
// scripts that are super expensive to process like OP_DUP OP_CHECKSIG OP_DROP // exhaustion attacks by "creative" use of scripts that are super expensive to
// repeated a large number of times followed by a final OP_TRUE. // process like OP_DUP OP_CHECKSIG OP_DROP repeated a large number of times
func checkInputsStandard(tx *btcutil.Tx) error { // followed by a final OP_TRUE.
// TODO(davec): Implement func checkInputsStandard(tx *btcutil.Tx, txStore btcchain.TxStore) error {
// NOTE: The reference implementation also does a coinbase check here,
// but coinbases have already been rejected prior to calling this
// function so no need to recheck.
for i, txIn := range tx.MsgTx().TxIn {
// It is safe to elide existence and index checks here since
// they have already been checked prior to calling this
// function.
prevOut := txIn.PreviousOutpoint
originTx := txStore[prevOut.Hash].Tx.MsgTx()
originPkScript := originTx.TxOut[prevOut.Index].PkScript
// Calculate stats for the script pair.
scriptInfo, err := btcscript.CalcScriptInfo(txIn.SignatureScript,
originPkScript, true)
if err != nil {
return err
}
// A negative value for expected inputs indicates the script is
// non-standard in some way.
if scriptInfo.ExpectedInputs < 0 {
str := fmt.Sprintf("transaction input #%d expects %d "+
"inputs", i, scriptInfo.ExpectedInputs)
return TxRuleError(str)
}
// The script pair is non-standard if the number of available
// inputs does not match the number of expected inputs.
if scriptInfo.NumInputs != scriptInfo.ExpectedInputs {
str := fmt.Sprintf("transaction input #%d expects %d "+
"inputs, but referenced output script only "+
"provides %d", i, scriptInfo.ExpectedInputs,
scriptInfo.NumInputs)
return TxRuleError(str)
}
}
return nil return nil
} }
@ -710,7 +782,7 @@ func (mp *txMemPool) maybeAcceptTransaction(tx *btcutil.Tx, isOrphan *bool) erro
// Don't allow transactions with non-standard inputs on the main // Don't allow transactions with non-standard inputs on the main
// network. // network.
if activeNetParams.btcnet == btcwire.MainNet { if activeNetParams.btcnet == btcwire.MainNet {
err := checkInputsStandard(tx) err := checkInputsStandard(tx, txStore)
if err != nil { if err != nil {
str := fmt.Sprintf("transaction %v has a non-standard "+ str := fmt.Sprintf("transaction %v has a non-standard "+
"input: %v", txHash, err) "input: %v", txHash, err)
@ -732,8 +804,8 @@ func (mp *txMemPool) maybeAcceptTransaction(tx *btcutil.Tx, isOrphan *bool) erro
} }
// TODO(davec): Rate-limit 'free' transactions. That is to say // TODO(davec): Rate-limit 'free' transactions. That is to say
// transactions which are less than the minimum relay fee and are there // transactions which are less than the minimum relay fee and are
// considered free. // therefore considered free.
// Verify crypto signatures for each input and reject the transaction if // Verify crypto signatures for each input and reject the transaction if
// any don't verify. // any don't verify.