lightningd: db infrastructure for network events.
What changed, and why it matters
This commit adds database infrastructure to record network events (peer connects, disconnects, failed connects, and pings) in Core Lightning. It creates a new database table, adds functions to save and retrieve those events, and updates test stubs. There is no security fix or vulnerability present in the diff itself; it is a feature/infrastructure change.
No security action required. Treat as normal feature/infrastructure review. If this commit is part of a larger series, evaluate the series as a whole for security implications once the event consumers and any RPC exposure are included.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The commit introduces a new network_events table in the wallet database with columns for peer ID, event type, timestamp, reason, duration, and connection-attempt flag. It adds wallet APIs (wallet_save_network_event, wallet_network_events_first/next/extract) and an enum for event types. It also updates wait.h documentation for a magic empty-string value in wait_index_increment, renames a wire parameter from connect_time_nsec to connected_time_nsec in generated stubs, and adds missing test stubs. No security-sensitive logic such as authentication, authorization, cryptographic handling, or input validation changes are visible.
Changed components
wallet/db.cwallet/wallet.cwallet/wallet.hlightningd/wait.hlightningd/test/run-invoice-select-inchan.cwallet/test/run-chain_moves_duplicate-detect.cwallet/test/run-db.cwallet/test/run-migrate_remove_chain_moves_duplicates.cwallet/test/run-wallet.cInspect captured patch +261 / −5
diff --git a/lightningd/test/run-invoice-select-inchan.c b/lightningd/test/run-invoice-select-inchan.c
index 44ce1a93..064c8bcb 100644
--- a/lightningd/test/run-invoice-select-inchan.c
+++ b/lightningd/test/run-invoice-select-inchan.c
@@ -273,10 +273,10 @@ bool fromwire_channeld_dev_memleak_reply(const void *p UNNEEDED, bool *leak UNNE
bool fromwire_connectd_peer_connected(const tal_t *ctx UNNEEDED, const void *p UNNEEDED, struct node_id *id UNNEEDED, u64 *counter UNNEEDED, struct wireaddr_internal *addr UNNEEDED, struct wireaddr **remote_addr UNNEEDED, bool *incoming UNNEEDED, u8 **features UNNEEDED, wirestring **connect_reason UNNEEDED, u64 *connect_nsec UNNEEDED)
{ fprintf(stderr, "fromwire_connectd_peer_connected called!\n"); abort(); }
/* Generated stub for fromwire_connectd_peer_disconnected */
-bool fromwire_connectd_peer_disconnected(const void *p UNNEEDED, struct node_id *id UNNEEDED, u64 *counter UNNEEDED, u64 *connect_time_nsec UNNEEDED)
+bool fromwire_connectd_peer_disconnected(const void *p UNNEEDED, struct node_id *id UNNEEDED, u64 *counter UNNEEDED, u64 *connected_time_nsec UNNEEDED)
{ fprintf(stderr, "fromwire_connectd_peer_disconnected called!\n"); abort(); }
/* Generated stub for fromwire_connectd_peer_reconnected */
-bool fromwire_connectd_peer_reconnected(const tal_t *ctx UNNEEDED, const void *p UNNEEDED, struct node_id *id UNNEEDED, u64 *prev_counter UNNEEDED, u64 *counter UNNEEDED, struct wireaddr_internal *addr UNNEEDED, struct wireaddr **remote_addr UNNEEDED, bool *incoming UNNEEDED, u8 **features UNNEEDED, u64 *connect_time_nsec UNNEEDED)
+bool fromwire_connectd_peer_reconnected(const tal_t *ctx UNNEEDED, const void *p UNNEEDED, struct node_id *id UNNEEDED, u64 *prev_counter UNNEEDED, u64 *counter UNNEEDED, struct wireaddr_internal *addr UNNEEDED, struct wireaddr **remote_addr UNNEEDED, bool *incoming UNNEEDED, u8 **features UNNEEDED, u64 *connected_time_nsec UNNEEDED)
{ fprintf(stderr, "fromwire_connectd_peer_reconnected called!\n"); abort(); }
/* Generated stub for fromwire_connectd_peer_spoke */
bool fromwire_connectd_peer_spoke(const tal_t *ctx UNNEEDED, const void *p UNNEEDED, struct node_id *id UNNEEDED, u64 *counter UNNEEDED, u16 *msgtype UNNEEDED, struct channel_id *channel_id UNNEEDED, wirestring **error UNNEEDED)
diff --git a/lightningd/wait.h b/lightningd/wait.h
index b248dd82..6232d52d 100644
--- a/lightningd/wait.h
+++ b/lightningd/wait.h
@@ -46,6 +46,7 @@ const char *wait_subsystem_name(enum wait_subsystem subsystem);
* Increase index, write to db, wake any waiters, give them any name/value pairs.
* If the value is NULL, omit that name.
* If the name starts with '=', the value is a JSON literal (and skip over the =)
+ * If the value is "", use the resulting index value.
*
* Returns the updated index value (always > 0).
*/
diff --git a/wallet/db.c b/wallet/db.c
index 24573496..61f5c28a 100644
--- a/wallet/db.c
+++ b/wallet/db.c
@@ -1087,7 +1087,17 @@ static struct migration dbmigrations[] = {
{SQL("CREATE INDEX chain_moves_utxo_idx ON chain_moves (utxo)"), NULL},
{NULL, migrate_from_account_db},
/* We accidentally allowed duplicate entries */
- {NULL, migrate_remove_chain_moves_duplicates}
+ {NULL, migrate_remove_chain_moves_duplicates},
+ {SQL("CREATE TABLE network_events ("
+ " id BIGSERIAL,"
+ " peer_id BLOB NOT NULL,"
+ " type INTEGER NOT NULL,"
+ " timestamp BIGINT,"
+ " reason TEXT,"
+ " duration_nsec BIGINT,"
+ " connect_attempted INTEGER NOT NULL,"
+ " PRIMARY KEY (id)"
+ ")"), NULL},
};
/**
diff --git a/wallet/test/run-chain_moves_duplicate-detect.c b/wallet/test/run-chain_moves_duplicate-detect.c
index 4b80a04e..0e518c4c 100644
--- a/wallet/test/run-chain_moves_duplicate-detect.c
+++ b/wallet/test/run-chain_moves_duplicate-detect.c
@@ -354,6 +354,13 @@ u8 *towire_hsmd_get_output_scriptpubkey(const tal_t *ctx UNNEEDED, u64 channel_i
/* Generated stub for txfilter_add_scriptpubkey */
void txfilter_add_scriptpubkey(struct txfilter *filter UNNEEDED, const u8 *script TAKES UNNEEDED)
{ fprintf(stderr, "txfilter_add_scriptpubkey called!\n"); abort(); }
+/* Generated stub for wait_index_increment */
+u64 wait_index_increment(struct lightningd *ld UNNEEDED,
+ struct db *db UNNEEDED,
+ enum wait_subsystem subsystem UNNEEDED,
+ enum wait_index index UNNEEDED,
+ ...)
+{ fprintf(stderr, "wait_index_increment called!\n"); abort(); }
/* Generated stub for wait_index_name */
const char *wait_index_name(enum wait_index index UNNEEDED)
{ fprintf(stderr, "wait_index_name called!\n"); abort(); }
diff --git a/wallet/test/run-db.c b/wallet/test/run-db.c
index 93d319ec..7649e1cb 100644
--- a/wallet/test/run-db.c
+++ b/wallet/test/run-db.c
@@ -367,6 +367,13 @@ u8 *towire_hsmd_get_output_scriptpubkey(const tal_t *ctx UNNEEDED, u64 channel_i
/* Generated stub for txfilter_add_scriptpubkey */
void txfilter_add_scriptpubkey(struct txfilter *filter UNNEEDED, const u8 *script TAKES UNNEEDED)
{ fprintf(stderr, "txfilter_add_scriptpubkey called!\n"); abort(); }
+/* Generated stub for wait_index_increment */
+u64 wait_index_increment(struct lightningd *ld UNNEEDED,
+ struct db *db UNNEEDED,
+ enum wait_subsystem subsystem UNNEEDED,
+ enum wait_index index UNNEEDED,
+ ...)
+{ fprintf(stderr, "wait_index_increment called!\n"); abort(); }
/* Generated stub for wait_index_name */
const char *wait_index_name(enum wait_index index UNNEEDED)
{ fprintf(stderr, "wait_index_name called!\n"); abort(); }
diff --git a/wallet/test/run-migrate_remove_chain_moves_duplicates.c b/wallet/test/run-migrate_remove_chain_moves_duplicates.c
index e8cea530..189152c6 100644
--- a/wallet/test/run-migrate_remove_chain_moves_duplicates.c
+++ b/wallet/test/run-migrate_remove_chain_moves_duplicates.c
@@ -377,6 +377,13 @@ u8 *towire_hsmd_get_output_scriptpubkey(const tal_t *ctx UNNEEDED, u64 channel_i
/* Generated stub for txfilter_add_scriptpubkey */
void txfilter_add_scriptpubkey(struct txfilter *filter UNNEEDED, const u8 *script TAKES UNNEEDED)
{ fprintf(stderr, "txfilter_add_scriptpubkey called!\n"); abort(); }
+/* Generated stub for wait_index_increment */
+u64 wait_index_increment(struct lightningd *ld UNNEEDED,
+ struct db *db UNNEEDED,
+ enum wait_subsystem subsystem UNNEEDED,
+ enum wait_index index UNNEEDED,
+ ...)
+{ fprintf(stderr, "wait_index_increment called!\n"); abort(); }
/* Generated stub for wait_index_name */
const char *wait_index_name(enum wait_index index UNNEEDED)
{ fprintf(stderr, "wait_index_name called!\n"); abort(); }
diff --git a/wallet/test/run-wallet.c b/wallet/test/run-wallet.c
index 45609a6b..05a1a0ce 100644
--- a/wallet/test/run-wallet.c
+++ b/wallet/test/run-wallet.c
@@ -302,10 +302,10 @@ bool fromwire_channeld_sending_commitsig(const tal_t *ctx UNNEEDED, const void *
bool fromwire_connectd_peer_connected(const tal_t *ctx UNNEEDED, const void *p UNNEEDED, struct node_id *id UNNEEDED, u64 *counter UNNEEDED, struct wireaddr_internal *addr UNNEEDED, struct wireaddr **remote_addr UNNEEDED, bool *incoming UNNEEDED, u8 **features UNNEEDED, wirestring **connect_reason UNNEEDED, u64 *connect_nsec UNNEEDED)
{ fprintf(stderr, "fromwire_connectd_peer_connected called!\n"); abort(); }
/* Generated stub for fromwire_connectd_peer_disconnected */
-bool fromwire_connectd_peer_disconnected(const void *p UNNEEDED, struct node_id *id UNNEEDED, u64 *counter UNNEEDED, u64 *connect_time_nsec UNNEEDED)
+bool fromwire_connectd_peer_disconnected(const void *p UNNEEDED, struct node_id *id UNNEEDED, u64 *counter UNNEEDED, u64 *connected_time_nsec UNNEEDED)
{ fprintf(stderr, "fromwire_connectd_peer_disconnected called!\n"); abort(); }
/* Generated stub for fromwire_connectd_peer_reconnected */
-bool fromwire_connectd_peer_reconnected(const tal_t *ctx UNNEEDED, const void *p UNNEEDED, struct node_id *id UNNEEDED, u64 *prev_counter UNNEEDED, u64 *counter UNNEEDED, struct wireaddr_internal *addr UNNEEDED, struct wireaddr **remote_addr UNNEEDED, bool *incoming UNNEEDED, u8 **features UNNEEDED, u64 *connect_time_nsec UNNEEDED)
+bool fromwire_connectd_peer_reconnected(const tal_t *ctx UNNEEDED, const void *p UNNEEDED, struct node_id *id UNNEEDED, u64 *prev_counter UNNEEDED, u64 *counter UNNEEDED, struct wireaddr_internal *addr UNNEEDED, struct wireaddr **remote_addr UNNEEDED, bool *incoming UNNEEDED, u8 **features UNNEEDED, u64 *connected_time_nsec UNNEEDED)
{ fprintf(stderr, "fromwire_connectd_peer_reconnected called!\n"); abort(); }
/* Generated stub for fromwire_connectd_peer_spoke */
bool fromwire_connectd_peer_spoke(const tal_t *ctx UNNEEDED, const void *p UNNEEDED, struct node_id *id UNNEEDED, u64 *counter UNNEEDED, u16 *msgtype UNNEEDED, struct channel_id *channel_id UNNEEDED, wirestring **error UNNEEDED)
diff --git a/wallet/wallet.c b/wallet/wallet.c
index b3258751..b4982fb2 100644
--- a/wallet/wallet.c
+++ b/wallet/wallet.c
@@ -7438,6 +7438,155 @@ struct db_stmt *wallet_channel_moves_next(struct wallet *wallet, struct db_stmt
return stmt;
}
+struct db_stmt *wallet_network_events_first(struct wallet *w,
+ const struct node_id *specific_id,
+ u64 liststart,
+ u32 *listlimit)
+{
+ struct db_stmt *stmt;
+
+ if (specific_id) {
+ stmt = db_prepare_v2(w->db,
+ SQL("SELECT"
+ " id"
+ ", peer_id"
+ ", type"
+ ", reason"
+ ", timestamp"
+ ", duration_nsec"
+ ", connect_attempted"
+ " FROM network_events"
+ " WHERE peer_id = ? AND id >= ?"
+ " ORDER BY id"
+ " LIMIT ?;"));
+ db_bind_node_id(stmt, specific_id);
+ } else {
+ stmt = db_prepare_v2(w->db,
+ SQL("SELECT"
+ " id"
+ ", peer_id"
+ ", type"
+ ", reason"
+ ", timestamp"
+ ", duration_nsec"
+ ", connect_attempted"
+ " FROM network_events"
+ " WHERE id >= ?"
+ " ORDER BY id"
+ " LIMIT ?;"));
+ }
+ db_bind_u64(stmt, liststart);
+ if (listlimit)
+ db_bind_int(stmt, *listlimit);
+ else
+ db_bind_int(stmt, INT_MAX);
+ db_query_prepared(stmt);
+ return wallet_channel_moves_next(w, stmt);
+}
+
+const char *network_event_name(enum network_event n)
+{
+ switch (n) {
+ case NETWORK_EVENT_CONNECT:
+ return "connect";
+ case NETWORK_EVENT_CONNECTFAIL:
+ return "connect_fail";
+ case NETWORK_EVENT_PING:
+ return "ping";
+ case NETWORK_EVENT_DISCONNECT:
+ return "disconnect";
+ }
+ fatal("%s: %u is invalid", __func__, n);
+}
+
+struct db_stmt *wallet_network_events_next(struct wallet *w,
+ struct db_stmt *stmt)
+{
+ if (!db_step(stmt))
+ return tal_free(stmt);
+
+ return stmt;
+}
+
+void wallet_network_events_extract(const tal_t *ctx,
+ struct db_stmt *stmt,
+ u64 *id,
+ struct node_id *peer_id,
+ u64 *timestamp,
+ enum network_event *etype,
+ const char **reason,
+ u64 *duration_nsec,
+ bool *connect_attempted)
+{
+ *id = db_col_u64(stmt, "id");
+ db_col_node_id(stmt, "peer_id", peer_id);
+ *etype = network_event_in_db(db_col_int(stmt, "type"));
+ *timestamp = db_col_u64(stmt, "timestamp");
+ *reason = db_col_strdup_optional(ctx, stmt, "reason");
+ *duration_nsec = db_col_u64(stmt, "duration_nsec");
+ *connect_attempted = db_col_int(stmt, "connect_attempted");
+}
+
+static u64 network_event_index_inc(struct lightningd *ld,
+ /* NULL means it's being created */
+ const u64 *created_index,
+ const enum network_event *etype,
+ const struct node_id *peer_id,
+ enum wait_index idx)
+{
+ return wait_index_increment(ld, ld->wallet->db,
+ WAIT_SUBSYSTEM_NETWORKEVENTS, idx,
+ /* "" is a magic value meaning 'current val' */
+ "=created_index", created_index ? tal_fmt(tmpctx, "%"PRIu64, *created_index) : "",
+ "type", etype ? network_event_name(*etype) : NULL,
+ "peer_id", peer_id ? fmt_node_id(tmpctx, peer_id) : NULL,
+ NULL);
+}
+
+static u64 network_event_index_created(struct lightningd *ld,
+ enum network_event etype,
+ const struct node_id *peer_id)
+{
+ return network_event_index_inc(ld, NULL,
+ &etype, peer_id,
+ WAIT_INDEX_CREATED);
+}
+
+/* Put the next network event into the db */
+void wallet_save_network_event(struct lightningd *ld,
+ const struct node_id *peer_id,
+ enum network_event etype,
+ const char *reason,
+ u64 duration_nsec,
+ bool connect_attempted)
+{
+ u64 id;
+ struct db_stmt *stmt;
+
+ stmt = db_prepare_v2(ld->wallet->db,
+ SQL("INSERT INTO network_events ("
+ " id,"
+ " peer_id,"
+ " type, "
+ " timestamp,"
+ " reason,"
+ " duration_nsec,"
+ " connect_attempted) VALUES "
+ "(?, ?, ?, ?, ?, ?, ?);"));
+ id = network_event_index_created(ld, etype, peer_id);
+ db_bind_u64(stmt, id);
+ db_bind_node_id(stmt, peer_id);
+ db_bind_int(stmt, network_event_in_db(etype));
+ db_bind_u64(stmt, time_now().ts.tv_sec);
+ if (reason)
+ db_bind_text(stmt, reason);
+ else
+ db_bind_null(stmt);
+ db_bind_u64(stmt, duration_nsec);
+ db_bind_int(stmt, connect_attempted);
+ db_exec_prepared_v2(take(stmt));
+}
+
struct missing {
size_t num_found;
struct missing_addr *addrs;
diff --git a/wallet/wallet.h b/wallet/wallet.h
index cacd3633..1835af77 100644
--- a/wallet/wallet.h
+++ b/wallet/wallet.h
@@ -1876,6 +1876,81 @@ struct issued_address_type *wallet_list_addresses(const tal_t *ctx, struct walle
u64 liststart, const u32 *listlimit);
+enum network_event {
+ NETWORK_EVENT_CONNECT = 1,
+ NETWORK_EVENT_CONNECTFAIL = 2,
+ NETWORK_EVENT_PING = 3,
+ NETWORK_EVENT_DISCONNECT = 4,
+};
+
+static inline enum network_event network_event_in_db(enum network_event n)
+{
+ switch (n) {
+ case NETWORK_EVENT_CONNECT:
+ BUILD_ASSERT(NETWORK_EVENT_CONNECT == 1);
+ return n;
+ case NETWORK_EVENT_CONNECTFAIL:
+ BUILD_ASSERT(NETWORK_EVENT_CONNECTFAIL == 2);
+ return n;
+ case NETWORK_EVENT_PING:
+ BUILD_ASSERT(NETWORK_EVENT_PING == 3);
+ return n;
+ case NETWORK_EVENT_DISCONNECT:
+ BUILD_ASSERT(NETWORK_EVENT_DISCONNECT == 4);
+ return n;
+ }
+ fatal("%s: %u is invalid", __func__, n);
+}
+
+const char *network_event_name(enum network_event n);
+
+/**
+ * Iterate through the network events.
+ * @w: the wallet
+ * @specific_id: filter by peer_id if non-NULL.
+ * @liststart: first index to return (0 == all).
+ * @listlimit: limit on number of entries to return (NULL == no limit).
+ *
+ * Returns pointer to hand as @stmt to wallet_network_events_next(), or NULL.
+ * If you choose not to call wallet_network_events_next() you must free it!
+ */
+struct db_stmt *wallet_network_events_first(struct wallet *w,
+ const struct node_id *specific_id,
+ u64 liststart,
+ u32 *listlimit);
+struct db_stmt *wallet_network_events_next(struct wallet *w,
+ struct db_stmt *stmt);
+
+/**
+ * Extract a network event from the db.
+ * @ctx: the tal ctx to allocate off
+ * @stmt: the db_stmt from wallet_network_events_first/next
+ * @id: the creation key
+ * @peer_id: the peer we're talking to
+ * @timestamp: the time the event was recorded
+ * @etype: the network_event type
+ * @reason: the optional reason (or set to NULL)
+ * @duration_nsec: the time it took (if applicable).
+ * @connect_attempted: whether we attempted at least one address (for NETWORK_EVENT_CONNECTFAIL)
+ */
+void wallet_network_events_extract(const tal_t *ctx,
+ struct db_stmt *stmt,
+ u64 *id,
+ struct node_id *peer_id,
+ u64 *timestamp,
+ enum network_event *etype,
+ const char **reason,
+ u64 *duration_nsec,
+ bool *connect_attempted);
+
+/* Put the next network event into the db */
+void wallet_save_network_event(struct lightningd *ld,
+ const struct node_id *peer_id,
+ enum network_event etype,
+ const char *reason,
+ u64 duration_nsec,
+ bool connect_attempted);
+
/**
* wallet_begin_old_close_rescan: rescan for missing mutual close p2wpkh outputs.
*
Why this scored 13/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.