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,99 @@ +#!/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 + +# Phonon dummy activation time +ACTIVATION_TIME = 2000000000 + +# Replay protection time needs to be moved beyond phonon activation +REPLAY_PROTECTION_TIME = ACTIVATION_TIME * 2 + +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)]] + + 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 txid and 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()