wallet: separate datastore access functions for the lightning-downgrade tool to access.
What changed, and why it matters
This commit is a straightforward code reorganization: it moves existing datastore (key-value storage) database access functions out of wallet.c into a new pair of files, datastore.c and datastore.h, so a separate downgrade tool can reuse them. No behavior changes, no new features, and no security fixes are visible in the diff.
No security action required. Treat as normal refactoring; review only for build/test integration correctness.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The change extracts static datastore helpers (db_bind_datastore_key, db_col_datastore_key, db_datastore_get, db_datastore_first, db_datastore_next, db_datastore_update) and the public key-comparison helpers (datastore_key_startswith, datastore_key_eq) from wallet/wallet.c into a new wallet/datastore.c unit with a corresponding wallet/datastore.h header. wallet.c now includes the header and calls the relocated functions. The Makefile and several test runners are updated to compile the new file or provide stubs. The SQL statements, binding logic, and iteration semantics are identical to the original code.
Changed components
wallet/wallet.cwallet/datastore.cwallet/datastore.hwallet/Makefilewallet test runnersInspect captured patch +249 / −164
diff --git a/wallet/Makefile b/wallet/Makefile
index 121a2e7..34ea6d4 100644
--- a/wallet/Makefile
+++ b/wallet/Makefile
@@ -2,6 +2,7 @@
WALLET_LIB_SRC := \
wallet/account_migration.c \
+ wallet/datastore.c \
wallet/db.c \
wallet/invoices.c \
wallet/migrations.c \
@@ -34,6 +35,7 @@ WALLET_SQL_FILES := \
$(DB_SQL_FILES) \
wallet/account_migration.c \
wallet/db.c \
+ wallet/datastore.c \
wallet/invoices.c \
wallet/migrations.c \
wallet/wallet.c \
diff --git a/wallet/datastore.c b/wallet/datastore.c
new file mode 100644
index 0000000..27112a2
--- /dev/null
+++ b/wallet/datastore.c
@@ -0,0 +1,177 @@
+#include "config.h"
+#include <ccan/cast/cast.h>
+#include <ccan/str/str.h>
+#include <ccan/tal/str/str.h>
+#include <db/bindings.h>
+#include <db/common.h>
+#include <db/utils.h>
+#include <wallet/datastore.h>
+#include <wallet/db.h>
+
+/* Does k1 match k2 as far as k2 goes? */
+bool datastore_key_startswith(const char **k1, const char **k2)
+{
+ size_t k1len = tal_count(k1), k2len = tal_count(k2);
+
+ if (k2len > k1len)
+ return false;
+
+ for (size_t i = 0; i < k2len; i++) {
+ if (!streq(k1[i], k2[i]))
+ return false;
+ }
+ return true;
+}
+
+bool datastore_key_eq(const char **k1, const char **k2)
+{
+ return tal_count(k1) == tal_count(k2)
+ && datastore_key_startswith(k1, k2);
+}
+
+/* We join key parts with nuls for now. */
+void db_bind_datastore_key(struct db_stmt *stmt, const char **key)
+{
+ u8 *joined;
+ size_t len;
+
+ if (tal_count(key) == 1) {
+ db_bind_blob(stmt, (u8 *)key[0], strlen(key[0]));
+ return;
+ }
+
+ len = strlen(key[0]);
+ joined = (u8 *)tal_strdup(tmpctx, key[0]);
+ for (size_t i = 1; i < tal_count(key); i++) {
+ tal_resize(&joined, len + 1 + strlen(key[i]));
+ joined[len] = '\0';
+ memcpy(joined + len + 1, key[i], strlen(key[i]));
+ len += 1 + strlen(key[i]);
+ }
+ db_bind_blob(stmt, joined, len);
+}
+
+u8 *db_datastore_get(const tal_t *ctx,
+ struct db *db,
+ const char **key,
+ u64 *generation)
+{
+ struct db_stmt *stmt;
+ u8 *ret;
+
+ stmt = db_prepare_v2(db,
+ SQL("SELECT data, generation"
+ " FROM datastore"
+ " WHERE key = ?"));
+ db_bind_datastore_key(stmt, key);
+ db_query_prepared(stmt);
+
+ if (!db_step(stmt)) {
+ tal_free(stmt);
+ return NULL;
+ }
+
+ ret = db_col_arr(ctx, stmt, "data", u8);
+ if (generation)
+ *generation = db_col_u64(stmt, "generation");
+ else
+ db_col_ignore(stmt, "generation");
+ tal_free(stmt);
+ return ret;
+}
+
+static const char **db_col_datastore_key(const tal_t *ctx,
+ struct db_stmt *stmt,
+ const char *colname)
+{
+ char **key;
+ const u8 *joined = db_col_blob(stmt, colname);
+ size_t len = db_col_bytes(stmt, colname);
+
+ key = tal_arr(ctx, char *, 0);
+ do {
+ size_t partlen;
+ for (partlen = 0; partlen < len; partlen++) {
+ if (joined[partlen] == '\0') {
+ partlen++;
+ break;
+ }
+ }
+ tal_arr_expand(&key, tal_strndup(key, (char *)joined, partlen));
+ len -= partlen;
+ joined += partlen;
+ } while (len != 0);
+
+ return cast_const2(const char **, key);
+}
+
+struct db_stmt *db_datastore_next(const tal_t *ctx,
+ struct db_stmt *stmt,
+ const char **startkey,
+ const char ***key,
+ const u8 **data,
+ u64 *generation)
+{
+ if (!db_step(stmt))
+ return tal_free(stmt);
+
+ *key = db_col_datastore_key(ctx, stmt, "key");
+
+ /* We select from startkey onwards, so once we're past it, stop */
+ if (startkey && !datastore_key_startswith(*key, startkey)) {
+ db_col_ignore(stmt, "data");
+ db_col_ignore(stmt, "generation");
+ return tal_free(stmt);
+ }
+
+ if (data)
+ *data = db_col_arr(ctx, stmt, "data", u8);
+ else
+ db_col_ignore(stmt, "data");
+
+ if (generation)
+ *generation = db_col_u64(stmt, "generation");
+ else
+ db_col_ignore(stmt, "generation");
+
+ return stmt;
+}
+
+struct db_stmt *db_datastore_first(const tal_t *ctx,
+ struct db *db,
+ const char **startkey,
+ const char ***key,
+ const u8 **data,
+ u64 *generation)
+{
+ struct db_stmt *stmt;
+
+ if (startkey) {
+ stmt = db_prepare_v2(db,
+ SQL("SELECT key, data, generation"
+ " FROM datastore"
+ " WHERE key >= ?"
+ " ORDER BY key;"));
+ db_bind_datastore_key(stmt, startkey);
+ } else {
+ stmt = db_prepare_v2(db,
+ SQL("SELECT key, data, generation"
+ " FROM datastore"
+ " ORDER BY key;"));
+ }
+ db_query_prepared(stmt);
+
+ return db_datastore_next(ctx, stmt, startkey, key, data, generation);
+}
+
+void db_datastore_update(struct db *db, const char **key, const u8 *data)
+{
+ struct db_stmt *stmt;
+
+ stmt = db_prepare_v2(db,
+ SQL("UPDATE datastore SET data=?, generation=generation+1 WHERE key=?;"));
+ db_bind_talarr(stmt, data);
+ db_bind_datastore_key(stmt, key);
+ db_exec_prepared_v2(take(stmt));
+}
+
diff --git a/wallet/datastore.h b/wallet/datastore.h
new file mode 100644
index 0000000..3b96f35
--- /dev/null
+++ b/wallet/datastore.h
@@ -0,0 +1,37 @@
+#ifndef LIGHTNING_WALLET_DATASTORE_H
+#define LIGHTNING_WALLET_DATASTORE_H
+/* Access routines for the datastore: here so that tools/lightningd-downgrade
+ * can use them */
+#include "config.h"
+#include <ccan/short_types/short_types.h>
+#include <ccan/tal/tal.h>
+#include <stdbool.h>
+
+struct db;
+struct db_stmt;
+
+/* Does k1 match k2 as far as k2 goes? */
+bool datastore_key_startswith(const char **k1, const char **k2);
+bool datastore_key_eq(const char **k1, const char **k2);
+void db_bind_datastore_key(struct db_stmt *stmt, const char **key);
+u8 *db_datastore_get(const tal_t *ctx,
+ struct db *db,
+ const char **key,
+ u64 *generation);
+struct db_stmt *db_datastore_next(const tal_t *ctx,
+ struct db_stmt *stmt,
+ const char **startkey,
+ const char ***key,
+ const u8 **data,
+ u64 *generation);
+
+struct db_stmt *db_datastore_first(const tal_t *ctx,
+ struct db *db,
+ const char **startkey,
+ const char ***key,
+ const u8 **data,
+ u64 *generation);
+
+/* Update existing record */
+void db_datastore_update(struct db *db, const char **key, const u8 *data);
+#endif /* LIGHTNING_WALLET_DATASTORE_H */
diff --git a/wallet/test/run-chain_moves_duplicate-detect.c b/wallet/test/run-chain_moves_duplicate-detect.c
index 6fecac0..428768d 100644
--- a/wallet/test/run-chain_moves_duplicate-detect.c
+++ b/wallet/test/run-chain_moves_duplicate-detect.c
@@ -21,6 +21,7 @@ static void db_log_(struct logger *log UNUSED, enum log_level level UNUSED, cons
#include "db/db_sqlite3.c"
#include "db/exec.c"
#include "db/utils.c"
+#include "wallet/datastore.c"
#include "wallet/db.c"
#include "wallet/migrations.c"
#include "common/coin_mvt.c"
diff --git a/wallet/test/run-db.c b/wallet/test/run-db.c
index 4a62354..c1b4cb1 100644
--- a/wallet/test/run-db.c
+++ b/wallet/test/run-db.c
@@ -10,6 +10,7 @@ static void db_log_(struct logger *log UNUSED, enum log_level level UNUSED, cons
#include "db/db_sqlite3.c"
#include "db/exec.c"
#include "db/utils.c"
+#include "wallet/datastore.c"
#include "wallet/db.c"
#include "wallet/wallet.c"
#include "wallet/migrations.c"
diff --git a/wallet/test/run-migrate_remove_chain_moves_duplicates.c b/wallet/test/run-migrate_remove_chain_moves_duplicates.c
index 029851a..2bb792e 100644
--- a/wallet/test/run-migrate_remove_chain_moves_duplicates.c
+++ b/wallet/test/run-migrate_remove_chain_moves_duplicates.c
@@ -78,6 +78,34 @@ void connect_htlc_in(struct htlc_in_map *map UNNEEDED, struct htlc_in *hin UNNEE
/* Generated stub for connect_htlc_out */
void connect_htlc_out(struct htlc_out_map *map UNNEEDED, struct htlc_out *hout UNNEEDED)
{ fprintf(stderr, "connect_htlc_out called!\n"); abort(); }
+/* Generated stub for db_bind_datastore_key */
+void db_bind_datastore_key(struct db_stmt *stmt UNNEEDED, const char **key UNNEEDED)
+{ fprintf(stderr, "db_bind_datastore_key called!\n"); abort(); }
+/* Generated stub for db_datastore_first */
+struct db_stmt *db_datastore_first(const tal_t *ctx UNNEEDED,
+ struct db *db UNNEEDED,
+ const char **startkey UNNEEDED,
+ const char ***key UNNEEDED,
+ const u8 **data UNNEEDED,
+ u64 *generation UNNEEDED)
+{ fprintf(stderr, "db_datastore_first called!\n"); abort(); }
+/* Generated stub for db_datastore_get */
+u8 *db_datastore_get(const tal_t *ctx UNNEEDED,
+ struct db *db UNNEEDED,
+ const char **key UNNEEDED,
+ u64 *generation UNNEEDED)
+{ fprintf(stderr, "db_datastore_get called!\n"); abort(); }
+/* Generated stub for db_datastore_next */
+struct db_stmt *db_datastore_next(const tal_t *ctx UNNEEDED,
+ struct db_stmt *stmt UNNEEDED,
+ const char **startkey UNNEEDED,
+ const char ***key UNNEEDED,
+ const u8 **data UNNEEDED,
+ u64 *generation UNNEEDED)
+{ fprintf(stderr, "db_datastore_next called!\n"); abort(); }
+/* Generated stub for db_datastore_update */
+void db_datastore_update(struct db *db UNNEEDED, const char **key UNNEEDED, const u8 *data UNNEEDED)
+{ fprintf(stderr, "db_datastore_update called!\n"); abort(); }
/* Generated stub for fatal */
void fatal(const char *fmt UNNEEDED, ...)
{ fprintf(stderr, "fatal called!\n"); abort(); }
diff --git a/wallet/test/run-wallet.c b/wallet/test/run-wallet.c
index 35efcb8..69b1414 100644
--- a/wallet/test/run-wallet.c
+++ b/wallet/test/run-wallet.c
@@ -35,6 +35,7 @@ static void test_error(struct lightningd *ld, bool fatal, const char *fmt, va_li
#include "db/db_sqlite3.c"
#include "db/exec.c"
#include "db/utils.c"
+#include "wallet/datastore.c"
#include "wallet/db.c"
#include "wallet/migrations.c"
diff --git a/wallet/wallet.c b/wallet/wallet.c
index 2e3421c..c6a272a 100644
--- a/wallet/wallet.c
+++ b/wallet/wallet.c
@@ -24,6 +24,7 @@
#include <lightningd/peer_htlcs.h>
#include <lightningd/runes.h>
#include <onchaind/onchaind_wiregen.h>
+#include <wallet/datastore.h>
#include <wallet/invoices.h>
#include <wallet/migrations.h>
#include <wallet/txfilter.h>
@@ -6333,63 +6334,9 @@ void wallet_invoice_request_mark_used(struct db *db, const struct sha256 *invreq
}
}
-/* We join key parts with nuls for now. */
-static void db_bind_datastore_key(struct db_stmt *stmt,
- const char **key)
-{
- u8 *joined;
- size_t len;
-
- if (tal_count(key) == 1) {
- db_bind_blob(stmt, (u8 *)key[0], strlen(key[0]));
- return;
- }
-
- len = strlen(key[0]);
- joined = (u8 *)tal_strdup(tmpctx, key[0]);
- for (size_t i = 1; i < tal_count(key); i++) {
- tal_resize(&joined, len + 1 + strlen(key[i]));
- joined[len] = '\0';
- memcpy(joined + len + 1, key[i], strlen(key[i]));
- len += 1 + strlen(key[i]);
- }
- db_bind_blob(stmt, joined, len);
-}
-
-static const char **db_col_datastore_key(const tal_t *ctx,
- struct db_stmt *stmt,
- const char *colname)
-{
- char **key;
- const u8 *joined = db_col_blob(stmt, colname);
- size_t len = db_col_bytes(stmt, colname);
-
- key = tal_arr(ctx, char *, 0);
- do {
- size_t partlen;
- for (partlen = 0; partlen < len; partlen++) {
- if (joined[partlen] == '\0') {
- partlen++;
- break;
- }
- }
- tal_arr_expand(&key, tal_strndup(key, (char *)joined, partlen));
- len -= partlen;
- joined += partlen;
- } while (len != 0);
-
- return cast_const2(const char **, key);
-}
-
void wallet_datastore_update(struct wallet *w, const char **key, const u8 *data)
{
- struct db_stmt *stmt;
-
- stmt = db_prepare_v2(w->db,
- SQL("UPDATE datastore SET data=?, generation=generation+1 WHERE key=?;"));
- db_bind_talarr(stmt, data);
- db_bind_datastore_key(stmt, key);
- db_exec_prepared_v2(take(stmt));
+ db_datastore_update(w->db, key, data);
}
static void db_datastore_create(struct db *db, const char **key, const u8 *data)
@@ -6458,56 +6405,6 @@ void wallet_datastore_remove(struct wallet *w, const char **key)
db_datastore_remove(w->db, key);
}
-/* Does k1 match k2 as far as k2 goes? */
-bool datastore_key_startswith(const char **k1, const char **k2)
-{
- size_t k1len = tal_count(k1), k2len = tal_count(k2);
-
- if (k2len > k1len)
- return false;
-
- for (size_t i = 0; i < k2len; i++) {
- if (!streq(k1[i], k2[i]))
- return false;
- }
- return true;
-}
-
-bool datastore_key_eq(const char **k1, const char **k2)
-{
- return tal_count(k1) == tal_count(k2)
- && datastore_key_startswith(k1, k2);
-}
-
-static u8 *db_datastore_get(const tal_t *ctx,
- struct db *db,
- const char **key,
- u64 *generation)
-{
- struct db_stmt *stmt;
- u8 *ret;
-
- stmt = db_prepare_v2(db,
- SQL("SELECT data, generation"
- " FROM datastore"
- " WHERE key = ?"));
- db_bind_datastore_key(stmt, key);
- db_query_prepared(stmt);
-
- if (!db_step(stmt)) {
- tal_free(stmt);
- return NULL;
- }
-
- ret = db_col_arr(ctx, stmt, "data", u8);
- if (generation)
- *generation = db_col_u64(stmt, "generation");
- else
- db_col_ignore(stmt, "generation");
- tal_free(stmt);
- return ret;
-}
-
u8 *wallet_datastore_get(const tal_t *ctx,
struct wallet *w,
const char **key,
@@ -6516,65 +6413,6 @@ u8 *wallet_datastore_get(const tal_t *ctx,
return db_datastore_get(ctx, w->db, key, generation);
}
-static struct db_stmt *db_datastore_next(const tal_t *ctx,
- struct db_stmt *stmt,
- const char **startkey,
- const char ***key,
- const u8 **data,
- u64 *generation)
-{
- if (!db_step(stmt))
- return tal_free(stmt);
-
- *key = db_col_datastore_key(ctx, stmt, "key");
-
- /* We select from startkey onwards, so once we're past it, stop */
- if (startkey && !datastore_key_startswith(*key, startkey)) {
- db_col_ignore(stmt, "data");
- db_col_ignore(stmt, "generation");
- return tal_free(stmt);
- }
-
- if (data)
- *data = db_col_arr(ctx, stmt, "data", u8);
- else
- db_col_ignore(stmt, "data");
-
- if (generation)
- *generation = db_col_u64(stmt, "generation");
- else
- db_col_ignore(stmt, "generation");
-
- return stmt;
-}
-
-static struct db_stmt *db_datastore_first(const tal_t *ctx,
- struct db *db,
- const char **startkey,
- const char ***key,
- const u8 **data,
- u64 *generation)
-{
- struct db_stmt *stmt;
-
- if (startkey) {
- stmt = db_prepare_v2(db,
- SQL("SELECT key, data, generation"
- " FROM datastore"
- " WHERE key >= ?"
- " ORDER BY key;"));
- db_bind_datastore_key(stmt, startkey);
- } else {
- stmt = db_prepare_v2(db,
- SQL("SELECT key, data, generation"
- " FROM datastore"
- " ORDER BY key;"));
- }
- db_query_prepared(stmt);
-
- return db_datastore_next(ctx, stmt, startkey, key, data, generation);
-}
-
struct db_stmt *wallet_datastore_first(const tal_t *ctx,
struct wallet *w,
const char **startkey,
Why this scored 15/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.