wallet: make datastore helpers self-wrap a wallet transaction
What changed, and why it matters
This change fixes a crash bug in Core Lightning's wallet datastore helpers. Previously, four datastore functions required the caller to already be inside a database transaction, and would fatally crash if called outside one. The patch makes these helpers automatically start and commit a transaction when needed. The commit message says this specifically fixes crashes in the watchman plugin, which saves pending operations through these helpers from plugin callbacks that run outside any transaction.
Treat as a routine stability/bug-fix patch. No immediate security response required, but include in normal release notes as a crash fix. Review whether other wallet helpers have the same implicit transaction requirement and consider a broader audit if crashes persist.
Security signals we found
Fixes fatal assertion/crash in database statement preparation
Resolves transaction-context mismatch in wallet datastore helpers
watchman plugin persistence path was affected
No input validation, authorization, or cryptographic changes present
Co-authored by an AI coding assistant (Cursor)
Evidence from the diff
The patch modifies wallet/wallet.c to wrap wallet_datastore_update, wallet_datastore_create, wallet_datastore_remove, and wallet_datastore_get with on-demand database transactions. Each helper now checks db_in_transaction(w->db), begins a transaction if absent, executes the underlying db_datastore_* call, and commits if it started the transaction. This resolves a fatal assertion at db/utils.c:103 (‘Attempting to prepare a db_stmt outside of a transaction’) that occurred when watchman plugin callbacks invoked these helpers outside a transaction context.
Changed components
wallet/wallet.cwallet_datastore_updatewallet_datastore_createwallet_datastore_removewallet_datastore_getwatchman plugin datastore persistenceInspect captured patch +23 / −1
diff --git a/wallet/wallet.c b/wallet/wallet.c
index 93a39617..0b12d37c 100644
--- a/wallet/wallet.c
+++ b/wallet/wallet.c
@@ -6356,7 +6356,12 @@ void wallet_invoice_request_mark_used(struct db *db, const struct sha256 *invreq
void wallet_datastore_update(struct wallet *w, const char **key, const u8 *data)
{
+ bool need_tx = !db_in_transaction(w->db);
+ if (need_tx)
+ db_begin_transaction(w->db);
db_datastore_update(w->db, key, data);
+ if (need_tx)
+ db_commit_transaction(w->db);
}
static void db_datastore_create(struct db *db, const char **key, const u8 *data)
@@ -6373,7 +6378,12 @@ static void db_datastore_create(struct db *db, const char **key, const u8 *data)
void wallet_datastore_create(struct wallet *w, const char **key, const u8 *data)
{
+ bool need_tx = !db_in_transaction(w->db);
+ if (need_tx)
+ db_begin_transaction(w->db);
db_datastore_create(w->db, key, data);
+ if (need_tx)
+ db_commit_transaction(w->db);
}
static void db_datastore_remove(struct db *db, const char **key)
@@ -6414,7 +6424,12 @@ void wallet_datastore_save_payment_description(struct db *db,
void wallet_datastore_remove(struct wallet *w, const char **key)
{
+ bool need_tx = !db_in_transaction(w->db);
+ if (need_tx)
+ db_begin_transaction(w->db);
db_datastore_remove(w->db, key);
+ if (need_tx)
+ db_commit_transaction(w->db);
}
u8 *wallet_datastore_get(const tal_t *ctx,
@@ -6422,7 +6437,14 @@ u8 *wallet_datastore_get(const tal_t *ctx,
const char **key,
u64 *generation)
{
- return db_datastore_get(ctx, w->db, key, generation);
+ bool need_tx = !db_in_transaction(w->db);
+ u8 *ret;
+ if (need_tx)
+ db_begin_transaction(w->db);
+ ret = db_datastore_get(ctx, w->db, key, generation);
+ if (need_tx)
+ db_commit_transaction(w->db);
+ return ret;
}
struct db_stmt *wallet_datastore_first(const tal_t *ctx,
Why this scored 31/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.