mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-07-28 14:53:03 +02:00
Merge bitcoin/bitcoin#31384: mining: bugfix: Fix duplicate coinbase tx weight reservation
386eecff5f
doc: add release notes (ismaelsadeeq)3eaa0a3b66
miner: init: add `-blockreservedweight` startup option (ismaelsadeeq)777434a2cd
doc: rpc: improve `getmininginfo` help text (ismaelsadeeq)c8acd4032d
init: fail to start when `-blockmaxweight` exceeds `MAX_BLOCK_WEIGHT` (ismaelsadeeq)5bb31633cc
test: add `-blockmaxweight` startup option functional test (ismaelsadeeq)2c7d90a6d6
miner: bugfix: fix duplicate weight reservation in block assembler (ismaelsadeeq) Pull request description: * This PR attempts to fix the duplicate coinbase weight reservation issue we currently have. * Fixes #21950 We reserve 4000 weight units for coinbase transaction in `DEFAULT_BLOCK_MAX_WEIGHT`7590e93bc7/src/policy/policy.h (L23)
And also reserve additional `4000` weight units in the default `BlockCreationOptions` struct.7590e93bc7/src/node/types.h (L36-L40)
**Motivation** - This issue was first noticed during a review here https://github.com/bitcoin/bitcoin/pull/11100#discussion_r136157411) - It was later reported in issue #21950. - I also came across the bug while writing a test for building the block template. I could not create a block template above `3,992,000` in the block assembler, and this was not documented anywhere. It took me a while to realize that we were reserving space for the coinbase transaction weight twice. --- This PR fixes this by consolidating the reservation to be in a single location in the codebase. This PR then adds a new startup option `-blockreservedweight` whose default is `8000` that can be used to lower or increase the block reserved weight for block header, txs count, coinbase tx. ACKs for top commit: Sjors: ACK386eecff5f
fjahr: Code review ACK386eecff5f
glozow: utACK386eecff5f
, nonblocking nits. I do think the release notes should be clarified more pinheadmz: ACK386eecff5f
Tree-SHA512: f27efa1da57947b7f4d42b9322b83d13afe73dd749dd9cac49360002824dd41c99a876a610554ac2d67bad7485020b9dcc423a8e6748fc79d6a10de6d4357d4c
This commit is contained in:
@@ -26,7 +26,11 @@ from test_framework.messages import (
|
||||
CBlock,
|
||||
CBlockHeader,
|
||||
COIN,
|
||||
DEFAULT_BLOCK_RESERVED_WEIGHT,
|
||||
MAX_BLOCK_WEIGHT,
|
||||
MINIMUM_BLOCK_RESERVED_WEIGHT,
|
||||
ser_uint256,
|
||||
WITNESS_SCALE_FACTOR
|
||||
)
|
||||
from test_framework.p2p import P2PDataStore
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
@@ -77,7 +81,7 @@ class MiningTest(BitcoinTestFramework):
|
||||
mining_info = self.nodes[0].getmininginfo()
|
||||
assert_equal(mining_info['blocks'], 200)
|
||||
assert_equal(mining_info['currentblocktx'], 0)
|
||||
assert_equal(mining_info['currentblockweight'], 4000)
|
||||
assert_equal(mining_info['currentblockweight'], DEFAULT_BLOCK_RESERVED_WEIGHT)
|
||||
|
||||
self.log.info('test blockversion')
|
||||
self.restart_node(0, extra_args=[f'-mocktime={t}', '-blockversion=1337'])
|
||||
@@ -195,6 +199,116 @@ 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_BLOCK_RESERVED_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_BLOCK_RESERVED_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_BLOCK_RESERVED_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_BLOCK_RESERVED_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_BLOCK_RESERVED_WEIGHT,
|
||||
)
|
||||
|
||||
self.log.info("Test -blockreservedweight startup option.")
|
||||
# Lowering the -blockreservedweight by 4000 will allow for two more transactions.
|
||||
self.restart_node(0, extra_args=[f"-datacarriersize={LARGE_VSIZE}", "-blockreservedweight=4000"])
|
||||
self.verify_block_template(
|
||||
expected_tx_count=12,
|
||||
expected_weight=MAX_BLOCK_WEIGHT - 4000,
|
||||
)
|
||||
|
||||
self.log.info("Test that node will fail to start when user provide invalid -blockreservedweight")
|
||||
self.stop_node(0)
|
||||
self.nodes[0].assert_start_raises_init_error(
|
||||
extra_args=[f"-blockreservedweight={MAX_BLOCK_WEIGHT + 1}"],
|
||||
expected_msg=f"Error: Specified -blockreservedweight ({MAX_BLOCK_WEIGHT + 1}) exceeds consensus maximum block weight ({MAX_BLOCK_WEIGHT})",
|
||||
)
|
||||
|
||||
self.log.info(f"Test that node will fail to start when user provide -blockreservedweight below {MINIMUM_BLOCK_RESERVED_WEIGHT}")
|
||||
self.stop_node(0)
|
||||
self.nodes[0].assert_start_raises_init_error(
|
||||
extra_args=[f"-blockreservedweight={MINIMUM_BLOCK_RESERVED_WEIGHT - 1}"],
|
||||
expected_msg=f"Error: Specified -blockreservedweight ({MINIMUM_BLOCK_RESERVED_WEIGHT - 1}) is lower than minimum safety value of ({MINIMUM_BLOCK_RESERVED_WEIGHT})",
|
||||
)
|
||||
|
||||
self.log.info("Test that node will fail to start when user provide invalid -blockmaxweight")
|
||||
self.stop_node(0)
|
||||
self.nodes[0].assert_start_raises_init_error(
|
||||
extra_args=[f"-blockmaxweight={MAX_BLOCK_WEIGHT + 1}"],
|
||||
expected_msg=f"Error: Specified -blockmaxweight ({MAX_BLOCK_WEIGHT + 1}) exceeds consensus maximum block weight ({MAX_BLOCK_WEIGHT})",
|
||||
)
|
||||
|
||||
|
||||
def run_test(self):
|
||||
node = self.nodes[0]
|
||||
self.wallet = MiniWallet(node)
|
||||
@@ -420,6 +534,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()
|
||||
|
||||
|
@@ -33,6 +33,8 @@ from test_framework.util import assert_equal
|
||||
|
||||
MAX_LOCATOR_SZ = 101
|
||||
MAX_BLOCK_WEIGHT = 4000000
|
||||
DEFAULT_BLOCK_RESERVED_WEIGHT = 8000
|
||||
MINIMUM_BLOCK_RESERVED_WEIGHT = 2000
|
||||
MAX_BLOOM_FILTER_SIZE = 36000
|
||||
MAX_BLOOM_HASH_FUNCS = 50
|
||||
|
||||
|
Reference in New Issue
Block a user