pytest: fix bcli tests after sync refactor
What changed, and why it matters
This commit only changes test files. It updates pytest test cases to match new behavior after a code refactor made the lightning node crash instead of retry when it gets invalid responses from the connected Bitcoin node. There is no change to production code, so it does not introduce or fix a security vulnerability by itself.
No security action needed. Review the related synchronous bcli refactor commit separately if assessing whether the production behavior change has security implications.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The diff modifies tests/test_closing.py and tests/test_misc.py. It rewrites test_bitcoin_failure and test_bitcoind_fail_first to expect that lightningd now aborts on bad bitcoind RPC responses rather than retrying. It also updates mock RPC responses to use proper JSON-RPC error objects. The commit is a test-only adjustment following a prior synchronous bcli refactor.
Changed components
tests/test_closing.pytests/test_misc.pyInspect captured patch +19 / −40
diff --git a/tests/test_closing.py b/tests/test_closing.py
index 0cc1c2b6..beb7542d 100644
--- a/tests/test_closing.py
+++ b/tests/test_closing.py
@@ -4286,7 +4286,7 @@ def test_onchain_reestablish_reply(node_factory, bitcoind, executor):
# We block l3 from seeing close, so it will try to reestablish.
def no_new_blocks(req):
- return {"error": "go away"}
+ return {"error": {"code": -8, "message": "Block height out of range"}}
l3.daemon.rpcproxy.mock_rpc('getblockhash', no_new_blocks)
l2.rpc.disconnect(l3.info['id'], force=True)
@@ -4369,7 +4369,7 @@ def test_reestablish_closed_channels(node_factory, bitcoind):
# We block l2 from seeing close, so it will try to reestablish.
def no_new_blocks(req):
- return {"error": "go away"}
+ return {"error": {"code": -8, "message": "Block height out of range"}}
l2.daemon.rpcproxy.mock_rpc('getblockhash', no_new_blocks)
# Make a payment, make sure it's entirely finished before we close.
diff --git a/tests/test_misc.py b/tests/test_misc.py
index 9e5416fd..faac4c7a 100644
--- a/tests/test_misc.py
+++ b/tests/test_misc.py
@@ -104,35 +104,28 @@ def test_db_upgrade(node_factory):
def test_bitcoin_failure(node_factory, bitcoind):
- l1 = node_factory.get_node()
+ # The node will crash when bitcoind fails, so we need `may_fail` and `broken_log`.
+ l1 = node_factory.get_node(may_fail=True, broken_log=r'getrawblockbyheight|FATAL SIGNAL|backtrace')
# Make sure we're not failing it between getblockhash and getblock.
sync_blockheight(bitcoind, [l1])
def crash_bitcoincli(r):
- return {'error': 'go away'}
+ return {'id': r['id'], 'result': 'not_a_valid_blockhash', 'error': None}
- # This is not a JSON-RPC response by purpose
- l1.daemon.rpcproxy.mock_rpc('estimatesmartfee', crash_bitcoincli)
+ # This is not a JSON-RPC response by purpose.
l1.daemon.rpcproxy.mock_rpc('getblockhash', crash_bitcoincli)
- # This should cause both estimatefee and getblockhash fail
- l1.daemon.wait_for_logs(['Unable to estimate any fees',
- 'getblockhash .* exited with status 1'])
-
- # And they should retry!
- l1.daemon.wait_for_logs(['Unable to estimate any fees',
- 'getblockhash .* exited with status 1'])
-
- # Restore, then it should recover and get blockheight.
- l1.daemon.rpcproxy.mock_rpc('estimatesmartfee', None)
- l1.daemon.rpcproxy.mock_rpc('getblockhash', None)
+ # Generate a block to trigger the topology update which calls getblockhash.
+ bitcoind.generate_block(1)
- bitcoind.generate_block(5)
- sync_blockheight(bitcoind, [l1])
+ # lightningd should crash with the error
+ # `fatal()` calls `abort()` when crashlog is set (during operation), so exit code is -6 (SIGABRT).
+ l1.daemon.wait_for_log(r'bad response to getrawblockbyheight')
+ assert l1.daemon.wait() != 0
# We refuse to start if bitcoind is in `blocksonly`
- l1.stop()
+ # l1 already crashed, so we just need to restart bitcoind.
bitcoind.stop()
bitcoind.cmd_line += ["-blocksonly"]
bitcoind.start()
@@ -2243,31 +2236,17 @@ def test_bitcoind_fail_first(node_factory, bitcoind):
"""
# Do not start the lightning node since we need to instrument bitcoind
# first.
- timeout = 5 if 5 < TIMEOUT // 3 else TIMEOUT // 3
- l1 = node_factory.get_node(start=False,
- broken_log=r'plugin-bcli: .*(-stdinrpcpass -stdin getblockhash 100 exited 1 \(after [0-9]* other errors\)|we have been retrying command for)',
- may_fail=True,
- options={'bitcoin-retry-timeout': timeout})
+ l1 = node_factory.get_node(start=False, may_fail=True)
# Instrument bitcoind to fail some queries first.
- def mock_fail(*args):
- raise ValueError()
-
- # If any of these succeed, they reset fail timeout.
- l1.daemon.rpcproxy.mock_rpc('getblockhash', mock_fail)
- l1.daemon.rpcproxy.mock_rpc('estimatesmartfee', mock_fail)
- l1.daemon.rpcproxy.mock_rpc('getmempoolinfo', mock_fail)
+ def crash_bitcoincli(r):
+ return {'id': r['id'], 'result': 'not_a_valid_blockhash', 'error': None}
+ l1.daemon.rpcproxy.mock_rpc('getblockhash', crash_bitcoincli)
l1.daemon.start(wait_for_initialized=False, stderr_redir=True)
- l1.daemon.wait_for_logs([r'getblockhash [a-z0-9]* exited with status 1',
- r'Unable to estimate any fees',
- r'BROKEN.*we have been retrying command for --bitcoin-retry-timeout={} seconds'.format(timeout)])
- # Will exit with failure code.
- assert l1.daemon.wait() == 1
- # Now unset the mock, so calls go through again
- l1.daemon.rpcproxy.mock_rpc('getblockhash', None)
- l1.daemon.rpcproxy.mock_rpc('estimatesmartfee', None)
+ assert l1.daemon.wait() == 1
+ assert l1.daemon.is_in_stderr('bad response to getrawblockbyheight')
@unittest.skipIf(TEST_NETWORK == 'liquid-regtest', "Fees on elements are different")
Why this scored 12/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.