lightningd: save previous short_channel_ids during splice, and keep in db.
What changed, and why it matters
This commit adds bookkeeping for previous channel identifiers (short_channel_ids) when a Lightning channel is spliced. Splicing changes the channel's identifier, so the software now remembers old identifiers in memory and in the wallet database. This is a routing/reliability improvement, not a security fix, and the commit message does not describe any vulnerability.
No security action required; treat as normal feature/maintenance commit. Reviewers may verify the database migration is backward-compatible and that old_scids are only persisted for public channels as intended.
Security signals we found
No security relevance claimed by commit message or diff
Database schema migration adds nullable BLOB column
No input sanitization, authentication, or cryptographic logic changed
Routing feature enhancement for splice channel identifier continuity
Evidence from the diff
The patch introduces an old_scids array on struct channel, populated in channel_add_old_scid() when a public channel’s scid changes (e.g., during splice). It adds a database migration to store old_scids as a BLOB in the channels table, updates wallet load/save paths, and adjusts call sites and test stubs. No cryptographic, authorization, or input-validation changes are present.
Changed components
lightningd/channel.clightningd/channel.hlightningd/channel_control.clightningd/opening_control.cwallet/db.cwallet/wallet.cwallet/test/run-db.cwallet/test/run-wallet.cInspect captured patch +39 / −2
diff --git a/lightningd/channel.c b/lightningd/channel.c
index 40cd6cb6..e0377ac3 100644
--- a/lightningd/channel.c
+++ b/lightningd/channel.c
@@ -254,6 +254,19 @@ struct channel_type *desired_channel_type(const tal_t *ctx,
return channel_type_static_remotekey(ctx);
}
+void channel_add_old_scid(struct channel *channel,
+ struct short_channel_id old_scid)
+{
+ /* If this is not public, we skip */
+ if (!(channel->channel_flags & CHANNEL_FLAGS_ANNOUNCE_CHANNEL))
+ return;
+
+ if (!channel->old_scids)
+ channel->old_scids = tal_dup(channel, struct short_channel_id, &old_scid);
+ else
+ tal_arr_expand(&channel->old_scids, old_scid);
+}
+
struct channel *new_unsaved_channel(struct peer *peer,
u32 feerate_base,
u32 feerate_ppm)
@@ -287,6 +300,7 @@ struct channel *new_unsaved_channel(struct peer *peer,
channel->last_htlc_sigs = NULL;
channel->remote_channel_ready = false;
channel->scid = NULL;
+ channel->old_scids = NULL;
channel->next_index[LOCAL] = 1;
channel->next_index[REMOTE] = 1;
channel->next_htlc_id = 0;
@@ -423,6 +437,7 @@ struct channel *new_channel(struct peer *peer, u64 dbid,
bool remote_channel_ready,
/* NULL or stolen */
struct short_channel_id *scid,
+ struct short_channel_id *old_scids TAKES,
struct short_channel_id *alias_local TAKES,
struct short_channel_id *alias_remote STEALS,
struct channel_id *cid,
@@ -550,6 +565,7 @@ struct channel *new_channel(struct peer *peer, u64 dbid,
channel->our_funds = our_funds;
channel->remote_channel_ready = remote_channel_ready;
channel->scid = tal_steal(channel, scid);
+ channel->old_scids = tal_dup_talarr(channel, struct short_channel_id, old_scids);
channel->alias[LOCAL] = tal_dup_or_null(channel, struct short_channel_id, alias_local);
/* We always make sure this is set (historical channels from db might not) */
if (!channel->alias[LOCAL]) {
diff --git a/lightningd/channel.h b/lightningd/channel.h
index 4dfcf0ac..5cc1a774 100644
--- a/lightningd/channel.h
+++ b/lightningd/channel.h
@@ -217,6 +217,8 @@ struct channel {
bool remote_channel_ready;
/* Channel if locked locally. */
struct short_channel_id *scid;
+ /* Old scids if we were spliced */
+ struct short_channel_id *old_scids;
/* Alias used for option_zeroconf, or option_scid_alias, if
* present. LOCAL are all the alias we told the peer about and
@@ -391,6 +393,7 @@ struct channel *new_channel(struct peer *peer, u64 dbid,
bool remote_channel_ready,
/* NULL or stolen */
struct short_channel_id *scid STEALS,
+ struct short_channel_id *old_scids TAKES,
struct short_channel_id *alias_local STEALS,
struct short_channel_id *alias_remote STEALS,
struct channel_id *cid,
@@ -491,6 +494,10 @@ u32 channel_last_funding_feerate(const struct channel *channel);
/* Only set completely_eliminate for never-existed channels */
void delete_channel(struct channel *channel STEALS, bool completely_eliminate);
+/* Add a historic (public) short_channel_id to this channel */
+void channel_add_old_scid(struct channel *channel,
+ struct short_channel_id old_scid);
+
const char *channel_state_name(const struct channel *channel);
const char *channel_state_str(enum channel_state state);
diff --git a/lightningd/channel_control.c b/lightningd/channel_control.c
index 1470dddf..c4fa0f3a 100644
--- a/lightningd/channel_control.c
+++ b/lightningd/channel_control.c
@@ -815,12 +815,16 @@ bool depthcb_update_scid(struct channel *channel,
lockin_has_completed(channel, false);
} else {
+ struct short_channel_id old_scid = *channel->scid;
+
/* We freaked out if required when original was
* removed, so just update now */
log_info(channel->log, "Short channel id changed from %s->%s",
fmt_short_channel_id(tmpctx, *channel->scid),
fmt_short_channel_id(tmpctx, scid));
*channel->scid = scid;
+ /* In case we broadcast it before (e.g. splice!) */
+ channel_add_old_scid(channel, old_scid);
channel_gossip_scid_changed(channel);
}
diff --git a/lightningd/opening_control.c b/lightningd/opening_control.c
index 43163835..adcb3f6b 100644
--- a/lightningd/opening_control.c
+++ b/lightningd/opening_control.c
@@ -188,6 +188,7 @@ wallet_commit_channel(struct lightningd *ld,
local_funding,
false, /* !remote_channel_ready */
NULL, /* no scid yet */
+ NULL, /* no old scids */
NULL, /* assign random local alias */
NULL, /* They haven't told us an alias yet */
cid,
@@ -1587,6 +1588,7 @@ static struct channel *stub_chan(struct command *cmd,
AMOUNT_SAT(0),
true, /* remote_channel_ready */
scid,
+ NULL,
scid,
scid,
&cid,
diff --git a/wallet/db.c b/wallet/db.c
index 17f0d337..15c54eb0 100644
--- a/wallet/db.c
+++ b/wallet/db.c
@@ -1043,6 +1043,7 @@ static struct migration dbmigrations[] = {
{SQL("ALTER TABLE channel_funding_inflights ADD locked_scid BIGINT DEFAULT 0;"), NULL},
{NULL, migrate_initialize_channel_htlcs_wait_indexes_and_fixup_forwards},
{SQL("ALTER TABLE channel_funding_inflights ADD i_sent_sigs INTEGER DEFAULT 0"), NULL},
+ {SQL("ALTER TABLE channels ADD old_scids BLOB DEFAULT NULL;"), NULL},
};
/**
diff --git a/wallet/test/run-db.c b/wallet/test/run-db.c
index a8552932..09ecb5b3 100644
--- a/wallet/test/run-db.c
+++ b/wallet/test/run-db.c
@@ -188,6 +188,7 @@ struct channel *new_channel(struct peer *peer UNNEEDED, u64 dbid UNNEEDED,
bool remote_channel_ready UNNEEDED,
/* NULL or stolen */
struct short_channel_id *scid STEALS UNNEEDED,
+ struct short_channel_id *old_scids TAKES UNNEEDED,
struct short_channel_id *alias_local STEALS UNNEEDED,
struct short_channel_id *alias_remote STEALS UNNEEDED,
struct channel_id *cid UNNEEDED,
diff --git a/wallet/test/run-wallet.c b/wallet/test/run-wallet.c
index 3084da4d..6cfe7f83 100644
--- a/wallet/test/run-wallet.c
+++ b/wallet/test/run-wallet.c
@@ -2026,7 +2026,8 @@ static bool test_channel_inflight_crud(struct lightningd *ld, const tal_t *ctx)
&outpoint,
funding_sats, AMOUNT_MSAT(0),
our_sats,
- 0, false,
+ 0, NULL,
+ NULL, /* old scids */
NULL, /* alias[LOCAL] */
NULL, /* alias[REMOTE] */
&cid,
diff --git a/wallet/wallet.c b/wallet/wallet.c
index 8ff8592b..7d9f2d36 100644
--- a/wallet/wallet.c
+++ b/wallet/wallet.c
@@ -1779,7 +1779,7 @@ static struct channel *wallet_stmt2channel(struct wallet *w, struct db_stmt *stm
struct channel_info channel_info;
struct fee_states *fee_states;
struct height_states *height_states;
- struct short_channel_id *scid, *alias[NUM_SIDES];
+ struct short_channel_id *scid, *alias[NUM_SIDES], *old_scids;
struct channel_id cid;
struct channel *chan;
u64 peer_dbid;
@@ -1818,6 +1818,7 @@ static struct channel *wallet_stmt2channel(struct wallet *w, struct db_stmt *stm
}
scid = db_col_optional_scid(tmpctx, stmt, "scid");
+ old_scids = db_col_short_channel_id_arr(tmpctx, stmt, "old_scids");
alias[LOCAL] = db_col_optional_scid(tmpctx, stmt, "alias_local");
alias[REMOTE] = db_col_optional_scid(tmpctx, stmt, "alias_remote");
@@ -2032,6 +2033,7 @@ static struct channel *wallet_stmt2channel(struct wallet *w, struct db_stmt *stm
our_funding_sat,
db_col_int(stmt, "funding_locked_remote") != 0,
scid,
+ old_scids,
alias[LOCAL],
alias[REMOTE],
&cid,
@@ -2246,6 +2248,7 @@ static bool wallet_channels_load_active(struct wallet *w)
" id"
", peer_id"
", scid"
+ ", old_scids"
", full_channel_id"
", channel_config_local"
", channel_config_remote"
@@ -2526,6 +2529,7 @@ void wallet_channel_save(struct wallet *w, struct channel *chan)
stmt = db_prepare_v2(w->db, SQL("UPDATE channels SET"
" shachain_remote_id=?,"
" scid=?,"
+ " old_scids=?,"
" full_channel_id=?,"
" state=?,"
" funder=?,"
@@ -2585,6 +2589,7 @@ void wallet_channel_save(struct wallet *w, struct channel *chan)
else
db_bind_null(stmt);
+ db_bind_short_channel_id_arr(stmt, chan->old_scids);
db_bind_channel_id(stmt, &chan->cid);
db_bind_int(stmt, channel_state_in_db(chan->state));
db_bind_int(stmt, chan->opener);
Why this scored 23/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.