Merge bitcoin/bitcoin#32406: policy: uncap datacarrier by default

a189d63618 add release note for datacarriersize default change (Greg Sanders)
a141e1bf50 Add more OP_RETURN mempool acceptance functional tests (Peter Todd)
0b4048c733 datacarrier: deprecate startup arguments for future removal (Greg Sanders)
63091b79e7 test: remove unnecessary -datacarriersize args from tests (Greg Sanders)
9f36962b07 policy: uncap datacarrier by default (Greg Sanders)

Pull request description:

  Retains the `-datacarrier*` args, marks them as deprecated, and does not require another startup argument for multiple OP_RETURN outputs.

  If a user has set `-datacarriersize` the value is "budgeted" across all seen OP_RETURN output scriptPubKeys. In other words the total script bytes stays the same, but can be spread across any number of outputs. This is done to not introduce an additional argument to support multiple outputs.

  I do not advise people use the option with custom arguments and it is marked as deprecated to not mislead as a promise to offer it forever. The argument itself can be removed in some future release to clean up the code and minimize footguns for users.

ACKs for top commit:
  stickies-v:
    re-ACK a189d63618
  Sjors:
    re-ACK a189d63618
  polespinasa:
    re-ACK a189d63618
  hodlinator:
    re-ACK a189d63618
  ajtowns:
    reACK a189d63618
  mzumsande:
    re-ACK a189d63618
  petertodd:
    ACK a189d63618
  theStack:
    re-ACK a189d63618
  1440000bytes:
    re-ACK a189d63618
  willcl-ark:
    ACK a189d63618
  dergoegge:
    ACK a189d63618
  fanquake:
    ACK a189d63618
  murchandamus:
    ACK a189d63618
  darosior:
    Concept ACK a189d63618.

Tree-SHA512: 3da2f1ef2f50884d4da7e50df2121bf175cb826edaa14ba7c3068a6d5b2a70beb426edc55d50338ee1d9686b9f74fdf9e10d30fb26a023a718dd82fa1e77b038
This commit is contained in:
merge-script
2025-06-09 08:23:56 -04:00
26 changed files with 147 additions and 95 deletions

View File

@@ -23,7 +23,6 @@ class BlocksXORTest(BitcoinTestFramework):
self.extra_args = [[
'-blocksxor=1',
'-fastprune=1', # use smaller block files
'-datacarriersize=100000', # needed to pad transaction with MiniWallet
]]
def run_test(self):

View File

@@ -51,7 +51,6 @@ class MaxUploadTest(BitcoinTestFramework):
self.num_nodes = 1
self.extra_args = [[
f"-maxuploadtarget={UPLOAD_TARGET_MB}M",
"-datacarriersize=100000",
]]
self.supports_cli = False

View File

