gossmap: keep stats on live/deleted records.
What changed, and why it matters
This commit adds internal bookkeeping counters that track how many gossip records are currently active versus how many have been deleted in Core Lightning's network map. It does not change any security behavior, fix a bug, or alter how data is validated. It simply provides statistics so that a future compaction routine can decide when to clean up the map.
No security action required. Treat as a normal feature/refactoring commit.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch introduces num_live and num_dead counters to struct gossmap and a new gossmap_stats() accessor. The counters are incremented/decremented when channel updates, node announcements, channels, and nodes are added or removed, and during initial store catchup. The commit message explicitly states the purpose: ‘This way gossmap_manage can decide when to compact.’ No logic changes affect parsing, validation, memory safety, or access control.
Changed components
common/gossmap.ccommon/gossmap.hInspect captured patch +44 / −2
diff --git a/common/gossmap.c b/common/gossmap.c
index 45ea204b..693ebd0e 100644
--- a/common/gossmap.c
+++ b/common/gossmap.c
@@ -89,6 +89,9 @@ struct gossmap {
/* local channel_update messages, if any. */
u8 *local_updates;
+ /* How many live and dead records? */
+ size_t num_live, num_dead;
+
/* Optional logging callback */
void (*logcb)(void *cbarg,
enum log_level level,
@@ -320,6 +323,13 @@ static void remove_node(struct gossmap *map, struct gossmap_node *node)
u32 nodeidx = gossmap_node_idx(map, node);
if (!nodeidx_htable_del(map->nodes, node2ptrint(node)))
abort();
+
+ /* If we had a node_announcement, it's now dead. */
+ if (gossmap_node_announced(node)) {
+ map->num_live--;
+ map->num_dead++;
+ }
+
node->nann_off = map->freed_nodes;
free(node->chan_idxs);
node->chan_idxs = NULL;
@@ -414,6 +424,8 @@ void gossmap_remove_chan(struct gossmap *map, struct gossmap_chan *chan)
chan->cann_off = map->freed_chans;
chan->plus_scid_off = 0;
map->freed_chans = chanidx;
+ map->num_live--;
+ map->num_dead++;
}
void gossmap_remove_node(struct gossmap *map, struct gossmap_node *node)
@@ -583,10 +595,17 @@ static bool update_channel(struct gossmap *map, u64 cupdate_off)
if (!chan)
return ret;
+ /* Are we replacing an existing one? Then old one is dead. */
+ if (gossmap_chan_set(chan, scidd.dir))
+ map->num_dead++;
+ else
+ map->num_live++;
+
/* Preserve this */
hc.nodeidx = chan->half[scidd.dir].nodeidx;
chan->half[scidd.dir] = hc;
chan->cupdate_off[scidd.dir] = cupdate_off;
+
return ret;
}
@@ -637,8 +656,15 @@ static void node_announcement(struct gossmap *map, u64 nann_off)
feature_len = map_be16(map, nann_off + feature_len_off);
map_nodeid(map, nann_off + feature_len_off + 2 + feature_len + 4, &id);
- if ((n = gossmap_find_node(map, &id)))
+ if ((n = gossmap_find_node(map, &id))) {
+ /* Did this replace old announcement? If so, that's dead. */
+ if (gossmap_node_announced(n)) {
+ map->num_live--;
+ map->num_dead++;
+ }
n->nann_off = nann_off;
+ }
+ map->num_live++;
}
static bool report_dying_cb(struct gossmap *map,
@@ -762,6 +788,9 @@ static bool reopen_store(struct gossmap *map, u64 ended_off)
map->map_end = 1;
map->generation++;
+ /* This isn't quite true, as there may be deleted ones, but not many. */
+ map->num_dead = 0;
+
/* Now do reload. */
map_catchup(map, NULL, NULL, false, &changed);
return changed;
@@ -812,8 +841,10 @@ static bool map_catchup(struct gossmap *map,
if (!(flags & GOSSIP_STORE_COMPLETED_BIT))
break;
- if (flags & GOSSIP_STORE_DELETED_BIT)
+ if (flags & GOSSIP_STORE_DELETED_BIT) {
+ map->num_dead++;
continue;
+ }
/* Partial write, should not happen with completed records. */
if (map->map_end + reclen > map->map_size)
@@ -863,6 +894,7 @@ static bool map_catchup(struct gossmap *map,
break;
if (redundant && must_be_clean)
return false;
+ map->num_live++;
} else if (type == WIRE_CHANNEL_UPDATE)
num_bad_cupdates += update_channel(map, off);
else if (type == WIRE_GOSSIP_STORE_DELETE_CHAN)
@@ -1383,6 +1415,7 @@ struct gossmap *gossmap_load_(const tal_t *ctx,
map->generation = 0;
map->fname = tal_strdup(map, filename);
map->fd = open(map->fname, O_RDONLY);
+ map->num_live = map->num_dead = 0;
if (map->fd < 0)
return tal_free(map);
if (logcb)
@@ -1992,3 +2025,9 @@ void gossmap_disable_mmap(struct gossmap *map)
munmap(map->mmap, map->map_size);
map->mmap = NULL;
}
+
+void gossmap_stats(const struct gossmap *map, u64 *num_live, u64 *num_dead)
+{
+ *num_live = map->num_live;
+ *num_dead = map->num_dead;
+}
diff --git a/common/gossmap.h b/common/gossmap.h
index 0c1a0425..c8bfbf19 100644
--- a/common/gossmap.h
+++ b/common/gossmap.h
@@ -308,6 +308,9 @@ void gossmap_iter_fast_forward(const struct gossmap *map,
/* Moves iterator to the end. */
void gossmap_iter_end(const struct gossmap *map, struct gossmap_iter *iter);
+/* How dense is this? */
+void gossmap_stats(const struct gossmap *map, u64 *num_live, u64 *num_dead);
+
/* For debugging: returns length read, and total known length of file */
u64 gossmap_lengths(const struct gossmap *map, u64 *total);
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.