bkpr: limp along if we lost our db.
What changed, and why it matters
This commit fixes a crash in Core Lightning's bookkeeper plugin. After a user loses their main database and runs emergency recovery, then closes a channel, the bookkeeper plugin would hit an internal assertion and crash the whole node. The fix makes the plugin log a warning and continue running instead of crashing.
Apply the patch. Users who have run emergencyrecover and then close channels should upgrade to avoid bookkeeper crashes. No immediate remote exploitability, but the crash is disruptive.
Security signals we found
Denial-of-service condition triggered by missing database state after recovery
Assertion failure in plugin leading to fatal signal and node crash
Fix removes assert() on nullable pointer and adds graceful degradation
Evidence from the diff
In plugins/bkpr/recorder.c, find_txo_chain() asserted that acct->open_event_db_id was non-NULL. After emergencyrecover, the bookkeeper database can be missing the original account open event, making this pointer NULL and triggering SIGABRT. The patch replaces the assertion with a NULL check that logs a BROKEN-level message and returns false, allowing the plugin to limp along. A previously expected-to-fail test is now enabled and updated to accept the new log message.
Changed components
plugins/bkpr/recorder.cbookkeeper pluginbkpr_listbalances RPC pathInspect captured patch +9 / −3
diff --git a/plugins/bkpr/recorder.c b/plugins/bkpr/recorder.c
index 82c3498c..4b5170f6 100644
--- a/plugins/bkpr/recorder.c
+++ b/plugins/bkpr/recorder.c
@@ -175,7 +175,14 @@ bool find_txo_chain(const tal_t *ctx,
bool is_complete = true;
const char *start_acct_name;
- assert(acct->open_event_db_id);
+ /* If we have lost our database and used recovery, this can be
+ * NULL. That's the least of our problems though! */
+ if (!acct->open_event_db_id) {
+ plugin_log(cmd->plugin, LOG_BROKEN,
+ "Cannot find the open_event for %s: did we lose our db?",
+ acct->name);
+ return false;
+ }
open_ev = find_chain_event_by_id(ctx, bkpr, cmd,
*acct->open_event_db_id);
diff --git a/tests/test_misc.py b/tests/test_misc.py
index c2e4f0ec..9e5416fd 100644
--- a/tests/test_misc.py
+++ b/tests/test_misc.py
@@ -3136,13 +3136,12 @@ def test_emergencyrecoverpenaltytxn(node_factory, bitcoind):
@unittest.skipIf(os.getenv('TEST_DB_PROVIDER', 'sqlite3') != 'sqlite3', "deletes database, which is assumed sqlite3")
-@pytest.mark.xfail(strict=True)
def test_emergencyrecover(node_factory, bitcoind):
"""
Test emergencyrecover
"""
l1, l2 = node_factory.get_nodes(2, opts=[{'may_reconnect': True,
- 'broken_log': 'ERROR: Unknown commitment #.*, recovering our funds'},
+ 'broken_log': 'ERROR: Unknown commitment #.*, recovering our funds|plugin-bookkeeper: Cannot find the open_event for '},
{'may_reconnect': True}])
l1.rpc.connect(l2.info['id'], 'localhost', l2.port)
Why this scored 44/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.