mempool: modify mempool sanity checks to be segwit aware

This commit is contained in:
Olaoluwa Osuntokun 2016-10-18 19:21:55 -07:00 committed by Dave Collins
parent 137aabd631
commit 26ff8ddce4
6 changed files with 82 additions and 29 deletions

View file

@ -53,7 +53,7 @@ const (
blockMaxSizeMax = wire.MaxBlockPayload - 1000 blockMaxSizeMax = wire.MaxBlockPayload - 1000
defaultGenerate = false defaultGenerate = false
defaultMaxOrphanTransactions = 100 defaultMaxOrphanTransactions = 100
defaultMaxOrphanTxSize = mempool.MaxStandardTxSize defaultMaxOrphanTxSize = 100000
defaultSigCacheMaxSize = 100000 defaultSigCacheMaxSize = 100000
sampleConfigFilename = "sample-btcd.conf" sampleConfigFilename = "sample-btcd.conf"
defaultTxIndex = false defaultTxIndex = false

View file

@ -73,9 +73,18 @@ type Config struct {
// utxo view. // utxo view.
CalcSequenceLock func(*btcutil.Tx, *blockchain.UtxoViewpoint) (*blockchain.SequenceLock, error) CalcSequenceLock func(*btcutil.Tx, *blockchain.UtxoViewpoint) (*blockchain.SequenceLock, error)
// IsDeploymentActive returns true if the target deploymentID is
// active, and false otherwise. The mempool uses this function to gauge
// if transactions using new to be soft-forked rules should be allowed
// into the mempool or not.
IsDeploymentActive func(deploymentID uint32) (bool, error)
// SigCache defines a signature cache to use. // SigCache defines a signature cache to use.
SigCache *txscript.SigCache SigCache *txscript.SigCache
// HashCache defines the transaction hash mid-state cache to use.
HashCache *txscript.HashCache
// AddrIndex defines the optional address index instance to use for // AddrIndex defines the optional address index instance to use for
// indexing the unconfirmed transactions in the memory pool. // indexing the unconfirmed transactions in the memory pool.
// This can be nil if the address index is not enabled. // This can be nil if the address index is not enabled.
@ -112,10 +121,10 @@ type Policy struct {
// of big orphans. // of big orphans.
MaxOrphanTxSize int MaxOrphanTxSize int
// MaxSigOpsPerTx is the maximum number of signature operations // MaxSigOpCostPerTx is the cumulative maximum cost of all the signature
// in a single transaction we will relay or mine. It is a fraction // operations in a single transaction we will relay or mine. It is a
// of the max signature operations for a block. // fraction of the max signature operations for a block.
MaxSigOpsPerTx int MaxSigOpCostPerTx int
// MinRelayTxFee defines the minimum transaction fee in BTC/kB to be // MinRelayTxFee defines the minimum transaction fee in BTC/kB to be
// considered a non-zero fee. // considered a non-zero fee.
@ -604,6 +613,22 @@ func (mp *TxPool) FetchTransaction(txHash *chainhash.Hash) (*btcutil.Tx, error)
func (mp *TxPool) maybeAcceptTransaction(tx *btcutil.Tx, isNew, rateLimit, rejectDupOrphans bool) ([]*chainhash.Hash, *TxDesc, error) { func (mp *TxPool) maybeAcceptTransaction(tx *btcutil.Tx, isNew, rateLimit, rejectDupOrphans bool) ([]*chainhash.Hash, *TxDesc, error) {
txHash := tx.Hash() txHash := tx.Hash()
// If a transaction has iwtness data, and segwit isn't active yet, If
// segwit isn't active yet, then we won't accept it into the mempool as
// it can't be mined yet.
if tx.MsgTx().HasWitness() {
segwitActive, err := mp.cfg.IsDeploymentActive(chaincfg.DeploymentSegwit)
if err != nil {
return nil, nil, err
}
if !segwitActive {
str := fmt.Sprintf("transaction %v has witness data, "+
"but segwit isn't active yet", txHash)
return nil, nil, txRuleError(wire.RejectNonstandard, str)
}
}
// Don't accept the transaction if it already exists in the pool. This // Don't accept the transaction if it already exists in the pool. This
// applies to orphan transactions as well when the reject duplicate // applies to orphan transactions as well when the reject duplicate
// orphans flag is set. This check is intended to be a quick check to // orphans flag is set. This check is intended to be a quick check to
@ -780,17 +805,17 @@ func (mp *TxPool) maybeAcceptTransaction(tx *btcutil.Tx, isNew, rateLimit, rejec
// the coinbase address itself can contain signature operations, the // the coinbase address itself can contain signature operations, the
// maximum allowed signature operations per transaction is less than // maximum allowed signature operations per transaction is less than
// the maximum allowed signature operations per block. // the maximum allowed signature operations per block.
numSigOps, err := blockchain.CountP2SHSigOps(tx, false, utxoView) // TODO(roasbeef): last bool should be conditional on segwit activation
sigOpCost, err := blockchain.GetSigOpCost(tx, false, utxoView, true, true)
if err != nil { if err != nil {
if cerr, ok := err.(blockchain.RuleError); ok { if cerr, ok := err.(blockchain.RuleError); ok {
return nil, nil, chainRuleError(cerr) return nil, nil, chainRuleError(cerr)
} }
return nil, nil, err return nil, nil, err
} }
numSigOps += blockchain.CountSigOps(tx) if sigOpCost > mp.cfg.Policy.MaxSigOpCostPerTx {
if numSigOps > mp.cfg.Policy.MaxSigOpsPerTx { str := fmt.Sprintf("transaction %v sigop cost is too high: %d > %d",
str := fmt.Sprintf("transaction %v has too many sigops: %d > %d", txHash, sigOpCost, mp.cfg.Policy.MaxSigOpCostPerTx)
txHash, numSigOps, mp.cfg.Policy.MaxSigOpsPerTx)
return nil, nil, txRuleError(wire.RejectNonstandard, str) return nil, nil, txRuleError(wire.RejectNonstandard, str)
} }
@ -857,7 +882,8 @@ func (mp *TxPool) maybeAcceptTransaction(tx *btcutil.Tx, isNew, rateLimit, rejec
// 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.
err = blockchain.ValidateTransactionScripts(tx, utxoView, err = blockchain.ValidateTransactionScripts(tx, utxoView,
txscript.StandardVerifyFlags, mp.cfg.SigCache) txscript.StandardVerifyFlags, mp.cfg.SigCache,
mp.cfg.HashCache)
if err != nil { if err != nil {
if cerr, ok := err.(blockchain.RuleError); ok { if cerr, ok := err.(blockchain.RuleError); ok {
return nil, nil, chainRuleError(cerr) return nil, nil, chainRuleError(cerr)

View file

@ -310,7 +310,7 @@ func newPoolHarness(chainParams *chaincfg.Params) (*poolHarness, []spendableOutp
FreeTxRelayLimit: 15.0, FreeTxRelayLimit: 15.0,
MaxOrphanTxs: 5, MaxOrphanTxs: 5,
MaxOrphanTxSize: 1000, MaxOrphanTxSize: 1000,
MaxSigOpsPerTx: blockchain.MaxSigOpsPerBlock / 5, MaxSigOpCostPerTx: blockchain.MaxBlockSigOpsCost / 4,
MinRelayTxFee: 1000, // 1 Satoshi per byte MinRelayTxFee: 1000, // 1 Satoshi per byte
MaxTxVersion: 1, MaxTxVersion: 1,
}, },

View file

@ -19,10 +19,9 @@ const (
// that are considered standard in a pay-to-script-hash script. // that are considered standard in a pay-to-script-hash script.
maxStandardP2SHSigOps = 15 maxStandardP2SHSigOps = 15
// MaxStandardTxSize is the maximum size allowed for transactions that // maxStandardTxCost is the max weight permitted by any transaction
// are considered standard and will therefore be relayed and considered // according to the current default policy.
// for mining. maxStandardTxWeight = 400000
MaxStandardTxSize = 100000
// maxStandardSigScriptSize is the maximum size allowed for a // maxStandardSigScriptSize is the maximum size allowed for a
// transaction input signature script to be considered standard. This // transaction input signature script to be considered standard. This
@ -217,17 +216,43 @@ func isDust(txOut *wire.TxOut, minRelayTxFee btcutil.Amount) bool {
// 36 prev outpoint, 1 script len, 73 script [1 OP_DATA_72, // 36 prev outpoint, 1 script len, 73 script [1 OP_DATA_72,
// 72 sig], 4 sequence // 72 sig], 4 sequence
// //
// Pay-to-witness-pubkey-hash bytes breakdown:
//
// Output to witness key hash (31 bytes);
// 8 value, 1 script len, 22 script [1 OP_0, 1 OP_DATA_20,
// 20 bytes hash160]
//
// Input (67 bytes as the 107 witness stack is discounted):
// 36 prev outpoint, 1 script len, 0 script (not sigScript), 107
// witness stack bytes [1 element length, 33 compressed pubkey,
// element length 72 sig], 4 sequence
//
//
// Theoretically this could examine the script type of the output script // Theoretically this could examine the script type of the output script
// and use a different size for the typical input script size for // 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, // 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 // but the only combination which is less than the value chosen is
// a pay-to-pubkey script with a compressed pubkey, which is not very // a pay-to-pubkey script with a compressed pubkey, which is not very
// common. // common.
// //
// The most common scripts are pay-to-pubkey-hash, and as per the above // 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 // breakdown, the minimum size of a p2pkh input script is 148 bytes. So
// that figure is used. // that figure is used. If the output being spent is a witness program,
totalSize := txOut.SerializeSize() + 148 // then we apply the witness discount to the size of the signature.
//
// The segwit analogue to p2pkh is a p2wkh output. This is the smallest
// output possible using the new segwit features. The 107 bytes of
// witness data is discounted by a factor of 4, leading to a computed
// value of 67 bytes of witness data.
//
// Both cases share a 41 byte preamble required to reference the input
// being spent and the sequence number of the input.
totalSize := txOut.SerializeSize() + 41
if txscript.IsWitnessProgram(txOut.PkScript) {
totalSize += (107 / blockchain.WitnessScaleFactor)
} else {
totalSize += 107
}
// The output is considered dust if the cost to the network to spend the // 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. // coins is more than 1/3 of the minimum free transaction relay fee.
@ -275,10 +300,10 @@ func checkTransactionStandard(tx *btcutil.Tx, height int32,
// almost as much to process as the sender fees, limit the maximum // almost as much to process as the sender fees, limit the maximum
// size of a transaction. This also helps mitigate CPU exhaustion // size of a transaction. This also helps mitigate CPU exhaustion
// attacks. // attacks.
serializedLen := msgTx.SerializeSize() txWeight := blockchain.GetTransactionWeight(tx)
if serializedLen > MaxStandardTxSize { if txWeight > maxStandardTxWeight {
str := fmt.Sprintf("transaction size of %v is larger than max "+ str := fmt.Sprintf("weight of transaction %v is larger than max "+
"allowed size of %v", serializedLen, MaxStandardTxSize) "allowed weight of %v", txWeight, maxStandardTxWeight)
return txRuleError(wire.RejectNonstandard, str) return txRuleError(wire.RejectNonstandard, str)
} }
@ -345,7 +370,7 @@ func checkTransactionStandard(tx *btcutil.Tx, height int32,
} }
// GetTxVirtualSize computes the virtual size of a given transaction. A // GetTxVirtualSize computes the virtual size of a given transaction. A
// transaction's virtual size is based off it's weight, creating a discount for // transaction's virtual size is based off its weight, creating a discount for
// any witness data it contains, proportional to the current // any witness data it contains, proportional to the current
// blockchain.WitnessScaleFactor value. // blockchain.WitnessScaleFactor value.
func GetTxVirtualSize(tx *btcutil.Tx) int64 { func GetTxVirtualSize(tx *btcutil.Tx) int64 {

View file

@ -41,13 +41,13 @@ func TestCalcMinRequiredTxRelayFee(t *testing.T) {
}, },
{ {
"max standard tx size with default minimum relay fee", "max standard tx size with default minimum relay fee",
MaxStandardTxSize, maxStandardTxWeight / 4,
DefaultMinRelayTxFee, DefaultMinRelayTxFee,
100000, 100000,
}, },
{ {
"max standard tx size with max satoshi relay fee", "max standard tx size with max satoshi relay fee",
MaxStandardTxSize, maxStandardTxWeight / 4,
btcutil.MaxSatoshi, btcutil.MaxSatoshi,
btcutil.MaxSatoshi, btcutil.MaxSatoshi,
}, },
@ -360,7 +360,7 @@ func TestCheckTransactionStandard(t *testing.T) {
TxOut: []*wire.TxOut{{ TxOut: []*wire.TxOut{{
Value: 0, Value: 0,
PkScript: bytes.Repeat([]byte{0x00}, PkScript: bytes.Repeat([]byte{0x00},
MaxStandardTxSize+1), (maxStandardTxWeight/4)+1),
}}, }},
LockTime: 0, LockTime: 0,
}, },

View file

@ -2470,7 +2470,9 @@ func newServer(listenAddrs []string, db database.DB, chainParams *chaincfg.Param
CalcSequenceLock: func(tx *btcutil.Tx, view *blockchain.UtxoViewpoint) (*blockchain.SequenceLock, error) { CalcSequenceLock: func(tx *btcutil.Tx, view *blockchain.UtxoViewpoint) (*blockchain.SequenceLock, error) {
return bm.chain.CalcSequenceLock(tx, view, true) return bm.chain.CalcSequenceLock(tx, view, true)
}, },
IsDeploymentActive: bm.chain.IsDeploymentActive,
SigCache: s.sigCache, SigCache: s.sigCache,
HashCache: s.hashCache,
AddrIndex: s.addrIndex, AddrIndex: s.addrIndex,
} }
s.txMemPool = mempool.New(&txC) s.txMemPool = mempool.New(&txC)