@@ -9,6 +9,7 @@ from decimal import Decimal
import math
from test_framework.test_framework import BitcoinTestFramework
from test_framework.blocktools import MAX_STANDARD_TX_WEIGHT
from test_framework.messages import (
MAX_BIP125_RBF_SEQUENCE,
COIN,
@@ -326,11 +327,52 @@ class MempoolAcceptanceTest(BitcoinTestFramework):
result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'dust'}],
rawtxs=[tx.serialize().hex()],
)
# OP_RETURN followed by non-push
tx = tx_from_hex(raw_tx_reference)
tx.vout[0].scriptPubKey = CScript([OP_RETURN, b'\xff'])
tx.vout = [tx.vout[0]] * 2
tx.vout[0].scriptPubKey = CScript([OP_RETURN, OP_HASH160])
self.check_mempool_result(
result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'multi-op-return'}],
result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'scriptpubkey'}],
rawtxs=[tx.serialize().hex()],
)
# Multiple OP_RETURN and more than 83 bytes, even if over MAX_SCRIPT_ELEMENT_SIZE
# are standard since v30
tx = tx_from_hex(raw_tx_reference)
tx.vout.append(CTxOut(0, CScript([OP_RETURN, b'\xff'])))
tx.vout.append(CTxOut(0, CScript([OP_RETURN, b'\xff' * 50000])))
self.check_mempool_result(
result_expected=[{'txid': tx.rehash(), 'allowed': True, 'vsize': tx.get_vsize(), 'fees': {'base': Decimal('0.05')}}],
rawtxs=[tx.serialize().hex()],
maxfeerate=0
)
self.log.info("A transaction with several OP_RETURN outputs.")
tx = tx_from_hex(raw_tx_reference)
op_return_count = 42
tx.vout[0].nValue = int(tx.vout[0].nValue / op_return_count)
tx.vout[0].scriptPubKey = CScript([OP_RETURN, b'\xff'])
tx.vout = [tx.vout[0]] * op_return_count
self.check_mempool_result(
result_expected=[{"txid": tx.rehash(), "allowed": True, "vsize": tx.get_vsize(), "fees": {"base": Decimal("0.05000026")}}],
rawtxs=[tx.serialize().hex()],
)
self.log.info("A transaction with an OP_RETURN output that bumps into the max standardness tx size.")
tx = tx_from_hex(raw_tx_reference)
tx.vout[0].scriptPubKey = CScript([OP_RETURN])
data_len = int(MAX_STANDARD_TX_WEIGHT / 4) - tx.get_vsize() - 5 - 4 # -5 for PUSHDATA4 and -4 for script size
tx.vout[0].scriptPubKey = CScript([OP_RETURN, b"\xff" * (data_len)])
assert_equal(tx.get_vsize(), int(MAX_STANDARD_TX_WEIGHT / 4))
self.check_mempool_result(
result_expected=[{"txid": tx.rehash(), "allowed": True, "vsize": tx.get_vsize(), "fees": {"base": Decimal("0.1") - Decimal("0.05")}}],
rawtxs=[tx.serialize().hex()],
)
tx.vout[0].scriptPubKey = CScript([OP_RETURN, b"\xff" * (data_len + 1)])
assert_greater_than(tx.get_vsize(), int(MAX_STANDARD_TX_WEIGHT / 4))
self.check_mempool_result(
result_expected=[{"txid": tx.rehash(), "allowed": False, "reject-reason": "tx-size"}],
rawtxs=[tx.serialize().hex()],
)

View File

@@ -18,14 +18,16 @@ from test_framework.wallet import MiniWallet
from random import randbytes
# The historical maximum, now used to test coverage
CUSTOM_DATACARRIER_ARG = 83
class DataCarrierTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 4
self.extra_args = [
[],
["-datacarrier=0"],
["-datacarrier=1", f"-datacarriersize={MAX_OP_RETURN_RELAY - 1}"],
[], # default is uncapped
["-datacarrier=0"], # no relay of datacarrier
["-datacarrier=1", f"-datacarriersize={CUSTOM_DATACARRIER_ARG}"],
["-datacarrier=1", "-datacarriersize=2"],
]
@@ -41,32 +43,33 @@ class DataCarrierTest(BitcoinTestFramework):
self.wallet.sendrawtransaction(from_node=node, tx_hex=tx_hex)
assert tx.rehash() in node.getrawmempool(True), f'{tx_hex} not in mempool'
else:
assert_raises_rpc_error(-26, "scriptpubkey", self.wallet.sendrawtransaction, from_node=node, tx_hex=tx_hex)
assert_raises_rpc_error(-26, "datacarrier", self.wallet.sendrawtransaction, from_node=node, tx_hex=tx_hex)
def run_test(self):
self.wallet = MiniWallet(self.nodes[0])
# By default, only 80 bytes are used for data (+1 for OP_RETURN, +2 for the pushdata opcodes).
default_size_data = randbytes(MAX_OP_RETURN_RELAY - 3)
too_long_data = randbytes(MAX_OP_RETURN_RELAY - 2)
small_data = randbytes(MAX_OP_RETURN_RELAY - 4)
# By default, any size is allowed.
# If it is custom set to 83, the historical value,
# only 80 bytes are used for data (+1 for OP_RETURN, +2 for the pushdata opcodes).
custom_size_data = randbytes(CUSTOM_DATACARRIER_ARG - 3)
too_long_data = randbytes(CUSTOM_DATACARRIER_ARG - 2)
extremely_long_data = randbytes(MAX_OP_RETURN_RELAY - 200)
one_byte = randbytes(1)
zero_bytes = randbytes(0)
self.log.info("Testing null data transaction with default -datacarrier and -datacarriersize values.")
self.test_null_data_transaction(node=self.nodes[0], data=default_size_data, success=True)
self.log.info("Testing a null data transaction larger than allowed by the default -datacarriersize value.")
self.test_null_data_transaction(node=self.nodes[0], data=too_long_data, success=False)
self.log.info("Testing a null data transaction succeeds for default arg regardless of size.")
self.test_null_data_transaction(node=self.nodes[0], data=too_long_data, success=True)
self.test_null_data_transaction(node=self.nodes[0], data=extremely_long_data, success=True)
self.log.info("Testing a null data transaction with -datacarrier=false.")
self.test_null_data_transaction(node=self.nodes[1], data=default_size_data, success=False)
self.test_null_data_transaction(node=self.nodes[1], data=custom_size_data, success=False)
self.log.info("Testing a null data transaction with a size larger than accepted by -datacarriersize.")
self.test_null_data_transaction(node=self.nodes[2], data=default_size_data, success=False)
self.test_null_data_transaction(node=self.nodes[2], data=too_long_data, success=False)
self.log.info("Testing a null data transaction with a size smaller than accepted by -datacarriersize.")
self.test_null_data_transaction(node=self.nodes[2], data=small_data, success=True)
self.log.info("Testing a null data transaction with a size equal to -datacarriersize.")
self.test_null_data_transaction(node=self.nodes[2], data=custom_size_data, success=True)
self.log.info("Testing a null data transaction with no data.")
self.test_null_data_transaction(node=self.nodes[0], data=None, success=True)
@@ -86,6 +89,17 @@ class DataCarrierTest(BitcoinTestFramework):
self.test_null_data_transaction(node=self.nodes[2], data=one_byte, success=True)
self.test_null_data_transaction(node=self.nodes[3], data=one_byte, success=False)
# Clean shutdown boilerplate due to deprecation
self.expected_stderr = [
"", # node 0 has no deprecated options
"Warning: Options '-datacarrier' or '-datacarriersize' are set but are marked as deprecated. They will be removed in a future version.",
"Warning: Options '-datacarrier' or '-datacarriersize' are set but are marked as deprecated. They will be removed in a future version.",
"Warning: Options '-datacarrier' or '-datacarriersize' are set but are marked as deprecated. They will be removed in a future version.",
]
for i in range(self.num_nodes):
self.stop_node(i, expected_stderr=self.expected_stderr[i])
if __name__ == '__main__':
DataCarrierTest(__file__).main()

