bookkeeper: test (failing!) for rebalances on restart.
What changed, and why it matters
This commit adds a new test that demonstrates a bug in Core Lightning's bookkeeping plugin: after a node restart, 'rebalance' fee records are lost and the plugin logs a 'BROKEN' warning about an unparsable datastore entry. The test is marked as expected-to-fail (xfail) so it documents the bug without breaking CI. It is a test-only change, not a fix, and there is no security exploit here.
Treat this as a known functional bug, not a security vulnerability. The next step is to fix the bookkeeper plugin so it correctly persists and reloads rebalance records across restarts, then remove the xfail marker. Users relying on bookkeeper accounting should be aware that rebalance fees may be missing after restart until the bug is fixed.
Security signals we found
Data persistence/serialization bug in plugin datastore
Loss of accounting records after node restart
Plugin logs BROKEN-level message for unparsable stored data
Evidence from the diff
The commit extends test_rebalance_tracking in tests/test_bookkeeper.py to restart node l1 and then verify that the bookkeeper still reports a ‘rebalance_fee’ income event with the same payment_id, debit/credit amounts, and account. The test is decorated with @pytest.mark.xfail(strict=True) because it currently fails: the bookkeeper datastore key [‘bookkeeper’,’rebalances’,‘1-2’] becomes unparsable after restart, causing the rebalance event to disappear. The diff only adds test code; no production code is changed.
Changed components
tests/test_bookkeeper.pybookkeeper plugin (implicated by test failure, not patched)Inspect captured patch +13 / −0
diff --git a/tests/test_bookkeeper.py b/tests/test_bookkeeper.py
index c6a4f06f..2ac03467 100644
--- a/tests/test_bookkeeper.py
+++ b/tests/test_bookkeeper.py
@@ -760,6 +760,7 @@ def test_empty_node(node_factory, bitcoind):
l1.rpc.bkpr_inspect('wallet')
+@pytest.mark.xfail(strict=True)
def test_rebalance_tracking(node_factory, bitcoind):
"""
We identify rebalances (invoices paid and received by our node),
@@ -822,6 +823,18 @@ def test_rebalance_tracking(node_factory, bitcoind):
assert outbound_ev['credit_msat'] == Millisatoshi(0)
assert outbound_ev['payment_id'] == pay_hash
+ # Will reload on restart!
+ l1.restart()
+
+ inc_evs = l1.rpc.bkpr_listincome()['income_events']
+ outbound_chan_id = only_one(l1.rpc.listpeerchannels(l2.info['id'])['channels'])['channel_id']
+
+ outbound_ev = only_one([ev for ev in inc_evs if ev['tag'] == 'rebalance_fee'])
+ assert outbound_ev['account'] == outbound_chan_id
+ assert outbound_ev['debit_msat'] == Millisatoshi(1001)
+ assert outbound_ev['credit_msat'] == Millisatoshi(0)
+ assert outbound_ev['payment_id'] == pay_hash
+
def test_bookkeeper_custom_notifs(node_factory, chainparams):
# FIXME: what happens if we send internal funds to 'external' wallet?
Why this scored 28/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.