pytest: test for parallel bookkeeper queries.
What changed, and why it matters
This commit only adds a new test case for the bookkeeper plugin. The test runs two list-income queries at the same time and is currently marked as expected to fail (xfail), meaning the underlying crash or assertion failure it checks for has not been fixed yet. The commit itself does not change any production code, so it cannot directly fix a vulnerability.
Treat this as a bug report / reproducer, not a security fix. Investigate the bookkeeper plugin's shared state and event-refresh logic for race conditions, and fix the assertion before removing xfail. Review whether the crash is reachable by normal RPC users and whether it could be abused for denial of service.
Security signals we found
Concurrency/crash condition in bookkeeper plugin (documented by test only)
Assertion failure when parallel queries both refresh new events (described in commit message)
No production code patch present
Evidence from the diff
The diff adds test_bkpr_parallel in tests/test_bookkeeper.py. It restores a node from a sqlite snapshot and submits two concurrent bkpr_listincome RPC calls via a thread executor. The test is decorated with @pytest.mark.xfail(strict=True), so it documents a known failure mode (assertion/crash under parallel bookkeeper queries) rather than resolving it. No bookkeeper or RPC code is modified.
Changed components
tests/test_bookkeeper.pybookkeeper plugin (bkpr_listincome RPC)Inspect captured patch +16 / −0
diff --git a/tests/test_bookkeeper.py b/tests/test_bookkeeper.py
index 4186d917..10b194af 100644
--- a/tests/test_bookkeeper.py
+++ b/tests/test_bookkeeper.py
@@ -1194,6 +1194,22 @@ def test_listincome_timebox(node_factory, bitcoind):
incomes = l1.rpc.bkpr_listincome(end_time=first_one)['income_events']
assert [i for i in incomes if i['timestamp'] > first_one] == []
+
+@pytest.mark.xfail(strict=True)
+@unittest.skipIf(TEST_NETWORK != 'regtest', "Snapshots are bitcoin regtest.")
+@unittest.skipIf(os.getenv('TEST_DB_PROVIDER', 'sqlite3') != 'sqlite3', "uses snapshots")
+def test_bkpr_parallel(node_factory, bitcoind, executor):
+ """Bookkeeper could crash with parallel requests"""
+ bitcoind.generate_block(1)
+ l1 = node_factory.get_node(dbfile="l1-before-moves-in-db.sqlite3.xz",
+ options={'database-upgrade': True})
+
+ fut1 = executor.submit(l1.rpc.bkpr_listincome)
+ fut2 = executor.submit(l1.rpc.bkpr_listincome)
+
+ fut1.result()
+ fut2.result()
+
# We save blockheights in storage, so make sure we restore them on restart!
acctevents_before = l1.rpc.bkpr_listaccountevents()
l1.restart()
Why this scored 29/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.