View File

@@ -29,7 +29,6 @@ class MempoolLimitTest(BitcoinTestFramework):
self.setup_clean_chain = True
self.num_nodes = 1
self.extra_args = [[
"-datacarriersize=100000",
"-maxmempool=5",
]]
self.supports_cli = False

View File

@@ -51,9 +51,6 @@ class MempoolPackageLimitsTest(BitcoinTestFramework):
self.test_anc_count_limits()
self.test_anc_count_limits_2()
self.test_anc_count_limits_bushy()
# The node will accept (nonstandard) extra large OP_RETURN outputs
self.restart_node(0, extra_args=["-datacarriersize=100000"])
self.test_anc_size_limits()
self.test_desc_size_limits()

View File

@@ -33,7 +33,6 @@ class PackageRBFTest(BitcoinTestFramework):
self.setup_clean_chain = True
# Required for fill_mempool()
self.extra_args = [[
"-datacarriersize=100000",
"-maxmempool=5",
]] * self.num_nodes

View File

@@ -44,8 +44,6 @@ MAX_PUBKEYS_PER_MULTISIG = 20
class BytesPerSigOpTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 1
# allow large datacarrier output to pad transactions
self.extra_args = [['-datacarriersize=100000']]
def create_p2wsh_spending_tx(self, witness_script, output_script):
"""Create a 1-input-1-output P2WSH spending transaction with only the
@@ -139,7 +137,7 @@ class BytesPerSigOpTest(BitcoinTestFramework):
self.log.info("Test a overly-large sigops-vbyte hits package limits")
# Make a 2-transaction package which fails vbyte checks even though
# separately they would work.
self.restart_node(0, extra_args=["-bytespersigop=5000","-permitbaremultisig=1"] + self.extra_args[0])
self.restart_node(0, extra_args=["-bytespersigop=5000","-permitbaremultisig=1"])
def create_bare_multisig_tx(utxo_to_spend=None):
_, pubkey = generate_keypair()
@@ -185,7 +183,7 @@ class BytesPerSigOpTest(BitcoinTestFramework):
else:
bytespersigop_parameter = f"-bytespersigop={bytes_per_sigop}"
self.log.info(f"Test sigops limit setting {bytespersigop_parameter}...")
self.restart_node(0, extra_args=[bytespersigop_parameter] + self.extra_args[0])
self.restart_node(0, extra_args=[bytespersigop_parameter])
for num_sigops in (69, 101, 142, 183, 222):
self.test_sigops_limit(bytes_per_sigop, num_sigops)

View File

@@ -49,7 +49,7 @@ class MempoolTRUC(BitcoinTestFramework):
assert_equal(len(txids), len(mempool_contents))
assert all([txid in txids for txid in mempool_contents])
@cleanup(extra_args=["-datacarriersize=20000"])
@cleanup()
def test_truc_max_vsize(self):
node = self.nodes[0]
self.log.info("Test TRUC-specific maximum transaction vsize")
@@ -63,7 +63,7 @@ class MempoolTRUC(BitcoinTestFramework):
tx_v2_heavy = self.wallet.send_self_transfer(from_node=node, target_vsize=TRUC_MAX_VSIZE + 1, version=2)
self.check_mempool([tx_v2_heavy["txid"]])
@cleanup(extra_args=["-datacarriersize=1000"])
@cleanup()
def test_truc_acceptance(self):
node = self.nodes[0]
self.log.info("Test a child of a TRUC transaction cannot be more than 1000vB")
@@ -160,7 +160,7 @@ class MempoolTRUC(BitcoinTestFramework):
self.check_mempool([tx_v3_bip125_rbf_v2["txid"], tx_v3_parent["txid"], tx_v3_child["txid"]])
@cleanup(extra_args=["-datacarriersize=40000"])
@cleanup()
def test_truc_reorg(self):
node = self.nodes[0]
self.log.info("Test that, during a reorg, TRUC rules are not enforced")
@@ -182,7 +182,7 @@ class MempoolTRUC(BitcoinTestFramework):
node.reconsiderblock(block[0])
@cleanup(extra_args=["-limitdescendantsize=10", "-datacarriersize=40000"])
@cleanup(extra_args=["-limitdescendantsize=10"])
def test_nondefault_package_limits(self):
"""
Max standard tx size + TRUC rules imply the ancestor/descendant rules (at their default
@@ -215,7 +215,7 @@ class MempoolTRUC(BitcoinTestFramework):
self.generate(node, 1)
self.log.info("Test that a decreased limitancestorsize also applies to v3 parent")
self.restart_node(0, extra_args=["-limitancestorsize=10", "-datacarriersize=40000"])
self.restart_node(0, extra_args=["-limitancestorsize=10"])
tx_v3_parent_large2 = self.wallet.send_self_transfer(
from_node=node,
target_vsize=parent_target_vsize,
@@ -235,7 +235,7 @@ class MempoolTRUC(BitcoinTestFramework):
assert_raises_rpc_error(-26, "too-long-mempool-chain, exceeds ancestor size limit", node.sendrawtransaction, tx_v3_child_large2["hex"])
self.check_mempool([tx_v3_parent_large2["txid"]])
@cleanup(extra_args=["-datacarriersize=1000"])
@cleanup()
def test_truc_ancestors_package(self):
self.log.info("Test that TRUC ancestor limits are checked within the package")
node = self.nodes[0]
@@ -384,7 +384,7 @@ class MempoolTRUC(BitcoinTestFramework):
assert_equal(result_package_cpfp["tx-results"][tx_sibling_3['wtxid']]['error'], expected_error_cpfp)
@cleanup(extra_args=["-datacarriersize=1000"])
@cleanup()
def test_truc_package_inheritance(self):
self.log.info("Test that TRUC inheritance is checked within package")
node = self.nodes[0]

View File

@@ -28,7 +28,7 @@ class MempoolUpdateFromBlockTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 1
# Ancestor and descendant limits depend on transaction_graph_test requirements
self.extra_args = [['-limitdescendantsize=1000', '-limitancestorsize=1000', f'-limitancestorcount={CUSTOM_ANCESTOR_COUNT}', f'-limitdescendantcount={CUSTOM_DESCENDANT_COUNT}', '-datacarriersize=100000']]
self.extra_args = [['-limitdescendantsize=1000', '-limitancestorsize=1000', f'-limitancestorcount={CUSTOM_ANCESTOR_COUNT}', f'-limitdescendantcount={CUSTOM_DESCENDANT_COUNT}']]
def create_empty_fork(self, fork_length):
'''

View File

@@ -187,6 +187,9 @@ class MiningTest(BitcoinTestFramework):
assert tx_below_min_feerate['txid'] not in block_template_txids
assert tx_below_min_feerate['txid'] not in block_txids
# Restart node to clear mempool for the next test
self.restart_node(0)
def test_timewarp(self):
self.log.info("Test timewarp attack mitigation (BIP94)")
node = self.nodes[0]
@@ -280,11 +283,9 @@ class MiningTest(BitcoinTestFramework):
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)
@@ -312,7 +313,7 @@ class MiningTest(BitcoinTestFramework):
# 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.restart_node(0, extra_args=[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(
@@ -322,7 +323,7 @@ class MiningTest(BitcoinTestFramework):
# 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.restart_node(0, extra_args=[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}.")
@@ -336,7 +337,7 @@ class MiningTest(BitcoinTestFramework):
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.restart_node(0, extra_args=["-blockreservedweight=4000"])
self.verify_block_template(
expected_tx_count=12,
expected_weight=MAX_BLOCK_WEIGHT - 4000,

View File

@@ -27,7 +27,6 @@ class PrioritiseTransactionTest(BitcoinTestFramework):
self.num_nodes = 1
self.extra_args = [[
"-printpriority=1",
"-datacarriersize=100000",
]] * self.num_nodes
self.supports_cli = False

View File

@@ -41,7 +41,6 @@ class PackageRelayTest(BitcoinTestFramework):
# hugely speeds up the test, as it involves multiple hops of tx relay.
self.noban_tx_relay = True
self.extra_args = [[
"-datacarriersize=100000",
"-maxmempool=5",
]] * self.num_nodes
self.supports_cli = False

View File

@@ -59,7 +59,6 @@ class PackageRelayTest(BitcoinTestFramework):
self.setup_clean_chain = True
self.num_nodes = 1
self.extra_args = [[
"-datacarriersize=100000",
"-maxmempool=5",
]]
self.supports_cli = False

View File

@@ -68,7 +68,7 @@ class ConnectionType(Enum):
class TxDownloadTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 2
self.extra_args= [['-datacarriersize=100000', '-maxmempool=5', '-persistmempool=0']] * self.num_nodes
self.extra_args= [['-maxmempool=5', '-persistmempool=0']] * self.num_nodes
def test_tx_requests(self):
self.log.info("Test that we request transactions from all our peers, eventually")

View File

@@ -438,7 +438,6 @@ class RPCPackagesTest(BitcoinTestFramework):
# but child is too high fee
# Lower mempool limit to make it easier to fill_mempool
self.restart_node(0, extra_args=[
"-datacarriersize=100000",
"-maxmempool=5",
"-persistmempool=0",
])
@@ -467,7 +466,7 @@ class RPCPackagesTest(BitcoinTestFramework):
assert parent["txid"] not in node.getrawmempool()
assert child["txid"] not in node.getrawmempool()
# Reset maxmempool, datacarriersize, reset dynamic mempool minimum feerate, and empty mempool.
# Reset maxmempool, reset dynamic mempool minimum feerate, and empty mempool.
self.restart_node(0)
self.wallet.rescan_utxos()

View File

@@ -41,7 +41,7 @@ def fill_mempool(test_framework, node, *, tx_sync_fun=None):
"""Fill mempool until eviction.
Allows for simpler testing of scenarios with floating mempoolminfee > minrelay
Requires -datacarriersize=100000 and -maxmempool=5 and assumes -minrelaytxfee
Requires -maxmempool=5 and assumes -minrelaytxfee
is 1 sat/vbyte.
To avoid unintentional tx dependencies, the mempool filling txs are created with a
tagged ephemeral miniwallet instance.

View File

@@ -73,8 +73,10 @@ WITNESS_SCALE_FACTOR = 4
DEFAULT_ANCESTOR_LIMIT = 25 # default max number of in-mempool ancestors
DEFAULT_DESCENDANT_LIMIT = 25 # default max number of in-mempool descendants
# Default setting for -datacarriersize. 80 bytes of data, +1 for OP_RETURN, +2 for the pushdata opcodes.
MAX_OP_RETURN_RELAY = 83
# Default setting for -datacarriersize.
MAX_OP_RETURN_RELAY = 100_000
DEFAULT_MEMPOOL_EXPIRY_HOURS = 336 # hours