test: bad-cb-length for createNewBlock() at low heights

On new regtest / signet chains (heights <= 16), createBlock() fails
internally with bad-cb-length. Since mining.capnp does not expose
include_dummy_extranonce, IPC clients can't work around this.

Add a functional test to illustrate this issue and use RPC to
work around it. The next commit introduces a fix.
This commit is contained in:
Sjors Provoost
2026-03-18 10:17:55 +01:00
parent 1966621b76
commit 605ff37403
2 changed files with 44 additions and 2 deletions

View File

@@ -403,6 +403,45 @@ class IPCMiningTest(BitcoinTestFramework):
asyncio.run(capnp.run(async_routine()))
def run_low_height_test(self):
"""Test that IPC createNewBlock() works at low block heights on a
clean chain. Currently fails with bad-cb-length and falls back to RPC."""
self.log.info("Running low block height test")
node = self.nodes[0]
self.stop_node(0)
# Clear chain data to start from genesis
self.cleanup_folder(node.chain_path)
node.start()
node.wait_for_rpc_connection()
assert_equal(node.getblockcount(), 0)
async def async_routine():
ctx, mining = await make_mining_ctx(self)
opts = self.capnp_modules['mining'].BlockCreateOptions()
# IPC createNewBlock() currently fails with bad-cb-length at heights
# <= 16, so use the generate RPC as a workaround. The next commit
# fixes this and replaces the loop body with a createNewBlock() /
# submitSolution() flow.
for i in range(17):
async with AsyncExitStack() as stack:
try:
# Disable cooldown to avoid hanging in the IBD loop on a fresh chain
template = await mining_create_block_template(mining, stack, ctx, opts, cooldown=False)
assert template is not None
# createNewBlock() only succeeds once the BIP34 height
# encoding is >= 2 bytes, i.e. for height >= 17.
if i != 16:
raise AssertionError(f"createNewBlock() should have failed at height {i + 1}")
except capnp.lib.capnp.KjException as e:
assert i < 16
assert_capnp_failed(e, "remote exception: std::exception: TestBlockValidity failed: bad-cb-length")
self.generate(node, 1, sync_fun=self.no_op)
assert_equal(node.getblockcount(), i + 1)
asyncio.run(capnp.run(async_routine()))
def run_test(self):
self.miniwallet = MiniWallet(self.nodes[0])
self.default_block_create_options = self.capnp_modules['mining'].BlockCreateOptions()
@@ -412,6 +451,9 @@ class IPCMiningTest(BitcoinTestFramework):
self.run_coinbase_and_submission_test()
self.run_ipc_option_override_test()
# Needs to run last because it resets the chain.
self.run_low_height_test()
if __name__ == '__main__':
IPCMiningTest(__file__).main()

View File

@@ -109,9 +109,9 @@ async def make_capnp_init_ctx(self):
return ctx, init
async def mining_create_block_template(mining, stack, ctx, opts):
async def mining_create_block_template(mining, stack, ctx, *args, **kwargs):
"""Call mining.createNewBlock() and return template, then call template.destroy() when stack exits."""
response = await mining.createNewBlock(ctx, opts)
response = await mining.createNewBlock(ctx, *args, **kwargs)
if not response._has("result"):
return None
return await stack.enter_async_context(destroying(response.result, ctx))