Changeset View
Changeset View
Standalone View
Standalone View
test/functional/abc-phonon-mempoolpolicy.py
- This file was added.
| Property | Old Value | New Value |
|---|---|---|
| File Mode | null | 100755 |
| #!/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 ancestor and descendants policies around phonon-activation.""" | |||||
| from test_framework.blocktools import ( | |||||
| create_block, | |||||
| create_coinbase, | |||||
| create_tx_with_script, | |||||
| make_conform_to_ctor, | |||||
| ) | |||||
| from test_framework.txtools import pad_tx | |||||
| from test_framework.messages import ( | |||||
| CBlock, | |||||
| COutPoint, | |||||
| CTransaction, | |||||
| CTxIn, | |||||
| CTxOut, | |||||
| FromHex, | |||||
| ToHex, | |||||
| ) | |||||
| from test_framework.mininode import ( | |||||
| P2PDataStore, | |||||
| ) | |||||
| from test_framework.script import ( | |||||
| CScript, | |||||
| OP_TRUE | |||||
| ) | |||||
| from test_framework.test_framework import BitcoinTestFramework | |||||
| from test_framework.util import assert_equal, assert_raises_rpc_error | |||||
| # Phonon dummy activation time | |||||
| PHONON_START_TIME = 2000000000 | |||||
| # Replay protection time needs to be moved beyond phonon activation | |||||
| REPLAY_PROTECTION_TIME = PHONON_START_TIME * 2 | |||||
| PREFORK_MAX_ANCESTORS = 25 | |||||
| PREFORK_MAX_DESCENDANTS = 25 | |||||
| POSTFORK_MAX_ANCESTORS = 50 | |||||
| POSTFORK_MAX_DESCENDANTS = 50 | |||||
| LONG_MEMPOOL_ERROR = "too-long-mempool-chain" | |||||
| class PhononPolicyChangeTest(BitcoinTestFramework): | |||||
| def set_test_params(self): | |||||
| self.num_nodes = 1 | |||||
| self.block_heights = {} | |||||
| self.extra_args = [[ | |||||
| "-phononactivationtime={}".format(PHONON_START_TIME), | |||||
| "-replayprotectionactivationtime={}".format( | |||||
deadalnix: That shoudn't be necessary. | |||||
| REPLAY_PROTECTION_TIME)]] | |||||
| def bootstrap_p2p(self): | |||||
| self.nodes[0].add_p2p_connection(P2PDataStore()) | |||||
| self.nodes[0].p2p.wait_for_verack() | |||||
| def get_best_block(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 check_for_no_ban_on_rejected_tx(self, tx, reject_reason): | |||||
deadalnixUnsubmitted Not Done Inline ActionsYou could have sent the txns the good old way as there is never really a ban situation here. deadalnix: You could have sent the txns the good old way as there is never really a ban situation here. | |||||
| """Check we are not disconnected when sending a txn that the node rejects.""" | |||||
| self.nodes[0].p2p.send_txs_and_test( | |||||
| [tx], self.nodes[0], success=False, reject_reason=reject_reason) | |||||
| def build_block(self, parent, transactions=(), n_time=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 n_time is None else n_time | |||||
| block = create_block( | |||||
| parent.sha256, create_coinbase(block_height), block_time) | |||||
| 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 build_ancestor_chain(self, spend_from, num_ancestors): | |||||
| fee = 1000 | |||||
| chain = [spend_from] | |||||
| for _ in range(num_ancestors): | |||||
| parent_tx = chain[-1] | |||||
| tx = create_tx_with_script( | |||||
| parent_tx, | |||||
| n=0, | |||||
| amount=parent_tx.vout[0].nValue - | |||||
| fee, | |||||
| script_pub_key=CScript( | |||||
| [OP_TRUE])) | |||||
| chain.append(tx) | |||||
| return chain[1:] | |||||
| def build_descendants_chain(self, spend_from, num_descendants): | |||||
| # -1 as parent counts toward descendants | |||||
| num_descendants -= 1 | |||||
| fee = 1000 | |||||
| descendants = [] | |||||
| parent_tx = CTransaction() | |||||
| parent_tx.vin.append( | |||||
| CTxIn( | |||||
| COutPoint( | |||||
| spend_from.sha256, | |||||
| 0), | |||||
| b'', | |||||
| 0xffffffff)) | |||||
| amount = (spend_from.vout[0].nValue - fee) // num_descendants | |||||
| for _ in range(num_descendants): | |||||
| parent_tx.vout.append(CTxOut(amount, CScript([OP_TRUE]))) | |||||
| pad_tx(parent_tx) | |||||
| parent_tx.rehash() | |||||
| for n in range(num_descendants): | |||||
| child_tx = create_tx_with_script( | |||||
| parent_tx, n=n, amount=parent_tx.vout[0].nValue - fee) | |||||
| descendants.append(child_tx) | |||||
| assert(len(descendants) == num_descendants) | |||||
| return [parent_tx] + descendants | |||||
| def run_test(self): | |||||
| node, = self.nodes | |||||
| self.bootstrap_p2p() | |||||
| tip = self.get_best_block(node) | |||||
| self.log.info("Create some blocks with OP_1 coinbase for spending.") | |||||
| blocks = [] | |||||
| for _ in range(10): | |||||
| tip = self.build_block(tip) | |||||
| blocks.append(tip) | |||||
| node.p2p.send_blocks_and_test(blocks, node, success=True) | |||||
| spendable_outputs = [block.vtx[0] for block in blocks] | |||||
| self.log.info("Mature the blocks and get out of IBD.") | |||||
| node.generatetoaddress(100, node.get_deterministic_priv_key().address) | |||||
| tip = self.get_best_block(node) | |||||
| self.log.info("Start pre-upgrade tests") | |||||
| # Test pre-upgrade max ancestor limit. | |||||
| # We create the full post-fork length chain for use later (+1 to test | |||||
| # too long chain) | |||||
| ancestors = self.build_ancestor_chain( | |||||
| spendable_outputs.pop(0), POSTFORK_MAX_ANCESTORS + 1) | |||||
| [node.sendrawtransaction(ToHex(tx)) | |||||
| for tx in ancestors[:PREFORK_MAX_ANCESTORS]] | |||||
| self.log.info( | |||||
| "Sending rejected transaction (too many ancestors) via RPC") | |||||
| assert_raises_rpc_error(-26, | |||||
| LONG_MEMPOOL_ERROR, | |||||
| node.sendrawtransaction, | |||||
| ToHex(ancestors[PREFORK_MAX_ANCESTORS])) | |||||
| self.log.info( | |||||
| "Sending rejected transaction (too many ancestors) via net") | |||||
| self.check_for_no_ban_on_rejected_tx( | |||||
| ancestors[PREFORK_MAX_ANCESTORS], LONG_MEMPOOL_ERROR) | |||||
| # Test pre-upgrade max descendants limit | |||||
| # We create the full post-fork length chain for use later (+1 to test | |||||
| # too long chain) | |||||
| descendants = self.build_descendants_chain( | |||||
| spendable_outputs.pop(0), POSTFORK_MAX_DESCENDANTS + 1) | |||||
| [node.sendrawtransaction(ToHex(tx)) | |||||
| for tx in descendants[:PREFORK_MAX_DESCENDANTS]] | |||||
| self.log.info( | |||||
| "Sending rejected transaction (too many descendants) via RPC") | |||||
| assert_raises_rpc_error(-26, | |||||
| LONG_MEMPOOL_ERROR, | |||||
| node.sendrawtransaction, | |||||
| ToHex(descendants[PREFORK_MAX_DESCENDANTS])) | |||||
| self.log.info( | |||||
| "Sending rejected transaction (too many descendants) via net") | |||||
| self.check_for_no_ban_on_rejected_tx( | |||||
| descendants[PREFORK_MAX_DESCENDANTS], LONG_MEMPOOL_ERROR) | |||||
| self.log.info("Start activation tests") | |||||
| self.log.info("Approach to just before upgrade activation") | |||||
| # Move our clock to the upgrade time so we will accept such | |||||
| # future-timestamped blocks. | |||||
| node.setmocktime(PHONON_START_TIME) | |||||
| # Mine six blocks with timestamp starting at PHONON_START_TIME-1 | |||||
| blocks = [] | |||||
| for i in range(-1, 5): | |||||
| tip = self.build_block(tip, n_time=PHONON_START_TIME + i) | |||||
| blocks.append(tip) | |||||
| node.p2p.send_blocks_and_test(blocks, node) | |||||
| # Ensure our MTP is PHONON_START_TIME-1, just before activation | |||||
| assert_equal(node.getblockchaininfo()['mediantime'], | |||||
| PHONON_START_TIME - 1) | |||||
| self.log.info( | |||||
| "The next block will activate, but at the activation block itself must follow old rules") | |||||
| self.check_for_no_ban_on_rejected_tx( | |||||
| ancestors[PREFORK_MAX_ANCESTORS], LONG_MEMPOOL_ERROR) | |||||
| self.check_for_no_ban_on_rejected_tx( | |||||
| descendants[PREFORK_MAX_DESCENDANTS], LONG_MEMPOOL_ERROR) | |||||
| # Save pre-upgrade block, we will reorg based on this block later | |||||
| pre_upgrade_block = tip | |||||
| self.log.info("Mine the activation block itself") | |||||
| tip = self.build_block(tip, []) | |||||
| node.p2p.send_blocks_and_test([tip], node) | |||||
| self.log.info("We have activated!") | |||||
| # Ensure our MTP is PHONON_START_TIME, exactly at activation | |||||
| assert_equal(node.getblockchaininfo()['mediantime'], PHONON_START_TIME) | |||||
| # Test post-upgrade max ancestor limit | |||||
| [node.sendrawtransaction( | |||||
| ToHex(tx)) for tx in ancestors[PREFORK_MAX_ANCESTORS:POSTFORK_MAX_ANCESTORS]] | |||||
| self.log.info( | |||||
| "Sending rejected transaction (too many ancestors) via RPC") | |||||
| assert_raises_rpc_error(-26, | |||||
| LONG_MEMPOOL_ERROR, | |||||
| node.sendrawtransaction, | |||||
| ToHex(ancestors[-1])) | |||||
| self.log.info( | |||||
| "Sending rejected transaction (too many ancestors) via net") | |||||
| self.check_for_no_ban_on_rejected_tx(ancestors[-1], LONG_MEMPOOL_ERROR) | |||||
| # Test post-upgrade max descendants limit | |||||
| [node.sendrawtransaction(ToHex( | |||||
| tx)) for tx in descendants[PREFORK_MAX_DESCENDANTS:POSTFORK_MAX_DESCENDANTS]] | |||||
| self.log.info( | |||||
| "Sending rejected transaction (too many descendants) via RPC") | |||||
| assert_raises_rpc_error(-26, | |||||
| LONG_MEMPOOL_ERROR, | |||||
| node.sendrawtransaction, | |||||
| ToHex(descendants[-1])) | |||||
| self.log.info( | |||||
| "Sending rejected transaction (too many descendants) via net") | |||||
| self.check_for_no_ban_on_rejected_tx( | |||||
| descendants[-1], LONG_MEMPOOL_ERROR) | |||||
| self.log.info("Start deactivation tests") | |||||
| self.log.info( | |||||
| "Invalidating the pre-upgrade blocks trims the mempool back to old policies") | |||||
| node.invalidateblock(pre_upgrade_block.hash) | |||||
| assert_equal(PREFORK_MAX_ANCESTORS + | |||||
| PREFORK_MAX_DESCENDANTS, len(node.getrawmempool())) | |||||
| expected = set([tx.hash for tx in ancestors[:PREFORK_MAX_ANCESTORS] + | |||||
| descendants[:PREFORK_MAX_ANCESTORS]]) | |||||
| assert_equal(set(node.getrawmempool()), expected) | |||||
| if __name__ == '__main__': | |||||
| PhononPolicyChangeTest().main() | |||||
That shoudn't be necessary.