Skip to content

Commit

Permalink
Move non-mempool specific functions to new file.
Browse files Browse the repository at this point in the history
No functional change. Add tests.
  • Loading branch information
dajohi committed Oct 27, 2015
1 parent 5c50db5 commit 3d6afcf
Show file tree
Hide file tree
Showing 3 changed files with 541 additions and 282 deletions.
284 changes: 2 additions & 282 deletions mempool.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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.
//
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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,
Expand Down
Loading

0 comments on commit 3d6afcf

Please sign in to comment.