test: bad-cb-length for createNewBlock() at low heights
What changed, and why it matters
This commit only adds a new automated test that demonstrates an existing bug in Bitcoin Core's mining IPC interface. The bug causes block creation to fail on brand-new test chains for the first 16 blocks. The commit does not fix the bug; it documents it and works around it in the test by using an RPC call instead. There is no direct security risk to users here, but it highlights a reliability issue in an experimental IPC feature.
Review the subsequent commit that claims to fix the bad-cb-length issue. This commit is safe to merge as-is because it only adds tests and a helper refactor, but it should not be treated as a security patch. Consider whether the IPC mining interface should expose include_dummy_extranonce or handle BIP34 encoding internally for low-height chains.
Security signals we found
Functional test addition only; no production code change
Demonstrates a bug in experimental IPC mining interface
Failure is bad-cb-length / TestBlockValidity on low-height chains
Workaround uses trusted RPC instead of untrusted IPC path
No fix included; fix deferred to next commit
Evidence from the diff
The commit adds run_low_height_test() to test/functional/interface_ipc_mining.py. It resets the regtest/signet chain to genesis and verifies that mining.createNewBlock() over Cap’n Proto fails with bad-cb-length for block heights 1-16, then succeeds at height 17. The failure is due to BIP34 height encoding requiring at least 2 bytes, which only happens once the height reaches 17. The test uses self.generate(node, 1) as a workaround because mining.capnp does not expose include_dummy_extranonce. A helper in ipc_util.py is generalized to accept arbitrary positional and keyword arguments. The commit message explicitly states the next commit will introduce the actual fix.
Changed components
test/functional/interface_ipc_mining.pytest/functional/test_framework/ipc_util.pyExperimental IPC mining interface (mining.capnp)createNewBlock() implementation at low block heightsInspect captured patch +44 / −2
diff --git a/test/functional/interface_ipc_mining.py b/test/functional/interface_ipc_mining.py
index 1400a914..0f54bb92 100755
--- a/test/functional/interface_ipc_mining.py
+++ b/test/functional/interface_ipc_mining.py
@@ -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()
diff --git a/test/functional/test_framework/ipc_util.py b/test/functional/test_framework/ipc_util.py
index 0e10b90e..340cd15c 100644
--- a/test/functional/test_framework/ipc_util.py
+++ b/test/functional/test_framework/ipc_util.py
@@ -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))
Why this scored 19/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.