From b55bb95d8d80175f30d2d4da5c6cfd72e5d8eedd Mon Sep 17 00:00:00 2001 From: ismaelsadeeq Date: Sat, 30 Nov 2024 15:47:46 -0500 Subject: [PATCH] test: add `-blockmaxweight` startup option functional test --- test/functional/mining_basic.py | 84 +++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/test/functional/mining_basic.py b/test/functional/mining_basic.py index 4ac3dd91c7..49d535d7cc 100755 --- a/test/functional/mining_basic.py +++ b/test/functional/mining_basic.py @@ -23,7 +23,9 @@ from test_framework.messages import ( CBlockHeader, COIN, DEFAULT_COINBASE_TX_WEIGHT, + MAX_BLOCK_WEIGHT, ser_uint256, + WITNESS_SCALE_FACTOR ) from test_framework.p2p import P2PDataStore from test_framework.test_framework import BitcoinTestFramework @@ -190,6 +192,87 @@ class MiningTest(BitcoinTestFramework): assert_equal(result, "inconclusive") assert_equal(prune_node.getblock(pruned_blockhash, verbosity=0), pruned_block) + + def send_transactions(self, utxos, fee_rate, target_vsize): + """ + Helper to create and send transactions with the specified target virtual size and fee rate. + """ + for utxo in utxos: + self.wallet.send_self_transfer( + from_node=self.nodes[0], + utxo_to_spend=utxo, + target_vsize=target_vsize, + fee_rate=fee_rate, + ) + + def verify_block_template(self, expected_tx_count, expected_weight): + """ + Create a block template and check that it satisfies the expected transaction count and total weight. + """ + response = self.nodes[0].getblocktemplate(NORMAL_GBT_REQUEST_PARAMS) + self.log.info(f"Testing block template: contains {expected_tx_count} transactions, and total weight <= {expected_weight}") + assert_equal(len(response["transactions"]), expected_tx_count) + total_weight = sum(transaction["weight"] for transaction in response["transactions"]) + assert_greater_than_or_equal(expected_weight, total_weight) + + def test_block_max_weight(self): + self.log.info("Testing default and custom -blockmaxweight startup options.") + + # Restart the node to allow large transactions + LARGE_TXS_COUNT = 10 + LARGE_VSIZE = int(((MAX_BLOCK_WEIGHT - DEFAULT_COINBASE_TX_WEIGHT) / WITNESS_SCALE_FACTOR) / LARGE_TXS_COUNT) + HIGH_FEERATE = Decimal("0.0003") + self.restart_node(0, extra_args=[f"-datacarriersize={LARGE_VSIZE}"]) + + # Ensure the mempool is empty + assert_equal(len(self.nodes[0].getrawmempool()), 0) + + # Generate UTXOs and send 10 large transactions with a high fee rate + utxos = [self.wallet.get_utxo(confirmed_only=True) for _ in range(LARGE_TXS_COUNT + 4)] # Add 4 more utxos that will be used in the test later + self.send_transactions(utxos[:LARGE_TXS_COUNT], HIGH_FEERATE, LARGE_VSIZE) + + # Send 2 normal transactions with a lower fee rate + NORMAL_VSIZE = int(2000 / WITNESS_SCALE_FACTOR) + NORMAL_FEERATE = Decimal("0.0001") + self.send_transactions(utxos[LARGE_TXS_COUNT:LARGE_TXS_COUNT + 2], NORMAL_FEERATE, NORMAL_VSIZE) + + # Check that the mempool contains all transactions + self.log.info(f"Testing that the mempool contains {LARGE_TXS_COUNT + 2} transactions.") + assert_equal(len(self.nodes[0].getrawmempool()), LARGE_TXS_COUNT + 2) + + # Verify the block template includes only the 10 high-fee transactions + self.log.info("Testing that the block template includes only the 10 large transactions.") + self.verify_block_template( + expected_tx_count=LARGE_TXS_COUNT, + expected_weight=MAX_BLOCK_WEIGHT - DEFAULT_COINBASE_TX_WEIGHT, + ) + + # Test block template creation with custom -blockmaxweight + custom_block_weight = MAX_BLOCK_WEIGHT - 2000 + # Reducing the weight by 2000 units will prevent 1 large transaction from fitting into the block. + self.restart_node(0, extra_args=[f"-datacarriersize={LARGE_VSIZE}", f"-blockmaxweight={custom_block_weight}"]) + + self.log.info("Testing the block template with custom -blockmaxweight to include 9 large and 2 normal transactions.") + self.verify_block_template( + expected_tx_count=11, + expected_weight=MAX_BLOCK_WEIGHT - DEFAULT_COINBASE_TX_WEIGHT - 2000, + ) + + # Ensure the block weight does not exceed the maximum + self.log.info(f"Testing that the block weight will never exceed {MAX_BLOCK_WEIGHT - DEFAULT_COINBASE_TX_WEIGHT}.") + self.restart_node(0, extra_args=[f"-datacarriersize={LARGE_VSIZE}", f"-blockmaxweight={MAX_BLOCK_WEIGHT}"]) + self.log.info("Sending 2 additional normal transactions to fill the mempool to the maximum block weight.") + self.send_transactions(utxos[LARGE_TXS_COUNT + 2:], NORMAL_FEERATE, NORMAL_VSIZE) + self.log.info(f"Testing that the mempool's weight matches the maximum block weight: {MAX_BLOCK_WEIGHT}.") + assert_equal(self.nodes[0].getmempoolinfo()['bytes'] * WITNESS_SCALE_FACTOR, MAX_BLOCK_WEIGHT) + + self.log.info("Testing that the block template includes only 10 transactions and cannot reach full block weight.") + self.verify_block_template( + expected_tx_count=LARGE_TXS_COUNT, + expected_weight=MAX_BLOCK_WEIGHT - DEFAULT_COINBASE_TX_WEIGHT, + ) + + def run_test(self): node = self.nodes[0] self.wallet = MiniWallet(node) @@ -407,6 +490,7 @@ class MiningTest(BitcoinTestFramework): assert_equal(node.submitblock(hexdata=block.serialize().hex()), 'duplicate') # valid self.test_blockmintxfee_parameter() + self.test_block_max_weight() self.test_timewarp() self.test_pruning()