Move non-mempool specific functions to new file.
No functional change. Add tests.
This commit is contained in:
parent
5c50db5357
commit
3d6afcffe7
3 changed files with 541 additions and 282 deletions
284
mempool.go
284
mempool.go
|
@ -63,11 +63,6 @@ const (
|
||||||
// (1 + 15*74 + 3) + (15*34 + 3) + 23 = 1650
|
// (1 + 15*74 + 3) + (15*34 + 3) + 23 = 1650
|
||||||
maxStandardSigScriptSize = 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
|
// defaultMinRelayTxFee is the minimum fee in satoshi that is required
|
||||||
// for a transaction to be treated as free for relay and mining
|
// for a transaction to be treated as free for relay and mining
|
||||||
// purposes. It is also used to help determine if a transaction is
|
// 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''
|
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
|
// checkTransactionStandard performs a series of checks on a transaction to
|
||||||
// ensure it is a "standard" transaction. A standard transaction is one that
|
// ensure it is a "standard" transaction. A standard transaction is one that
|
||||||
// conforms to several additional limiting cases over what is considered a
|
// 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".
|
// "dust".
|
||||||
if scriptClass == txscript.NullDataTy {
|
if scriptClass == txscript.NullDataTy {
|
||||||
numNullDataOutputs++
|
numNullDataOutputs++
|
||||||
} else if isDust(txOut) {
|
} else if isDust(txOut, cfg.minRelayTxFee) {
|
||||||
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)
|
||||||
return txRuleError(wire.RejectDust, str)
|
return txRuleError(wire.RejectDust, str)
|
||||||
|
@ -320,78 +196,6 @@ func (mp *txMemPool) checkTransactionStandard(tx *btcutil.Tx, height int32) erro
|
||||||
return nil
|
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 is the internal function which implements the public
|
||||||
// RemoveOrphan. See the comment for RemoveOrphan for more details.
|
// 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
|
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
|
// StartingPriority calculates the priority of this tx descriptor's underlying
|
||||||
// transaction relative to when it was first added to the mempool. The result
|
// transaction relative to when it was first added to the mempool. The result
|
||||||
// is lazily computed and then cached for subsequent function calls.
|
// 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
|
// transaction does not exceeed 1000 less than the reserved space for
|
||||||
// high-priority transactions, don't require a fee for it.
|
// high-priority transactions, don't require a fee for it.
|
||||||
serializedSize := int64(tx.MsgTx().SerializeSize())
|
serializedSize := int64(tx.MsgTx().SerializeSize())
|
||||||
minFee := calcMinRequiredTxRelayFee(serializedSize)
|
minFee := calcMinRequiredTxRelayFee(serializedSize, cfg.minRelayTxFee)
|
||||||
if serializedSize >= (defaultBlockPrioritySize-1000) && txFee < minFee {
|
if serializedSize >= (defaultBlockPrioritySize-1000) && txFee < minFee {
|
||||||
str := fmt.Sprintf("transaction %v has %d fees which is under "+
|
str := fmt.Sprintf("transaction %v has %d fees which is under "+
|
||||||
"the required amount of %d", txHash, txFee,
|
"the required amount of %d", txHash, txFee,
|
||||||
|
|
297
policy.go
Normal file
297
policy.go
Normal file
|
@ -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
|
||||||
|
}
|
242
policy_test.go
Normal file
242
policy_test.go
Normal file
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue