diff --git a/src/miner.h b/src/miner.h --- a/src/miner.h +++ b/src/miner.h @@ -186,14 +186,16 @@ * Increments nPackagesSelected / nDescendantsUpdated with corresponding * statistics from the package selection (for logging statistics). */ - void addPackageTxs(int &nPackagesSelected, int &nDescendantsUpdated) + void addPackageTxs(int &nPackagesSelected, int &nDescendantsUpdated, + bool fLimitSigOpCount) EXCLUSIVE_LOCKS_REQUIRED(mempool->cs); // helper functions for addPackageTxs() /** Remove confirmed (inBlock) entries from given set */ void onlyUnconfirmed(CTxMemPool::setEntries &testSet); /** Test if a new package would "fit" in the block */ - bool TestPackage(uint64_t packageSize, int64_t packageSigOpCount) const; + bool TestPackage(uint64_t packageSize, int64_t packageSigOpCount, + bool fLimitSigOpCount) const; /** * Perform checks on each transaction in a package: * locktime, serialized size (if necessary). These checks should always diff --git a/src/miner.cpp b/src/miner.cpp --- a/src/miner.cpp +++ b/src/miner.cpp @@ -155,9 +155,12 @@ ? nMedianTimePast : pblock->GetBlockTime(); + const bool fLimitSigOpCount = + !IsPhononEnabled(chainparams.GetConsensus(), pindexPrev); + int nPackagesSelected = 0; int nDescendantsUpdated = 0; - addPackageTxs(nPackagesSelected, nDescendantsUpdated); + addPackageTxs(nPackagesSelected, nDescendantsUpdated, fLimitSigOpCount); if (IsMagneticAnomalyEnabled(chainparams.GetConsensus(), pindexPrev)) { // If magnetic anomaly is enabled, we make sure transaction are @@ -247,15 +250,15 @@ } } -bool BlockAssembler::TestPackage(uint64_t packageSize, - int64_t packageSigOps) const { +bool BlockAssembler::TestPackage(uint64_t packageSize, int64_t packageSigOps, + bool fLimitSigOpCount) const { auto blockSizeWithPackage = nBlockSize + packageSize; if (blockSizeWithPackage >= nMaxGeneratedBlockSize) { return false; } - if (nBlockSigOps + packageSigOps >= - GetMaxBlockSigOpsCount(blockSizeWithPackage)) { + if (fLimitSigOpCount && nBlockSigOps + packageSigOps >= + GetMaxBlockSigOpsCount(blockSizeWithPackage)) { return false; } @@ -373,7 +376,8 @@ * @param[out] nDescendantsUpdated Number of descendant transactions updated */ void BlockAssembler::addPackageTxs(int &nPackagesSelected, - int &nDescendantsUpdated) { + int &nDescendantsUpdated, + bool fLimitSigOpCount) { // selection algorithm orders the mempool based on feerate of a // transaction including all unconfirmed ancestors. Since we don't remove // transactions from the mempool as we select them for block inclusion, we @@ -471,7 +475,7 @@ // The following must not use virtual size since TestPackage relies on // having an accurate call to // GetMaxBlockSigOpsCount(blockSizeWithPackage). - if (!TestPackage(packageSize, packageSigOps)) { + if (!TestPackage(packageSize, packageSigOps, fLimitSigOpCount)) { if (fUsingModified) { // Since we always look at the best entry in mapModifiedTx, we // must erase failed entries so that we can consider the next diff --git a/src/policy/policy.h b/src/policy/policy.h --- a/src/policy/policy.h +++ b/src/policy/policy.h @@ -129,8 +129,8 @@ * spending * @return True if all inputs (scriptSigs) use only standard transaction forms */ -bool AreInputsStandard(const CTransaction &tx, - const CCoinsViewCache &mapInputs); +bool AreInputsStandard(const CTransaction &tx, const CCoinsViewCache &mapInputs, + bool fLimitSigOpCount = true); extern CFeeRate dustRelayFee; extern uint32_t nBytesPerSigOp; diff --git a/src/policy/policy.cpp b/src/policy/policy.cpp --- a/src/policy/policy.cpp +++ b/src/policy/policy.cpp @@ -140,8 +140,8 @@ * expensive-to-check-upon-redemption script like: * DUP CHECKSIG DROP ... repeated 100 times... OP_1 */ -bool AreInputsStandard(const CTransaction &tx, - const CCoinsViewCache &mapInputs) { +bool AreInputsStandard(const CTransaction &tx, const CCoinsViewCache &mapInputs, + bool fLimitSigOpCount /*= true*/) { if (tx.IsCoinBase()) { // Coinbases don't use vin normally. return true; @@ -154,7 +154,7 @@ txnouttype whichType = Solver(prev.scriptPubKey, vSolutions); if (whichType == TX_NONSTANDARD) { return false; - } else if (whichType == TX_SCRIPTHASH) { + } else if (whichType == TX_SCRIPTHASH && fLimitSigOpCount) { std::vector> stack; // convert the scriptSig into a stack, so we can inspect the // redeemScript diff --git a/src/validation.cpp b/src/validation.cpp --- a/src/validation.cpp +++ b/src/validation.cpp @@ -661,8 +661,12 @@ tx.GetId().ToString(), FormatStateMessage(state)); } + const bool fLimitSigOpCount = + !IsPhononEnabled(consensusParams, chainActive.Tip()); + // Check for non-standard pay-to-script-hash in inputs - if (fRequireStandard && !AreInputsStandard(tx, view)) { + if (fRequireStandard && + !AreInputsStandard(tx, view, fLimitSigOpCount)) { return state.Invalid(false, REJECT_NONSTANDARD, "bad-txns-nonstandard-inputs"); } @@ -692,7 +696,7 @@ // sigops. static_assert(MAX_STANDARD_TX_SIGOPS <= MAX_TX_SIGOPS_COUNT, "we don't want transactions we can't even mine"); - if (nSigOpsCount > MAX_STANDARD_TX_SIGOPS) { + if (fLimitSigOpCount && nSigOpsCount > MAX_STANDARD_TX_SIGOPS) { return state.DoS(0, false, REJECT_NONSTANDARD, "bad-txns-too-many-sigops", false, strprintf("%d", nSigOpsCount)); @@ -1817,6 +1821,8 @@ int nInputs = 0; // Sigops counting. We need to do it again because of P2SH. + const bool fLimitSigOpCount = + !IsPhononEnabled(consensusParams, pindex->pprev); uint64_t nSigOpsCount = 0; const uint64_t currentBlockSize = ::GetSerializeSize(block, PROTOCOL_VERSION); @@ -1855,18 +1861,20 @@ REJECT_INVALID, "bad-txns-accumulated-fee-outofrange"); } - // GetTransactionSigOpCount counts 2 types of sigops: - // * legacy (always) - // * p2sh (when P2SH enabled in flags and excludes coinbase) - auto txSigOpsCount = GetTransactionSigOpCount(tx, view, flags); - if (txSigOpsCount > MAX_TX_SIGOPS_COUNT) { - return state.DoS(100, false, REJECT_INVALID, "bad-txn-sigops"); - } + if (fLimitSigOpCount) { + // GetTransactionSigOpCount counts 2 types of sigops: + // * legacy (always) + // * p2sh (when P2SH enabled in flags and excludes coinbase) + auto txSigOpsCount = GetTransactionSigOpCount(tx, view, flags); + if (txSigOpsCount > MAX_TX_SIGOPS_COUNT) { + return state.DoS(100, false, REJECT_INVALID, "bad-txn-sigops"); + } - nSigOpsCount += txSigOpsCount; - if (nSigOpsCount > nMaxSigOpsCount) { - return state.DoS(100, error("ConnectBlock(): too many sigops"), - REJECT_INVALID, "bad-blk-sigops"); + nSigOpsCount += txSigOpsCount; + if (nSigOpsCount > nMaxSigOpsCount) { + return state.DoS(100, error("ConnectBlock(): too many sigops"), + REJECT_INVALID, "bad-blk-sigops"); + } } // The following checks do not apply to the coinbase. @@ -3742,6 +3750,7 @@ // Keep track of the sigops count. uint64_t nSigOps = 0; + const bool fLimitSigOpCount = !IsPhononEnabled(params, pindexPrev); const auto currentBlockSize = ::GetSerializeSize(block, PROTOCOL_VERSION); auto nMaxSigOpsCount = GetMaxBlockSigOpsCount(currentBlockSize); // Note that pindexPrev may be null if reindexing genesis block. @@ -3780,17 +3789,19 @@ } } - // Count the sigops for the current transaction. If the tx or total - // sigops counts are too high, then the block is invalid. - const auto txSigOps = GetSigOpCountWithoutP2SH(tx, scriptFlags); - if (txSigOps > MAX_TX_SIGOPS_COUNT) { - return state.DoS(100, false, REJECT_INVALID, "bad-txn-sigops", - false, "out-of-bounds SigOpCount"); - } - nSigOps += txSigOps; - if (nSigOps > nMaxSigOpsCount) { - return state.DoS(100, false, REJECT_INVALID, "bad-blk-sigops", - false, "out-of-bounds SigOpCount"); + if (fLimitSigOpCount) { + // Count the sigops for the current transaction. If the tx or total + // sigops counts are too high, then the block is invalid. + const auto txSigOps = GetSigOpCountWithoutP2SH(tx, scriptFlags); + if (txSigOps > MAX_TX_SIGOPS_COUNT) { + return state.DoS(100, false, REJECT_INVALID, "bad-txn-sigops", + false, "out-of-bounds SigOpCount"); + } + nSigOps += txSigOps; + if (nSigOps > nMaxSigOpsCount) { + return state.DoS(100, false, REJECT_INVALID, "bad-blk-sigops", + false, "out-of-bounds SigOpCount"); + } } if (!ContextualCheckTransaction(params, tx, state, nHeight, diff --git a/test/functional/abc-mempool-accept-txn.py b/test/functional/abc-mempool-accept-txn.py --- a/test/functional/abc-mempool-accept-txn.py +++ b/test/functional/abc-mempool-accept-txn.py @@ -47,6 +47,9 @@ # Error for too many sigops in one TX RPC_TXNS_TOO_MANY_SIGOPS_ERROR = "bad-txns-too-many-sigops" +# Set test to run with sigops deactivation far in the future. +SIGOPS_DEACTIVATION_TIME = 2000000000 + class PreviousSpendableOutput(): @@ -67,6 +70,8 @@ self.coinbase_pubkey = self.coinbase_key.get_pubkey() self.tip = None self.blocks = {} + self.extra_args = [ + ['-phononactivationtime={}'.format(SIGOPS_DEACTIVATION_TIME)]] def add_options(self, parser): super().add_options(parser) diff --git a/test/functional/abc-p2p-fullblocktest-sigops.py b/test/functional/abc-p2p-fullblocktest-sigops.py --- a/test/functional/abc-p2p-fullblocktest-sigops.py +++ b/test/functional/abc-p2p-fullblocktest-sigops.py @@ -54,6 +54,9 @@ from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal +# Set test to run with sigops deactivation far in the future. +SIGOPS_DEACTIVATION_TIME = 2000000000 + class PreviousSpendableOutput(): @@ -73,7 +76,7 @@ self.blocks = {} self.excessive_block_size = 100 * ONE_MEGABYTE self.extra_args = [['-whitelist=127.0.0.1', - "-excessiveblocksize={}".format(self.excessive_block_size)]] + "-excessiveblocksize={}".format(self.excessive_block_size), '-phononactivationtime={}'.format(SIGOPS_DEACTIVATION_TIME)]] def add_options(self, parser): super().add_options(parser) diff --git a/test/functional/abc-sigops-deactivation.py b/test/functional/abc-sigops-deactivation.py new file mode 100755 --- /dev/null +++ b/test/functional/abc-sigops-deactivation.py @@ -0,0 +1,359 @@ +#!/usr/bin/env python3 +# Copyright (c) 2020 The Bitcoin Developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +""" +Test deactivation of sigops counting + +based on abc-schnorrmultisig-activation.py (D3736). +""" + +from test_framework.blocktools import ( + create_block, + create_coinbase, + make_conform_to_ctor, +) +from test_framework.cdefs import ( + ONE_MEGABYTE, +) +from test_framework.messages import ( + CBlock, + COutPoint, + CTransaction, + CTxIn, + CTxOut, + FromHex, + ToHex, +) +from test_framework.mininode import ( + P2PDataStore, +) +from test_framework.script import ( + CScript, + hash160, + OP_CHECKMULTISIG, + OP_CHECKDATASIG, + OP_ENDIF, + OP_EQUAL, + OP_FALSE, + OP_HASH160, + OP_IF, + OP_RETURN, + OP_TRUE, +) +from test_framework.test_framework import BitcoinTestFramework +from test_framework.txtools import pad_tx +from test_framework.util import ( + assert_equal, + assert_raises_rpc_error, + sync_blocks, +) +from decimal import Decimal +from collections import deque + +SATOSHI = Decimal('0.00000001') + +# Set test to run with sigops deactivation far in the future. +SIGOPS_DEACTIVATION_TIME = 2000000000 + +# If we don't do this, autoreplay protection will activate before graviton and +# all our sigs will mysteriously fail. +REPLAY_PROTECTION_START_TIME = SIGOPS_DEACTIVATION_TIME * 2 + +# Transactions for mempool with too many sigops give this error: +MEMPOOL_TXSIGOPS_ERROR = 'bad-txns-too-many-sigops' +MEMPOOL_P2SH_SIGOPS_ERROR = 'bad-txns-nonstandard-inputs' +# Blocks that have single txes with too many sigops give this error: +BLOCK_TXSIGOPS_ERROR = 'bad-txn-sigops' +# Blocks with too many sigops give this error: +BLOCK_TOTALSIGOPS_ERROR = 'bad-blk-sigops' + + +def create_transaction(spendfrom, custom_script, amount=None): + # Fund and sign a transaction to a given output. + # spendfrom should be a CTransaction with first output to OP_TRUE. + + # custom output will go on position 1, after position 0 which will be + # OP_TRUE (so it can be reused). + customout = CTxOut(0, bytes(custom_script)) + # set output amount to required dust if not given + customout.nValue = amount or (len(customout.serialize()) + 148) * 3 + + ctx = CTransaction() + ctx.vin.append(CTxIn(COutPoint(spendfrom.sha256, 0), b'')) + ctx.vout.append( + CTxOut(0, bytes([OP_TRUE]))) + ctx.vout.append(customout) + pad_tx(ctx) + + fee = len(ctx.serialize()) + ctx.vout[0].nValue = spendfrom.vout[0].nValue - customout.nValue - fee + ctx.rehash() + + return ctx + + +def check_for_ban_on_rejected_tx(node, tx, reject_reason=None): + """Check we are disconnected when sending a txn that the node rejects, + then reconnect after. + + (Can't actually get banned, since bitcoind won't ban local peers.)""" + node.p2p.send_txs_and_test( + [tx], node, success=False, expect_disconnect=True, reject_reason=reject_reason) + node.disconnect_p2ps() + node.add_p2p_connection(P2PDataStore()) + + +def check_for_ban_on_rejected_block(node, block, reject_reason=None): + """Check we are disconnected when sending a block that the node rejects, + then reconnect after. + + (Can't actually get banned, since bitcoind won't ban local peers.)""" + node.p2p.send_blocks_and_test( + [block], node, success=False, reject_reason=reject_reason, expect_disconnect=True) + node.disconnect_p2ps() + node.add_p2p_connection(P2PDataStore()) + + +def check_for_no_ban_on_rejected_tx(node, tx, reject_reason=None): + """Check we are not disconnected when sending a txn that the node rejects.""" + node.p2p.send_txs_and_test( + [tx], node, success=False, reject_reason=reject_reason) + + +class SigopsDeactivationTest(BitcoinTestFramework): + + def set_test_params(self): + self.setup_clean_chain = True + self.num_nodes = 2 + self.block_heights = {} + timeargs = ["-phononactivationtime={}".format( + SIGOPS_DEACTIVATION_TIME), + "-replayprotectionactivationtime={}".format( + REPLAY_PROTECTION_START_TIME)] + # many standardness rules are actually enforced on regtest, except for P2SH sigops. + self.extra_args = [timeargs, timeargs + ['-acceptnonstdtxn=0']] + + def getbestblock(self, node): + """Get the best block. Register its height so we can use build_block.""" + block_height = node.getblockcount() + blockhash = node.getblockhash(block_height) + block = FromHex(CBlock(), node.getblock(blockhash, 0)) + block.calc_sha256() + self.block_heights[block.sha256] = block_height + return block + + def build_block(self, parent, transactions=(), nTime=None, cbextrascript=None): + """Make a new block with an OP_1 coinbase output. + + Requires parent to have its height registered.""" + parent.calc_sha256() + block_height = self.block_heights[parent.sha256] + 1 + block_time = (parent.nTime + 1) if nTime is None else nTime + + block = create_block( + parent.sha256, create_coinbase(block_height), block_time) + if cbextrascript is not None: + block.vtx[0].vout.append(CTxOut(0, cbextrascript)) + block.vtx[0].rehash() + block.vtx.extend(transactions) + make_conform_to_ctor(block) + block.hashMerkleRoot = block.calc_merkle_root() + block.solve() + self.block_heights[block.sha256] = block_height + return block + + def run_test(self): + (node, std_node) = self.nodes + node.add_p2p_connection(P2PDataStore()) + std_node.add_p2p_connection(P2PDataStore()) + # Get out of IBD + node.generate(1) + + tip = self.getbestblock(node) + + self.log.info("Create some blocks with OP_1 coinbase for spending.") + blocks = [] + for _ in range(20): + tip = self.build_block(tip) + blocks.append(tip) + node.p2p.send_blocks_and_test(blocks, node, success=True) + self.spendable_outputs = deque(block.vtx[0] for block in blocks) + + self.log.info("Mature the blocks.") + node.generate(100) + + tip = self.getbestblock(node) + + self.log.info("Generating some high-sigop transactions.") + + # Tx with 4001 sigops (valid but non standard) + tx_4001 = create_transaction(self.spendable_outputs.popleft(), [ + OP_CHECKMULTISIG] * 200 + [OP_CHECKDATASIG]) + + # Tx with 20001 sigops (consensus-invalid) + tx_20001 = create_transaction(self.spendable_outputs.popleft(), [ + OP_CHECKMULTISIG] * 1000 + [OP_CHECKDATASIG]) + + # P2SH tx with too many sigops (valid but nonstandard for std_node) + redeem_script = bytes( + [OP_IF, OP_CHECKMULTISIG, OP_ENDIF, OP_TRUE]) + p2sh_script = CScript([OP_HASH160, hash160(redeem_script), OP_EQUAL]) + tx_fundp2sh = create_transaction( + self.spendable_outputs.popleft(), p2sh_script) + tx_spendp2sh = CTransaction() + tx_spendp2sh.vin.append( + CTxIn(COutPoint(tx_fundp2sh.sha256, 1), CScript([OP_FALSE, redeem_script]))) + tx_spendp2sh.vout.append( + CTxOut(0, CScript([OP_RETURN, b'pad' * 20]))) + tx_spendp2sh.rehash() + + # Chain of 10 txes with 2000 sigops each. + txes_10x2000_sigops = [] + tx = self.spendable_outputs.popleft() + for _ in range(10): + tx = create_transaction(tx, [OP_CHECKMULTISIG] * 100) + txes_10x2000_sigops.append(tx) + + def make_hightotalsigop_block(): + # 20001 total sigops + return self.build_block(tip, txes_10x2000_sigops, cbextrascript=bytes([OP_CHECKDATASIG])) + + def make_highsigop_coinbase_block(): + # 60000 sigops in the coinbase + return self.build_block(tip, cbextrascript=bytes([OP_CHECKMULTISIG] * 3000)) + + self.log.info( + "Try various high-sigop transactions in blocks / mempool before upgrade") + + # mempool refuses over 4001. + check_for_no_ban_on_rejected_tx(node, tx_4001, MEMPOOL_TXSIGOPS_ERROR) + # it used to be that exceeding 20000 would cause a ban, but it's + # important that this causes no ban: we want that upgraded nodes + # can't get themselves banned by relaying huge-sigops transactions. + check_for_no_ban_on_rejected_tx(node, tx_20001, MEMPOOL_TXSIGOPS_ERROR) + + # the 20001 tx can't be mined + check_for_ban_on_rejected_block(node, self.build_block( + tip, [tx_20001]), BLOCK_TXSIGOPS_ERROR) + + self.log.info( + "The P2SH script has too many sigops (20 > 15) for a standard node.") + # Mine the P2SH funding first because it's nonstandard. + tip = self.build_block(tip, [tx_fundp2sh]) + std_node.p2p.send_blocks_and_test([tip], node) + assert_raises_rpc_error(-26, MEMPOOL_P2SH_SIGOPS_ERROR, + std_node.sendrawtransaction, ToHex(tx_spendp2sh)) + + self.log.info( + "A bunch of 2000-sigops txes can be put in mempool but not mined all at once.") + # Send the 2000-sigop transactions, which are acceptable. + for tx in txes_10x2000_sigops: + node.sendrawtransaction(ToHex(tx)) + + # They can't be mined all at once if the coinbase has a single sigop (total 20001) + check_for_ban_on_rejected_block( + node, make_hightotalsigop_block(), BLOCK_TOTALSIGOPS_ERROR) + + # Activation tests + + self.log.info("Approach to just before upgrade activation") + # Move our clock to the uprade time so we will accept such future-timestamped blocks. + node.setmocktime(SIGOPS_DEACTIVATION_TIME) + std_node.setmocktime(SIGOPS_DEACTIVATION_TIME) + # Mine six blocks with timestamp starting at SIGOPS_DEACTIVATION_TIME-1 + blocks = [] + for i in range(-1, 5): + tip = self.build_block(tip, nTime=SIGOPS_DEACTIVATION_TIME + i) + blocks.append(tip) + node.p2p.send_blocks_and_test(blocks, node) + assert_equal(node.getblockchaininfo()[ + 'mediantime'], SIGOPS_DEACTIVATION_TIME - 1) + + self.log.info( + "The next block will activate, but the activation block itself must follow old rules") + + check_for_ban_on_rejected_block(node, self.build_block( + tip, [tx_20001]), BLOCK_TXSIGOPS_ERROR) + check_for_ban_on_rejected_block( + node, make_hightotalsigop_block(), BLOCK_TOTALSIGOPS_ERROR) + check_for_ban_on_rejected_block( + node, make_highsigop_coinbase_block(), BLOCK_TXSIGOPS_ERROR) + + self.log.info("Mine the activation block itself") + tip = self.build_block(tip) + node.p2p.send_blocks_and_test([tip], node) + sync_blocks(self.nodes) + + self.log.info("We have activated!") + assert_equal(node.getblockchaininfo()[ + 'mediantime'], SIGOPS_DEACTIVATION_TIME) + assert_equal(std_node.getblockchaininfo()[ + 'mediantime'], SIGOPS_DEACTIVATION_TIME) + + # save this tip for later + upgrade_block = tip + + self.log.info( + "The mempool is now a free-for-all, and we can get all the high-sigops transactions in") + std_node.sendrawtransaction(ToHex(tx_spendp2sh)) + node.sendrawtransaction(ToHex(tx_spendp2sh)) + node.sendrawtransaction(ToHex(tx_4001)) + node.sendrawtransaction(ToHex(tx_20001)) + # resend the 2000-sigop transactions, which will have expired due to setmocktime. + for tx in txes_10x2000_sigops: + node.sendrawtransaction(ToHex(tx)) + + alltxes = set(tx.hash for tx in [ + tx_spendp2sh, tx_4001, tx_20001] + txes_10x2000_sigops) + assert_equal(set(node.getrawmempool()), alltxes) + + self.log.info( + "The miner will include all the high-sigops transactions at once, without issue.") + node.generate(1) + tip = self.getbestblock(node) + assert_equal(set(tx.rehash() for tx in tip.vtx[1:]), alltxes) + # even though it is far smaller than one megabyte, we got in something like 44000 sigops + assert len(tip.serialize()) < ONE_MEGABYTE + + # save this tip for later + postupgrade_block = tip + + # Deactivation tests + + self.log.info( + "Invalidating the post-upgrade block returns the transactions to mempool") + node.invalidateblock(postupgrade_block.hash) + assert_equal(set(node.getrawmempool()), alltxes) + + self.log.info("Test some weird alternative blocks") + tip = upgrade_block + self.log.info("A 40000-sigop coinbase is acceptable now") + tip = make_highsigop_coinbase_block() + node.p2p.send_blocks_and_test([tip], node) + self.log.info("We can get in our 20001 sigop total block") + tip = make_hightotalsigop_block() + node.p2p.send_blocks_and_test([tip], node) + + # NOTE: These deactivation tests are specific to current behaviour without + # any consensus flags being changed, so the mempool is not being reprocessed. + self.log.info( + "Invalidating the upgrade block evicts nothing") + node.invalidateblock(upgrade_block.hash) + assert_equal(set(node.getrawmempool()), alltxes) + + self.log.info( + "Even though the mempool now contains high-sigops transactions, the node does not create an invalid block.") + node.generate(1) + assert len(self.getbestblock(node).vtx) > 1 + 8, \ + "we should have been able to get in at least 8 tx from mempool." + self.log.info( + "Once upgraded again, the node clears everything out anyway.") + node.generate(1) + assert len(self.getbestblock(node).vtx) > 1 + 2, \ + "at least a couple of txes should have been left over." + assert_equal(node.getrawmempool(), []) + + +if __name__ == '__main__': + SigopsDeactivationTest().main() diff --git a/test/functional/feature_block_sigops.py b/test/functional/feature_block_sigops.py --- a/test/functional/feature_block_sigops.py +++ b/test/functional/feature_block_sigops.py @@ -46,12 +46,16 @@ from test_framework.txtools import pad_tx from test_framework.util import assert_equal +# Set test to run with sigops deactivation far in the future. +SIGOPS_DEACTIVATION_TIME = 2000000000 + class FullBlockSigOpsTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 self.setup_clean_chain = True - self.extra_args = [['-noparkdeepreorg', '-maxreorgdepth=-1']] + self.extra_args = [['-noparkdeepreorg', '-maxreorgdepth=-1', + '-phononactivationtime={}'.format(SIGOPS_DEACTIVATION_TIME)]] def run_test(self): self.bootstrap_p2p() # Add one p2p connection to the node