lightningd: maintain a hash table of short_channel_id, for faster lookup.
What changed, and why it matters
This commit is a performance and code-organization change. It builds a fast lookup table (hash map) so the node can quickly find a channel from any of its short channel IDs, including old IDs and aliases. It also centralizes how channel IDs are set so the table stays consistent. There is no security fix or vulnerability described in the commit.
No security action required; review as normal code-quality/performance change. If auditing, verify that `channel_set_scid(NULL)` and alias updates correctly remove stale map entries and that stub-channel scids are intentionally excluded from the map.
Security signals we found
No security relevance claimed by the commit message or diff
Change is a data-structure refactor with centralized setter functions
No input validation, parsing, cryptographic, or authorization changes observed
No advisory, CVE, or researcher attribution present in supplied materials
Evidence from the diff
The patch introduces ld->channels_by_scid, a htable mapping short_channel_id to struct channel, covering real scids, local aliases, and old scids. It adds channel_set_scid() and channel_set_local_alias() helpers so all mutations update the map, and wires them into funding-depth callbacks, dual-open control, and stub-channel creation. A memleak scan entry is added. The change is purely architectural/performance.
Changed components
lightningd/channel.clightningd/channel.hlightningd/channel_control.clightningd/dual_open_control.clightningd/lightningd.clightningd/lightningd.hlightningd/memdump.clightningd/opening_control.clightningd/peer_control.cInspect captured patch +115 / −15
diff --git a/lightningd/channel.c b/lightningd/channel.c
index 2ea68021..ff673df1 100644
--- a/lightningd/channel.c
+++ b/lightningd/channel.c
@@ -254,6 +254,60 @@ struct channel_type *desired_channel_type(const tal_t *ctx,
return channel_type_static_remotekey(ctx);
}
+static void chanmap_remove(struct lightningd *ld,
+ const struct channel *channel,
+ struct short_channel_id scid)
+{
+ struct scid_to_channel *scc = channel_scid_map_get(ld->channels_by_scid, scid);
+ assert(scc->channel == channel);
+ tal_free(scc);
+}
+
+static void destroy_scid_to_channel(struct scid_to_channel *scc,
+ struct lightningd *ld)
+{
+ if (!channel_scid_map_del(ld->channels_by_scid, scc))
+ abort();
+}
+
+static void chanmap_add(struct lightningd *ld,
+ struct channel *channel,
+ struct short_channel_id scid)
+{
+ struct scid_to_channel *scc = tal(channel, struct scid_to_channel);
+ scc->channel = channel;
+ scc->scid = scid;
+ channel_scid_map_add(ld->channels_by_scid, scc);
+ tal_add_destructor2(scc, destroy_scid_to_channel, ld);
+}
+
+static void channel_set_random_local_alias(struct channel *channel)
+{
+ assert(channel->alias[LOCAL] == NULL);
+ channel->alias[LOCAL] = tal(channel, struct short_channel_id);
+ randombytes_buf(channel->alias[LOCAL], sizeof(struct short_channel_id));
+ /* We don't check for uniqueness. We would crash on a clash, but your machine is
+ * probably broken beyond repair if it gets two equal 64 bit numbers */
+ chanmap_add(channel->peer->ld, channel, *channel->alias[LOCAL]);
+}
+
+void channel_set_scid(struct channel *channel, const struct short_channel_id *new_scid)
+{
+ struct lightningd *ld = channel->peer->ld;
+
+ /* Get rid of old one (if any) */
+ if (channel->scid != NULL) {
+ chanmap_remove(ld, channel, *channel->scid);
+ channel->scid = tal_free(channel->scid);
+ }
+
+ /* Add new one (if any) */
+ if (new_scid) {
+ channel->scid = tal_dup(channel, struct short_channel_id, new_scid);
+ chanmap_add(ld, channel, *new_scid);
+ }
+}
+
void channel_add_old_scid(struct channel *channel,
struct short_channel_id old_scid)
{
@@ -265,6 +319,8 @@ void channel_add_old_scid(struct channel *channel,
channel->old_scids = tal_dup(channel, struct short_channel_id, &old_scid);
else
tal_arr_expand(&channel->old_scids, old_scid);
+
+ chanmap_add(channel->peer->ld, channel, old_scid);
}
struct channel *new_unsaved_channel(struct peer *peer,
@@ -312,10 +368,8 @@ struct channel *new_unsaved_channel(struct peer *peer,
= CLOSING_FEE_NEGOTIATION_STEP_UNIT_PERCENTAGE;
channel->shutdown_wrong_funding = NULL;
channel->closing_feerate_range = NULL;
- channel->alias[REMOTE] = NULL;
- /* We don't even bother checking for clashes. */
- channel->alias[LOCAL] = tal(channel, struct short_channel_id);
- randombytes_buf(channel->alias[LOCAL], sizeof(struct short_channel_id));
+ channel->alias[REMOTE] = channel->alias[LOCAL] = NULL;
+ channel_set_random_local_alias(channel);
channel->shutdown_scriptpubkey[REMOTE] = NULL;
channel->last_was_revoke = false;
@@ -567,11 +621,19 @@ struct channel *new_channel(struct peer *peer, u64 dbid,
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);
+ /* All these possible short_channel_id variants go in the lookup table! */
+ /* Stub channels all have the same scid though, *and* get loaded from db! */
+ if (channel->scid && !is_stub_scid(*channel->scid))
+ chanmap_add(peer->ld, channel, *channel->scid);
+ if (channel->alias[LOCAL])
+ chanmap_add(peer->ld, channel, *channel->alias[LOCAL]);
+ for (size_t i = 0; i < tal_count(channel->old_scids); i++)
+ chanmap_add(peer->ld, channel, channel->old_scids[i]);
+
/* We always make sure this is set (historical channels from db might not) */
- if (!channel->alias[LOCAL]) {
- channel->alias[LOCAL] = tal(channel, struct short_channel_id);
- randombytes_buf(channel->alias[LOCAL], sizeof(struct short_channel_id));
- }
+ if (!channel->alias[LOCAL])
+ channel_set_random_local_alias(channel);
+
channel->alias[REMOTE] = tal_steal(channel, alias_remote); /* Haven't gotten one yet. */
channel->cid = *cid;
channel->our_msat = our_msat;
diff --git a/lightningd/channel.h b/lightningd/channel.h
index 5cc1a774..82889071 100644
--- a/lightningd/channel.h
+++ b/lightningd/channel.h
@@ -1,6 +1,7 @@
#ifndef LIGHTNING_LIGHTNINGD_CHANNEL_H
#define LIGHTNING_LIGHTNINGD_CHANNEL_H
#include "config.h"
+#include <ccan/htable/htable_type.h>
#include <common/channel_config.h>
#include <common/channel_id.h>
#include <common/channel_type.h>
@@ -844,6 +845,35 @@ struct channel *peer_any_channel_bystate(struct peer *peer,
struct channel *channel_by_dbid(struct lightningd *ld, const u64 dbid);
+struct scid_to_channel {
+ struct short_channel_id scid;
+ struct channel *channel;
+};
+
+static inline const struct short_channel_id scid_to_channel_key(const struct scid_to_channel *scidchan)
+{
+ return scidchan->scid;
+}
+
+static inline bool scid_to_channel_eq_scid(const struct scid_to_channel *scidchan,
+ struct short_channel_id scid)
+{
+ return short_channel_id_eq(scidchan->scid, scid);
+}
+
+/* Define channel_scid_map */
+HTABLE_DEFINE_NODUPS_TYPE(struct scid_to_channel,
+ scid_to_channel_key,
+ short_channel_id_hash,
+ scid_to_channel_eq_scid,
+ channel_scid_map);
+
+/* The only allowed way to set channel->scid */
+void channel_set_scid(struct channel *channel, const struct short_channel_id *new_scid);
+
+/* The only allowed way to set channel->alias[LOCAL] */
+void channel_set_local_alias(struct channel *channel, struct short_channel_id alias_scid);
+
/* Includes both real scids and aliases. If !privacy_leak_ok, then private
* channels' real scids are not included. */
struct channel *any_channel_by_scid(struct lightningd *ld,
diff --git a/lightningd/channel_control.c b/lightningd/channel_control.c
index c4fa0f3a..3e341e9d 100644
--- a/lightningd/channel_control.c
+++ b/lightningd/channel_control.c
@@ -803,7 +803,7 @@ bool depthcb_update_scid(struct channel *channel,
if (!channel->scid) {
wallet_annotate_txout(ld->wallet, outpoint,
TX_CHANNEL_FUNDING, channel->dbid);
- channel->scid = tal_dup(channel, struct short_channel_id, &scid);
+ channel_set_scid(channel, &scid);
/* If we have a zeroconf channel, i.e., no scid yet
* but have exchange `channel_ready` messages, then we
@@ -822,7 +822,7 @@ bool depthcb_update_scid(struct channel *channel,
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;
+ channel_set_scid(channel, &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/dual_open_control.c b/lightningd/dual_open_control.c
index 7835dcf6..8e106c1b 100644
--- a/lightningd/dual_open_control.c
+++ b/lightningd/dual_open_control.c
@@ -1038,7 +1038,7 @@ static enum watch_result opening_depth_cb(struct lightningd *ld,
if (!inflight->channel->scid) {
wallet_annotate_txout(ld->wallet, &inflight->funding->outpoint,
TX_CHANNEL_FUNDING, inflight->channel->dbid);
- inflight->channel->scid = tal_dup(inflight->channel, struct short_channel_id, &scid);
+ channel_set_scid(inflight->channel, &scid);
wallet_channel_save(ld->wallet, inflight->channel);
} else if (!short_channel_id_eq(*inflight->channel->scid, scid)) {
/* We freaked out if required when original was
@@ -1046,7 +1046,7 @@ static enum watch_result opening_depth_cb(struct lightningd *ld,
log_info(inflight->channel->log, "Short channel id changed from %s->%s",
fmt_short_channel_id(tmpctx, *inflight->channel->scid),
fmt_short_channel_id(tmpctx, scid));
- *inflight->channel->scid = scid;
+ channel_set_scid(inflight->channel, &scid);
wallet_channel_save(ld->wallet, inflight->channel);
}
diff --git a/lightningd/lightningd.c b/lightningd/lightningd.c
index 0d7faa0d..d76d5b60 100644
--- a/lightningd/lightningd.c
+++ b/lightningd/lightningd.c
@@ -209,6 +209,10 @@ static struct lightningd *new_lightningd(const tal_t *ctx)
ld->peers_by_dbid = tal(ld, struct peer_dbid_map);
peer_dbid_map_init(ld->peers_by_dbid);
+ /*~ This speeds lookups for short_channel_ids to their channels. */
+ ld->channels_by_scid = tal(ld, struct channel_scid_map);
+ channel_scid_map_init(ld->channels_by_scid);
+
/*~ For multi-part payments, we need to keep some incoming payments
* in limbo until we get all the parts, or we time them out. */
ld->htlc_sets = tal(ld, struct htlc_set_map);
diff --git a/lightningd/lightningd.h b/lightningd/lightningd.h
index 34c8a158..44a76e18 100644
--- a/lightningd/lightningd.h
+++ b/lightningd/lightningd.h
@@ -215,6 +215,8 @@ struct lightningd {
struct peer_node_id_map *peers;
/* And those in database by dbid */
struct peer_dbid_map *peers_by_dbid;
+ /* Here are all our channels and their aliases */
+ struct channel_scid_map *channels_by_scid;
/* Outstanding connect commands. */
struct list_head connects;
diff --git a/lightningd/memdump.c b/lightningd/memdump.c
index c999db71..b2f32a10 100644
--- a/lightningd/memdump.c
+++ b/lightningd/memdump.c
@@ -11,6 +11,7 @@
#include <gossipd/gossipd_wiregen.h>
#include <hsmd/hsmd_wiregen.h>
#include <lightningd/chaintopology.h>
+#include <lightningd/channel.h>
#include <lightningd/closed_channel.h>
#include <lightningd/hsm_control.h>
#include <lightningd/jsonrpc.h>
@@ -202,6 +203,7 @@ static bool lightningd_check_leaks(struct command *cmd)
memleak_scan_htable(memtable, &ld->htlc_sets->raw);
memleak_scan_htable(memtable, &ld->peers->raw);
memleak_scan_htable(memtable, &ld->peers_by_dbid->raw);
+ memleak_scan_htable(memtable, &ld->channels_by_scid->raw);
memleak_scan_htable(memtable, &ld->closed_channels->raw);
wallet_memleak_scan(memtable, ld->wallet);
diff --git a/lightningd/opening_control.c b/lightningd/opening_control.c
index adcb3f6b..521fd833 100644
--- a/lightningd/opening_control.c
+++ b/lightningd/opening_control.c
@@ -1589,8 +1589,8 @@ static struct channel *stub_chan(struct command *cmd,
true, /* remote_channel_ready */
scid,
NULL,
- scid,
- scid,
+ NULL,
+ NULL,
&cid,
/* The three arguments below are msatoshi_to_us,
* msatoshi_to_us_min, and msatoshi_to_us_max.
diff --git a/lightningd/peer_control.c b/lightningd/peer_control.c
index ae944a69..35b88089 100644
--- a/lightningd/peer_control.c
+++ b/lightningd/peer_control.c
@@ -2225,7 +2225,7 @@ static enum watch_result funding_depth_cb(struct lightningd *ld,
/* That's not entirely unexpected in early states */
log_debug(channel->log, "Funding tx %s reorganized out!",
fmt_bitcoin_txid(tmpctx, txid));
- channel->scid = tal_free(channel->scid);
+ channel_set_scid(channel, NULL);
return KEEP_WATCHING;
/* But it's often Bad News in later states */
Why this scored 12/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.