diff --git a/src/validation.cpp b/src/validation.cpp --- a/src/validation.cpp +++ b/src/validation.cpp @@ -2278,15 +2278,28 @@ // If this block is deactivating a fork, we move all mempool transactions // in front of disconnectpool for reprocessing in a future // updateMempoolForReorg call - if (pindexDelete->pprev != nullptr && - GetNextBlockScriptFlags(consensusParams, pindexDelete) != - GetNextBlockScriptFlags(consensusParams, pindexDelete->pprev)) { - LogPrint(BCLog::MEMPOOL, - "Disconnecting mempool due to rewind of upgrade block\n"); - if (disconnectpool) { - disconnectpool->importMempool(g_mempool); + if (pindexDelete->pprev != nullptr) { + bool changes = false; + changes |= + (GetNextBlockScriptFlags(consensusParams, pindexDelete) != + GetNextBlockScriptFlags(consensusParams, pindexDelete->pprev)); + + changes |= + (GetDefaultAncestorLimit(consensusParams, pindexDelete) != + GetDefaultAncestorLimit(consensusParams, pindexDelete->pprev)); + + changes |= + (GetDefaultDescendantLimit(consensusParams, pindexDelete) != + GetDefaultDescendantLimit(consensusParams, pindexDelete->pprev)); + + if (changes) { + LogPrint(BCLog::MEMPOOL, + "Disconnecting mempool due to rewind of upgrade block\n"); + if (disconnectpool) { + disconnectpool->importMempool(g_mempool); + } + g_mempool.clear(); } - g_mempool.clear(); } if (disconnectpool) { diff --git a/test/functional/abc-phonon-mempoolpolicy.py b/test/functional/abc-phonon-mempoolpolicy.py new file mode 100755 --- /dev/null +++ b/test/functional/abc-phonon-mempoolpolicy.py @@ -0,0 +1,100 @@ +#!/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 that long chains are trimmed from mempool on phonon-rollback. + +This test does not cover that the limits are enforced by sendrawtransaction, +the test mempool_packages.py covers that. + +This test can be deleted after new policy takes effect.""" + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import ( + assert_equal, + satoshi_round, +) + +from decimal import Decimal + +ACTIVATION_TIME = 2000000000 +"""Phonos dummy activation time""" + +REPLAY_PROTECTION_TIME = ACTIVATION_TIME * 2 +"""Replay protection time needs to be moved beyond phonos activation""" + +PREFORK_MAX_ANCESTORS = 25 +POSTFORK_MAX_ANCESTORS = 50 + + +def mine_until_mtp(node, new_mtp): + """Generates blocks until new mtp is reached""" + node.setmocktime(new_mtp) + + while True: + current_mtp = node.getblockheader( + node.getbestblockhash())['mediantime'] + assert(current_mtp <= new_mtp) + if current_mtp == new_mtp: + break + node.generate(1) + + +class PhononMempoolPolicyTest(BitcoinTestFramework): + def set_test_params(self): + self.num_nodes = 1 + + self.extra_args = [[ + "-phononactivationtime={}".format(ACTIVATION_TIME), + "-replayprotectionactivationtime={}".format( + REPLAY_PROTECTION_TIME)]] + # "-maxorphantx=1000"]] + + def skip_test_if_missing_module(self): + self.skip_if_no_wallet() + + def chain_transaction(self, node, parent_txid, vout, value): + """Build a transaction that spends parent_txid:vout. Return amount sent""" + fee = Decimal(0.0001) + send_value = satoshi_round(value - fee) + inputs = [{'txid': parent_txid, 'vout': vout}] + outputs = {node.getnewaddress(): send_value} + rawtx = node.createrawtransaction(inputs, outputs) + signedtx = node.signrawtransactionwithwallet(rawtx) + txid = node.sendrawtransaction(signedtx['hex']) + fulltx = node.getrawtransaction(txid, 1) + # make sure we didn't generate a change output + assert len(fulltx['vout']) == 1 + return (txid, send_value) + + def run_test(self): + # Mine some pre-fork blocks + node = self.nodes[0] + mine_until_mtp(node, ACTIVATION_TIME - 1) + pre_fork_height = node.getblockcount() + + # Activate phonon + mine_until_mtp(node, ACTIVATION_TIME) + + # Mine a POSTFORK_MAX_ANCESTORS length chain + assert_equal(0, len(node.getrawmempool(True))) + utxo = self.nodes[0].listunspent(1)[0] + txid = utxo['txid'] + value = utxo['amount'] + + for _ in range(POSTFORK_MAX_ANCESTORS): + (txid, value) = self.chain_transaction( + node, txid, vout=0, value=value) + + assert_equal(POSTFORK_MAX_ANCESTORS, len(node.getrawmempool(True))) + + # Rollback the fork + node.invalidateblock(node.getblockhash(pre_fork_height + 1)) + + # Assert that the mempool was trimmed to PREFORK_MAX_ANCESTORS + assert_equal(PREFORK_MAX_ANCESTORS, len(node.getrawmempool(True))) + + +if __name__ == '__main__': + PhononMempoolPolicyTest().main()