From 3d6afcffe7b758fcb7f65e1ef593c9cbca50f7ab Mon Sep 17 00:00:00 2001 From: David Hill Date: Wed, 21 Oct 2015 13:53:25 -0400 Subject: [PATCH] Move non-mempool specific functions to new file. No functional change. Add tests. --- mempool.go | 284 +--------------------------------------------- policy.go | 297 +++++++++++++++++++++++++++++++++++++++++++++++++ policy_test.go | 242 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 541 insertions(+), 282 deletions(-) create mode 100644 policy.go create mode 100644 policy_test.go diff --git a/mempool.go b/mempool.go index 69171745a3..12e12b39b4 100644 --- a/mempool.go +++ b/mempool.go @@ -63,11 +63,6 @@ const ( // (1 + 15*74 + 3) + (15*34 + 3) + 23 = 1650 maxStandardSigScriptSize = 1650 - // maxStandardMultiSigKeys is the maximum number of public keys allowed - // in a multi-signature transaction output script for it to be - // considered standard. - maxStandardMultiSigKeys = 3 - // defaultMinRelayTxFee is the minimum fee in satoshi that is required // for a transaction to be treated as free for relay and mining // purposes. It is also used to help determine if a transaction is @@ -102,125 +97,6 @@ type txMemPool struct { lastPennyUnix int64 // unix time of last ``penny spend'' } -// isDust returns whether or not the passed transaction output amount is -// considered dust or not. Dust is defined in terms of the minimum transaction -// relay fee. In particular, if the cost to the network to spend coins is more -// than 1/3 of the minimum transaction relay fee, it is considered dust. -func isDust(txOut *wire.TxOut) bool { - // Unspendable outputs are considered dust. - if txscript.IsUnspendable(txOut.PkScript) { - return true - } - - // The total serialized size consists of the output and the associated - // input script to redeem it. Since there is no input script - // to redeem it yet, use the minimum size of a typical input script. - // - // Pay-to-pubkey-hash bytes breakdown: - // - // Output to hash (34 bytes): - // 8 value, 1 script len, 25 script [1 OP_DUP, 1 OP_HASH_160, - // 1 OP_DATA_20, 20 hash, 1 OP_EQUALVERIFY, 1 OP_CHECKSIG] - // - // Input with compressed pubkey (148 bytes): - // 36 prev outpoint, 1 script len, 107 script [1 OP_DATA_72, 72 sig, - // 1 OP_DATA_33, 33 compressed pubkey], 4 sequence - // - // Input with uncompressed pubkey (180 bytes): - // 36 prev outpoint, 1 script len, 139 script [1 OP_DATA_72, 72 sig, - // 1 OP_DATA_65, 65 compressed pubkey], 4 sequence - // - // Pay-to-pubkey bytes breakdown: - // - // Output to compressed pubkey (44 bytes): - // 8 value, 1 script len, 35 script [1 OP_DATA_33, - // 33 compressed pubkey, 1 OP_CHECKSIG] - // - // Output to uncompressed pubkey (76 bytes): - // 8 value, 1 script len, 67 script [1 OP_DATA_65, 65 pubkey, - // 1 OP_CHECKSIG] - // - // Input (114 bytes): - // 36 prev outpoint, 1 script len, 73 script [1 OP_DATA_72, - // 72 sig], 4 sequence - // - // Theoretically this could examine the script type of the output script - // and use a different size for the typical input script size for - // pay-to-pubkey vs pay-to-pubkey-hash inputs per the above breakdowns, - // but the only combinination which is less than the value chosen is - // a pay-to-pubkey script with a compressed pubkey, which is not very - // common. - // - // The most common scripts are pay-to-pubkey-hash, and as per the above - // breakdown, the minimum size of a p2pkh input script is 148 bytes. So - // that figure is used. - totalSize := txOut.SerializeSize() + 148 - - // The output is considered dust if the cost to the network to spend the - // coins is more than 1/3 of the minimum free transaction relay fee. - // minFreeTxRelayFee is in Satoshi/KB, so multiply by 1000 to - // convert to bytes. - // - // Using the typical values for a pay-to-pubkey-hash transaction from - // the breakdown above and the default minimum free transaction relay - // fee of 1000, this equates to values less than 546 satoshi being - // considered dust. - // - // The following is equivalent to (value/totalSize) * (1/3) * 1000 - // without needing to do floating point math. - return txOut.Value*1000/(3*int64(totalSize)) < int64(cfg.minRelayTxFee) -} - -// checkPkScriptStandard performs a series of checks on a transaction ouput -// 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 -// multi-signature scripts, only contains from 1 to maxStandardMultiSigKeys -// public keys. -func checkPkScriptStandard(pkScript []byte, scriptClass txscript.ScriptClass) error { - switch scriptClass { - case txscript.MultiSigTy: - numPubKeys, numSigs, err := txscript.CalcMultiSigStats(pkScript) - if err != nil { - str := fmt.Sprintf("multi-signature script parse "+ - "failure: %v", err) - return txRuleError(wire.RejectNonstandard, str) - } - - // A standard multi-signature public key script must contain - // from 1 to maxStandardMultiSigKeys public keys. - if numPubKeys < 1 { - str := "multi-signature script with no pubkeys" - return txRuleError(wire.RejectNonstandard, 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(wire.RejectNonstandard, 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 { - return txRuleError(wire.RejectNonstandard, - "multi-signature script with no signatures") - } - if numSigs > numPubKeys { - str := fmt.Sprintf("multi-signature script with %d "+ - "signatures which is more than the available "+ - "%d public keys", numSigs, numPubKeys) - return txRuleError(wire.RejectNonstandard, str) - } - - case txscript.NonStandardTy: - return txRuleError(wire.RejectNonstandard, - "non-standard script form") - } - - return nil -} - // checkTransactionStandard performs a series of checks on a transaction to // ensure it is a "standard" transaction. A standard transaction is one that // conforms to several additional limiting cases over what is considered a @@ -303,7 +179,7 @@ func (mp *txMemPool) checkTransactionStandard(tx *btcutil.Tx, height int32) erro // "dust". if scriptClass == txscript.NullDataTy { numNullDataOutputs++ - } else if isDust(txOut) { + } else if isDust(txOut, cfg.minRelayTxFee) { str := fmt.Sprintf("transaction output %d: payment "+ "of %d is dust", i, txOut.Value) return txRuleError(wire.RejectDust, str) @@ -320,78 +196,6 @@ func (mp *txMemPool) checkTransactionStandard(tx *btcutil.Tx, height int32) erro return nil } -// checkInputsStandard performs a series of checks on a transaction's inputs -// to ensure they are "standard". A standard transaction input is one that -// that consumes the expected number of elements from the stack and that number -// is the same as the output script pushes. This help prevent resource -// exhaustion attacks by "creative" use of scripts that are super expensive to -// process like OP_DUP OP_CHECKSIG OP_DROP repeated a large number of times -// followed by a final OP_TRUE. -func checkInputsStandard(tx *btcutil.Tx, txStore blockchain.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 := txscript.CalcScriptInfo(txIn.SignatureScript, - originPkScript, true) - if err != nil { - str := fmt.Sprintf("transaction input #%d script parse "+ - "failure: %v", i, err) - return txRuleError(wire.RejectNonstandard, str) - } - - // 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(wire.RejectNonstandard, 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 provides "+ - "%d", i, scriptInfo.ExpectedInputs, - scriptInfo.NumInputs) - return txRuleError(wire.RejectNonstandard, str) - } - } - - return nil -} - -// calcMinRequiredTxRelayFee returns the minimum transaction fee required for a -// transaction with the passed serialized size to be accepted into the memory -// pool and relayed. -func calcMinRequiredTxRelayFee(serializedSize int64) int64 { - // Calculate the minimum fee for a transaction to be allowed into the - // mempool and relayed by scaling the base fee (which is the minimum - // free transaction relay fee). cfg.minRelayTxFee is in Satoshi/KB, so - // divide the transaction size by 1000 to convert to kilobytes. Also, - // integer division is used so fees only increase on full kilobyte - // boundaries. - minFee := (1 + serializedSize/1000) * int64(cfg.minRelayTxFee) - - // Set the minimum fee to the maximum possible value if the calculated - // fee is not in the valid range for monetary amounts. - if minFee < 0 || minFee > btcutil.MaxSatoshi { - minFee = btcutil.MaxSatoshi - } - - return minFee -} - // removeOrphan is the internal function which implements the public // RemoveOrphan. See the comment for RemoveOrphan for more details. // @@ -788,90 +592,6 @@ func (mp *txMemPool) indexScriptAddressToTx(pkScript []byte, tx *btcutil.Tx) err return nil } -// calcInputValueAge is a helper function used to calculate the input age of -// a transaction. The input age for a txin is the number of confirmations -// since the referenced txout multiplied by its output value. The total input -// age is the sum of this value for each txin. Any inputs to the transaction -// which are currently in the mempool and hence not mined into a block yet, -// contribute no additional input age to the transaction. -func calcInputValueAge(txDesc *TxDesc, txStore blockchain.TxStore, nextBlockHeight int32) float64 { - var totalInputAge float64 - for _, txIn := range txDesc.Tx.MsgTx().TxIn { - originHash := &txIn.PreviousOutPoint.Hash - originIndex := txIn.PreviousOutPoint.Index - - // Don't attempt to accumulate the total input age if the txIn - // in question doesn't exist. - if txData, exists := txStore[*originHash]; exists && txData.Tx != nil { - // Inputs with dependencies currently in the mempool - // have their block height set to a special constant. - // Their input age should computed as zero since their - // parent hasn't made it into a block yet. - var inputAge int32 - if txData.BlockHeight == mempoolHeight { - inputAge = 0 - } else { - inputAge = nextBlockHeight - txData.BlockHeight - } - - // Sum the input value times age. - originTxOut := txData.Tx.MsgTx().TxOut[originIndex] - inputValue := originTxOut.Value - totalInputAge += float64(inputValue * int64(inputAge)) - } - } - - return totalInputAge -} - -// minInt is a helper function to return the minimum of two ints. This avoids -// a math import and the need to cast to floats. -func minInt(a, b int) int { - if a < b { - return a - } - return b -} - -// calcPriority returns a transaction priority given a transaction and the sum -// of each of its input values multiplied by their age (# of confirmations). -// Thus, the final formula for the priority is: -// sum(inputValue * inputAge) / adjustedTxSize -func calcPriority(tx *btcutil.Tx, inputValueAge float64) float64 { - // In order to encourage spending multiple old unspent transaction - // outputs thereby reducing the total set, don't count the constant - // overhead for each input as well as enough bytes of the signature - // script to cover a pay-to-script-hash redemption with a compressed - // pubkey. This makes additional inputs free by boosting the priority - // of the transaction accordingly. No more incentive is given to avoid - // encouraging gaming future transactions through the use of junk - // outputs. This is the same logic used in the reference - // implementation. - // - // The constant overhead for a txin is 41 bytes since the previous - // outpoint is 36 bytes + 4 bytes for the sequence + 1 byte the - // signature script length. - // - // A compressed pubkey pay-to-script-hash redemption with a maximum len - // signature is of the form: - // [OP_DATA_73 <73-byte sig> + OP_DATA_35 + {OP_DATA_33 - // <33 byte compresed pubkey> + OP_CHECKSIG}] - // - // Thus 1 + 73 + 1 + 1 + 33 + 1 = 110 - overhead := 0 - for _, txIn := range tx.MsgTx().TxIn { - // Max inputs + size can't possibly overflow here. - overhead += 41 + minInt(110, len(txIn.SignatureScript)) - } - - serializedTxSize := tx.MsgTx().SerializeSize() - if overhead >= serializedTxSize { - return 0.0 - } - - return inputValueAge / float64(serializedTxSize-overhead) -} - // StartingPriority calculates the priority of this tx descriptor's underlying // transaction relative to when it was first added to the mempool. The result // is lazily computed and then cached for subsequent function calls. @@ -1168,7 +888,7 @@ func (mp *txMemPool) maybeAcceptTransaction(tx *btcutil.Tx, isNew, rateLimit boo // transaction does not exceeed 1000 less than the reserved space for // high-priority transactions, don't require a fee for it. serializedSize := int64(tx.MsgTx().SerializeSize()) - minFee := calcMinRequiredTxRelayFee(serializedSize) + minFee := calcMinRequiredTxRelayFee(serializedSize, cfg.minRelayTxFee) if serializedSize >= (defaultBlockPrioritySize-1000) && txFee < minFee { str := fmt.Sprintf("transaction %v has %d fees which is under "+ "the required amount of %d", txHash, txFee, diff --git a/policy.go b/policy.go new file mode 100644 index 0000000000..a0f78b33dd --- /dev/null +++ b/policy.go @@ -0,0 +1,297 @@ +// Copyright (c) 2013-2015 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package main + +import ( + "fmt" + + "github.com/btcsuite/btcd/blockchain" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcutil" +) + +const ( + // maxStandardMultiSigKeys is the maximum number of public keys allowed + // in a multi-signature transaction output script for it to be + // considered standard. + maxStandardMultiSigKeys = 3 +) + +// calcMinRequiredTxRelayFee returns the minimum transaction fee required for a +// transaction with the passed serialized size to be accepted into the memory +// pool and relayed. +func calcMinRequiredTxRelayFee(serializedSize int64, minRelayTxFee btcutil.Amount) int64 { + // Calculate the minimum fee for a transaction to be allowed into the + // mempool and relayed by scaling the base fee (which is the minimum + // free transaction relay fee). minRelayTxFee is in Satoshi/KB, so + // divide the transaction size by 1000 to convert to kilobytes. Also, + // integer division is used so fees only increase on full kilobyte + // boundaries. + minFee := (1 + serializedSize/1000) * int64(minRelayTxFee) + + // Set the minimum fee to the maximum possible value if the calculated + // fee is not in the valid range for monetary amounts. + if minFee < 0 || minFee > btcutil.MaxSatoshi { + minFee = btcutil.MaxSatoshi + } + + return minFee +} + +// calcPriority returns a transaction priority given a transaction and the sum +// of each of its input values multiplied by their age (# of confirmations). +// Thus, the final formula for the priority is: +// sum(inputValue * inputAge) / adjustedTxSize +func calcPriority(tx *btcutil.Tx, inputValueAge float64) float64 { + // In order to encourage spending multiple old unspent transaction + // outputs thereby reducing the total set, don't count the constant + // overhead for each input as well as enough bytes of the signature + // script to cover a pay-to-script-hash redemption with a compressed + // pubkey. This makes additional inputs free by boosting the priority + // of the transaction accordingly. No more incentive is given to avoid + // encouraging gaming future transactions through the use of junk + // outputs. This is the same logic used in the reference + // implementation. + // + // The constant overhead for a txin is 41 bytes since the previous + // outpoint is 36 bytes + 4 bytes for the sequence + 1 byte the + // signature script length. + // + // A compressed pubkey pay-to-script-hash redemption with a maximum len + // signature is of the form: + // [OP_DATA_73 <73-byte sig> + OP_DATA_35 + {OP_DATA_33 + // <33 byte compresed pubkey> + OP_CHECKSIG}] + // + // Thus 1 + 73 + 1 + 1 + 33 + 1 = 110 + overhead := 0 + for _, txIn := range tx.MsgTx().TxIn { + // Max inputs + size can't possibly overflow here. + overhead += 41 + minInt(110, len(txIn.SignatureScript)) + } + + serializedTxSize := tx.MsgTx().SerializeSize() + if overhead >= serializedTxSize { + return 0.0 + } + + return inputValueAge / float64(serializedTxSize-overhead) +} + +// calcInputValueAge is a helper function used to calculate the input age of +// a transaction. The input age for a txin is the number of confirmations +// since the referenced txout multiplied by its output value. The total input +// age is the sum of this value for each txin. Any inputs to the transaction +// which are currently in the mempool and hence not mined into a block yet, +// contribute no additional input age to the transaction. +func calcInputValueAge(txDesc *TxDesc, txStore blockchain.TxStore, nextBlockHeight int32) float64 { + var totalInputAge float64 + for _, txIn := range txDesc.Tx.MsgTx().TxIn { + originHash := &txIn.PreviousOutPoint.Hash + originIndex := txIn.PreviousOutPoint.Index + + // Don't attempt to accumulate the total input age if the txIn + // in question doesn't exist. + if txData, exists := txStore[*originHash]; exists && txData.Tx != nil { + // Inputs with dependencies currently in the mempool + // have their block height set to a special constant. + // Their input age should computed as zero since their + // parent hasn't made it into a block yet. + var inputAge int32 + if txData.BlockHeight == mempoolHeight { + inputAge = 0 + } else { + inputAge = nextBlockHeight - txData.BlockHeight + } + + // Sum the input value times age. + originTxOut := txData.Tx.MsgTx().TxOut[originIndex] + inputValue := originTxOut.Value + totalInputAge += float64(inputValue * int64(inputAge)) + } + } + + return totalInputAge +} + +// checkInputsStandard performs a series of checks on a transaction's inputs +// to ensure they are "standard". A standard transaction input is one that +// that consumes the expected number of elements from the stack and that number +// is the same as the output script pushes. This help prevent resource +// exhaustion attacks by "creative" use of scripts that are super expensive to +// process like OP_DUP OP_CHECKSIG OP_DROP repeated a large number of times +// followed by a final OP_TRUE. +func checkInputsStandard(tx *btcutil.Tx, txStore blockchain.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 := txscript.CalcScriptInfo(txIn.SignatureScript, + originPkScript, true) + if err != nil { + str := fmt.Sprintf("transaction input #%d script parse "+ + "failure: %v", i, err) + return txRuleError(wire.RejectNonstandard, str) + } + + // 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(wire.RejectNonstandard, 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 provides "+ + "%d", i, scriptInfo.ExpectedInputs, + scriptInfo.NumInputs) + return txRuleError(wire.RejectNonstandard, str) + } + } + + return nil +} + +// checkPkScriptStandard performs a series of checks on a transaction ouput +// 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 +// multi-signature scripts, only contains from 1 to maxStandardMultiSigKeys +// public keys. +func checkPkScriptStandard(pkScript []byte, scriptClass txscript.ScriptClass) error { + switch scriptClass { + case txscript.MultiSigTy: + numPubKeys, numSigs, err := txscript.CalcMultiSigStats(pkScript) + if err != nil { + str := fmt.Sprintf("multi-signature script parse "+ + "failure: %v", err) + return txRuleError(wire.RejectNonstandard, str) + } + + // A standard multi-signature public key script must contain + // from 1 to maxStandardMultiSigKeys public keys. + if numPubKeys < 1 { + str := "multi-signature script with no pubkeys" + return txRuleError(wire.RejectNonstandard, 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(wire.RejectNonstandard, 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 { + return txRuleError(wire.RejectNonstandard, + "multi-signature script with no signatures") + } + if numSigs > numPubKeys { + str := fmt.Sprintf("multi-signature script with %d "+ + "signatures which is more than the available "+ + "%d public keys", numSigs, numPubKeys) + return txRuleError(wire.RejectNonstandard, str) + } + + case txscript.NonStandardTy: + return txRuleError(wire.RejectNonstandard, + "non-standard script form") + } + + return nil +} + +// isDust returns whether or not the passed transaction output amount is +// considered dust or not based on the passed minimum transaction relay fee. +// Dust is defined in terms of the minimum transaction relay fee. In +// particular, if the cost to the network to spend coins is more than 1/3 of the +// minimum transaction relay fee, it is considered dust. +func isDust(txOut *wire.TxOut, minRelayTxFee btcutil.Amount) bool { + // Unspendable outputs are considered dust. + if txscript.IsUnspendable(txOut.PkScript) { + return true + } + + // The total serialized size consists of the output and the associated + // input script to redeem it. Since there is no input script + // to redeem it yet, use the minimum size of a typical input script. + // + // Pay-to-pubkey-hash bytes breakdown: + // + // Output to hash (34 bytes): + // 8 value, 1 script len, 25 script [1 OP_DUP, 1 OP_HASH_160, + // 1 OP_DATA_20, 20 hash, 1 OP_EQUALVERIFY, 1 OP_CHECKSIG] + // + // Input with compressed pubkey (148 bytes): + // 36 prev outpoint, 1 script len, 107 script [1 OP_DATA_72, 72 sig, + // 1 OP_DATA_33, 33 compressed pubkey], 4 sequence + // + // Input with uncompressed pubkey (180 bytes): + // 36 prev outpoint, 1 script len, 139 script [1 OP_DATA_72, 72 sig, + // 1 OP_DATA_65, 65 compressed pubkey], 4 sequence + // + // Pay-to-pubkey bytes breakdown: + // + // Output to compressed pubkey (44 bytes): + // 8 value, 1 script len, 35 script [1 OP_DATA_33, + // 33 compressed pubkey, 1 OP_CHECKSIG] + // + // Output to uncompressed pubkey (76 bytes): + // 8 value, 1 script len, 67 script [1 OP_DATA_65, 65 pubkey, + // 1 OP_CHECKSIG] + // + // Input (114 bytes): + // 36 prev outpoint, 1 script len, 73 script [1 OP_DATA_72, + // 72 sig], 4 sequence + // + // Theoretically this could examine the script type of the output script + // and use a different size for the typical input script size for + // pay-to-pubkey vs pay-to-pubkey-hash inputs per the above breakdowns, + // but the only combinination which is less than the value chosen is + // a pay-to-pubkey script with a compressed pubkey, which is not very + // common. + // + // The most common scripts are pay-to-pubkey-hash, and as per the above + // breakdown, the minimum size of a p2pkh input script is 148 bytes. So + // that figure is used. + totalSize := txOut.SerializeSize() + 148 + + // The output is considered dust if the cost to the network to spend the + // coins is more than 1/3 of the minimum free transaction relay fee. + // minFreeTxRelayFee is in Satoshi/KB, so multiply by 1000 to + // convert to bytes. + // + // Using the typical values for a pay-to-pubkey-hash transaction from + // the breakdown above and the default minimum free transaction relay + // fee of 1000, this equates to values less than 546 satoshi being + // considered dust. + // + // The following is equivalent to (value/totalSize) * (1/3) * 1000 + // without needing to do floating point math. + return txOut.Value*1000/(3*int64(totalSize)) < int64(minRelayTxFee) +} + +// minInt is a helper function to return the minimum of two ints. This avoids +// a math import and the need to cast to floats. +func minInt(a, b int) int { + if a < b { + return a + } + return b +} diff --git a/policy_test.go b/policy_test.go new file mode 100644 index 0000000000..8ad0f79bda --- /dev/null +++ b/policy_test.go @@ -0,0 +1,242 @@ +// Copyright (c) 2013-2015 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package main + +import ( + "testing" + + "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcutil" +) + +// TestCalcMinRequiredTxRelayFee tests the calcMinRequiredTxRelayFee API. +func TestCalcMinRequiredTxRelayFee(t *testing.T) { + tests := []struct { + name string // test description. + size int64 // Transaction size in bytes. + relayFee btcutil.Amount // minimum relay transaction fee. + want int64 // Expected fee. + }{ + { + "zero value with default minimum relay fee", + 0, + defaultMinRelayTxFee, + int64(defaultMinRelayTxFee), + }, + { + "100 bytes with default minimum relay fee", + 100, + defaultMinRelayTxFee, + int64(defaultMinRelayTxFee), + }, + { + "max standard tx size with default minimum relay fee", + maxStandardTxSize, + defaultMinRelayTxFee, + 101000, + }, + { + "max standard tx size with max satoshi relay fee", + maxStandardTxSize, + btcutil.MaxSatoshi, + btcutil.MaxSatoshi, + }, + } + + for _, test := range tests { + got := calcMinRequiredTxRelayFee(test.size, test.relayFee) + if got != test.want { + t.Errorf("TestCalcMinRequiredTxRelayFee test '%s' "+ + "failed: got %v want %v", test.name, got, + test.want) + continue + } + } +} + +// TestCheckPkScriptStandard tests the checkPkScriptStandard API. +func TestCheckPkScriptStandard(t *testing.T) { + var pubKeys [][]byte + for i := 0; i < 4; i++ { + pk, err := btcec.NewPrivateKey(btcec.S256()) + if err != nil { + t.Fatalf("TestCheckPkScriptStandard NewPrivateKey failed: %v", + err) + return + } + pubKeys = append(pubKeys, pk.PubKey().SerializeCompressed()) + } + + tests := []struct { + name string // test description. + script *txscript.ScriptBuilder + isStandard bool + }{ + { + "key1 and key2", + txscript.NewScriptBuilder().AddOp(txscript.OP_2). + AddData(pubKeys[0]).AddData(pubKeys[1]). + AddOp(txscript.OP_2).AddOp(txscript.OP_CHECKMULTISIG), + true, + }, + { + "key1 or key2", + txscript.NewScriptBuilder().AddOp(txscript.OP_1). + AddData(pubKeys[0]).AddData(pubKeys[1]). + AddOp(txscript.OP_2).AddOp(txscript.OP_CHECKMULTISIG), + true, + }, + { + "escrow", + txscript.NewScriptBuilder().AddOp(txscript.OP_2). + AddData(pubKeys[0]).AddData(pubKeys[1]). + AddData(pubKeys[2]). + AddOp(txscript.OP_3).AddOp(txscript.OP_CHECKMULTISIG), + true, + }, + { + "one of four", + txscript.NewScriptBuilder().AddOp(txscript.OP_1). + AddData(pubKeys[0]).AddData(pubKeys[1]). + AddData(pubKeys[2]).AddData(pubKeys[3]). + AddOp(txscript.OP_4).AddOp(txscript.OP_CHECKMULTISIG), + false, + }, + { + "malformed1", + txscript.NewScriptBuilder().AddOp(txscript.OP_3). + AddData(pubKeys[0]).AddData(pubKeys[1]). + AddOp(txscript.OP_2).AddOp(txscript.OP_CHECKMULTISIG), + false, + }, + { + "malformed2", + txscript.NewScriptBuilder().AddOp(txscript.OP_2). + AddData(pubKeys[0]).AddData(pubKeys[1]). + AddOp(txscript.OP_3).AddOp(txscript.OP_CHECKMULTISIG), + false, + }, + { + "malformed3", + txscript.NewScriptBuilder().AddOp(txscript.OP_0). + AddData(pubKeys[0]).AddData(pubKeys[1]). + AddOp(txscript.OP_2).AddOp(txscript.OP_CHECKMULTISIG), + false, + }, + { + "malformed4", + txscript.NewScriptBuilder().AddOp(txscript.OP_1). + AddData(pubKeys[0]).AddData(pubKeys[1]). + AddOp(txscript.OP_0).AddOp(txscript.OP_CHECKMULTISIG), + false, + }, + { + "malformed5", + txscript.NewScriptBuilder().AddOp(txscript.OP_1). + AddData(pubKeys[0]).AddData(pubKeys[1]). + AddOp(txscript.OP_CHECKMULTISIG), + false, + }, + { + "malformed6", + txscript.NewScriptBuilder().AddOp(txscript.OP_1). + AddData(pubKeys[0]).AddData(pubKeys[1]), + false, + }, + } + + for _, test := range tests { + script, err := test.script.Script() + if err != nil { + t.Fatalf("TestCheckPkScriptStandard test '%s' "+ + "failed: %v", test.name, err) + continue + } + scriptClass := txscript.GetScriptClass(script) + got := checkPkScriptStandard(script, scriptClass) + if (test.isStandard && got != nil) || + (!test.isStandard && got == nil) { + + t.Fatalf("TestCheckPkScriptStandard test '%s' failed", + test.name) + return + } + } +} + +// TestDust tests the isDust API. +func TestDust(t *testing.T) { + pkScript := []byte{0x76, 0xa9, 0x21, 0x03, 0x2f, 0x7e, 0x43, + 0x0a, 0xa4, 0xc9, 0xd1, 0x59, 0x43, 0x7e, 0x84, 0xb9, + 0x75, 0xdc, 0x76, 0xd9, 0x00, 0x3b, 0xf0, 0x92, 0x2c, + 0xf3, 0xaa, 0x45, 0x28, 0x46, 0x4b, 0xab, 0x78, 0x0d, + 0xba, 0x5e, 0x88, 0xac} + + tests := []struct { + name string // test description + txOut wire.TxOut + relayFee btcutil.Amount // minimum relay transaction fee. + isDust bool + }{ + { + // Any value is allowed with a zero relay fee. + "zero value with zero relay fee", + wire.TxOut{0, pkScript}, + 0, + false, + }, + { + // Zero value is dust with any relay fee" + "zero value with very small tx fee", + wire.TxOut{0, pkScript}, + 1, + true, + }, + { + "38 byte public key script with value 584", + wire.TxOut{584, pkScript}, + 1000, + true, + }, + { + "38 byte public key script with value 585", + wire.TxOut{585, pkScript}, + 1000, + false, + }, + { + // Maximum allowed value is never dust. + "max satoshi amount is never dust", + wire.TxOut{btcutil.MaxSatoshi, pkScript}, + btcutil.MaxSatoshi, + false, + }, + { + // Maximum int64 value causes overflow. + "maximum int64 value", + wire.TxOut{1<<63 - 1, pkScript}, + 1<<63 - 1, + true, + }, + { + // Unspendable pkScript due to an invalid public key + // script. + "unspendable pkScript", + wire.TxOut{5000, []byte{0x01}}, + 0, // no relay fee + true, + }, + } + for _, test := range tests { + res := isDust(&test.txOut, test.relayFee) + if res != test.isDust { + t.Fatalf("Dust test '%s' failed: want %v got %v", + test.name, test.isDust, res) + continue + } + } +}