From 9c16014dd4361c69c0751789ab0a354753070f26 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 31 Jan 2024 14:48:33 +1030 Subject: [PATCH 01/33] common/gossmap: remove now-unused private flag. The only way you'll see private channel_updates is if you put them there yourself with localmods. I also renamed the confusing gossmap_chan_capacity to gossmap_chan_has_capacity. Signed-off-by: Rusty Russell --- common/gossmap.c | 55 +++++++++++--------------------- common/gossmap.h | 15 +++++---- common/route.c | 2 +- common/test/run-gossmap_canned.c | 2 +- common/test/run-gossmap_local.c | 4 +-- plugins/topology.c | 4 +-- 6 files changed, 34 insertions(+), 48 deletions(-) diff --git a/common/gossmap.c b/common/gossmap.c index a61947bc5636..26c661d667fe 100644 --- a/common/gossmap.c +++ b/common/gossmap.c @@ -318,14 +318,9 @@ static u32 init_chan_arr(struct gossmap_chan *chan_arr, size_t start) for (i = start; i < tal_count(chan_arr) - 1; i++) { chan_arr[i].cann_off = i + 1; chan_arr[i].plus_scid_off = 0; - /* We don't need to initialize this, *but* on some platforms - * (ppc, arm64) valgrind complains: this is a bitfield shared - * with plus_scid_off */ - chan_arr[i].private = false; } chan_arr[i].cann_off = UINT_MAX; chan_arr[i].plus_scid_off = 0; - chan_arr[i].private = false; return start; } @@ -350,13 +345,11 @@ static struct gossmap_chan *next_free_chan(struct gossmap *map) static struct gossmap_chan *new_channel(struct gossmap *map, u32 cannounce_off, u32 plus_scid_off, - bool private, u32 n1idx, u32 n2idx) { struct gossmap_chan *chan = next_free_chan(map); chan->cann_off = cannounce_off; - chan->private = private; chan->plus_scid_off = plus_scid_off; chan->cupdate_off[0] = chan->cupdate_off[1] = 0; memset(chan->half, 0, sizeof(chan->half)); @@ -421,8 +414,7 @@ void gossmap_remove_node(struct gossmap *map, struct gossmap_node *node) * * [`point`:`node_id_2`] */ static struct gossmap_chan *add_channel(struct gossmap *map, - size_t cannounce_off, - bool private) + size_t cannounce_off) { /* Note that first two bytes are message type */ const size_t feature_len_off = 2 + (64 + 64 + 64 + 64); @@ -440,12 +432,9 @@ static struct gossmap_chan *add_channel(struct gossmap *map, map_nodeid(map, cannounce_off + plus_scid_off + 8, &node_id[0]); map_nodeid(map, cannounce_off + plus_scid_off + 8 + PUBKEY_CMPR_LEN, &node_id[1]); - /* We can have a channel upgrade from private->public, but - * that's the only time we get duplicates */ + /* We 1should not get duplicates. */ scid.u64 = map_be64(map, cannounce_off + plus_scid_off); - chan = gossmap_find_chan(map, &scid); - if (chan) - gossmap_remove_chan(map, chan); + assert(!gossmap_find_chan(map, &scid)); /* We carefully map pointers to indexes, since new_node can move them! */ n[0] = gossmap_find_node(map, &node_id[0]); @@ -460,7 +449,7 @@ static struct gossmap_chan *add_channel(struct gossmap *map, else nidx[1] = new_node(map); - chan = new_channel(map, cannounce_off, plus_scid_off, private, + chan = new_channel(map, cannounce_off, plus_scid_off, nidx[0], nidx[1]); /* Now we have a channel, we can add nodes to htable */ @@ -635,7 +624,7 @@ static bool map_catchup(struct gossmap *map, size_t *num_rejected) off = map->map_end + sizeof(ghdr); type = map_be16(map, off); if (type == WIRE_CHANNEL_ANNOUNCEMENT) - add_channel(map, off, false); + add_channel(map, off); else if (type == WIRE_CHANNEL_UPDATE) num_bad += !update_channel(map, off); else if (type == WIRE_GOSSIP_STORE_DELETE_CHAN) @@ -902,8 +891,7 @@ void gossmap_apply_localmods(struct gossmap *map, continue; /* Create new channel, pointing into local. */ - chan = add_channel(map, map->map_size + mod->local_off, - true); + chan = add_channel(map, map->map_size + mod->local_off); } /* Save old, overwrite (keep nodeidx) */ @@ -995,6 +983,12 @@ void gossmap_node_get_id(const struct gossmap *map, + 8 + PUBKEY_CMPR_LEN*dir, id); } +bool gossmap_chan_is_localmod(const struct gossmap *map, + const struct gossmap_chan *c) +{ + return c->cann_off >= map->map_size; +} + bool gossmap_chan_get_capacity(const struct gossmap *map, const struct gossmap_chan *c, struct amount_sat *amount) @@ -1004,16 +998,9 @@ bool gossmap_chan_get_capacity(const struct gossmap *map, u16 type; /* Fail for local channels */ - if (c->cann_off >= map->map_size) + if (gossmap_chan_is_localmod(map, c)) return false; - /* For private, we need to go back WIRE_GOSSIP_STORE_PRIVATE_CHANNEL, - * which is 8 (satoshis) + 2 (len) */ - if (c->private) { - *amount = amount_sat(map_be64(map, c->cann_off - 8 - 2)); - return true; - } - /* Skip over this record to next; expect a gossip_store_channel_amount */ off = c->cann_off - sizeof(ghdr); map_copy(map, off, &ghdr, sizeof(ghdr)); @@ -1113,9 +1100,9 @@ struct gossmap_chan *gossmap_next_chan(const struct gossmap *map, return chan_iter(map, prev - map->chan_arr + 1); } -bool gossmap_chan_capacity(const struct gossmap_chan *chan, - int direction, - struct amount_msat amount) +bool gossmap_chan_has_capacity(const struct gossmap_chan *chan, + int direction, + struct amount_msat amount) { if (amount_msat_less_fp16(amount, chan->half[direction].htlc_min)) return false; @@ -1133,14 +1120,10 @@ u8 *gossmap_chan_get_announce(const tal_t *ctx, { u16 len; u8 *msg; - u32 pre_off; - /* We need to go back to struct gossip_hdr to get len */ - if (c->private) - pre_off = 2 + 8 + 2 + sizeof(struct gossip_hdr); - else - pre_off = sizeof(struct gossip_hdr); - len = map_be16(map, c->cann_off - pre_off + if (gossmap_chan_is_localmod(map, c)) + return NULL; + len = map_be16(map, c->cann_off - sizeof(struct gossip_hdr) + offsetof(struct gossip_hdr, len)); msg = tal_arr(ctx, u8, len); diff --git a/common/gossmap.h b/common/gossmap.h index edaec7415f6d..a5ae9227ec8e 100644 --- a/common/gossmap.h +++ b/common/gossmap.h @@ -18,9 +18,8 @@ struct gossmap_node { struct gossmap_chan { u32 cann_off; - u32 private: 1; /* Technically redundant, but we have a hole anyway: from cann_off */ - u32 plus_scid_off: 31; + u32 plus_scid_off; /* Offsets of cupdates (0 if missing). Logically inside half_chan, * but that would add padding. */ u32 cupdate_off[2]; @@ -85,6 +84,10 @@ void gossmap_apply_localmods(struct gossmap *map, void gossmap_remove_localmods(struct gossmap *map, const struct gossmap_localmods *localmods); +/* Is this channel a localmod? */ +bool gossmap_chan_is_localmod(const struct gossmap *map, + const struct gossmap_chan *c); + /* Each channel has a unique (low) index. */ u32 gossmap_node_idx(const struct gossmap *map, const struct gossmap_node *node); u32 gossmap_chan_idx(const struct gossmap *map, const struct gossmap_chan *chan); @@ -124,7 +127,7 @@ bool gossmap_chan_get_capacity(const struct gossmap *map, const struct gossmap_chan *c, struct amount_sat *amount); -/* Get the announcement msg which created this chan */ +/* Get the announcement msg which created this chan (NULL for localmods) */ u8 *gossmap_chan_get_announce(const tal_t *ctx, const struct gossmap *map, const struct gossmap_chan *c); @@ -180,9 +183,9 @@ struct gossmap_node *gossmap_nth_node(const struct gossmap *map, int n); /* Can this channel send this amount? */ -bool gossmap_chan_capacity(const struct gossmap_chan *chan, - int direction, - struct amount_msat amount); +bool gossmap_chan_has_capacity(const struct gossmap_chan *chan, + int direction, + struct amount_msat amount); /* Remove a channel from the map (warning! realloc can move gossmap_chan * and gossmap_node ptrs!) */ diff --git a/common/route.c b/common/route.c index 64beb206313d..1fc5ffd3b7ef 100644 --- a/common/route.c +++ b/common/route.c @@ -15,7 +15,7 @@ bool route_can_carry_even_disabled(const struct gossmap *map, return false; /* Amount 0 is a special "ignore min" probe case */ if (!amount_msat_eq(amount, AMOUNT_MSAT(0)) - && !gossmap_chan_capacity(c, dir, amount)) + && !gossmap_chan_has_capacity(c, dir, amount)) return false; return true; } diff --git a/common/test/run-gossmap_canned.c b/common/test/run-gossmap_canned.c index 2bd9f6cc2e28..4b3f77190a7c 100644 --- a/common/test/run-gossmap_canned.c +++ b/common/test/run-gossmap_canned.c @@ -332,7 +332,7 @@ int main(int argc, char *argv[]) assert(short_channel_id_from_str("110x1x0", 7, &scid12)); assert(gossmap_find_chan(map, &scid12)); - assert(!gossmap_find_chan(map, &scid12)->private); + assert(!gossmap_chan_is_localmod(map, gossmap_find_chan(map, &scid12))); assert(gossmap_chan_get_capacity(map, gossmap_find_chan(map, &scid12), &capacity)); assert(amount_sat_eq(capacity, AMOUNT_SAT(1000000))); diff --git a/common/test/run-gossmap_local.c b/common/test/run-gossmap_local.c index b95e6af5f14c..f1519b0eb276 100644 --- a/common/test/run-gossmap_local.c +++ b/common/test/run-gossmap_local.c @@ -358,9 +358,9 @@ int main(int argc, char *argv[]) assert(short_channel_id_from_str("103x1x0", 7, &scid23)); assert(short_channel_id_from_str("110x1x0", 7, &scid12)); assert(gossmap_find_chan(map, &scid23)); - assert(!gossmap_find_chan(map, &scid23)->private); + assert(!gossmap_chan_is_localmod(map, gossmap_find_chan(map, &scid23))); assert(gossmap_find_chan(map, &scid12)); - assert(!gossmap_find_chan(map, &scid12)->private); + assert(!gossmap_chan_is_localmod(map, gossmap_find_chan(map, &scid12))); assert(gossmap_chan_get_capacity(map, gossmap_find_chan(map, &scid23), &capacity)); assert(amount_sat_eq(capacity, AMOUNT_SAT(1000000))); diff --git a/plugins/topology.c b/plugins/topology.c index dd934c02f386..6cb12c3083af 100644 --- a/plugins/topology.c +++ b/plugins/topology.c @@ -264,7 +264,7 @@ static void json_add_halfchan(struct json_stream *response, json_add_node_id(response, "destination", &node_id[!dir]); json_add_short_channel_id(response, "short_channel_id", &scid); json_add_num(response, "direction", dir); - json_add_bool(response, "public", !c->private); + json_add_bool(response, "public", !gossmap_chan_is_localmod(gossmap, c)); if (c->private) { /* Local additions don't have a channel_update @@ -666,7 +666,7 @@ listpeerchannels_listincoming_done(struct command *cmd, json_add_u32(js, "cltv_expiry_delta", ourchan->half[!dir].delay); json_add_amount_msat(js, "incoming_capacity_msat", peer_capacity(gossmap, me, peer, ourchan)); - json_add_bool(js, "public", !ourchan->private); + json_add_bool(js, "public", !gossmap_chan_is_localmod(gossmap, ourchan)); peer_features = gossmap_node_get_features(tmpctx, gossmap, peer); if (peer_features) json_add_hex_talarr(js, "peer_features", peer_features); From de49f02e7709c056fd0ce3fa304acd0860c7d459 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 31 Jan 2024 14:49:33 +1030 Subject: [PATCH 02/33] gossipd: new routines to support gossmap compatibility. gossip_store_del - takes a gossmap-style offset-of-msg not offset-of-hdr. gossip_store_flag: set an arbitrary flag on a gossip_store hdr. gossip_store_get_timestamp/gossip_store_set_timestamp: access gossip_store hdr. Signed-off-by: Rusty Russell --- gossipd/gossip_store.c | 120 ++++++++++++++++++++++++++++++++--------- gossipd/gossip_store.h | 35 ++++++++++++ plugins/topology.c | 2 +- 3 files changed, 130 insertions(+), 27 deletions(-) diff --git a/gossipd/gossip_store.c b/gossipd/gossip_store.c index 2c03719d54d7..fbd954783d6a 100644 --- a/gossipd/gossip_store.c +++ b/gossipd/gossip_store.c @@ -667,7 +667,7 @@ void gossip_store_mark_dying(struct gossip_store *gs, } /* Returns index of following entry. */ -static u32 delete_by_index(struct gossip_store *gs, u32 index, int type) +static u32 flag_by_index(struct gossip_store *gs, u32 index, int flag, int type) { struct { beint16_t beflags; @@ -682,28 +682,28 @@ static u32 delete_by_index(struct gossip_store *gs, u32 index, int type) /* FIXME: debugging a gs->len overrun issue reported in #6270 */ if (pread(gs->fd, &hdr, sizeof(hdr), index) != sizeof(hdr)) { - status_broken("gossip_store overrun during delete @%u type: %i" - " gs->len: %"PRIu64, index, type, gs->len); + status_broken("gossip_store pread fail during flag %u @%u type: %i" + " gs->len: %"PRIu64, flag, index, type, gs->len); return index; } if (index + sizeof(struct gossip_hdr) + be16_to_cpu(hdr.belen) > gs->len) { - status_broken("gossip_store overrun during delete @%u type: %i" - " gs->len: %"PRIu64, index, type, gs->len); + status_broken("gossip_store overrun during flag-%u @%u type: %i" + " gs->len: %"PRIu64, flag, index, type, gs->len); return index; } const u8 *msg = gossip_store_get(tmpctx, gs, index); if(fromwire_peektype(msg) != type) { - status_broken("asked to delete type %i @%u but store contains " + status_broken("asked to flag-%u type %i @%u but store contains " "%i (gs->len=%"PRIu64"): %s", - type, index, fromwire_peektype(msg), + flag, type, index, fromwire_peektype(msg), gs->len, tal_hex(tmpctx, msg)); return index; } - assert((be16_to_cpu(hdr.beflags) & GOSSIP_STORE_DELETED_BIT) == 0); - hdr.beflags |= cpu_to_be16(GOSSIP_STORE_DELETED_BIT); + assert((be16_to_cpu(hdr.beflags) & flag) == 0); + hdr.beflags |= cpu_to_be16(flag); if (pwrite(gs->fd, &hdr.beflags, sizeof(hdr.beflags), index) != sizeof(hdr.beflags)) status_failed(STATUS_FAIL_INTERNAL_ERROR, "Failed writing flags to delete @%u: %s", @@ -713,24 +713,45 @@ static u32 delete_by_index(struct gossip_store *gs, u32 index, int type) return index + sizeof(struct gossip_hdr) + be16_to_cpu(hdr.belen); } +void gossip_store_del(struct gossip_store *gs, + u64 offset, + int type) +{ + u32 next_index; + + assert(offset > sizeof(struct gossip_hdr)); + next_index = flag_by_index(gs, offset - sizeof(struct gossip_hdr), + GOSSIP_STORE_DELETED_BIT, + type); + + /* For a channel_announcement, we need to delete amount too */ + if (type == WIRE_CHANNEL_ANNOUNCEMENT) + flag_by_index(gs, next_index, + GOSSIP_STORE_DELETED_BIT, + WIRE_GOSSIP_STORE_CHANNEL_AMOUNT); +} + +void gossip_store_flag(struct gossip_store *gs, + u64 offset, + u16 flag, + int type) +{ + assert(offset > sizeof(struct gossip_hdr)); + + flag_by_index(gs, offset - sizeof(struct gossip_hdr), flag, type); +} + void gossip_store_delete(struct gossip_store *gs, struct broadcastable *bcast, int type) { - u32 next_index; - if (!bcast->index) return; - next_index = delete_by_index(gs, bcast->index, type); + gossip_store_del(gs, bcast->index + sizeof(struct gossip_hdr), type); /* Reset index. */ bcast->index = 0; - - /* For a channel_announcement, we need to delete amount too */ - if (type == WIRE_CHANNEL_ANNOUNCEMENT) - delete_by_index(gs, next_index, - WIRE_GOSSIP_STORE_CHANNEL_AMOUNT); } void gossip_store_mark_channel_deleted(struct gossip_store *gs, @@ -786,11 +807,26 @@ void gossip_store_mark_cupdate_zombie(struct gossip_store *gs, mark_zombie(gs, bcast, WIRE_CHANNEL_UPDATE); } -const u8 *gossip_store_get(const tal_t *ctx, - struct gossip_store *gs, - u64 offset) +u32 gossip_store_get_timestamp(struct gossip_store *gs, u64 offset) { struct gossip_hdr hdr; + + assert(offset > sizeof(struct gossip_hdr)); + + if (pread(gs->fd, &hdr, sizeof(hdr), offset - sizeof(hdr)) != sizeof(hdr)) { + status_broken("gossip_store overrun during get_timestamp @%"PRIu64 + " gs->len: %"PRIu64, offset, gs->len); + return 0; + } + + return be32_to_cpu(hdr.timestamp); +} + +static const u8 *gossip_store_get_with_hdr(const tal_t *ctx, + struct gossip_store *gs, + u64 offset, + struct gossip_hdr *hdr) +{ u32 msglen, checksum; u8 *msg; @@ -798,28 +834,28 @@ const u8 *gossip_store_get(const tal_t *ctx, status_failed(STATUS_FAIL_INTERNAL_ERROR, "gossip_store: can't access offset %"PRIu64, offset); - if (pread(gs->fd, &hdr, sizeof(hdr), offset) != sizeof(hdr)) { + if (pread(gs->fd, hdr, sizeof(*hdr), offset) != sizeof(*hdr)) { status_failed(STATUS_FAIL_INTERNAL_ERROR, "gossip_store: can't read hdr offset %"PRIu64 "/%"PRIu64": %s", offset, gs->len, strerror(errno)); } - if (be16_to_cpu(hdr.flags) & GOSSIP_STORE_DELETED_BIT) + if (be16_to_cpu(hdr->flags) & GOSSIP_STORE_DELETED_BIT) status_failed(STATUS_FAIL_INTERNAL_ERROR, "gossip_store: get delete entry offset %"PRIu64 "/%"PRIu64"", offset, gs->len); - msglen = be16_to_cpu(hdr.len); - checksum = be32_to_cpu(hdr.crc); + msglen = be16_to_cpu(hdr->len); + checksum = be32_to_cpu(hdr->crc); msg = tal_arr(ctx, u8, msglen); - if (pread(gs->fd, msg, msglen, offset + sizeof(hdr)) != msglen) + if (pread(gs->fd, msg, msglen, offset + sizeof(*hdr)) != msglen) status_failed(STATUS_FAIL_INTERNAL_ERROR, "gossip_store: can't read len %u offset %"PRIu64 "/%"PRIu64, msglen, offset, gs->len); - if (checksum != crc32c(be32_to_cpu(hdr.timestamp), msg, msglen)) + if (checksum != crc32c(be32_to_cpu(hdr->timestamp), msg, msglen)) status_failed(STATUS_FAIL_INTERNAL_ERROR, "gossip_store: bad checksum offset %"PRIu64": %s", offset, tal_hex(tmpctx, msg)); @@ -827,6 +863,38 @@ const u8 *gossip_store_get(const tal_t *ctx, return msg; } +void gossip_store_set_timestamp(struct gossip_store *gs, u64 offset, u32 timestamp) +{ + struct gossip_hdr hdr; + const u8 *msg; + + assert(offset > sizeof(struct gossip_hdr)); + msg = gossip_store_get_with_hdr(tmpctx, gs, offset - sizeof(hdr), &hdr); + if (pread(gs->fd, &hdr, sizeof(hdr), offset - sizeof(hdr)) != sizeof(hdr)) { + status_broken("gossip_store overrun during set_timestamp @%"PRIu64 + " gs->len: %"PRIu64, offset, gs->len); + return; + } + + /* Change timestamp and crc */ + hdr.timestamp = cpu_to_be32(timestamp); + hdr.crc = cpu_to_be32(crc32c(timestamp, msg, tal_bytelen(msg))); + + if (pwrite(gs->fd, &hdr, sizeof(hdr), offset - sizeof(hdr)) != sizeof(hdr)) + status_failed(STATUS_FAIL_INTERNAL_ERROR, + "Failed writing header to re-timestamp @%"PRIu64": %s", + offset, strerror(errno)); +} + +const u8 *gossip_store_get(const tal_t *ctx, + struct gossip_store *gs, + u64 offset) +{ + struct gossip_hdr hdr; + + return gossip_store_get_with_hdr(ctx, gs, offset, &hdr); +} + int gossip_store_readonly_fd(struct gossip_store *gs) { int fd = open(GOSSIP_STORE_FILENAME, O_RDONLY); diff --git a/gossipd/gossip_store.h b/gossipd/gossip_store.h index b318842f150a..4f86a5b60a9c 100644 --- a/gossipd/gossip_store.h +++ b/gossipd/gossip_store.h @@ -53,6 +53,27 @@ void gossip_store_delete(struct gossip_store *gs, struct broadcastable *bcast, int type); +/** + * Delete the record at this offset (offset is that of + * record, not header, unlike bcast->index!). + * + * In developer mode, checks that type is correct. + */ +void gossip_store_del(struct gossip_store *gs, + u64 offset, + int type); + +/** + * Add a flag the record at this offset (offset is that of + * record, not header, unlike bcast->index!). + * + * In developer mode, checks that type is correct. + */ +void gossip_store_flag(struct gossip_store *gs, + u64 offset, + u16 flag, + int type); + /** * Mark that the channel is about to be deleted, for convenience of * others mapping the gossip_store. @@ -91,6 +112,20 @@ const u8 *gossip_store_get(const tal_t *ctx, struct gossip_store *gs, u64 offset); +/** + * Direct store accessor: get timestamp header for a record. + * + * Offset is *after* the header. + */ +u32 gossip_store_get_timestamp(struct gossip_store *gs, u64 offset); + +/** + * Direct store accessor: set timestamp header for a record. + * + * Offset is *after* the header. + */ +void gossip_store_set_timestamp(struct gossip_store *gs, u64 offset, u32 timestamp); + /* Exposed for dev-compact-gossip-store to force compaction. */ bool gossip_store_compact(struct gossip_store *gs); diff --git a/plugins/topology.c b/plugins/topology.c index 6cb12c3083af..f43688e713bb 100644 --- a/plugins/topology.c +++ b/plugins/topology.c @@ -266,7 +266,7 @@ static void json_add_halfchan(struct json_stream *response, json_add_num(response, "direction", dir); json_add_bool(response, "public", !gossmap_chan_is_localmod(gossmap, c)); - if (c->private) { + if (gossmap_chan_is_localmod(gossmap, c)) { /* Local additions don't have a channel_update * in gossmap. This is deprecated anyway, but * fill in values from entry we added. */ From 61a26f65b5b453e9ab2bfb08d4bb831c03188335 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 31 Jan 2024 14:50:33 +1030 Subject: [PATCH 03/33] common: gossmap method to load fd directly, not filename. And helpers to tell if a node_announcement exists, and get a full channel_update. Signed-off-by: Rusty Russell --- common/gossmap.c | 61 ++++++++++++++++++++++++++++++++++++++---------- common/gossmap.h | 16 +++++++++++++ 2 files changed, 65 insertions(+), 12 deletions(-) diff --git a/common/gossmap.c b/common/gossmap.c index 26c661d667fe..5a897fa3837c 100644 --- a/common/gossmap.c +++ b/common/gossmap.c @@ -51,6 +51,7 @@ HTABLE_DEFINE_TYPE(ptrint_t, nodeidx_id, nodeid_hash, nodeidx_eq_id, struct gossmap { /* The file descriptor and filename to monitor */ int fd; + /* NULL means we don't own fd */ const char *fname; /* The memory map of the file: u8 for arithmetic portability */ @@ -582,8 +583,12 @@ static void node_announcement(struct gossmap *map, size_t nann_off) static void reopen_store(struct gossmap *map, size_t ended_off) { - int fd = open(map->fname, O_RDONLY); + int fd; + + if (!map->fname) + errx(1, "Need to reopen, but not fname!"); + fd = open(map->fname, O_RDONLY); if (fd < 0) err(1, "Failed to reopen %s", map->fname); @@ -646,10 +651,6 @@ static bool map_catchup(struct gossmap *map, size_t *num_rejected) static bool load_gossip_store(struct gossmap *map, size_t *num_rejected) { - map->fd = open(map->fname, O_RDONLY); - if (map->fd < 0) - return false; - map->map_size = lseek(map->fd, 0, SEEK_END); map->local = NULL; /* If this fails, we fall back to read */ @@ -659,9 +660,6 @@ static bool load_gossip_store(struct gossmap *map, size_t *num_rejected) /* We only support major version 0 */ if (GOSSIP_STORE_MAJOR_VERSION(map_u8(map, 0)) != 0) { - close(map->fd); - if (map->mmap) - munmap(map->mmap, map->map_size); errno = EINVAL; return false; } @@ -695,6 +693,9 @@ static void destroy_map(struct gossmap *map) for (size_t i = 0; i < tal_count(map->node_arr); i++) free(map->node_arr[i].chan_idxs); + + if (map->fname) + close(map->fd); } /* Local modifications. We only expect a few, so we use a simple @@ -964,10 +965,26 @@ struct gossmap *gossmap_load(const tal_t *ctx, const char *filename, { map = tal(ctx, struct gossmap); map->fname = tal_strdup(map, filename); - if (load_gossip_store(map, num_channel_updates_rejected)) - tal_add_destructor(map, destroy_map); - else - map = tal_free(map); + map->fd = open(map->fname, O_RDONLY); + if (map->fd < 0) + return tal_free(map); + tal_add_destructor(map, destroy_map); + + if (!load_gossip_store(map, num_channel_updates_rejected)) + return tal_free(map); + return map; +} + +struct gossmap *gossmap_load_fd(const tal_t *ctx, int fd, + size_t *num_channel_updates_rejected) +{ + map = tal(ctx, struct gossmap); + map->fname = NULL; + map->fd = fd; + tal_add_destructor(map, destroy_map); + + if (!load_gossip_store(map, num_channel_updates_rejected)) + return tal_free(map); return map; } @@ -1194,6 +1211,26 @@ u8 *gossmap_chan_get_features(const tal_t *ctx, return ret; } +/* Return the channel_update (or NULL if !gossmap_chan_set) */ +u8 *gossmap_chan_get_update(const tal_t *ctx, + const struct gossmap *map, + const struct gossmap_chan *chan, + int dir) +{ + u16 len; + u8 *msg; + + if (chan->cupdate_off[dir] == 0) + return NULL; + + len = map_be16(map, chan->cupdate_off[dir] - sizeof(struct gossip_hdr) + + offsetof(struct gossip_hdr, len)); + msg = tal_arr(ctx, u8, len); + + map_copy(map, chan->cupdate_off[dir], msg, len); + return msg; +} + /* BOLT #7: * 1. type: 258 (`channel_update`) * 2. data: diff --git a/common/gossmap.h b/common/gossmap.h index a5ae9227ec8e..7185f86e2e89 100644 --- a/common/gossmap.h +++ b/common/gossmap.h @@ -44,6 +44,10 @@ struct gossmap_chan { struct gossmap *gossmap_load(const tal_t *ctx, const char *filename, size_t *num_channel_updates_rejected); +/* Version which uses existing fd */ +struct gossmap *gossmap_load_fd(const tal_t *ctx, int fd, + size_t *num_channel_updates_rejected); + /* Call this before using to ensure it's up-to-date. Returns true if something * was updated. Note: this can scramble node and chan indexes! */ bool gossmap_refresh(struct gossmap *map, size_t *num_channel_updates_rejected); @@ -132,6 +136,12 @@ u8 *gossmap_chan_get_announce(const tal_t *ctx, const struct gossmap *map, const struct gossmap_chan *c); +/* Do we have a node_announcement for this onde ? */ +static inline bool gossmap_node_announced(const struct gossmap_node *node) +{ + return node->nann_off != 0; +} + /* Get the announcement msg (if any) for this node. */ u8 *gossmap_node_get_announce(const tal_t *ctx, const struct gossmap *map, @@ -157,6 +167,12 @@ u8 *gossmap_node_get_features(const tal_t *ctx, const struct gossmap *map, const struct gossmap_node *n); +/* Return the channel_update (or NULL if !gossmap_chan_set) */ +u8 *gossmap_chan_get_update(const tal_t *ctx, + const struct gossmap *map, + const struct gossmap_chan *chan, + int dir); + /* Returns details from channel_update (must be gossmap_chan_set, and * does not work for local_updatechan)! */ void gossmap_chan_get_update_details(const struct gossmap *map, From 441c7cfa39daffbe1727ce7da1aa70da738c66c3 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 31 Jan 2024 14:51:33 +1030 Subject: [PATCH 04/33] common: optional gossmap callbacks for better failure handling. In particular, allow callers to see unknown records we ignore (and let them fail as a result), and get called if we can't pack a channel_update into our internal format. Signed-off-by: Rusty Russell --- common/gossmap.c | 174 +++++++++++++++++++++++++++++++++++------------ common/gossmap.h | 34 ++++++++- 2 files changed, 164 insertions(+), 44 deletions(-) diff --git a/common/gossmap.c b/common/gossmap.c index 5a897fa3837c..d8e7e3a6b631 100644 --- a/common/gossmap.c +++ b/common/gossmap.c @@ -80,6 +80,20 @@ struct gossmap { /* local messages, if any. */ const u8 *local; + + /* Callbacks for different events: return false to fail. */ + void (*cupdate_fail)(struct gossmap *map, + const struct short_channel_id_dir *scidd, + u16 cltv_expiry_delta, + u32 fee_base_msat, + u32 fee_proportional_millionths, + void *cb_arg); + bool (*unknown_record)(struct gossmap *map, + int type, + size_t off, + size_t msglen, + void *cb_arg); + void *cb_arg; }; /* Accessors for the gossmap */ @@ -157,6 +171,18 @@ static int map_feature_test(const struct gossmap *map, return -1; } +/* Helper callback which simply increments counter */ +static void cupdate_fail_inc_ctr(struct gossmap *map, + const struct short_channel_id_dir *scidd, + u16 cltv_expiry_delta, + u32 fee_base_msat, + u32 fee_proportional_millionths, + void *cb_arg) +{ + size_t *num = cb_arg; + (*num)++; +} + /* These values can change across calls to gossmap_check. */ u32 gossmap_max_node_idx(const struct gossmap *map) { @@ -479,7 +505,7 @@ static struct gossmap_chan *add_channel(struct gossmap *map, * * [`u32`:`fee_proportional_millionths`] * * [`u64`:`htlc_maximum_msat`] */ -static bool update_channel(struct gossmap *map, size_t cupdate_off) +static void update_channel(struct gossmap *map, size_t cupdate_off) { /* Note that first two bytes are message type */ const size_t scid_off = cupdate_off + 2 + (64 + 32); @@ -490,44 +516,49 @@ static bool update_channel(struct gossmap *map, size_t cupdate_off) const size_t fee_base_off = htlc_minimum_off + 8; const size_t fee_prop_off = fee_base_off + 4; const size_t htlc_maximum_off = fee_prop_off + 4; - struct short_channel_id scid; + struct short_channel_id_dir scidd; struct gossmap_chan *chan; struct half_chan hc; u8 chanflags; - bool dumb_values; + u32 base_fee, proportional_fee; + u16 delay; - scid.u64 = map_be64(map, scid_off); - chan = gossmap_find_chan(map, &scid); + scidd.scid.u64 = map_be64(map, scid_off); + chan = gossmap_find_chan(map, &scidd.scid); /* This can happen if channel gets deleted! */ if (!chan) - return false; + return; /* We round this *down*, since too-low min is more conservative */ hc.htlc_min = u64_to_fp16(map_be64(map, htlc_minimum_off), false); hc.htlc_max = u64_to_fp16(map_be64(map, htlc_maximum_off), true); chanflags = map_u8(map, channel_flags_off); - hc.enabled = !(chanflags & 2); - hc.base_fee = map_be32(map, fee_base_off); - hc.proportional_fee = map_be32(map, fee_prop_off); - hc.delay = map_be16(map, cltv_expiry_delta_off); - + hc.enabled = !(chanflags & ROUTING_FLAGS_DISABLED); + scidd.dir = (chanflags & ROUTING_FLAGS_DIRECTION); + base_fee = map_be32(map, fee_base_off); + proportional_fee = map_be32(map, fee_prop_off); + delay = map_be16(map, cltv_expiry_delta_off); + + hc.base_fee = base_fee; + hc.proportional_fee = proportional_fee; + hc.delay = delay; /* Check they fit: we turn off if not. */ - if (hc.base_fee != map_be32(map, fee_base_off) - || hc.proportional_fee != map_be32(map, fee_prop_off) - || hc.delay != map_be16(map, cltv_expiry_delta_off)) { - dumb_values = true; + if (hc.base_fee != base_fee + || hc.proportional_fee != proportional_fee + || hc.delay != delay) { + if (map->cupdate_fail) + map->cupdate_fail(map, &scidd, + delay, base_fee, proportional_fee, + map->cb_arg); hc.htlc_max = 0; hc.enabled = false; - } else - dumb_values = false; + } /* Preserve this */ hc.nodeidx = chan->half[chanflags & 1].nodeidx; chan->half[chanflags & 1] = hc; chan->cupdate_off[chanflags & 1] = cupdate_off; - - return !dumb_values; } static void remove_channel_by_deletemsg(struct gossmap *map, size_t del_off) @@ -581,7 +612,7 @@ static void node_announcement(struct gossmap *map, size_t nann_off) n->nann_off = nann_off; } -static void reopen_store(struct gossmap *map, size_t ended_off) +static bool reopen_store(struct gossmap *map, size_t ended_off) { int fd; @@ -597,15 +628,16 @@ static void reopen_store(struct gossmap *map, size_t ended_off) close(map->fd); map->fd = fd; - gossmap_refresh(map, NULL); + return gossmap_refresh_mayfail(map, NULL); } -static bool map_catchup(struct gossmap *map, size_t *num_rejected) +/* Returns false only if unknown_cb returns false */ +static bool map_catchup(struct gossmap *map, bool *changed) { size_t reclen; - bool changed = false; - size_t num_bad = 0; + if (changed) + *changed = false; for (; map->map_end + sizeof(struct gossip_hdr) < map->map_size; map->map_end += reclen) { struct gossip_hdr ghdr; @@ -631,25 +663,33 @@ static bool map_catchup(struct gossmap *map, size_t *num_rejected) if (type == WIRE_CHANNEL_ANNOUNCEMENT) add_channel(map, off); else if (type == WIRE_CHANNEL_UPDATE) - num_bad += !update_channel(map, off); + update_channel(map, off); else if (type == WIRE_GOSSIP_STORE_DELETE_CHAN) remove_channel_by_deletemsg(map, off); else if (type == WIRE_NODE_ANNOUNCEMENT) node_announcement(map, off); - else if (type == WIRE_GOSSIP_STORE_ENDED) - reopen_store(map, off); - else + else if (type == WIRE_GOSSIP_STORE_ENDED && map->fname) { + /* This can recurse! */ + if (!reopen_store(map, off)) + return false; + } else { + if (map->unknown_record + && !map->unknown_record(map, type, off, + reclen - sizeof(ghdr), + map->cb_arg)) { + return false; + } continue; + } - changed = true; + if (changed) + *changed = true; } - if (num_rejected) - *num_rejected = num_bad; - return changed; + return true; } -static bool load_gossip_store(struct gossmap *map, size_t *num_rejected) +static bool load_gossip_store(struct gossmap *map) { map->map_size = lseek(map->fd, 0, SEEK_END); map->local = NULL; @@ -682,7 +722,7 @@ static bool load_gossip_store(struct gossmap *map, size_t *num_rejected) map->freed_nodes = init_node_arr(map->node_arr, 0); map->map_end = 1; - map_catchup(map, num_rejected); + map_catchup(map, NULL); return true; } @@ -939,7 +979,7 @@ void gossmap_remove_localmods(struct gossmap *map, map->local = NULL; } -bool gossmap_refresh(struct gossmap *map, size_t *num_rejected) +bool gossmap_refresh_mayfail(struct gossmap *map, bool *updated) { off_t len; @@ -948,8 +988,11 @@ bool gossmap_refresh(struct gossmap *map, size_t *num_rejected) /* If file has gotten larger, try rereading */ len = lseek(map->fd, 0, SEEK_END); - if (len == map->map_size) - return false; + if (len == map->map_size) { + if (updated) + *updated = false; + return true; + } if (map->mmap) munmap(map->mmap, map->map_size); @@ -957,7 +1000,33 @@ bool gossmap_refresh(struct gossmap *map, size_t *num_rejected) map->mmap = mmap(NULL, map->map_size, PROT_READ, MAP_SHARED, map->fd, 0); if (map->mmap == MAP_FAILED) map->mmap = NULL; - return map_catchup(map, num_rejected); + + return map_catchup(map, updated); +} + +bool gossmap_refresh(struct gossmap *map, size_t *num_rejected) +{ + bool updated; + void (*old_cupdate_fail)(struct gossmap *map, + const struct short_channel_id_dir *scidd, + u16 cltv_expiry_delta, + u32 fee_base_msat, + u32 fee_proportional_millionths, + void *cb_arg); + + /* If they asked for counter, temporarily override cb */ + old_cupdate_fail = map->cupdate_fail; + if (num_rejected) { + map->cupdate_fail = cupdate_fail_inc_ctr; + map->cb_arg = num_rejected; + } + + /* This can only fail if you set unknown_cb, and it failed. So wrong API! */ + if (!gossmap_refresh_mayfail(map, &updated)) + abort(); + + map->cupdate_fail = old_cupdate_fail; + return updated; } struct gossmap *gossmap_load(const tal_t *ctx, const char *filename, @@ -969,21 +1038,42 @@ struct gossmap *gossmap_load(const tal_t *ctx, const char *filename, if (map->fd < 0) return tal_free(map); tal_add_destructor(map, destroy_map); + if (num_channel_updates_rejected) { + *num_channel_updates_rejected = 0; + map->cupdate_fail = cupdate_fail_inc_ctr; + map->cb_arg = num_channel_updates_rejected; + } + map->unknown_record = NULL; - if (!load_gossip_store(map, num_channel_updates_rejected)) + if (!load_gossip_store(map)) return tal_free(map); + map->cupdate_fail = NULL; return map; } -struct gossmap *gossmap_load_fd(const tal_t *ctx, int fd, - size_t *num_channel_updates_rejected) +struct gossmap *gossmap_load_fd_(const tal_t *ctx, int fd, + void (*cupdate_fail)(struct gossmap *map, + const struct short_channel_id_dir *scidd, + u16 cltv_expiry_delta, + u32 fee_base_msat, + u32 fee_proportional_millionths, + void *cb_arg), + bool (*unknown_record)(struct gossmap *map, + int type, + size_t off, + size_t msglen, + void *cb_arg), + void *cb_arg) { map = tal(ctx, struct gossmap); map->fname = NULL; map->fd = fd; + map->cupdate_fail = cupdate_fail; + map->unknown_record = unknown_record; + map->cb_arg = cb_arg; tal_add_destructor(map, destroy_map); - if (!load_gossip_store(map, num_channel_updates_rejected)) + if (!load_gossip_store(map)) return tal_free(map); return map; } diff --git a/common/gossmap.h b/common/gossmap.h index 7185f86e2e89..d9d2ad3994a7 100644 --- a/common/gossmap.h +++ b/common/gossmap.h @@ -45,13 +45,43 @@ struct gossmap *gossmap_load(const tal_t *ctx, const char *filename, size_t *num_channel_updates_rejected); /* Version which uses existing fd */ -struct gossmap *gossmap_load_fd(const tal_t *ctx, int fd, - size_t *num_channel_updates_rejected); +#define gossmap_load_fd(ctx, fd, cupdate_fail, unknown_record, cbarg) \ + gossmap_load_fd_((ctx), (fd), \ + typesafe_cb_preargs(void, void *, (cupdate_fail), (cbarg), \ + struct gossmap *, \ + const struct short_channel_id_dir *, \ + u16 cltv_expiry_delta, \ + u32 fee_base_msat, \ + u32 fee_proportional_millionths), \ + typesafe_cb_preargs(bool, void *, (unknown_record), (cbarg), \ + struct gossmap *, \ + int type, \ + size_t off, \ + size_t msglen), \ + (cbarg)) + +struct gossmap *gossmap_load_fd_(const tal_t *ctx, int fd, + void (*cupdate_fail)(struct gossmap *map, + const struct short_channel_id_dir *scidd, + u16 cltv_expiry_delta, + u32 fee_base_msat, + u32 fee_proportional_millionths, + void *cb_arg), + bool (*unknown_record)(struct gossmap *map, + int type, + size_t off, + size_t msglen, + void *cb_arg), + void *cb_arg); + /* Call this before using to ensure it's up-to-date. Returns true if something * was updated. Note: this can scramble node and chan indexes! */ bool gossmap_refresh(struct gossmap *map, size_t *num_channel_updates_rejected); +/* Call this if you have set unknown_cb, and thus this can fail! */ +bool gossmap_refresh_mayfail(struct gossmap *map, bool *updated); + /* Local modifications. */ struct gossmap_localmods *gossmap_localmods_new(const tal_t *ctx); From 1f10dbfa64f6df91f04cce067d0e0ceeb7a22096 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 31 Jan 2024 14:52:33 +1030 Subject: [PATCH 05/33] gossipd: take txout failure cache out of routing.c Signed-off-by: Rusty Russell --- gossipd/Makefile | 1 + gossipd/routing.c | 51 +------- gossipd/routing.h | 4 +- gossipd/test/run-check_channel_announcement.c | 17 ++- gossipd/test/run-txout_failure.c | 112 ++++-------------- gossipd/txout_failures.c | 59 +++++++++ gossipd/txout_failures.h | 42 +++++++ 7 files changed, 144 insertions(+), 142 deletions(-) create mode 100644 gossipd/txout_failures.c create mode 100644 gossipd/txout_failures.h diff --git a/gossipd/Makefile b/gossipd/Makefile index 42e79f256963..061d08167d18 100644 --- a/gossipd/Makefile +++ b/gossipd/Makefile @@ -7,6 +7,7 @@ GOSSIPD_HEADERS_WSRC := gossipd/gossipd_wiregen.h \ gossipd/gossip_store.h \ gossipd/queries.h \ gossipd/routing.h \ + gossipd/txout_failures.h \ gossipd/seeker.h GOSSIPD_HEADERS := $(GOSSIPD_HEADERS_WSRC) gossipd/broadcast.h diff --git a/gossipd/routing.c b/gossipd/routing.c index 65f620b0fd59..23c093d6d3f6 100644 --- a/gossipd/routing.c +++ b/gossipd/routing.c @@ -16,6 +16,7 @@ #include #include #include +#include #ifndef SUPERVERBOSE #define SUPERVERBOSE(...) @@ -248,43 +249,6 @@ static void memleak_help_routing_tables(struct htable *memtable, } } -/* Once an hour, or at 10000 entries, we expire old ones */ -static void txout_failure_age(struct routing_state *rstate) -{ - uintmap_clear(&rstate->txout_failures_old); - rstate->txout_failures_old = rstate->txout_failures; - uintmap_init(&rstate->txout_failures); - rstate->num_txout_failures = 0; - - rstate->txout_failure_timer = new_reltimer(&rstate->daemon->timers, - rstate, time_from_sec(3600), - txout_failure_age, rstate); -} - -static void add_to_txout_failures(struct routing_state *rstate, - const struct short_channel_id *scid) -{ - if (uintmap_add(&rstate->txout_failures, scid->u64, true) - && ++rstate->num_txout_failures == 10000) { - tal_free(rstate->txout_failure_timer); - txout_failure_age(rstate); - } -} - -static bool in_txout_failures(struct routing_state *rstate, - const struct short_channel_id *scid) -{ - if (uintmap_get(&rstate->txout_failures, scid->u64)) - return true; - - /* If we were going to expire it, we no longer are. */ - if (uintmap_get(&rstate->txout_failures_old, scid->u64)) { - add_to_txout_failures(rstate, scid); - return true; - } - return false; -} - struct routing_state *new_routing_state(const tal_t *ctx, struct daemon *daemon, const u32 *dev_gossip_time TAKES, @@ -303,10 +267,7 @@ struct routing_state *new_routing_state(const tal_t *ctx, uintmap_init(&rstate->chanmap); uintmap_init(&rstate->unupdated_chanmap); - rstate->num_txout_failures = 0; - uintmap_init(&rstate->txout_failures); - uintmap_init(&rstate->txout_failures_old); - txout_failure_age(rstate); + rstate->txf = txout_failures_new(rstate, rstate->daemon); rstate->pending_node_map = tal(ctx, struct pending_node_map); pending_node_map_init(rstate->pending_node_map); @@ -974,7 +935,7 @@ u8 *handle_channel_announcement(struct routing_state *rstate, /* If a prior txout lookup failed there is little point it trying * again. Just drop the announcement and walk away whistling. */ - if (in_txout_failures(rstate, &pending->short_channel_id)) { + if (in_txout_failures(rstate->txf, pending->short_channel_id)) { SUPERVERBOSE( "Ignoring channel_announcement of %s due to a prior txout " "query failure. The channel was likely closed on-chain.", @@ -1147,7 +1108,7 @@ bool handle_pending_cannouncement(struct daemon *daemon, struct short_channel_id, scid)); tal_free(pending); - add_to_txout_failures(rstate, scid); + txout_failures_add(rstate->txf, *scid); return false; } @@ -1665,7 +1626,7 @@ u8 *handle_channel_update(struct routing_state *rstate, const u8 *update TAKES, /* If we dropped the matching announcement for this channel due to the * txout query failing, don't report failure, it's just too noisy on * mainnet */ - if (in_txout_failures(rstate, &short_channel_id)) + if (in_txout_failures(rstate->txf, short_channel_id)) return NULL; /* If we have an unvalidated channel, just queue on that */ @@ -2213,7 +2174,7 @@ static void channel_spent(struct routing_state *rstate, type_to_string(tmpctx, struct short_channel_id, &chan->scid)); /* Suppress any now-obsolete updates/announcements */ - add_to_txout_failures(rstate, &chan->scid); + txout_failures_add(rstate->txf, chan->scid); remove_channel_from_store(rstate, chan); /* Freeing is sufficient since everything else is allocated off * of the channel and this takes care of unregistering diff --git a/gossipd/routing.h b/gossipd/routing.h index 71062b65837f..3f8946e59511 100644 --- a/gossipd/routing.h +++ b/gossipd/routing.h @@ -198,9 +198,7 @@ struct routing_state { /* Cache for txout queries that failed. Allows us to skip failed * checks if we get another announcement for the same scid. */ - size_t num_txout_failures; - UINTMAP(bool) txout_failures, txout_failures_old; - struct oneshot *txout_failure_timer; + struct txout_failures *txf; /* Highest timestamp of gossip we accepted (before now) */ u32 last_timestamp; diff --git a/gossipd/test/run-check_channel_announcement.c b/gossipd/test/run-check_channel_announcement.c index 95eb5a2a0ede..77184b16b1cf 100644 --- a/gossipd/test/run-check_channel_announcement.c +++ b/gossipd/test/run-check_channel_announcement.c @@ -89,6 +89,10 @@ void gossip_store_mark_dying(struct gossip_store *gs UNNEEDED, /* Generated stub for gossip_store_new */ struct gossip_store *gossip_store_new(struct routing_state *rstate UNNEEDED) { fprintf(stderr, "gossip_store_new called!\n"); abort(); } +/* Generated stub for in_txout_failures */ +bool in_txout_failures(struct txout_failures *txf UNNEEDED, + const struct short_channel_id scid UNNEEDED) +{ fprintf(stderr, "in_txout_failures called!\n"); abort(); } /* Generated stub for memleak_add_helper_ */ void memleak_add_helper_(const tal_t *p UNNEEDED, void (*cb)(struct htable *memtable UNNEEDED, const tal_t *)){ } @@ -98,12 +102,6 @@ void memleak_scan_htable(struct htable *memtable UNNEEDED, const struct htable * /* Generated stub for memleak_scan_intmap_ */ void memleak_scan_intmap_(struct htable *memtable UNNEEDED, const struct intmap *m UNNEEDED) { fprintf(stderr, "memleak_scan_intmap_ called!\n"); abort(); } -/* Generated stub for new_reltimer_ */ -struct oneshot *new_reltimer_(struct timers *timers UNNEEDED, - const tal_t *ctx UNNEEDED, - struct timerel expire UNNEEDED, - void (*cb)(void *) UNNEEDED, void *arg UNNEEDED) -{ fprintf(stderr, "new_reltimer_ called!\n"); abort(); } /* Generated stub for notleak_ */ void *notleak_(void *ptr UNNEEDED, bool plus_children UNNEEDED) { fprintf(stderr, "notleak_ called!\n"); abort(); } @@ -121,6 +119,13 @@ void status_fmt(enum log_level level UNNEEDED, /* Generated stub for towire_gossipd_remote_channel_update */ u8 *towire_gossipd_remote_channel_update(const tal_t *ctx UNNEEDED, const struct node_id *source_node UNNEEDED, const struct peer_update *peer_update UNNEEDED) { fprintf(stderr, "towire_gossipd_remote_channel_update called!\n"); abort(); } +/* Generated stub for txout_failures_add */ +void txout_failures_add(struct txout_failures *txf UNNEEDED, + const struct short_channel_id scid UNNEEDED) +{ fprintf(stderr, "txout_failures_add called!\n"); abort(); } +/* Generated stub for txout_failures_new */ +struct txout_failures *txout_failures_new(const tal_t *ctx UNNEEDED, struct daemon *daemon UNNEEDED) +{ fprintf(stderr, "txout_failures_new called!\n"); abort(); } /* AUTOGENERATED MOCKS END */ int main(int argc, char *argv[]) diff --git a/gossipd/test/run-txout_failure.c b/gossipd/test/run-txout_failure.c index d9047c994fda..70b079ff9054 100644 --- a/gossipd/test/run-txout_failure.c +++ b/gossipd/test/run-txout_failure.c @@ -1,12 +1,15 @@ #include "config.h" -#include "../routing.c" +#include "../txout_failures.c" #include "../common/timeout.c" #include #include +#include #include #include #include #include +#include +#include #include /* AUTOGENERATED MOCKS START */ @@ -25,73 +28,6 @@ bool blinding_next_pubkey(const struct pubkey *pk UNNEEDED, const struct sha256 *h UNNEEDED, struct pubkey *next UNNEEDED) { fprintf(stderr, "blinding_next_pubkey called!\n"); abort(); } -/* Generated stub for daemon_conn_send */ -void daemon_conn_send(struct daemon_conn *dc UNNEEDED, const u8 *msg UNNEEDED) -{ fprintf(stderr, "daemon_conn_send called!\n"); abort(); } -/* Generated stub for get_cupdate_parts */ -void get_cupdate_parts(const u8 *channel_update UNNEEDED, - const u8 *parts[2] UNNEEDED, - size_t sizes[2]) -{ fprintf(stderr, "get_cupdate_parts called!\n"); abort(); } -/* Generated stub for gossip_store_add */ -u64 gossip_store_add(struct gossip_store *gs UNNEEDED, const u8 *gossip_msg UNNEEDED, - u32 timestamp UNNEEDED, bool zombie UNNEEDED, bool spam UNNEEDED, bool dying UNNEEDED, - const u8 *addendum UNNEEDED) -{ fprintf(stderr, "gossip_store_add called!\n"); abort(); } -/* Generated stub for gossip_store_delete */ -void gossip_store_delete(struct gossip_store *gs UNNEEDED, - struct broadcastable *bcast UNNEEDED, - int type UNNEEDED) -{ fprintf(stderr, "gossip_store_delete called!\n"); abort(); } -/* Generated stub for gossip_store_get */ -const u8 *gossip_store_get(const tal_t *ctx UNNEEDED, - struct gossip_store *gs UNNEEDED, - u64 offset UNNEEDED) -{ fprintf(stderr, "gossip_store_get called!\n"); abort(); } -/* Generated stub for gossip_store_mark_channel_deleted */ -void gossip_store_mark_channel_deleted(struct gossip_store *gs UNNEEDED, - const struct short_channel_id *scid UNNEEDED) -{ fprintf(stderr, "gossip_store_mark_channel_deleted called!\n"); abort(); } -/* Generated stub for gossip_store_mark_dying */ -void gossip_store_mark_dying(struct gossip_store *gs UNNEEDED, - const struct broadcastable *bcast UNNEEDED, - int type UNNEEDED) -{ fprintf(stderr, "gossip_store_mark_dying called!\n"); abort(); } -/* Generated stub for memleak_add_helper_ */ -void memleak_add_helper_(const tal_t *p UNNEEDED, void (*cb)(struct htable *memtable UNNEEDED, - const tal_t *)){ } -/* Generated stub for memleak_scan_htable */ -void memleak_scan_htable(struct htable *memtable UNNEEDED, const struct htable *ht UNNEEDED) -{ fprintf(stderr, "memleak_scan_htable called!\n"); abort(); } -/* Generated stub for memleak_scan_intmap_ */ -void memleak_scan_intmap_(struct htable *memtable UNNEEDED, const struct intmap *m UNNEEDED) -{ fprintf(stderr, "memleak_scan_intmap_ called!\n"); abort(); } -/* Generated stub for notleak_ */ -void *notleak_(void *ptr UNNEEDED, bool plus_children UNNEEDED) -{ fprintf(stderr, "notleak_ called!\n"); abort(); } -/* Generated stub for peer_supplied_good_gossip */ -void peer_supplied_good_gossip(struct daemon *daemon UNNEEDED, - const struct node_id *source_peer UNNEEDED, - size_t amount UNNEEDED) -{ fprintf(stderr, "peer_supplied_good_gossip called!\n"); abort(); } -/* Generated stub for sanitize_error */ -char *sanitize_error(const tal_t *ctx UNNEEDED, const u8 *errmsg UNNEEDED, - struct channel_id *channel_id UNNEEDED) -{ fprintf(stderr, "sanitize_error called!\n"); abort(); } -/* Generated stub for status_fmt */ -void status_fmt(enum log_level level UNNEEDED, - const struct node_id *peer UNNEEDED, - const char *fmt UNNEEDED, ...) - -{ fprintf(stderr, "status_fmt called!\n"); abort(); } -/* Generated stub for towire_gossipd_remote_channel_update */ -u8 *towire_gossipd_remote_channel_update(const tal_t *ctx UNNEEDED, const struct node_id *source_node UNNEEDED, const struct peer_update *peer_update UNNEEDED) -{ fprintf(stderr, "towire_gossipd_remote_channel_update called!\n"); abort(); } -/* Generated stub for towire_warningfmt */ -u8 *towire_warningfmt(const tal_t *ctx UNNEEDED, - const struct channel_id *channel UNNEEDED, - const char *fmt UNNEEDED, ...) -{ fprintf(stderr, "towire_warningfmt called!\n"); abort(); } /* AUTOGENERATED MOCKS END */ /* NOOP stub for gossip_store_new */ @@ -102,7 +38,7 @@ struct gossip_store *gossip_store_new(struct routing_state *rstate UNNEEDED) int main(int argc, char *argv[]) { - struct routing_state *rstate; + struct txout_failures *txf; struct timer *t; struct short_channel_id scid1, scid2; struct daemon *daemon; @@ -111,22 +47,22 @@ int main(int argc, char *argv[]) daemon = tal(tmpctx, struct daemon); timers_init(&daemon->timers, time_mono()); - rstate = new_routing_state(tmpctx, daemon, NULL, false, false); + txf = txout_failures_new(tmpctx, daemon); scid1.u64 = 100; scid2.u64 = 200; - assert(!in_txout_failures(rstate, &scid1)); - assert(!in_txout_failures(rstate, &scid2)); + assert(!in_txout_failures(txf, scid1)); + assert(!in_txout_failures(txf, scid2)); - add_to_txout_failures(rstate, &scid1); - assert(in_txout_failures(rstate, &scid1)); - assert(!in_txout_failures(rstate, &scid2)); - assert(rstate->num_txout_failures == 1); + txout_failures_add(txf, scid1); + assert(in_txout_failures(txf, scid1)); + assert(!in_txout_failures(txf, scid2)); + assert(txf->num == 1); - add_to_txout_failures(rstate, &scid2); - assert(in_txout_failures(rstate, &scid1)); - assert(in_txout_failures(rstate, &scid2)); - assert(rstate->num_txout_failures == 2); + txout_failures_add(txf, scid2); + assert(in_txout_failures(txf, scid1)); + assert(in_txout_failures(txf, scid2)); + assert(txf->num == 2); /* Move time forward 1 hour. */ t = timers_expire(&daemon->timers, @@ -136,9 +72,9 @@ int main(int argc, char *argv[]) timer_expired(t); /* Still there, just old. Refresh scid1 */ - assert(rstate->num_txout_failures == 0); - assert(in_txout_failures(rstate, &scid1)); - assert(rstate->num_txout_failures == 1); + assert(txf->num == 0); + assert(in_txout_failures(txf, scid1)); + assert(txf->num == 1); t = timers_expire(&daemon->timers, timemono_add(time_mono(), @@ -146,12 +82,12 @@ int main(int argc, char *argv[]) assert(t); timer_expired(t); - assert(rstate->num_txout_failures == 0); - assert(in_txout_failures(rstate, &scid1)); - assert(rstate->num_txout_failures == 1); - assert(!in_txout_failures(rstate, &scid2)); + assert(txf->num == 0); + assert(in_txout_failures(txf, scid1)); + assert(txf->num == 1); + assert(!in_txout_failures(txf, scid2)); - tal_free(rstate); + tal_free(txf); timers_cleanup(&daemon->timers); common_shutdown(); diff --git a/gossipd/txout_failures.c b/gossipd/txout_failures.c new file mode 100644 index 000000000000..823f150e11a4 --- /dev/null +++ b/gossipd/txout_failures.c @@ -0,0 +1,59 @@ +#include "config.h" +#include +#include +#include + +/* Convenience to tell the two failure maps apart */ +#define RECENT 0 +#define AGED 1 + +#define MAX_ENTRIES 10000 + +/* Once an hour, or at 10000 entries, we expire old ones */ +static void txout_failures_age(struct txout_failures *txf) +{ + uintmap_clear(&txf->failures[AGED]); + txf->failures[AGED] = txf->failures[RECENT]; + uintmap_init(&txf->failures[RECENT]); + txf->num = 0; + + txf->age_timer = new_reltimer(&txf->daemon->timers, txf, + time_from_sec(3600), + txout_failures_age, txf); +} + +struct txout_failures *txout_failures_new(const tal_t *ctx, struct daemon *daemon) +{ + struct txout_failures *txf = tal(ctx, struct txout_failures); + + txf->daemon = daemon; + uintmap_init(&txf->failures[RECENT]); + uintmap_init(&txf->failures[AGED]); + + txout_failures_age(txf); + return txf; +} + +void txout_failures_add(struct txout_failures *txf, + const struct short_channel_id scid) +{ + if (uintmap_add(&txf->failures[RECENT], scid.u64, true) + && ++txf->num == MAX_ENTRIES) { + tal_free(txf->age_timer); + txout_failures_age(txf); + } +} + +bool in_txout_failures(struct txout_failures *txf, + const struct short_channel_id scid) +{ + if (uintmap_get(&txf->failures[RECENT], scid.u64)) + return true; + + /* If we were going to expire it, we no longer are. */ + if (uintmap_get(&txf->failures[AGED], scid.u64)) { + txout_failures_add(txf, scid); + return true; + } + return false; +} diff --git a/gossipd/txout_failures.h b/gossipd/txout_failures.h new file mode 100644 index 000000000000..6afcbc3bd571 --- /dev/null +++ b/gossipd/txout_failures.h @@ -0,0 +1,42 @@ +#ifndef LIGHTNING_GOSSIPD_TXOUT_FAILURES_H +#define LIGHTNING_GOSSIPD_TXOUT_FAILURES_H +#include "config.h" +#include + +/* Cache for txout queries that failed. Allows us to skip failed + * checks if we get another announcement for the same scid. */ +struct txout_failures { + /* Cache for txout queries that failed. Allows us to skip failed + * checks if we get another announcement for the same scid. */ + size_t num; + UINTMAP(bool) failures[2]; + struct oneshot *age_timer; + + /* For access to timers */ + struct daemon *daemon; +}; + +/** + * txout_failures_new: allocate a new failure set. + * @ctx: tal context + * @daemon: global daemon struct + */ +struct txout_failures *txout_failures_new(const tal_t *ctx, struct daemon *daemon); + +/** + * txout_failures_add: add a failed scid to the set. + * @txf: the struct txout_failures. + * @scid: the short_channel_id to add. + */ +void txout_failures_add(struct txout_failures *txf, + const struct short_channel_id scid); + +/** + * in_txout_failures: is this scid in the set? + * @txf: the struct txout_failures. + * @scid: the short_channel_id to test. + */ +bool in_txout_failures(struct txout_failures *txf, + const struct short_channel_id scid); + +#endif /* LIGHTNING_GOSSIPD_TXOUT_FAILURES_H */ From a75537e88507de1c204a9a63ff5238997a4078bb Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 31 Jan 2024 14:53:33 +1030 Subject: [PATCH 06/33] gossipd: take signature checks out of routing.c Signed-off-by: Rusty Russell --- gossipd/Makefile | 1 + gossipd/routing.c | 165 +++------------- gossipd/sigcheck.c | 184 ++++++++++++++++++ gossipd/sigcheck.h | 29 +++ gossipd/test/run-check_channel_announcement.c | 112 ++--------- 5 files changed, 262 insertions(+), 229 deletions(-) create mode 100644 gossipd/sigcheck.c create mode 100644 gossipd/sigcheck.h diff --git a/gossipd/Makefile b/gossipd/Makefile index 061d08167d18..5a12f93c1a57 100644 --- a/gossipd/Makefile +++ b/gossipd/Makefile @@ -8,6 +8,7 @@ GOSSIPD_HEADERS_WSRC := gossipd/gossipd_wiregen.h \ gossipd/queries.h \ gossipd/routing.h \ gossipd/txout_failures.h \ + gossipd/sigcheck.h \ gossipd/seeker.h GOSSIPD_HEADERS := $(GOSSIPD_HEADERS_WSRC) gossipd/broadcast.h diff --git a/gossipd/routing.c b/gossipd/routing.c index 23c093d6d3f6..45721133a3aa 100644 --- a/gossipd/routing.c +++ b/gossipd/routing.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #ifndef SUPERVERBOSE @@ -579,96 +580,6 @@ struct chan *new_chan(struct routing_state *rstate, return chan; } -/* Verify the signature of a channel_update message */ -static u8 *check_channel_update(const tal_t *ctx, - const struct node_id *node_id, - const secp256k1_ecdsa_signature *node_sig, - const u8 *update) -{ - /* 2 byte msg type + 64 byte signatures */ - int offset = 66; - struct sha256_double hash; - sha256_double(&hash, update + offset, tal_count(update) - offset); - - if (!check_signed_hash_nodeid(&hash, node_sig, node_id)) - return towire_warningfmt(ctx, NULL, - "Bad signature for %s hash %s" - " on channel_update %s", - type_to_string(tmpctx, - secp256k1_ecdsa_signature, - node_sig), - type_to_string(tmpctx, - struct sha256_double, - &hash), - tal_hex(tmpctx, update)); - return NULL; -} - -static u8 *check_channel_announcement(const tal_t *ctx, - const struct node_id *node1_id, const struct node_id *node2_id, - const struct pubkey *bitcoin1_key, const struct pubkey *bitcoin2_key, - const secp256k1_ecdsa_signature *node1_sig, - const secp256k1_ecdsa_signature *node2_sig, - const secp256k1_ecdsa_signature *bitcoin1_sig, - const secp256k1_ecdsa_signature *bitcoin2_sig, const u8 *announcement) -{ - /* 2 byte msg type + 256 byte signatures */ - int offset = 258; - struct sha256_double hash; - sha256_double(&hash, announcement + offset, - tal_count(announcement) - offset); - - if (!check_signed_hash_nodeid(&hash, node1_sig, node1_id)) { - return towire_warningfmt(ctx, NULL, - "Bad node_signature_1 %s hash %s" - " on channel_announcement %s", - type_to_string(tmpctx, - secp256k1_ecdsa_signature, - node1_sig), - type_to_string(tmpctx, - struct sha256_double, - &hash), - tal_hex(tmpctx, announcement)); - } - if (!check_signed_hash_nodeid(&hash, node2_sig, node2_id)) { - return towire_warningfmt(ctx, NULL, - "Bad node_signature_2 %s hash %s" - " on channel_announcement %s", - type_to_string(tmpctx, - secp256k1_ecdsa_signature, - node2_sig), - type_to_string(tmpctx, - struct sha256_double, - &hash), - tal_hex(tmpctx, announcement)); - } - if (!check_signed_hash(&hash, bitcoin1_sig, bitcoin1_key)) { - return towire_warningfmt(ctx, NULL, - "Bad bitcoin_signature_1 %s hash %s" - " on channel_announcement %s", - type_to_string(tmpctx, - secp256k1_ecdsa_signature, - bitcoin1_sig), - type_to_string(tmpctx, - struct sha256_double, - &hash), - tal_hex(tmpctx, announcement)); - } - if (!check_signed_hash(&hash, bitcoin2_sig, bitcoin2_key)) { - return towire_warningfmt(ctx, NULL, - "Bad bitcoin_signature_2 %s hash %s" - " on channel_announcement %s", - type_to_string(tmpctx, - secp256k1_ecdsa_signature, - bitcoin2_sig), - type_to_string(tmpctx, - struct sha256_double, - &hash), - tal_hex(tmpctx, announcement)); - } - return NULL; -} - /* We allow node announcements for this node if it doesn't otherwise exist, so * we can process them once it does exist (a channel_announce is being * validated right now). @@ -889,6 +800,7 @@ u8 *handle_channel_announcement(struct routing_state *rstate, secp256k1_ecdsa_signature node_signature_1, node_signature_2; secp256k1_ecdsa_signature bitcoin_signature_1, bitcoin_signature_2; struct chan *chan; + const char *err; pending = tal(rstate, struct pending_cannouncement); pending->source_peer = tal_dup_or_null(pending, struct node_id, source_peer); @@ -990,17 +902,17 @@ u8 *handle_channel_announcement(struct routing_state *rstate, } /* Note that if node_id_1 or node_id_2 are malformed, it's caught here */ - warn = check_channel_announcement(rstate, - &pending->node_id_1, - &pending->node_id_2, - &pending->bitcoin_key_1, - &pending->bitcoin_key_2, - &node_signature_1, - &node_signature_2, - &bitcoin_signature_1, - &bitcoin_signature_2, - pending->announce); - if (warn) { + err = sigcheck_channel_announcement(tmpctx, + &pending->node_id_1, + &pending->node_id_2, + &pending->bitcoin_key_1, + &pending->bitcoin_key_2, + &node_signature_1, + &node_signature_2, + &bitcoin_signature_1, + &bitcoin_signature_2, + pending->announce); + if (err) { /* BOLT #7: * * - if `bitcoin_signature_1`, `bitcoin_signature_2`, @@ -1010,6 +922,7 @@ u8 *handle_channel_announcement(struct routing_state *rstate, * - MAY close the connection. * - MUST ignore the message. */ + warn = towire_warningfmt(rstate, NULL, "%s", err); goto malformed; } @@ -1585,7 +1498,7 @@ u8 *handle_channel_update(struct routing_state *rstate, const u8 *update TAKES, struct bitcoin_blkid chain_hash; u8 direction; struct pending_cannouncement *pending; - u8 *warn; + const char *err; serialized = tal_dup_talarr(tmpctx, u8, update); if (!fromwire_channel_update(serialized, &signature, @@ -1647,7 +1560,7 @@ u8 *handle_channel_update(struct routing_state *rstate, const u8 *update TAKES, /* This may be a local channel we don't know about. If it's from a peer, * check signature assuming it's from that peer, and if it's valid, hand to ld */ if (source_peer - && check_channel_update(tmpctx, source_peer, &signature, serialized) == NULL) { + && sigcheck_channel_update(tmpctx, source_peer, &signature, serialized) == NULL) { tell_lightningd_peer_update(rstate, source_peer, short_channel_id, fee_base_msat, fee_proportional_millionths, @@ -1668,8 +1581,8 @@ u8 *handle_channel_update(struct routing_state *rstate, const u8 *update TAKES, return NULL; } - warn = check_channel_update(rstate, owner, &signature, serialized); - if (warn) { + err = sigcheck_channel_update(tmpctx, owner, &signature, serialized); + if (err) { /* BOLT #7: * * - if `signature` is not a valid signature, using `node_id` @@ -1679,7 +1592,7 @@ u8 *handle_channel_update(struct routing_state *rstate, const u8 *update TAKES, * - SHOULD send a `warning` and close the connection. * - MUST NOT process the message further. */ - return warn; + return towire_warningfmt(rstate, NULL, "%s", err); } routing_add_channel_update(rstate, take(serialized), 0, source_peer, force, @@ -1956,7 +1869,6 @@ u8 *handle_node_announcement(struct routing_state *rstate, const u8 *node_ann, bool *was_unknown) { u8 *serialized; - struct sha256_double hash; secp256k1_ecdsa_signature signature; u32 timestamp; struct node_id node_id; @@ -1966,6 +1878,7 @@ u8 *handle_node_announcement(struct routing_state *rstate, const u8 *node_ann, struct wireaddr *wireaddrs; size_t len = tal_count(node_ann); struct tlv_node_ann_tlvs *na_tlv; + const char *err; if (was_unknown) *was_unknown = false; @@ -1991,31 +1904,10 @@ u8 *handle_node_announcement(struct routing_state *rstate, const u8 *node_ann, return NULL; } - sha256_double(&hash, serialized + 66, tal_count(serialized) - 66); - /* If node_id is invalid, it fails here */ - if (!check_signed_hash_nodeid(&hash, &signature, &node_id)) { - /* BOLT #7: - * - * - if `signature` is not a valid signature, using - * `node_id` of the double-SHA256 of the entire - * message following the `signature` field - * (including unknown fields following - * `fee_proportional_millionths`): - * - SHOULD send a `warning` and close the connection. - * - MUST NOT process the message further. - */ - u8 *warn = towire_warningfmt(rstate, NULL, - "Bad signature for %s hash %s" - " on node_announcement %s", - type_to_string(tmpctx, - secp256k1_ecdsa_signature, - &signature), - type_to_string(tmpctx, - struct sha256_double, - &hash), - tal_hex(tmpctx, node_ann)); - return warn; - } + err = sigcheck_node_announcement(tmpctx, &node_id, &signature, + serialized); + if (err) + return towire_warningfmt(rstate, NULL, "%s", err); wireaddrs = fromwire_wireaddr_array(tmpctx, addresses); if (!wireaddrs) { @@ -2026,11 +1918,10 @@ u8 *handle_node_announcement(struct routing_state *rstate, const u8 *node_ann, * - SHOULD send a `warning`. * - MAY close the connection. */ - u8 *warn = towire_warningfmt(rstate, NULL, - "Malformed wireaddrs %s in %s.", - tal_hex(tmpctx, wireaddrs), - tal_hex(tmpctx, node_ann)); - return warn; + return towire_warningfmt(rstate, NULL, + "Malformed wireaddrs %s in %s.", + tal_hex(tmpctx, wireaddrs), + tal_hex(tmpctx, node_ann)); } /* May still fail, if we don't know the node. */ diff --git a/gossipd/sigcheck.c b/gossipd/sigcheck.c new file mode 100644 index 000000000000..1ca94fdecb65 --- /dev/null +++ b/gossipd/sigcheck.c @@ -0,0 +1,184 @@ +#include "config.h" +#include +#include +#include +#include +#include +#include + +/* Verify the signature of a channel_update message */ +const char *sigcheck_channel_update(const tal_t *ctx, + const struct node_id *node_id, + const secp256k1_ecdsa_signature *node_sig, + const u8 *update) +{ + /* BOLT #7: + * 1. type: 258 (`channel_update`) + * 2. data: + * * [`signature`:`signature`] + * * [`chain_hash`:`chain_hash`] + * * [`short_channel_id`:`short_channel_id`] + * * [`u32`:`timestamp`] + * * [`byte`:`message_flags`] + * * [`byte`:`channel_flags`] + * * [`u16`:`cltv_expiry_delta`] + * * [`u64`:`htlc_minimum_msat`] + * * [`u32`:`fee_base_msat`] + * * [`u32`:`fee_proportional_millionths`] + * * [`u64`:`htlc_maximum_msat`] + */ + /* 2 byte msg type + 64 byte signatures */ + int offset = 66; + struct sha256_double hash; + + sha256_double(&hash, update + offset, tal_count(update) - offset); + + if (!check_signed_hash_nodeid(&hash, node_sig, node_id)) + return tal_fmt(ctx, + "Bad signature for %s hash %s" + " on channel_update %s", + type_to_string(tmpctx, + secp256k1_ecdsa_signature, + node_sig), + type_to_string(tmpctx, + struct sha256_double, + &hash), + tal_hex(tmpctx, update)); + return NULL; +} + +const char *sigcheck_channel_announcement(const tal_t *ctx, + const struct node_id *node1_id, + const struct node_id *node2_id, + const struct pubkey *bitcoin1_key, + const struct pubkey *bitcoin2_key, + const secp256k1_ecdsa_signature *node1_sig, + const secp256k1_ecdsa_signature *node2_sig, + const secp256k1_ecdsa_signature *bitcoin1_sig, + const secp256k1_ecdsa_signature *bitcoin2_sig, + const u8 *announcement) +{ + /* BOLT #7: + * 1. type: 256 (`channel_announcement`) + * 2. data: + * * [`signature`:`node_signature_1`] + * * [`signature`:`node_signature_2`] + * * [`signature`:`bitcoin_signature_1`] + * * [`signature`:`bitcoin_signature_2`] + * * [`u16`:`len`] + * * [`len*byte`:`features`] + * * [`chain_hash`:`chain_hash`] + * * [`short_channel_id`:`short_channel_id`] + * * [`point`:`node_id_1`] + * * [`point`:`node_id_2`] + * * [`point`:`bitcoin_key_1`] + * * [`point`:`bitcoin_key_2`] + */ + /* 2 byte msg type + 256 byte signatures */ + int offset = 258; + struct sha256_double hash; + sha256_double(&hash, announcement + offset, + tal_count(announcement) - offset); + + if (!check_signed_hash_nodeid(&hash, node1_sig, node1_id)) { + return tal_fmt(ctx, + "Bad node_signature_1 %s hash %s" + " on channel_announcement %s", + type_to_string(tmpctx, + secp256k1_ecdsa_signature, + node1_sig), + type_to_string(tmpctx, + struct sha256_double, + &hash), + tal_hex(tmpctx, announcement)); + } + if (!check_signed_hash_nodeid(&hash, node2_sig, node2_id)) { + return tal_fmt(ctx, + "Bad node_signature_2 %s hash %s" + " on channel_announcement %s", + type_to_string(tmpctx, + secp256k1_ecdsa_signature, + node2_sig), + type_to_string(tmpctx, + struct sha256_double, + &hash), + tal_hex(tmpctx, announcement)); + } + if (!check_signed_hash(&hash, bitcoin1_sig, bitcoin1_key)) { + return tal_fmt(ctx, + "Bad bitcoin_signature_1 %s hash %s" + " on channel_announcement %s", + type_to_string(tmpctx, + secp256k1_ecdsa_signature, + bitcoin1_sig), + type_to_string(tmpctx, + struct sha256_double, + &hash), + tal_hex(tmpctx, announcement)); + } + if (!check_signed_hash(&hash, bitcoin2_sig, bitcoin2_key)) { + return tal_fmt(ctx, + "Bad bitcoin_signature_2 %s hash %s" + " on channel_announcement %s", + type_to_string(tmpctx, + secp256k1_ecdsa_signature, + bitcoin2_sig), + type_to_string(tmpctx, + struct sha256_double, + &hash), + tal_hex(tmpctx, announcement)); + } + return NULL; +} + +/* Returns warning msg if signature wrong, else NULL */ +const char *sigcheck_node_announcement(const tal_t *ctx, + const struct node_id *node_id, + const secp256k1_ecdsa_signature *signature, + const u8 *node_announcement) +{ + /* BOLT #7: + * + * 1. type: 257 (`node_announcement`) + * 2. data: + * * [`signature`:`signature`] + * * [`u16`:`flen`] + * * [`flen*byte`:`features`] + * * [`u32`:`timestamp`] + * * [`point`:`node_id`] + * * [`3*byte`:`rgb_color`] + * * [`32*byte`:`alias`] + * * [`u16`:`addrlen`] + * * [`addrlen*byte`:`addresses`] + */ + /* 2 byte msg type + 64 byte signatures */ + int offset = 66; + struct sha256_double hash; + + sha256_double(&hash, node_announcement + offset, tal_count(node_announcement) - offset); + /* If node_id is invalid, it fails here */ + if (!check_signed_hash_nodeid(&hash, signature, node_id)) { + /* BOLT #7: + * + * - if `signature` is not a valid signature, using + * `node_id` of the double-SHA256 of the entire + * message following the `signature` field + * (including unknown fields following + * `fee_proportional_millionths`): + * - SHOULD send a `warning` and close the connection. + * - MUST NOT process the message further. + */ + return tal_fmt(ctx, + "Bad signature for %s hash %s" + " on node_announcement %s", + type_to_string(tmpctx, + secp256k1_ecdsa_signature, + signature), + type_to_string(tmpctx, + struct sha256_double, + &hash), + tal_hex(tmpctx, node_announcement)); + } + + return NULL; +} diff --git a/gossipd/sigcheck.h b/gossipd/sigcheck.h new file mode 100644 index 000000000000..ac15b831e55c --- /dev/null +++ b/gossipd/sigcheck.h @@ -0,0 +1,29 @@ +#ifndef LIGHTNING_GOSSIPD_SIGCHECK_H +#define LIGHTNING_GOSSIPD_SIGCHECK_H +#include "config.h" +#include + +/* Returns error msg if signature wrong, else NULL */ +const char *sigcheck_channel_update(const tal_t *ctx, + const struct node_id *node_id, + const secp256k1_ecdsa_signature *node_sig, + const u8 *update); + +/* Returns error msg if signature wrong, else NULL */ +const char *sigcheck_channel_announcement(const tal_t *ctx, + const struct node_id *node1_id, + const struct node_id *node2_id, + const struct pubkey *bitcoin1_key, + const struct pubkey *bitcoin2_key, + const secp256k1_ecdsa_signature *node1_sig, + const secp256k1_ecdsa_signature *node2_sig, + const secp256k1_ecdsa_signature *bitcoin1_sig, + const secp256k1_ecdsa_signature *bitcoin2_sig, + const u8 *announcement); + +/* Returns error msg if signature wrong, else NULL */ +const char *sigcheck_node_announcement(const tal_t *ctx, + const struct node_id *node_id, + const secp256k1_ecdsa_signature *node_sig, + const u8 *node_announcement); +#endif /* LIGHTNING_GOSSIPD_SIGCHECK_H */ diff --git a/gossipd/test/run-check_channel_announcement.c b/gossipd/test/run-check_channel_announcement.c index 77184b16b1cf..284d1d21b434 100644 --- a/gossipd/test/run-check_channel_announcement.c +++ b/gossipd/test/run-check_channel_announcement.c @@ -29,7 +29,8 @@ In particular, we set feature bit 19. The spec says we should set feature bit 1 #include "config.h" #include "../common/wire_error.c" -#include "../routing.c" +#include "../sigcheck.c" +#include #include #include #include @@ -54,84 +55,13 @@ bool blinding_next_pubkey(const struct pubkey *pk UNNEEDED, const struct sha256 *h UNNEEDED, struct pubkey *next UNNEEDED) { fprintf(stderr, "blinding_next_pubkey called!\n"); abort(); } -/* Generated stub for daemon_conn_send */ -void daemon_conn_send(struct daemon_conn *dc UNNEEDED, const u8 *msg UNNEEDED) -{ fprintf(stderr, "daemon_conn_send called!\n"); abort(); } -/* Generated stub for get_cupdate_parts */ -void get_cupdate_parts(const u8 *channel_update UNNEEDED, - const u8 *parts[2] UNNEEDED, - size_t sizes[2]) -{ fprintf(stderr, "get_cupdate_parts called!\n"); abort(); } -/* Generated stub for gossip_store_add */ -u64 gossip_store_add(struct gossip_store *gs UNNEEDED, const u8 *gossip_msg UNNEEDED, - u32 timestamp UNNEEDED, bool zombie UNNEEDED, bool spam UNNEEDED, bool dying UNNEEDED, - const u8 *addendum UNNEEDED) -{ fprintf(stderr, "gossip_store_add called!\n"); abort(); } -/* Generated stub for gossip_store_delete */ -void gossip_store_delete(struct gossip_store *gs UNNEEDED, - struct broadcastable *bcast UNNEEDED, - int type UNNEEDED) -{ fprintf(stderr, "gossip_store_delete called!\n"); abort(); } -/* Generated stub for gossip_store_get */ -const u8 *gossip_store_get(const tal_t *ctx UNNEEDED, - struct gossip_store *gs UNNEEDED, - u64 offset UNNEEDED) -{ fprintf(stderr, "gossip_store_get called!\n"); abort(); } -/* Generated stub for gossip_store_mark_channel_deleted */ -void gossip_store_mark_channel_deleted(struct gossip_store *gs UNNEEDED, - const struct short_channel_id *scid UNNEEDED) -{ fprintf(stderr, "gossip_store_mark_channel_deleted called!\n"); abort(); } -/* Generated stub for gossip_store_mark_dying */ -void gossip_store_mark_dying(struct gossip_store *gs UNNEEDED, - const struct broadcastable *bcast UNNEEDED, - int type UNNEEDED) -{ fprintf(stderr, "gossip_store_mark_dying called!\n"); abort(); } -/* Generated stub for gossip_store_new */ -struct gossip_store *gossip_store_new(struct routing_state *rstate UNNEEDED) -{ fprintf(stderr, "gossip_store_new called!\n"); abort(); } -/* Generated stub for in_txout_failures */ -bool in_txout_failures(struct txout_failures *txf UNNEEDED, - const struct short_channel_id scid UNNEEDED) -{ fprintf(stderr, "in_txout_failures called!\n"); abort(); } -/* Generated stub for memleak_add_helper_ */ -void memleak_add_helper_(const tal_t *p UNNEEDED, void (*cb)(struct htable *memtable UNNEEDED, - const tal_t *)){ } -/* Generated stub for memleak_scan_htable */ -void memleak_scan_htable(struct htable *memtable UNNEEDED, const struct htable *ht UNNEEDED) -{ fprintf(stderr, "memleak_scan_htable called!\n"); abort(); } -/* Generated stub for memleak_scan_intmap_ */ -void memleak_scan_intmap_(struct htable *memtable UNNEEDED, const struct intmap *m UNNEEDED) -{ fprintf(stderr, "memleak_scan_intmap_ called!\n"); abort(); } -/* Generated stub for notleak_ */ -void *notleak_(void *ptr UNNEEDED, bool plus_children UNNEEDED) -{ fprintf(stderr, "notleak_ called!\n"); abort(); } -/* Generated stub for peer_supplied_good_gossip */ -void peer_supplied_good_gossip(struct daemon *daemon UNNEEDED, - const struct node_id *source_peer UNNEEDED, - size_t amount UNNEEDED) -{ fprintf(stderr, "peer_supplied_good_gossip called!\n"); abort(); } -/* Generated stub for status_fmt */ -void status_fmt(enum log_level level UNNEEDED, - const struct node_id *peer UNNEEDED, - const char *fmt UNNEEDED, ...) - -{ fprintf(stderr, "status_fmt called!\n"); abort(); } -/* Generated stub for towire_gossipd_remote_channel_update */ -u8 *towire_gossipd_remote_channel_update(const tal_t *ctx UNNEEDED, const struct node_id *source_node UNNEEDED, const struct peer_update *peer_update UNNEEDED) -{ fprintf(stderr, "towire_gossipd_remote_channel_update called!\n"); abort(); } -/* Generated stub for txout_failures_add */ -void txout_failures_add(struct txout_failures *txf UNNEEDED, - const struct short_channel_id scid UNNEEDED) -{ fprintf(stderr, "txout_failures_add called!\n"); abort(); } -/* Generated stub for txout_failures_new */ -struct txout_failures *txout_failures_new(const tal_t *ctx UNNEEDED, struct daemon *daemon UNNEEDED) -{ fprintf(stderr, "txout_failures_new called!\n"); abort(); } /* AUTOGENERATED MOCKS END */ int main(int argc, char *argv[]) { struct bitcoin_blkid chain_hash; - u8 *features, *err; + u8 *features; + const char *err; secp256k1_ecdsa_signature node_signature_1, node_signature_2; secp256k1_ecdsa_signature bitcoin_signature_1, bitcoin_signature_2; struct short_channel_id short_channel_id; @@ -156,16 +86,15 @@ int main(int argc, char *argv[]) &bitcoin_key_2)) abort(); - err = check_channel_announcement(cannounce, - &node_id_1, &node_id_2, - &bitcoin_key_1, &bitcoin_key_2, - &node_signature_1, &node_signature_2, - &bitcoin_signature_1, - &bitcoin_signature_2, - cannounce); + err = sigcheck_channel_announcement(cannounce, + &node_id_1, &node_id_2, + &bitcoin_key_1, &bitcoin_key_2, + &node_signature_1, &node_signature_2, + &bitcoin_signature_1, + &bitcoin_signature_2, + cannounce); assert(err); - assert(memmem(err, tal_bytelen(err), - "Bad node_signature_1", strlen("Bad node_signature_1"))); + assert(strstr(err, "Bad node_signature_1")); /* Turns out they didn't include the feature bit at all. */ cannounce = towire_channel_announcement(tmpctx, @@ -180,16 +109,15 @@ int main(int argc, char *argv[]) &node_id_2, &bitcoin_key_1, &bitcoin_key_2); - err = check_channel_announcement(cannounce, - &node_id_1, &node_id_2, - &bitcoin_key_1, &bitcoin_key_2, - &node_signature_1, &node_signature_2, - &bitcoin_signature_1, - &bitcoin_signature_2, - cannounce); + err = sigcheck_channel_announcement(cannounce, + &node_id_1, &node_id_2, + &bitcoin_key_1, &bitcoin_key_2, + &node_signature_1, &node_signature_2, + &bitcoin_signature_1, + &bitcoin_signature_2, + cannounce); assert(err); - assert(memmem(err, tal_bytelen(err), - "Bad node_signature_2", strlen("Bad node_signature_2"))); + assert(strstr(err, "Bad node_signature_2")); common_shutdown(); return 0; From d309d60cc0ceeae31b1f5f0e3bdb5f02e9c3725d Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 31 Jan 2024 14:54:33 +1030 Subject: [PATCH 07/33] gossipd: move tell_lightningd_peer_update from routing.c into gossipd.c Signed-off-by: Rusty Russell --- gossipd/gossip_store.c | 2 +- gossipd/gossipd.c | 21 +++++++++++++++++++++ gossipd/gossipd.h | 10 ++++++++++ gossipd/routing.c | 27 +++------------------------ gossipd/routing.h | 9 --------- 5 files changed, 35 insertions(+), 34 deletions(-) diff --git a/gossipd/gossip_store.c b/gossipd/gossip_store.c index fbd954783d6a..f4f581a738d8 100644 --- a/gossipd/gossip_store.c +++ b/gossipd/gossip_store.c @@ -152,7 +152,7 @@ static void give_lightningd_canned_private_update(struct routing_state *rstate, } /* From NULL source (i.e. trust us!) */ - tell_lightningd_peer_update(rstate, + tell_lightningd_peer_update(rstate->daemon, NULL, short_channel_id, fee_base_msat, diff --git a/gossipd/gossipd.c b/gossipd/gossipd.c index af6ec048c1ec..866cf77c7697 100644 --- a/gossipd/gossipd.c +++ b/gossipd/gossipd.c @@ -549,6 +549,27 @@ static void gossip_refresh_network(struct daemon *daemon) route_prune(daemon->rstate); } +void tell_lightningd_peer_update(struct daemon *daemon, + const struct node_id *source_peer, + struct short_channel_id scid, + u32 fee_base_msat, + u32 fee_ppm, + u16 cltv_delta, + struct amount_msat htlc_minimum, + struct amount_msat htlc_maximum) +{ + struct peer_update remote_update; + u8* msg; + remote_update.scid = scid; + remote_update.fee_base = fee_base_msat; + remote_update.fee_ppm = fee_ppm; + remote_update.cltv_delta = cltv_delta; + remote_update.htlc_minimum_msat = htlc_minimum; + remote_update.htlc_maximum_msat = htlc_maximum; + msg = towire_gossipd_remote_channel_update(NULL, source_peer, &remote_update); + daemon_conn_send(daemon->master, take(msg)); +} + static void tell_master_local_cupdates(struct daemon *daemon) { struct chan_map_iter i; diff --git a/gossipd/gossipd.h b/gossipd/gossipd.h index 6d4b6ddbf902..62f1a393f8fa 100644 --- a/gossipd/gossipd.h +++ b/gossipd/gossipd.h @@ -136,4 +136,14 @@ void queue_peer_msg(struct peer *peer, const u8 *msg TAKES); void queue_peer_from_store(struct peer *peer, const struct broadcastable *bcast); +/* We have an update for one of our channels (or unknown). */ +void tell_lightningd_peer_update(struct daemon *daemon, + const struct node_id *source_peer, + struct short_channel_id scid, + u32 fee_base_msat, + u32 fee_ppm, + u16 cltv_delta, + struct amount_msat htlc_minimum, + struct amount_msat htlc_maximum); + #endif /* LIGHTNING_GOSSIPD_GOSSIPD_H */ diff --git a/gossipd/routing.c b/gossipd/routing.c index 45721133a3aa..cd7fcc24e1fc 100644 --- a/gossipd/routing.c +++ b/gossipd/routing.c @@ -1123,27 +1123,6 @@ static bool is_chan_dying(struct routing_state *rstate, return false; } -void tell_lightningd_peer_update(struct routing_state *rstate, - const struct node_id *source_peer, - struct short_channel_id scid, - u32 fee_base_msat, - u32 fee_ppm, - u16 cltv_delta, - struct amount_msat htlc_minimum, - struct amount_msat htlc_maximum) -{ - struct peer_update remote_update; - u8* msg; - remote_update.scid = scid; - remote_update.fee_base = fee_base_msat; - remote_update.fee_ppm = fee_ppm; - remote_update.cltv_delta = cltv_delta; - remote_update.htlc_minimum_msat = htlc_minimum; - remote_update.htlc_maximum_msat = htlc_maximum; - msg = towire_gossipd_remote_channel_update(NULL, source_peer, &remote_update); - daemon_conn_send(rstate->daemon->master, take(msg)); -} - /* Is this channel_update different from prev (not sigs and timestamps)? */ static bool cupdate_different(struct gossip_store *gs, const struct half_chan *hc, @@ -1217,7 +1196,7 @@ bool routing_add_channel_update(struct routing_state *rstate, if (index) return false; /* Allow ld to process a private channel update */ - tell_lightningd_peer_update(rstate, source_peer, + tell_lightningd_peer_update(rstate->daemon, source_peer, short_channel_id, fee_base_msat, fee_proportional_millionths, expiry, htlc_minimum, @@ -1330,7 +1309,7 @@ bool routing_add_channel_update(struct routing_state *rstate, /* If this is a peer's update to one of our local channels, tell lightningd. */ if (node_id_eq(&chan->nodes[!direction]->id, &rstate->daemon->id)) { /* give lightningd the channel's inbound info to store to db */ - tell_lightningd_peer_update(rstate, + tell_lightningd_peer_update(rstate->daemon, /* Note: we can get public * channel_updates from other than * direct peer! So tell lightningd @@ -1561,7 +1540,7 @@ u8 *handle_channel_update(struct routing_state *rstate, const u8 *update TAKES, * check signature assuming it's from that peer, and if it's valid, hand to ld */ if (source_peer && sigcheck_channel_update(tmpctx, source_peer, &signature, serialized) == NULL) { - tell_lightningd_peer_update(rstate, source_peer, + tell_lightningd_peer_update(rstate->daemon, source_peer, short_channel_id, fee_base_msat, fee_proportional_millionths, expiry, htlc_minimum, diff --git a/gossipd/routing.h b/gossipd/routing.h index 3f8946e59511..0e478ac6ac18 100644 --- a/gossipd/routing.h +++ b/gossipd/routing.h @@ -397,13 +397,4 @@ const char *unfinalized_entries(const tal_t *ctx, struct routing_state *rstate); void remove_all_gossip(struct routing_state *rstate); -/* We have an update for one of our channels (or unknown). */ -void tell_lightningd_peer_update(struct routing_state *rstate, - const struct node_id *source_peer, - struct short_channel_id scid, - u32 fee_base_msat, - u32 fee_ppm, - u16 cltv_delta, - struct amount_msat htlc_minimum, - struct amount_msat htlc_maximum); #endif /* LIGHTNING_GOSSIPD_ROUTING_H */ From 22bf9d6b2cee03bfafaf04515107254078b5f4dd Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 31 Jan 2024 14:55:33 +1030 Subject: [PATCH 08/33] gossipd: remove online gossip_store compaction. It was an obscure dev command, as it never worked reliably. It would be much easier to re-implement once this is done. This turned out to reveal a tiny leak on tests/test_gossip.py::test_gossip_store_load_amount_truncated where we didn't immedately free chan_ann if it was dangling. Signed-off-by: Rusty Russell --- gossipd/gossip_store.c | 263 +----------------------------------- gossipd/gossip_store.h | 3 - gossipd/gossipd.c | 16 --- gossipd/gossipd_wire.csv | 7 - lightningd/gossip_control.c | 46 ------- tests/test_gossip.py | 39 ------ tools/bench-gossipd.sh | 8 +- 7 files changed, 5 insertions(+), 377 deletions(-) diff --git a/gossipd/gossip_store.c b/gossipd/gossip_store.c index f4f581a738d8..6b47adbdc8cd 100644 --- a/gossipd/gossip_store.c +++ b/gossipd/gossip_store.c @@ -29,19 +29,10 @@ struct gossip_store { /* Offset of current EOF */ u64 len; - /* Counters for entries in the gossip_store entries. This is used to - * decide whether we should rewrite the on-disk store or not. - * Note: count includes deleted. */ - size_t count, deleted; - /* Handle to the routing_state to retrieve additional information, * should it be needed */ struct routing_state *rstate; - /* Disable compaction if we encounter an error during a prior - * compaction */ - bool disable_compaction; - /* Timestamp of store when we opened it (0 if we created it) */ u32 timestamp; }; @@ -326,7 +317,6 @@ static u32 gossip_store_compact_offline(struct routing_state *rstate) struct gossip_store *gossip_store_new(struct routing_state *rstate) { struct gossip_store *gs = tal(rstate, struct gossip_store); - gs->count = gs->deleted = 0; gs->writable = true; gs->timestamp = gossip_store_compact_offline(rstate); gs->fd = open(GOSSIP_STORE_FILENAME, O_RDWR|O_CREAT, 0600); @@ -335,7 +325,6 @@ struct gossip_store *gossip_store_new(struct routing_state *rstate) "Opening gossip_store store: %s", strerror(errno)); gs->rstate = rstate; - gs->disable_compaction = false; gs->len = sizeof(gs->version); tal_add_destructor(gs, gossip_store_destroy); @@ -368,244 +357,6 @@ struct gossip_store *gossip_store_new(struct routing_state *rstate) return gs; } -/* Returns bytes transferred, or 0 on error */ -static size_t transfer_store_msg(int from_fd, size_t from_off, - int to_fd, size_t to_off, - int *type) -{ - struct gossip_hdr hdr; - u16 flags, msglen; - u8 *msg; - const u8 *p; - size_t tmplen; - - *type = -1; - if (pread(from_fd, &hdr, sizeof(hdr), from_off) != sizeof(hdr)) { - status_broken("Failed reading header from to gossip store @%zu" - ": %s", - from_off, strerror(errno)); - return 0; - } - - flags = be16_to_cpu(hdr.flags); - if (flags & GOSSIP_STORE_DELETED_BIT) { - status_broken("Can't transfer deleted msg from gossip store @%zu", - from_off); - return 0; - } - - msglen = be16_to_cpu(hdr.len); - - /* FIXME: Reuse buffer? */ - msg = tal_arr(tmpctx, u8, sizeof(hdr) + msglen); - memcpy(msg, &hdr, sizeof(hdr)); - if (pread(from_fd, msg + sizeof(hdr), msglen, from_off + sizeof(hdr)) - != msglen) { - status_broken("Failed reading %u from to gossip store @%zu" - ": %s", - msglen, from_off, strerror(errno)); - return 0; - } - - if (pwrite(to_fd, msg, msglen + sizeof(hdr), to_off) - != msglen + sizeof(hdr)) { - status_broken("Failed writing to gossip store: %s", - strerror(errno)); - return 0; - } - - /* Can't use peektype here, since we have header on front */ - p = msg + sizeof(hdr); - tmplen = msglen; - *type = fromwire_u16(&p, &tmplen); - if (!p) - *type = -1; - tal_free(msg); - return sizeof(hdr) + msglen; -} - -/* We keep a htable map of old gossip_store offsets to new ones. */ -struct offset_map { - size_t from, to; -}; - -static size_t offset_map_key(const struct offset_map *omap) -{ - return omap->from; -} - -static size_t hash_offset(size_t from) -{ - /* Crappy fast hash is "good enough" */ - return (from >> 5) ^ from; -} - -static bool offset_map_eq(const struct offset_map *omap, const size_t from) -{ - return omap->from == from; -} -HTABLE_DEFINE_TYPE(struct offset_map, - offset_map_key, hash_offset, offset_map_eq, offmap); - -static void move_broadcast(struct offmap *offmap, - struct broadcastable *bcast, - const char *what) -{ - struct offset_map *omap; - - if (!bcast->index) - return; - - omap = offmap_get(offmap, bcast->index); - if (!omap) - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "Could not relocate %s at offset %u", - what, bcast->index); - bcast->index = omap->to; - offmap_del(offmap, omap); -} - -/** - * Rewrite the on-disk gossip store, compacting it along the way - * - * Creates a new file, writes all the updates from the `broadcast_state`, and - * then atomically swaps the files. - */ -bool gossip_store_compact(struct gossip_store *gs) -{ - size_t count = 0, deleted = 0; - int fd; - u64 off, len = sizeof(gs->version), idx; - struct offmap *offmap; - struct gossip_hdr hdr; - struct offmap_iter oit; - struct node_map_iter nit; - struct offset_map *omap; - - if (gs->disable_compaction) - return false; - - status_debug( - "Compacting gossip_store with %zu entries, %zu of which are stale", - gs->count, gs->deleted); - - fd = open(GOSSIP_STORE_TEMP_FILENAME, O_RDWR|O_TRUNC|O_CREAT, 0600); - - if (fd < 0) { - status_broken( - "Could not open file for gossip_store compaction"); - goto disable; - } - - if (write(fd, &gs->version, sizeof(gs->version)) - != sizeof(gs->version)) { - status_broken("Writing version to store: %s", strerror(errno)); - goto unlink_disable; - } - - /* Walk old file, copy everything and remember new offsets. */ - offmap = tal(tmpctx, struct offmap); - offmap_init_sized(offmap, gs->count); - - /* Start by writing all channel announcements and updates. */ - off = 1; - while (pread(gs->fd, &hdr, sizeof(hdr), off) == sizeof(hdr)) { - u16 msglen; - u32 wlen; - int msgtype; - - msglen = be16_to_cpu(hdr.len); - if (be16_to_cpu(hdr.flags) & GOSSIP_STORE_DELETED_BIT) { - off += sizeof(hdr) + msglen; - deleted++; - continue; - } - - count++; - wlen = transfer_store_msg(gs->fd, off, fd, len, &msgtype); - if (wlen == 0) - goto unlink_disable; - - /* We track location of all these message types. */ - if (msgtype == WIRE_CHANNEL_ANNOUNCEMENT - || msgtype == WIRE_CHANNEL_UPDATE - || msgtype == WIRE_NODE_ANNOUNCEMENT) { - omap = tal(offmap, struct offset_map); - omap->from = off; - omap->to = len; - offmap_add(offmap, omap); - } - len += wlen; - off += wlen; - } - - /* OK, now we've written file successfully, we can move broadcasts. */ - /* Remap node announcements. */ - for (struct node *n = node_map_first(gs->rstate->nodes, &nit); - n; - n = node_map_next(gs->rstate->nodes, &nit)) { - move_broadcast(offmap, &n->bcast, "node_announce"); - } - - /* Remap channel announcements and updates */ - for (struct chan *c = uintmap_first(&gs->rstate->chanmap, &idx); - c; - c = uintmap_after(&gs->rstate->chanmap, &idx)) { - move_broadcast(offmap, &c->bcast, "channel_announce"); - move_broadcast(offmap, &c->half[0].bcast, "channel_update"); - move_broadcast(offmap, &c->half[1].bcast, "channel_update"); - } - - /* That should be everything. */ - omap = offmap_first(offmap, &oit); - if (omap) - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "gossip_store: Entry at %zu->%zu not updated?", - omap->from, omap->to); - - if (count != gs->count - gs->deleted) - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "gossip_store: Expected %zu msgs in new" - " gossip store, got %zu", - gs->count - gs->deleted, count); - - if (deleted != gs->deleted) - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "gossip_store: Expected %zu deleted msgs in old" - " gossip store, got %zu", - gs->deleted, deleted); - - if (rename(GOSSIP_STORE_TEMP_FILENAME, GOSSIP_STORE_FILENAME) == -1) - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "Error swapping compacted gossip_store into place:" - " %s", - strerror(errno)); - - status_debug( - "Compaction completed: dropped %zu messages, new count %zu, len %"PRIu64, - deleted, count, len); - - /* Write end marker now new one is ready */ - append_msg(gs->fd, towire_gossip_store_ended(tmpctx, len), - 0, false, false, false, &gs->len); - - gs->count = count; - gs->deleted = 0; - gs->len = len; - close(gs->fd); - gs->fd = fd; - - return true; - -unlink_disable: - unlink(GOSSIP_STORE_TEMP_FILENAME); -disable: - status_debug("Encountered an error while compacting, disabling " - "future compactions."); - gs->disable_compaction = true; - return false; -} - u64 gossip_store_add(struct gossip_store *gs, const u8 *gossip_msg, u32 timestamp, bool zombie, bool spam, bool dying, const u8 *addendum) @@ -626,9 +377,6 @@ u64 gossip_store_add(struct gossip_store *gs, const u8 *gossip_msg, return 0; } - gs->count++; - if (addendum) - gs->count++; return off; } @@ -708,7 +456,6 @@ static u32 flag_by_index(struct gossip_store *gs, u32 index, int flag, int type) status_failed(STATUS_FAIL_INTERNAL_ERROR, "Failed writing flags to delete @%u: %s", index, strerror(errno)); - gs->deleted++; return index + sizeof(struct gossip_hdr) + be16_to_cpu(hdr.belen); } @@ -916,6 +663,7 @@ u32 gossip_store_load(struct routing_state *rstate, struct gossip_store *gs) const char *bad; size_t stats[] = {0, 0, 0, 0}; struct timeabs start = time_now(); + size_t deleted = 0; u8 *chan_ann = NULL; u64 chan_ann_off = 0; /* Spurious gcc-9 (Ubuntu 9-20190402-1ubuntu1) 9.0.1 20190402 (experimental) warning */ @@ -940,9 +688,7 @@ u32 gossip_store_load(struct routing_state *rstate, struct gossip_store *gs) /* Skip deleted entries */ if (be16_to_cpu(hdr.flags) & GOSSIP_STORE_DELETED_BIT) { - /* Count includes deleted! */ - gs->count++; - gs->deleted++; + deleted++; goto next; } spam = (be16_to_cpu(hdr.flags) & GOSSIP_STORE_RATELIMIT_BIT); @@ -1013,13 +759,13 @@ u32 gossip_store_load(struct routing_state *rstate, struct gossip_store *gs) goto badmsg; } - gs->count++; next: gs->len += sizeof(hdr) + msglen; clean_tmpctx(); } if (chan_ann) { + tal_free(chan_ann); bad = "dangling channel_announcement"; goto corrupt; } @@ -1045,7 +791,6 @@ u32 gossip_store_load(struct routing_state *rstate, struct gossip_store *gs) status_failed(STATUS_FAIL_INTERNAL_ERROR, "Truncating new store file: %s", strerror(errno)); remove_all_gossip(rstate); - gs->count = gs->deleted = 0; gs->len = 1; gs->timestamp = 0; out: @@ -1053,7 +798,7 @@ u32 gossip_store_load(struct routing_state *rstate, struct gossip_store *gs) status_debug("total store load time: %"PRIu64" msec", time_to_msec(time_between(time_now(), start))); status_debug("gossip_store: Read %zu/%zu/%zu/%zu cannounce/cupdate/nannounce/cdelete from store (%zu deleted) in %"PRIu64" bytes", - stats[0], stats[1], stats[2], stats[3], gs->deleted, + stats[0], stats[1], stats[2], stats[3], deleted, gs->len); return gs->timestamp; diff --git a/gossipd/gossip_store.h b/gossipd/gossip_store.h index 4f86a5b60a9c..8a4752ba071f 100644 --- a/gossipd/gossip_store.h +++ b/gossipd/gossip_store.h @@ -126,9 +126,6 @@ u32 gossip_store_get_timestamp(struct gossip_store *gs, u64 offset); */ void gossip_store_set_timestamp(struct gossip_store *gs, u64 offset, u32 timestamp); -/* Exposed for dev-compact-gossip-store to force compaction. */ -bool gossip_store_compact(struct gossip_store *gs); - /** * Get a readonly fd for the gossip_store. * @gs: the gossip store. diff --git a/gossipd/gossipd.c b/gossipd/gossipd.c index 866cf77c7697..c8a1adc48b87 100644 --- a/gossipd/gossipd.c +++ b/gossipd/gossipd.c @@ -745,15 +745,6 @@ static void dev_gossip_memleak(struct daemon *daemon, const u8 *msg) found_leak))); } -static void dev_compact_store(struct daemon *daemon, const u8 *msg) -{ - bool done = gossip_store_compact(daemon->rstate->gs); - - daemon_conn_send(daemon->master, - take(towire_gossipd_dev_compact_store_reply(NULL, - done))); -} - static void dev_gossip_set_time(struct daemon *daemon, const u8 *msg) { u32 time; @@ -895,12 +886,6 @@ static struct io_plan *recv_req(struct io_conn *conn, goto done; } /* fall thru */ - case WIRE_GOSSIPD_DEV_COMPACT_STORE: - if (daemon->developer) { - dev_compact_store(daemon, msg); - goto done; - } - /* fall thru */ case WIRE_GOSSIPD_DEV_SET_TIME: if (daemon->developer) { dev_gossip_set_time(daemon, msg); @@ -914,7 +899,6 @@ static struct io_plan *recv_req(struct io_conn *conn, case WIRE_GOSSIPD_INIT_REPLY: case WIRE_GOSSIPD_GET_TXOUT: case WIRE_GOSSIPD_DEV_MEMLEAK_REPLY: - case WIRE_GOSSIPD_DEV_COMPACT_STORE_REPLY: case WIRE_GOSSIPD_ADDGOSSIP_REPLY: case WIRE_GOSSIPD_NEW_BLOCKHEIGHT_REPLY: case WIRE_GOSSIPD_GET_ADDRS_REPLY: diff --git a/gossipd/gossipd_wire.csv b/gossipd/gossipd_wire.csv index 63dabd05ea5b..6a0cc5d088d4 100644 --- a/gossipd/gossipd_wire.csv +++ b/gossipd/gossipd_wire.csv @@ -57,13 +57,6 @@ msgtype,gossipd_dev_memleak,3033 msgtype,gossipd_dev_memleak_reply,3133 msgdata,gossipd_dev_memleak_reply,leak,bool, -# master -> gossipd: please rewrite the gossip_store -msgtype,gossipd_dev_compact_store,3034 - -# gossipd -> master: ok -msgtype,gossipd_dev_compact_store_reply,3134 -msgdata,gossipd_dev_compact_store_reply,success,bool, - # master -> gossipd: blockheight increased. msgtype,gossipd_new_blockheight,3026 msgdata,gossipd_new_blockheight,blockheight,u32, diff --git a/lightningd/gossip_control.c b/lightningd/gossip_control.c index db3d8867e581..362a132f0a91 100644 --- a/lightningd/gossip_control.c +++ b/lightningd/gossip_control.c @@ -174,7 +174,6 @@ static unsigned gossip_msg(struct subd *gossip, const u8 *msg, const int *fds) case WIRE_GOSSIPD_OUTPOINTS_SPENT: case WIRE_GOSSIPD_DEV_SET_MAX_SCIDS_ENCODE_SIZE: case WIRE_GOSSIPD_DEV_MEMLEAK: - case WIRE_GOSSIPD_DEV_COMPACT_STORE: case WIRE_GOSSIPD_DEV_SET_TIME: case WIRE_GOSSIPD_NEW_BLOCKHEIGHT: case WIRE_GOSSIPD_ADDGOSSIP: @@ -182,7 +181,6 @@ static unsigned gossip_msg(struct subd *gossip, const u8 *msg, const int *fds) /* This is a reply, so never gets through to here. */ case WIRE_GOSSIPD_INIT_REPLY: case WIRE_GOSSIPD_DEV_MEMLEAK_REPLY: - case WIRE_GOSSIPD_DEV_COMPACT_STORE_REPLY: case WIRE_GOSSIPD_ADDGOSSIP_REPLY: case WIRE_GOSSIPD_NEW_BLOCKHEIGHT_REPLY: case WIRE_GOSSIPD_GET_ADDRS_REPLY: @@ -456,50 +454,6 @@ static const struct json_command dev_set_max_scids_encode_size = { }; AUTODATA(json_command, &dev_set_max_scids_encode_size); -static void dev_compact_gossip_store_reply(struct subd *gossip UNUSED, - const u8 *reply, - const int *fds UNUSED, - struct command *cmd) -{ - bool success; - - if (!fromwire_gossipd_dev_compact_store_reply(reply, &success)) { - was_pending(command_fail(cmd, LIGHTNINGD, - "Gossip gave bad dev_gossip_compact_store_reply")); - return; - } - - if (!success) - was_pending(command_fail(cmd, LIGHTNINGD, - "gossip_compact_store failed")); - else - was_pending(command_success(cmd, json_stream_success(cmd))); -} - -static struct command_result *json_dev_compact_gossip_store(struct command *cmd, - const char *buffer, - const jsmntok_t *obj UNNEEDED, - const jsmntok_t *params) -{ - u8 *msg; - if (!param(cmd, buffer, params, NULL)) - return command_param_failed(); - - msg = towire_gossipd_dev_compact_store(NULL); - subd_req(cmd->ld->gossip, cmd->ld->gossip, - take(msg), -1, 0, dev_compact_gossip_store_reply, cmd); - return command_still_pending(cmd); -} - -static const struct json_command dev_compact_gossip_store = { - "dev-compact-gossip-store", - "developer", - json_dev_compact_gossip_store, - "Ask gossipd to rewrite the gossip store.", - .dev_only = true, -}; -AUTODATA(json_command, &dev_compact_gossip_store); - static struct command_result *json_dev_gossip_set_time(struct command *cmd, const char *buffer, const jsmntok_t *obj UNNEEDED, diff --git a/tests/test_gossip.py b/tests/test_gossip.py index 2079efd8b9fe..a11fd86ed1d2 100644 --- a/tests/test_gossip.py +++ b/tests/test_gossip.py @@ -1236,11 +1236,6 @@ def test_gossip_store_load_announce_before_update(node_factory): wait_for(lambda: l1.daemon.is_in_log(r'gossip_store: Read 1/1/1/0 cannounce/cupdate/nannounce/cdelete from store \(0 deleted\) in 778 bytes')) assert not l1.daemon.is_in_log('gossip_store.*truncating') - # Extra sanity check if we can. - l1.rpc.call('dev-compact-gossip-store') - l1.restart() - l1.rpc.call('dev-compact-gossip-store') - def test_gossip_store_load_amount_truncated(node_factory): """Make sure we can read canned gossip store with truncated amount""" @@ -1259,11 +1254,6 @@ def test_gossip_store_load_amount_truncated(node_factory): wait_for(lambda: l1.daemon.is_in_log(r'gossip_store: Read 0/0/0/0 cannounce/cupdate/nannounce/cdelete from store \(0 deleted\) in 1 bytes')) assert os.path.exists(os.path.join(l1.daemon.lightning_dir, TEST_NETWORK, 'gossip_store.corrupt')) - # Extra sanity check if we can. - l1.rpc.call('dev-compact-gossip-store') - l1.restart() - l1.rpc.call('dev-compact-gossip-store') - @pytest.mark.openchannel('v1') @pytest.mark.openchannel('v2') @@ -1582,7 +1572,6 @@ def test_gossip_store_compact_noappend(node_factory, bitcoind): with open(os.path.join(l2.daemon.lightning_dir, TEST_NETWORK, 'gossip_store.tmp'), 'wb') as f: f.write(bytearray.fromhex("07deadbeef")) - l2.rpc.call('dev-compact-gossip-store') l2.restart() wait_for(lambda: l2.daemon.is_in_log('gossip_store: Read ')) assert not l2.daemon.is_in_log('gossip_store:.*truncate') @@ -1596,32 +1585,6 @@ def test_gossip_store_load_complex(node_factory, bitcoind): wait_for(lambda: l2.daemon.is_in_log('gossip_store: Read ')) -def test_gossip_store_compact(node_factory, bitcoind): - l2 = setup_gossip_store_test(node_factory, bitcoind) - - # Now compact store. - l2.rpc.call('dev-compact-gossip-store') - - # Should still be connected. - time.sleep(1) - assert len(l2.rpc.listpeers()['peers']) == 2 - - # Should restart ok. - l2.restart() - wait_for(lambda: l2.daemon.is_in_log('gossip_store: Read ')) - - -def test_gossip_store_compact_restart(node_factory, bitcoind): - l2 = setup_gossip_store_test(node_factory, bitcoind) - - # Should restart ok. - l2.restart() - wait_for(lambda: l2.daemon.is_in_log('gossip_store: Read ')) - - # Now compact store. - l2.rpc.call('dev-compact-gossip-store') - - def test_gossip_store_load_no_channel_update(node_factory): """Make sure we can read truncated gossip store with a channel_announcement and no channel_update""" l1 = node_factory.get_node(start=False, allow_broken_log=True) @@ -1652,8 +1615,6 @@ def test_gossip_store_load_no_channel_update(node_factory): assert os.path.exists(os.path.join(l1.daemon.lightning_dir, TEST_NETWORK, 'gossip_store.corrupt')) # This should actually result in an empty store. - l1.rpc.call('dev-compact-gossip-store') - with open(os.path.join(l1.daemon.lightning_dir, TEST_NETWORK, 'gossip_store'), "rb") as f: assert bytearray(f.read()) == bytearray.fromhex("0d") diff --git a/tools/bench-gossipd.sh b/tools/bench-gossipd.sh index cd9ba96f1af6..79540f1fcccf 100755 --- a/tools/bench-gossipd.sh +++ b/tools/bench-gossipd.sh @@ -5,7 +5,7 @@ set -e DIR="" TARGETS="" -DEFAULT_TARGETS=" store_load_msec vsz_kb store_rewrite_sec listnodes_sec listchannels_sec routing_sec peer_write_all_sec peer_read_all_sec " +DEFAULT_TARGETS=" store_load_msec vsz_kb listnodes_sec listchannels_sec routing_sec peer_write_all_sec peer_read_all_sec " MCP_DIR=../million-channels-project/data/1M/gossip/ CSV=false @@ -111,12 +111,6 @@ if [ -z "${TARGETS##* vsz_kb *}" ]; then ps -o vsz= -p "$(pidof lightning_gossipd)" | print_stat vsz_kb fi -# How long does rewriting the store take? -if [ -z "${TARGETS##* store_rewrite_sec *}" ]; then - # shellcheck disable=SC2086 - /usr/bin/time --append -f %e $LCLI1 dev-compact-gossip-store 2>&1 > /dev/null | print_stat store_rewrite_sec -fi - # Now, how long does listnodes take? if [ -z "${TARGETS##* listnodes_sec *}" ]; then # shellcheck disable=SC2086 From 64cf8754226a6dcfbafa8d83062233cd4edd5360 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 31 Jan 2024 14:56:33 +1030 Subject: [PATCH 09/33] gossipd: make gossip_store hold daemon ptr, not rstate. Makes it easier to wean off routing.c. Signed-off-by: Rusty Russell --- gossipd/gossip_store.c | 41 ++++++++++++++++---------------- gossipd/gossip_store.h | 5 ++-- gossipd/gossipd.c | 2 +- gossipd/routing.c | 2 +- gossipd/test/run-txout_failure.c | 2 +- 5 files changed, 25 insertions(+), 27 deletions(-) diff --git a/gossipd/gossip_store.c b/gossipd/gossip_store.c index 6b47adbdc8cd..89ad5ba3424d 100644 --- a/gossipd/gossip_store.c +++ b/gossipd/gossip_store.c @@ -20,6 +20,9 @@ #define GOSSIP_STORE_VER ((0 << 5) | 13) struct gossip_store { + /* Back pointer. */ + struct daemon *daemon; + /* This is false when we're loading */ bool writable; @@ -29,10 +32,6 @@ struct gossip_store { /* Offset of current EOF */ u64 len; - /* Handle to the routing_state to retrieve additional information, - * should it be needed */ - struct routing_state *rstate; - /* Timestamp of store when we opened it (0 if we created it) */ u32 timestamp; }; @@ -107,7 +106,7 @@ static bool can_upgrade(u8 oldversion) /* On upgrade, do best effort on private channels: hand them to * lightningd as if we just receive them, before removing from the * store */ -static void give_lightningd_canned_private_update(struct routing_state *rstate, +static void give_lightningd_canned_private_update(struct daemon *daemon, const u8 *msg) { u8 *update; @@ -143,7 +142,7 @@ static void give_lightningd_canned_private_update(struct routing_state *rstate, } /* From NULL source (i.e. trust us!) */ - tell_lightningd_peer_update(rstate->daemon, + tell_lightningd_peer_update(daemon, NULL, short_channel_id, fee_base_msat, @@ -154,7 +153,7 @@ static void give_lightningd_canned_private_update(struct routing_state *rstate, } static bool upgrade_field(u8 oldversion, - struct routing_state *rstate, + struct daemon *daemon, u8 **msg) { int type = fromwire_peektype(*msg); @@ -172,7 +171,7 @@ static bool upgrade_field(u8 oldversion, if (type == WIRE_GOSSIP_STORE_PRIVATE_CHANNEL_OBS) { *msg = tal_free(*msg); } else if (type == WIRE_GOSSIP_STORE_PRIVATE_UPDATE_OBS) { - give_lightningd_canned_private_update(rstate, *msg); + give_lightningd_canned_private_update(daemon, *msg); *msg = tal_free(*msg); } } @@ -182,7 +181,7 @@ static bool upgrade_field(u8 oldversion, /* Read gossip store entries, copy non-deleted ones. This code is written * as simply and robustly as possible! */ -static u32 gossip_store_compact_offline(struct routing_state *rstate) +static u32 gossip_store_compact_offline(struct daemon *daemon) { size_t count = 0, deleted = 0; int old_fd, new_fd; @@ -251,7 +250,7 @@ static u32 gossip_store_compact_offline(struct routing_state *rstate) } if (oldversion != version) { - if (!upgrade_field(oldversion, rstate, &msg)) { + if (!upgrade_field(oldversion, daemon, &msg)) { tal_free(msg); goto close_and_delete; } @@ -314,17 +313,17 @@ static u32 gossip_store_compact_offline(struct routing_state *rstate) return 0; } -struct gossip_store *gossip_store_new(struct routing_state *rstate) +struct gossip_store *gossip_store_new(struct daemon *daemon) { - struct gossip_store *gs = tal(rstate, struct gossip_store); + struct gossip_store *gs = tal(daemon, struct gossip_store); gs->writable = true; - gs->timestamp = gossip_store_compact_offline(rstate); + gs->timestamp = gossip_store_compact_offline(daemon); gs->fd = open(GOSSIP_STORE_FILENAME, O_RDWR|O_CREAT, 0600); if (gs->fd < 0) status_failed(STATUS_FAIL_INTERNAL_ERROR, "Opening gossip_store store: %s", strerror(errno)); - gs->rstate = rstate; + gs->daemon = daemon; gs->len = sizeof(gs->version); tal_add_destructor(gs, gossip_store_destroy); @@ -654,7 +653,7 @@ int gossip_store_readonly_fd(struct gossip_store *gs) return fd; } -u32 gossip_store_load(struct routing_state *rstate, struct gossip_store *gs) +u32 gossip_store_load(struct gossip_store *gs) { struct gossip_hdr hdr; u32 msglen, checksum; @@ -703,7 +702,7 @@ u32 gossip_store_load(struct routing_state *rstate, struct gossip_store *gs) /* Previous channel_announcement may have been deleted */ if (!chan_ann) break; - if (!routing_add_channel_announcement(rstate, + if (!routing_add_channel_announcement(gs->daemon->rstate, take(chan_ann), satoshis, chan_ann_off, @@ -731,11 +730,11 @@ u32 gossip_store_load(struct routing_state *rstate, struct gossip_store *gs) bad = "Bad gossip_store_chan_dying"; goto badmsg; } - remember_chan_dying(rstate, &scid, deadline, gs->len); + remember_chan_dying(gs->daemon->rstate, &scid, deadline, gs->len); break; } case WIRE_CHANNEL_UPDATE: - if (!routing_add_channel_update(rstate, + if (!routing_add_channel_update(gs->daemon->rstate, take(msg), gs->len, NULL, false, spam, false)) { @@ -745,7 +744,7 @@ u32 gossip_store_load(struct routing_state *rstate, struct gossip_store *gs) stats[1]++; break; case WIRE_NODE_ANNOUNCEMENT: - if (!routing_add_node_announcement(rstate, + if (!routing_add_node_announcement(gs->daemon->rstate, take(msg), gs->len, NULL, NULL, spam)) { /* FIXME: This has been reported: routing.c @@ -770,7 +769,7 @@ u32 gossip_store_load(struct routing_state *rstate, struct gossip_store *gs) goto corrupt; } - bad = unfinalized_entries(tmpctx, rstate); + bad = unfinalized_entries(tmpctx, gs->daemon->rstate); if (bad) goto corrupt; @@ -790,7 +789,7 @@ u32 gossip_store_load(struct routing_state *rstate, struct gossip_store *gs) if (gs->fd < 0 || !write_all(gs->fd, &gs->version, sizeof(gs->version))) status_failed(STATUS_FAIL_INTERNAL_ERROR, "Truncating new store file: %s", strerror(errno)); - remove_all_gossip(rstate); + remove_all_gossip(gs->daemon->rstate); gs->len = 1; gs->timestamp = 0; out: diff --git a/gossipd/gossip_store.h b/gossipd/gossip_store.h index 8a4752ba071f..f0d95cf0f642 100644 --- a/gossipd/gossip_store.h +++ b/gossipd/gossip_store.h @@ -16,17 +16,16 @@ struct gossip_store; struct routing_state; -struct gossip_store *gossip_store_new(struct routing_state *rstate); +struct gossip_store *gossip_store_new(struct daemon *daemon); /** * Load the initial gossip store, if any. * - * @param rstate The routing state to load init. * @param gs The `gossip_store` to read from * * Returns the last-modified time of the store, or 0 if it was created new. */ -u32 gossip_store_load(struct routing_state *rstate, struct gossip_store *gs); +u32 gossip_store_load(struct gossip_store *gs); /** * Add a gossip message to the gossip_store (and optional addendum) diff --git a/gossipd/gossipd.c b/gossipd/gossipd.c index c8a1adc48b87..e8ba5fa77048 100644 --- a/gossipd/gossipd.c +++ b/gossipd/gossipd.c @@ -668,7 +668,7 @@ static void gossip_init(struct daemon *daemon, const u8 *msg) dev_fast_gossip_prune); /* Load stored gossip messages, get last modified time of file */ - timestamp = gossip_store_load(daemon->rstate, daemon->rstate->gs); + timestamp = gossip_store_load(daemon->rstate->gs); /* If last_timestamp was > modified time of file, reduce it. * Usually it's capped to "now", but in the reload case it needs to diff --git a/gossipd/routing.c b/gossipd/routing.c index cd7fcc24e1fc..f7192591b9df 100644 --- a/gossipd/routing.c +++ b/gossipd/routing.c @@ -259,7 +259,7 @@ struct routing_state *new_routing_state(const tal_t *ctx, struct routing_state *rstate = tal(ctx, struct routing_state); rstate->daemon = daemon; rstate->nodes = new_node_map(rstate); - rstate->gs = gossip_store_new(rstate); + rstate->gs = gossip_store_new(daemon); rstate->last_timestamp = 0; rstate->dying_channels = tal_arr(rstate, struct dying_channel, 0); diff --git a/gossipd/test/run-txout_failure.c b/gossipd/test/run-txout_failure.c index 70b079ff9054..017523fd6c34 100644 --- a/gossipd/test/run-txout_failure.c +++ b/gossipd/test/run-txout_failure.c @@ -31,7 +31,7 @@ bool blinding_next_pubkey(const struct pubkey *pk UNNEEDED, /* AUTOGENERATED MOCKS END */ /* NOOP stub for gossip_store_new */ -struct gossip_store *gossip_store_new(struct routing_state *rstate UNNEEDED) +struct gossip_store *gossip_store_new(struct daemon *daemon UNNEEDED) { return NULL; } From b49ddfbfe03a81ce48e799b47f96fec13757eb09 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 31 Jan 2024 14:57:33 +1030 Subject: [PATCH 10/33] gossipd: pass node_id to queue_peer_msg, not peer. This is easier for future callers, which don't have a convenient peer structure: in particular, asynchronous processing of gossip for peers. Signed-off-by: Rusty Russell --- gossipd/gossipd.c | 13 ++++++++----- gossipd/gossipd.h | 4 +++- gossipd/queries.c | 10 +++++----- gossipd/seeker.c | 4 ++-- gossipd/test/run-crc32_of_update.c | 4 +++- gossipd/test/run-extended-info.c | 4 +++- gossipd/test/run-next_block_range.c | 4 +++- 7 files changed, 27 insertions(+), 16 deletions(-) diff --git a/gossipd/gossipd.c b/gossipd/gossipd.c index e8ba5fa77048..b702c93586cb 100644 --- a/gossipd/gossipd.c +++ b/gossipd/gossipd.c @@ -86,10 +86,12 @@ void peer_supplied_good_gossip(struct daemon *daemon, /* Queue a gossip message for the peer: connectd simply forwards it to * the peer. */ -void queue_peer_msg(struct peer *peer, const u8 *msg TAKES) +void queue_peer_msg(struct daemon *daemon, + const struct node_id *peer, + const u8 *msg TAKES) { - u8 *outermsg = towire_gossipd_send_gossip(NULL, &peer->id, msg); - daemon_conn_send(peer->daemon->connectd, take(outermsg)); + u8 *outermsg = towire_gossipd_send_gossip(NULL, peer, msg); + daemon_conn_send(daemon->connectd, take(outermsg)); if (taken(msg)) tal_free(msg); @@ -100,7 +102,8 @@ void queue_peer_from_store(struct peer *peer, const struct broadcastable *bcast) { struct gossip_store *gs = peer->daemon->rstate->gs; - queue_peer_msg(peer, take(gossip_store_get(NULL, gs, bcast->index))); + queue_peer_msg(peer->daemon, &peer->id, + take(gossip_store_get(NULL, gs, bcast->index))); } /*~ We don't actually keep node_announcements in memory; we keep them in @@ -494,7 +497,7 @@ static void handle_recv_gossip(struct daemon *daemon, const u8 *outermsg) handled_msg: if (err) - queue_peer_msg(peer, take(err)); + queue_peer_msg(peer->daemon, &peer->id, take(err)); } /*~ connectd's input handler is very simple. */ diff --git a/gossipd/gossipd.h b/gossipd/gossipd.h index 62f1a393f8fa..258f101ef74a 100644 --- a/gossipd/gossipd.h +++ b/gossipd/gossipd.h @@ -129,7 +129,9 @@ struct peer *next_random_peer(struct daemon *daemon, /* Queue a gossip message for the peer: the subdaemon on the other end simply * forwards it to the peer. */ -void queue_peer_msg(struct peer *peer, const u8 *msg TAKES); +void queue_peer_msg(struct daemon *daemon, + const struct node_id *peer, + const u8 *msg TAKES); /* Queue a gossip_store message for the peer: the subdaemon on the * other end simply forwards it to the peer. */ diff --git a/gossipd/queries.c b/gossipd/queries.c index 61d70293a893..027485b2a16a 100644 --- a/gossipd/queries.c +++ b/gossipd/queries.c @@ -147,7 +147,7 @@ bool query_short_channel_ids(struct daemon *daemon, msg = towire_query_short_channel_ids(NULL, &chainparams->genesis_blockhash, encoded, tlvs); - queue_peer_msg(peer, take(msg)); + queue_peer_msg(daemon, &peer->id, take(msg)); peer->scid_query_outstanding = true; peer->scid_query_cb = cb; @@ -320,7 +320,7 @@ static void send_reply_channel_range(struct peer *peer, first_blocknum, number_of_blocks, final, encoded_scids, tlvs); - queue_peer_msg(peer, take(msg)); + queue_peer_msg(peer->daemon, &peer->id, take(msg)); } /* Helper to get non-signature, non-timestamp parts of (valid!) channel_update */ @@ -617,7 +617,7 @@ const u8 *handle_query_channel_range(struct peer *peer, const u8 *msg) &chain_hash)); u8 *end = towire_reply_channel_range(NULL, &chain_hash, first_blocknum, number_of_blocks, false, NULL, NULL); - queue_peer_msg(peer, take(end)); + queue_peer_msg(peer->daemon, &peer->id, take(end)); return NULL; } @@ -1052,7 +1052,7 @@ static bool maybe_send_query_responses_peer(struct peer *peer) u8 *end = towire_reply_short_channel_ids_end(peer, &chainparams->genesis_blockhash, true); - queue_peer_msg(peer, take(end)); + queue_peer_msg(peer->daemon, &peer->id, take(end)); /* We're done! Clean up so we simply pass-through next time. */ peer->scid_queries = tal_free(peer->scid_queries); @@ -1107,7 +1107,7 @@ bool query_channel_range(struct daemon *daemon, msg = towire_query_channel_range(NULL, &chainparams->genesis_blockhash, first_blocknum, number_of_blocks, tlvs); - queue_peer_msg(peer, take(msg)); + queue_peer_msg(peer->daemon, &peer->id, take(msg)); peer->range_first_blocknum = first_blocknum; peer->range_end_blocknum = first_blocknum + number_of_blocks; peer->range_blocks_outstanding = number_of_blocks; diff --git a/gossipd/seeker.c b/gossipd/seeker.c index 159b0a2c1cb9..a6bb23feb827 100644 --- a/gossipd/seeker.c +++ b/gossipd/seeker.c @@ -204,7 +204,7 @@ static void disable_gossip_stream(struct seeker *seeker, struct peer *peer) &chainparams->genesis_blockhash, UINT32_MAX, UINT32_MAX); - queue_peer_msg(peer, take(msg)); + queue_peer_msg(peer->daemon, &peer->id, take(msg)); } static void enable_gossip_stream(struct seeker *seeker, struct peer *peer) @@ -226,7 +226,7 @@ static void enable_gossip_stream(struct seeker *seeker, struct peer *peer) &chainparams->genesis_blockhash, start, UINT32_MAX); - queue_peer_msg(peer, take(msg)); + queue_peer_msg(peer->daemon, &peer->id, take(msg)); } static void normal_gossip_start(struct seeker *seeker, struct peer *peer) diff --git a/gossipd/test/run-crc32_of_update.c b/gossipd/test/run-crc32_of_update.c index c14c5f360cf5..980274279101 100644 --- a/gossipd/test/run-crc32_of_update.c +++ b/gossipd/test/run-crc32_of_update.c @@ -75,7 +75,9 @@ void queue_peer_from_store(struct peer *peer UNNEEDED, const struct broadcastable *bcast UNNEEDED) { fprintf(stderr, "queue_peer_from_store called!\n"); abort(); } /* Generated stub for queue_peer_msg */ -void queue_peer_msg(struct peer *peer UNNEEDED, const u8 *msg TAKES UNNEEDED) +void queue_peer_msg(struct daemon *daemon UNNEEDED, + const struct node_id *peer UNNEEDED, + const u8 *msg TAKES UNNEEDED) { fprintf(stderr, "queue_peer_msg called!\n"); abort(); } /* Generated stub for status_fmt */ void status_fmt(enum log_level level UNNEEDED, diff --git a/gossipd/test/run-extended-info.c b/gossipd/test/run-extended-info.c index b38d990d753d..11d53e86107d 100644 --- a/gossipd/test/run-extended-info.c +++ b/gossipd/test/run-extended-info.c @@ -79,7 +79,9 @@ void queue_peer_from_store(struct peer *peer UNNEEDED, const struct broadcastable *bcast UNNEEDED) { fprintf(stderr, "queue_peer_from_store called!\n"); abort(); } /* Generated stub for queue_peer_msg */ -void queue_peer_msg(struct peer *peer UNNEEDED, const u8 *msg TAKES UNNEEDED) +void queue_peer_msg(struct daemon *daemon UNNEEDED, + const struct node_id *peer UNNEEDED, + const u8 *msg TAKES UNNEEDED) { fprintf(stderr, "queue_peer_msg called!\n"); abort(); } /* Generated stub for towire_warningfmt */ u8 *towire_warningfmt(const tal_t *ctx UNNEEDED, diff --git a/gossipd/test/run-next_block_range.c b/gossipd/test/run-next_block_range.c index dfebada2e481..62b8db773b30 100644 --- a/gossipd/test/run-next_block_range.c +++ b/gossipd/test/run-next_block_range.c @@ -62,7 +62,9 @@ bool query_short_channel_ids(struct daemon *daemon UNNEEDED, void (*cb)(struct peer *peer_ UNNEEDED, bool complete)) { fprintf(stderr, "query_short_channel_ids called!\n"); abort(); } /* Generated stub for queue_peer_msg */ -void queue_peer_msg(struct peer *peer UNNEEDED, const u8 *msg TAKES UNNEEDED) +void queue_peer_msg(struct daemon *daemon UNNEEDED, + const struct node_id *peer UNNEEDED, + const u8 *msg TAKES UNNEEDED) { fprintf(stderr, "queue_peer_msg called!\n"); abort(); } /* Generated stub for random_select */ bool random_select(double weight UNNEEDED, double *tot_weight UNNEEDED) From 04373d9ad1b4b13fca4b89e0843355566af9d8c3 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 31 Jan 2024 14:58:33 +1030 Subject: [PATCH 11/33] gossipd: have seeker quert interfaces take an id, not a struct peer. Again, we don't necessarily have a peer pointer. Signed-off-by: Rusty Russell --- gossipd/gossipd.c | 4 +-- gossipd/seeker.c | 49 ++++++++++++++++------------- gossipd/seeker.h | 15 ++++----- gossipd/test/run-next_block_range.c | 3 ++ 4 files changed, 40 insertions(+), 31 deletions(-) diff --git a/gossipd/gossipd.c b/gossipd/gossipd.c index b702c93586cb..28694f20ed47 100644 --- a/gossipd/gossipd.c +++ b/gossipd/gossipd.c @@ -239,7 +239,7 @@ static u8 *handle_channel_update_msg(struct peer *peer, const u8 *msg) /* If it's an unknown channel, ask someone about it */ if (unknown_scid.u64 != 0) - query_unknown_channel(peer->daemon, peer, &unknown_scid); + query_unknown_channel(peer->daemon, &peer->id, unknown_scid); return NULL; } @@ -252,7 +252,7 @@ static u8 *handle_node_announce(struct peer *peer, const u8 *msg) err = handle_node_announcement(peer->daemon->rstate, msg, &peer->id, &was_unknown); if (was_unknown) - query_unknown_node(peer->daemon->seeker, peer); + query_unknown_node(peer->daemon, &peer->id, NULL); return err; } diff --git a/gossipd/seeker.c b/gossipd/seeker.c index a6bb23feb827..6f86caae31c2 100644 --- a/gossipd/seeker.c +++ b/gossipd/seeker.c @@ -626,6 +626,18 @@ static void check_timestamps(struct seeker *seeker, *stale |= query_flag; } +static bool add_unknown_scid(struct seeker *seeker, + struct short_channel_id scid, + struct peer *peer) +{ + /* Check we're not already getting this one. */ + if (!uintmap_add(&seeker->unknown_scids, scid.u64, true)) + return false; + + set_preferred_peer(seeker, peer); + return true; +} + static void process_scid_probe(struct peer *peer, u32 first_blocknum, u32 number_of_blocks, const struct range_query_reply *replies) @@ -647,7 +659,8 @@ static void process_scid_probe(struct peer *peer, continue; } - new_unknown_scids |= add_unknown_scid(seeker, &replies[i].scid, + new_unknown_scids |= add_unknown_scid(seeker, + replies[i].scid, peer); } @@ -932,34 +945,26 @@ bool remove_unknown_scid(struct seeker *seeker, return uintmap_del(&seeker->unknown_scids, scid->u64); } -bool add_unknown_scid(struct seeker *seeker, - const struct short_channel_id *scid, - struct peer *peer) -{ - /* Check we're not already getting this one. */ - if (!uintmap_add(&seeker->unknown_scids, scid->u64, true)) - return false; - - set_preferred_peer(seeker, peer); - return true; -} - /* This peer told us about an update to an unknown channel. Ask it for a * channel_announcement. */ void query_unknown_channel(struct daemon *daemon, - struct peer *peer, - const struct short_channel_id *id) + const struct node_id *source_peer, + struct short_channel_id unknown_scid) { - /* Too many, or duplicate? */ - if (!add_unknown_scid(daemon->seeker, id, peer)) - return; + add_unknown_scid(daemon->seeker, + unknown_scid, + source_peer ? find_peer(daemon, source_peer) : NULL); } /* This peer told us about an unknown node. Start probing it. */ -void query_unknown_node(struct seeker *seeker, struct peer *peer) -{ - seeker->unknown_nodes = true; - set_preferred_peer(seeker, peer); +void query_unknown_node(struct daemon *daemon, + const struct node_id *source_peer, + const struct node_id *unknown_node) +{ + daemon->seeker->unknown_nodes = true; + if (source_peer) + set_preferred_peer(daemon->seeker, + find_peer(daemon, source_peer)); } /* Peer has died, NULL out any pointers we have */ diff --git a/gossipd/seeker.h b/gossipd/seeker.h index 2edd2662355d..90afde80520d 100644 --- a/gossipd/seeker.h +++ b/gossipd/seeker.h @@ -3,25 +3,26 @@ #include "config.h" struct daemon; -struct peer; +struct node_id; struct short_channel_id; struct seeker *new_seeker(struct daemon *daemon); +/* source_peer can be NULL! */ void query_unknown_channel(struct daemon *daemon, - struct peer *peer, - const struct short_channel_id *id); + const struct node_id *source_peer, + const struct short_channel_id unknown_scid); -void query_unknown_node(struct seeker *seeker, struct peer *peer); +/* source_peer can be NULL! */ +void query_unknown_node(struct daemon *daemon, + const struct node_id *source_peer, + const struct node_id *unknown_node); void seeker_setup_peer_gossip(struct seeker *seeker, struct peer *peer); bool remove_unknown_scid(struct seeker *seeker, const struct short_channel_id *scid, bool found); -bool add_unknown_scid(struct seeker *seeker, - const struct short_channel_id *scid, - struct peer *peer); void seeker_peer_gone(struct seeker *seeker, const struct peer *peer); diff --git a/gossipd/test/run-next_block_range.c b/gossipd/test/run-next_block_range.c index 62b8db773b30..3599c3949da5 100644 --- a/gossipd/test/run-next_block_range.c +++ b/gossipd/test/run-next_block_range.c @@ -26,6 +26,9 @@ bool blinding_next_pubkey(const struct pubkey *pk UNNEEDED, const struct sha256 *h UNNEEDED, struct pubkey *next UNNEEDED) { fprintf(stderr, "blinding_next_pubkey called!\n"); abort(); } +/* Generated stub for find_peer */ +struct peer *find_peer(struct daemon *daemon UNNEEDED, const struct node_id *id UNNEEDED) +{ fprintf(stderr, "find_peer called!\n"); abort(); } /* Generated stub for first_random_peer */ struct peer *first_random_peer(struct daemon *daemon UNNEEDED, struct peer_node_id_map_iter *it UNNEEDED) From 793ccb19b67dd39c6f480d2ceefd1ee223e44765 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 31 Jan 2024 14:59:33 +1030 Subject: [PATCH 12/33] gossipd: move dev flags from routing struct to daemon struct. Signed-off-by: Rusty Russell --- gossipd/gossipd.c | 40 ++++++++++++++++++++++++++-------------- gossipd/gossipd.h | 16 ++++++++++++++++ gossipd/routing.c | 41 +++++++++-------------------------------- gossipd/routing.h | 21 +-------------------- gossipd/seeker.c | 2 +- 5 files changed, 53 insertions(+), 67 deletions(-) diff --git a/gossipd/gossipd.c b/gossipd/gossipd.c index 28694f20ed47..190378223b21 100644 --- a/gossipd/gossipd.c +++ b/gossipd/gossipd.c @@ -544,7 +544,7 @@ static void gossip_refresh_network(struct daemon *daemon) { /* Schedule next run now */ notleak(new_reltimer(&daemon->timers, daemon, - time_from_sec(GOSSIP_PRUNE_INTERVAL(daemon->rstate->dev_fast_gossip_prune)/4), + time_from_sec(GOSSIP_PRUNE_INTERVAL(daemon->dev_fast_gossip_prune)/4), gossip_refresh_network, daemon)); /* Prune: I hope lightningd is keeping up with our own channel @@ -647,11 +647,18 @@ static void master_or_connectd_gone(struct daemon_conn *dc UNUSED) exit(2); } +struct timeabs gossip_time_now(const struct daemon *daemon) +{ + if (daemon->dev_gossip_time) + return *daemon->dev_gossip_time; + + return time_now(); +} + /*~ Parse init message from lightningd: starts the daemon properly. */ static void gossip_init(struct daemon *daemon, const u8 *msg) { u32 *dev_gossip_time; - bool dev_fast_gossip, dev_fast_gossip_prune; u32 timestamp; if (!fromwire_gossipd_init(daemon, msg, @@ -659,16 +666,20 @@ static void gossip_init(struct daemon *daemon, const u8 *msg) &daemon->our_features, &daemon->id, &dev_gossip_time, - &dev_fast_gossip, - &dev_fast_gossip_prune)) { + &daemon->dev_fast_gossip, + &daemon->dev_fast_gossip_prune)) { master_badmsg(WIRE_GOSSIPD_INIT, msg); } - daemon->rstate = new_routing_state(daemon, - daemon, - take(dev_gossip_time), - dev_fast_gossip, - dev_fast_gossip_prune); + if (dev_gossip_time) { + assert(daemon->developer); + daemon->dev_gossip_time = tal(daemon, struct timeabs); + daemon->dev_gossip_time->ts.tv_sec = *dev_gossip_time; + daemon->dev_gossip_time->ts.tv_nsec = 0; + tal_free(dev_gossip_time); + } + + daemon->rstate = new_routing_state(daemon, daemon); /* Load stored gossip messages, get last modified time of file */ timestamp = gossip_store_load(daemon->rstate->gs); @@ -681,7 +692,7 @@ static void gossip_init(struct daemon *daemon, const u8 *msg) /* Start the twice- weekly refresh timer. */ notleak(new_reltimer(&daemon->timers, daemon, - time_from_sec(GOSSIP_PRUNE_INTERVAL(daemon->rstate->dev_fast_gossip_prune) / 4), + time_from_sec(GOSSIP_PRUNE_INTERVAL(daemon->dev_fast_gossip_prune) / 4), gossip_refresh_network, daemon)); /* Fire up the seeker! */ @@ -754,10 +765,10 @@ static void dev_gossip_set_time(struct daemon *daemon, const u8 *msg) if (!fromwire_gossipd_dev_set_time(msg, &time)) master_badmsg(WIRE_GOSSIPD_DEV_SET_TIME, msg); - if (!daemon->rstate->dev_gossip_time) - daemon->rstate->dev_gossip_time = tal(daemon->rstate, struct timeabs); - daemon->rstate->dev_gossip_time->ts.tv_sec = time; - daemon->rstate->dev_gossip_time->ts.tv_nsec = 0; + if (!daemon->dev_gossip_time) + daemon->dev_gossip_time = tal(daemon, struct timeabs); + daemon->dev_gossip_time->ts.tv_sec = time; + daemon->dev_gossip_time->ts.tv_nsec = 0; } /*~ We queue incoming channel_announcement pending confirmation from lightningd @@ -928,6 +939,7 @@ int main(int argc, char *argv[]) daemon = tal(NULL, struct daemon); daemon->developer = developer; + daemon->dev_gossip_time = NULL; daemon->peers = tal(daemon, struct peer_node_id_map); peer_node_id_map_init(daemon->peers); daemon->deferred_txouts = tal_arr(daemon, struct short_channel_id, 0); diff --git a/gossipd/gossipd.h b/gossipd/gossipd.h index 258f101ef74a..b3b2ce0f4eb4 100644 --- a/gossipd/gossipd.h +++ b/gossipd/gossipd.h @@ -64,6 +64,15 @@ struct daemon { /* Features lightningd told us to set. */ struct feature_set *our_features; + + /* Override local time for gossip messages */ + struct timeabs *dev_gossip_time; + + /* Speed up gossip. */ + bool dev_fast_gossip; + + /* Speed up pruning. */ + bool dev_fast_gossip_prune; }; struct range_query_reply { @@ -148,4 +157,11 @@ void tell_lightningd_peer_update(struct daemon *daemon, struct amount_msat htlc_minimum, struct amount_msat htlc_maximum); +/** + * Get the local time. + * + * This gets overridden in dev mode so we can use canned (stale) gossip. + */ +struct timeabs gossip_time_now(const struct daemon *daemon); + #endif /* LIGHTNING_GOSSIPD_GOSSIPD_H */ diff --git a/gossipd/routing.c b/gossipd/routing.c index f7192591b9df..fb4a83f632e5 100644 --- a/gossipd/routing.c +++ b/gossipd/routing.c @@ -67,7 +67,7 @@ static u8 update_tokens(const struct routing_state *rstate, assert(new_timestamp >= prev_timestamp); num_tokens += ((new_timestamp - prev_timestamp) - / GOSSIP_TOKEN_TIME(rstate->dev_fast_gossip)); + / GOSSIP_TOKEN_TIME(rstate->daemon->dev_fast_gossip)); if (num_tokens > TOKEN_MAX) num_tokens = TOKEN_MAX; return num_tokens; @@ -220,13 +220,13 @@ static void destroy_routing_state(struct routing_state *rstate) * our canned tests, and usually old gossip is better than no gossip */ static bool timestamp_reasonable(struct routing_state *rstate, u32 timestamp) { - u64 now = gossip_time_now(rstate).ts.tv_sec; + u64 now = gossip_time_now(rstate->daemon).ts.tv_sec; /* More than one day ahead? */ if (timestamp > now + 24*60*60) return false; /* More than 2 weeks behind? */ - if (timestamp < now - GOSSIP_PRUNE_INTERVAL(rstate->dev_fast_gossip_prune)) + if (timestamp < now - GOSSIP_PRUNE_INTERVAL(rstate->daemon->dev_fast_gossip_prune)) return false; return true; } @@ -251,10 +251,7 @@ static void memleak_help_routing_tables(struct htable *memtable, } struct routing_state *new_routing_state(const tal_t *ctx, - struct daemon *daemon, - const u32 *dev_gossip_time TAKES, - bool dev_fast_gossip, - bool dev_fast_gossip_prune) + struct daemon *daemon) { struct routing_state *rstate = tal(ctx, struct routing_state); rstate->daemon = daemon; @@ -272,21 +269,9 @@ struct routing_state *new_routing_state(const tal_t *ctx, rstate->pending_node_map = tal(ctx, struct pending_node_map); pending_node_map_init(rstate->pending_node_map); - if (dev_gossip_time) { - assert(daemon->developer); - rstate->dev_gossip_time = tal(rstate, struct timeabs); - rstate->dev_gossip_time->ts.tv_sec = *dev_gossip_time; - rstate->dev_gossip_time->ts.tv_nsec = 0; - } else - rstate->dev_gossip_time = NULL; - rstate->dev_fast_gossip = dev_fast_gossip; - rstate->dev_fast_gossip_prune = dev_fast_gossip_prune; tal_add_destructor(rstate, destroy_routing_state); memleak_add_helper(rstate, memleak_help_routing_tables); - if (taken(dev_gossip_time)) - tal_free(dev_gossip_time); - return rstate; } @@ -771,7 +756,7 @@ bool routing_add_channel_announcement(struct routing_state *rstate, uc = tal(rstate, struct unupdated_channel); uc->channel_announce = tal_dup_talarr(uc, u8, msg); - uc->added = gossip_time_now(rstate); + uc->added = gossip_time_now(rstate->daemon); uc->index = index; uc->sat = sat; uc->scid = scid; @@ -1261,7 +1246,7 @@ bool routing_add_channel_update(struct routing_state *rstate, } /* Allow redundant updates once every 7 days */ - if (timestamp < hc->bcast.timestamp + GOSSIP_PRUNE_INTERVAL(rstate->dev_fast_gossip_prune) / 2 + if (timestamp < hc->bcast.timestamp + GOSSIP_PRUNE_INTERVAL(rstate->daemon->dev_fast_gossip_prune) / 2 && !cupdate_different(rstate->gs, hc, update)) { SUPERVERBOSE("Ignoring redundant update for %s/%u" " (last %u, now %u)", @@ -1765,7 +1750,7 @@ bool routing_add_node_announcement(struct routing_state *rstate, } /* Allow redundant updates once a day (faster in dev-fast-gossip-prune mode) */ - redundant_time = GOSSIP_PRUNE_INTERVAL(rstate->dev_fast_gossip_prune) / 14; + redundant_time = GOSSIP_PRUNE_INTERVAL(rstate->daemon->dev_fast_gossip_prune) / 14; if (timestamp < node->bcast.timestamp + redundant_time && !nannounce_different(rstate->gs, node, msg)) { SUPERVERBOSE( @@ -1910,9 +1895,9 @@ u8 *handle_node_announcement(struct routing_state *rstate, const u8 *node_ann, void route_prune(struct routing_state *rstate) { - u64 now = gossip_time_now(rstate).ts.tv_sec; + u64 now = gossip_time_now(rstate->daemon).ts.tv_sec; /* Anything below this highwater mark ought to be pruned */ - const s64 highwater = now - GOSSIP_PRUNE_INTERVAL(rstate->dev_fast_gossip_prune); + const s64 highwater = now - GOSSIP_PRUNE_INTERVAL(rstate->daemon->dev_fast_gossip_prune); struct chan **pruned = tal_arr(tmpctx, struct chan *, 0); u64 idx; @@ -1970,14 +1955,6 @@ void route_prune(struct routing_state *rstate) } } -struct timeabs gossip_time_now(const struct routing_state *rstate) -{ - if (rstate->dev_gossip_time) - return *rstate->dev_gossip_time; - - return time_now(); -} - const char *unfinalized_entries(const tal_t *ctx, struct routing_state *rstate) { struct unupdated_channel *uc; diff --git a/gossipd/routing.h b/gossipd/routing.h index 0e478ac6ac18..e630d1e86baa 100644 --- a/gossipd/routing.h +++ b/gossipd/routing.h @@ -205,15 +205,6 @@ struct routing_state { /* Channels which are closed, but we're waiting 12 blocks */ struct dying_channel *dying_channels; - - /* Override local time for gossip messages */ - struct timeabs *dev_gossip_time; - - /* Speed up gossip. */ - bool dev_fast_gossip; - - /* Speed up pruning. */ - bool dev_fast_gossip_prune; }; /* Which direction are we? False if neither. */ @@ -239,10 +230,7 @@ get_channel(const struct routing_state *rstate, } struct routing_state *new_routing_state(const tal_t *ctx, - struct daemon *daemon, - const u32 *dev_gossip_time TAKES, - bool dev_fast_gossip, - bool dev_fast_gossip_prune); + struct daemon *daemon); /** * Add a new bidirectional channel from id1 to id2 with the given @@ -353,13 +341,6 @@ bool routing_add_node_announcement(struct routing_state *rstate, bool *was_unknown, bool force_spam_flag); -/** - * Get the local time. - * - * This gets overridden in dev mode so we can use canned (stale) gossip. - */ -struct timeabs gossip_time_now(const struct routing_state *rstate); - /** * Add to rstate->dying_channels * diff --git a/gossipd/seeker.c b/gossipd/seeker.c index 6f86caae31c2..65ad8afb57c4 100644 --- a/gossipd/seeker.c +++ b/gossipd/seeker.c @@ -16,7 +16,7 @@ #include #define GOSSIP_SEEKER_INTERVAL(seeker) \ - DEV_FAST_GOSSIP((seeker)->daemon->rstate->dev_fast_gossip, 5, 60) + DEV_FAST_GOSSIP((seeker)->daemon->dev_fast_gossip, 5, 60) enum seeker_state { /* Still streaming gossip from single peer. */ From 19149e1be5abcc8751049ed10e5095a4c88ee351 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 31 Jan 2024 15:00:33 +1030 Subject: [PATCH 13/33] gossipd: move timestamp_reasonable out of routing.c Signed-off-by: Rusty Russell --- gossipd/gossipd.c | 15 +++++++++++++++ gossipd/gossipd.h | 5 +++++ gossipd/routing.c | 19 ++----------------- 3 files changed, 22 insertions(+), 17 deletions(-) diff --git a/gossipd/gossipd.c b/gossipd/gossipd.c index 190378223b21..a3e89889f8ce 100644 --- a/gossipd/gossipd.c +++ b/gossipd/gossipd.c @@ -655,6 +655,21 @@ struct timeabs gossip_time_now(const struct daemon *daemon) return time_now(); } +/* We don't check this when loading from the gossip_store: that would break + * our canned tests, and usually old gossip is better than no gossip */ +bool timestamp_reasonable(const struct daemon *daemon, u32 timestamp) +{ + u64 now = gossip_time_now(daemon).ts.tv_sec; + + /* More than one day ahead? */ + if (timestamp > now + 24*60*60) + return false; + /* More than 2 weeks behind? */ + if (timestamp < now - GOSSIP_PRUNE_INTERVAL(daemon->dev_fast_gossip_prune)) + return false; + return true; +} + /*~ Parse init message from lightningd: starts the daemon properly. */ static void gossip_init(struct daemon *daemon, const u8 *msg) { diff --git a/gossipd/gossipd.h b/gossipd/gossipd.h index b3b2ce0f4eb4..baef9737a911 100644 --- a/gossipd/gossipd.h +++ b/gossipd/gossipd.h @@ -164,4 +164,9 @@ void tell_lightningd_peer_update(struct daemon *daemon, */ struct timeabs gossip_time_now(const struct daemon *daemon); +/** + * Is this gossip timestamp reasonable? + */ +bool timestamp_reasonable(const struct daemon *daemon, u32 timestamp); + #endif /* LIGHTNING_GOSSIPD_GOSSIPD_H */ diff --git a/gossipd/routing.c b/gossipd/routing.c index fb4a83f632e5..3ce8c03e94d5 100644 --- a/gossipd/routing.c +++ b/gossipd/routing.c @@ -216,21 +216,6 @@ static void destroy_routing_state(struct routing_state *rstate) free_chan(rstate, chan); } -/* We don't check this when loading from the gossip_store: that would break - * our canned tests, and usually old gossip is better than no gossip */ -static bool timestamp_reasonable(struct routing_state *rstate, u32 timestamp) -{ - u64 now = gossip_time_now(rstate->daemon).ts.tv_sec; - - /* More than one day ahead? */ - if (timestamp > now + 24*60*60) - return false; - /* More than 2 weeks behind? */ - if (timestamp < now - GOSSIP_PRUNE_INTERVAL(rstate->daemon->dev_fast_gossip_prune)) - return false; - return true; -} - static void memleak_help_routing_tables(struct htable *memtable, struct routing_state *rstate) { @@ -1200,7 +1185,7 @@ bool routing_add_channel_update(struct routing_state *rstate, return false; /* Check timestamp is sane (unless from store). */ - if (!index && !timestamp_reasonable(rstate, timestamp)) { + if (!index && !timestamp_reasonable(rstate->daemon, timestamp)) { SUPERVERBOSE("Ignoring update timestamp %u for %s/%u", timestamp, type_to_string(tmpctx, struct short_channel_id, @@ -1321,7 +1306,7 @@ bool routing_add_channel_update(struct routing_state *rstate, /* Handle resurrection of zombie channels if the other side of the * zombie channel has a recent timestamp. */ - if (zombie && timestamp_reasonable(rstate, + if (zombie && timestamp_reasonable(rstate->daemon, chan->half[!direction].bcast.timestamp) && chan->half[!direction].bcast.index && !index) { status_peer_debug(source_peer, From bef9fd948f9532ee85dc0e66d45d51ef36e2f422 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 31 Jan 2024 15:01:33 +1030 Subject: [PATCH 14/33] gossipd: simplify seeker startup. Most nodes don't really care about exact timestamps on gossip filters, so just keep a flag on whether we have anything in the gossip_store, and use that to determine whether we ask peers for everything. Signed-off-by: Rusty Russell --- gossipd/gossipd.c | 15 ++++++--------- gossipd/gossipd.h | 3 +++ gossipd/routing.c | 7 ------- gossipd/routing.h | 3 --- gossipd/seeker.c | 18 +++++++++++------- 5 files changed, 20 insertions(+), 26 deletions(-) diff --git a/gossipd/gossipd.c b/gossipd/gossipd.c index a3e89889f8ce..6e1700679152 100644 --- a/gossipd/gossipd.c +++ b/gossipd/gossipd.c @@ -498,6 +498,9 @@ static void handle_recv_gossip(struct daemon *daemon, const u8 *outermsg) handled_msg: if (err) queue_peer_msg(peer->daemon, &peer->id, take(err)); + else + /* Some peer gave us gossip, so we're not at zero. */ + peer->daemon->gossip_store_populated = true; } /*~ connectd's input handler is very simple. */ @@ -674,7 +677,6 @@ bool timestamp_reasonable(const struct daemon *daemon, u32 timestamp) static void gossip_init(struct daemon *daemon, const u8 *msg) { u32 *dev_gossip_time; - u32 timestamp; if (!fromwire_gossipd_init(daemon, msg, &chainparams, @@ -696,14 +698,9 @@ static void gossip_init(struct daemon *daemon, const u8 *msg) daemon->rstate = new_routing_state(daemon, daemon); - /* Load stored gossip messages, get last modified time of file */ - timestamp = gossip_store_load(daemon->rstate->gs); - - /* If last_timestamp was > modified time of file, reduce it. - * Usually it's capped to "now", but in the reload case it needs to - * be the gossip_store mtime. */ - if (daemon->rstate->last_timestamp > timestamp) - daemon->rstate->last_timestamp = timestamp; + /* Load stored gossip messages (FIXME: API sucks)*/ + daemon->gossip_store_populated = + (gossip_store_load(daemon->rstate->gs) != 0); /* Start the twice- weekly refresh timer. */ notleak(new_reltimer(&daemon->timers, daemon, diff --git a/gossipd/gossipd.h b/gossipd/gossipd.h index baef9737a911..ac324da59479 100644 --- a/gossipd/gossipd.h +++ b/gossipd/gossipd.h @@ -65,6 +65,9 @@ struct daemon { /* Features lightningd told us to set. */ struct feature_set *our_features; + /* Was there anything in the gossip store at startup? */ + bool gossip_store_populated; + /* Override local time for gossip messages */ struct timeabs *dev_gossip_time; diff --git a/gossipd/routing.c b/gossipd/routing.c index 3ce8c03e94d5..2e88ec21ad2c 100644 --- a/gossipd/routing.c +++ b/gossipd/routing.c @@ -242,7 +242,6 @@ struct routing_state *new_routing_state(const tal_t *ctx, rstate->daemon = daemon; rstate->nodes = new_node_map(rstate); rstate->gs = gossip_store_new(daemon); - rstate->last_timestamp = 0; rstate->dying_channels = tal_arr(rstate, struct dying_channel, 0); rstate->pending_cannouncements = tal(rstate, struct pending_cannouncement_map); @@ -1375,9 +1374,6 @@ bool routing_add_channel_update(struct routing_state *rstate, hc->rgraph.index = gossip_store_add(rstate->gs, update, timestamp, zombie, spam, dying, NULL); - if (hc->bcast.timestamp > rstate->last_timestamp - && hc->bcast.timestamp < time_now().ts.tv_sec) - rstate->last_timestamp = hc->bcast.timestamp; if (!spam) hc->bcast.index = hc->rgraph.index; @@ -1794,9 +1790,6 @@ bool routing_add_node_announcement(struct routing_state *rstate, node->rgraph.index = gossip_store_add(rstate->gs, msg, timestamp, false, spam, false, NULL); - if (node->bcast.timestamp > rstate->last_timestamp - && node->bcast.timestamp < time_now().ts.tv_sec) - rstate->last_timestamp = node->bcast.timestamp; if (!spam) node->bcast.index = node->rgraph.index; diff --git a/gossipd/routing.h b/gossipd/routing.h index e630d1e86baa..63ff8d1bda62 100644 --- a/gossipd/routing.h +++ b/gossipd/routing.h @@ -200,9 +200,6 @@ struct routing_state { * checks if we get another announcement for the same scid. */ struct txout_failures *txf; - /* Highest timestamp of gossip we accepted (before now) */ - u32 last_timestamp; - /* Channels which are closed, but we're waiting 12 blocks */ struct dying_channel *dying_channels; }; diff --git a/gossipd/seeker.c b/gossipd/seeker.c index 65ad8afb57c4..bd1ec17b6882 100644 --- a/gossipd/seeker.c +++ b/gossipd/seeker.c @@ -209,17 +209,21 @@ static void disable_gossip_stream(struct seeker *seeker, struct peer *peer) static void enable_gossip_stream(struct seeker *seeker, struct peer *peer) { - /* We seek some way back, to take into account propagation time */ - const u32 polltime = GOSSIP_SEEKER_INTERVAL(seeker) * 10; - u32 start = seeker->daemon->rstate->last_timestamp; + u32 start; u8 *msg; - if (start > polltime) - start -= polltime; - else + /* Modern timestamp_filter is a trinary: 0 = all, FFFFFFFF = none, + * other = from now on */ + if (seeker->daemon->gossip_store_populated) { + /* Just in case they care */ + start = time_now().ts.tv_sec - GOSSIP_SEEKER_INTERVAL(seeker) * 10; + } else { start = 0; + } - status_peer_debug(&peer->id, "seeker: starting gossip"); + status_peer_debug(&peer->id, "seeker: starting gossip (%s)", + seeker->daemon->gossip_store_populated + ? "streaming" : "EVERTHING"); /* This is allowed even if they don't understand it (odd) */ msg = towire_gossip_timestamp_filter(NULL, From 31b89c0098849de1dfd92732cde0d5a2c57d20d7 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 31 Jan 2024 15:02:33 +1030 Subject: [PATCH 15/33] gossipd: move gossip_store pointer from struct routing_state to daemon. Signed-off-by: Rusty Russell --- gossipd/gossipd.c | 11 ++++--- gossipd/gossipd.h | 3 ++ gossipd/queries.c | 2 +- gossipd/routing.c | 81 +++++++++++++++++++++++------------------------ gossipd/routing.h | 3 -- 5 files changed, 50 insertions(+), 50 deletions(-) diff --git a/gossipd/gossipd.c b/gossipd/gossipd.c index 6e1700679152..7ec69907db8b 100644 --- a/gossipd/gossipd.c +++ b/gossipd/gossipd.c @@ -101,7 +101,7 @@ void queue_peer_msg(struct daemon *daemon, void queue_peer_from_store(struct peer *peer, const struct broadcastable *bcast) { - struct gossip_store *gs = peer->daemon->rstate->gs; + struct gossip_store *gs = peer->daemon->gs; queue_peer_msg(peer->daemon, &peer->id, take(gossip_store_get(NULL, gs, bcast->index))); } @@ -128,7 +128,7 @@ static bool get_node_announcement(const tal_t *ctx, if (!n->bcast.index) return false; - msg = gossip_store_get(tmpctx, daemon->rstate->gs, n->bcast.index); + msg = gossip_store_get(tmpctx, daemon->gs, n->bcast.index); /* Note: validity of node_id is already checked. */ if (!fromwire_node_announcement(ctx, msg, @@ -599,7 +599,7 @@ static void tell_master_local_cupdates(struct daemon *daemon) continue; cupdate = gossip_store_get(tmpctx, - daemon->rstate->gs, + daemon->gs, hc->bcast.index); daemon_conn_send(daemon->master, take(towire_gossipd_init_cupdate(NULL, @@ -611,7 +611,7 @@ static void tell_master_local_cupdates(struct daemon *daemon) if (me->bcast.index) { const u8 *nannounce; nannounce = gossip_store_get(tmpctx, - daemon->rstate->gs, + daemon->gs, me->bcast.index); daemon_conn_send(daemon->master, take(towire_gossipd_init_nannounce(NULL, @@ -696,11 +696,12 @@ static void gossip_init(struct daemon *daemon, const u8 *msg) tal_free(dev_gossip_time); } + daemon->gs = gossip_store_new(daemon); daemon->rstate = new_routing_state(daemon, daemon); /* Load stored gossip messages (FIXME: API sucks)*/ daemon->gossip_store_populated = - (gossip_store_load(daemon->rstate->gs) != 0); + (gossip_store_load(daemon->gs) != 0); /* Start the twice- weekly refresh timer. */ notleak(new_reltimer(&daemon->timers, daemon, diff --git a/gossipd/gossipd.h b/gossipd/gossipd.h index ac324da59479..5b61043d0f40 100644 --- a/gossipd/gossipd.h +++ b/gossipd/gossipd.h @@ -65,6 +65,9 @@ struct daemon { /* Features lightningd told us to set. */ struct feature_set *our_features; + /* Gossip store */ + struct gossip_store *gs; + /* Was there anything in the gossip store at startup? */ bool gossip_store_populated; diff --git a/gossipd/queries.c b/gossipd/queries.c index 027485b2a16a..68c0dfcb752d 100644 --- a/gossipd/queries.c +++ b/gossipd/queries.c @@ -377,7 +377,7 @@ static void get_checksum_and_timestamp(struct routing_state *rstate, if (!is_halfchan_defined(hc)) { *tstamp = *csum = 0; } else { - const u8 *update = gossip_store_get(tmpctx, rstate->gs, + const u8 *update = gossip_store_get(tmpctx, rstate->daemon->gs, hc->bcast.index); *tstamp = hc->bcast.timestamp; *csum = crc32_of_update(update); diff --git a/gossipd/routing.c b/gossipd/routing.c index 2e88ec21ad2c..0779fa5c6d49 100644 --- a/gossipd/routing.c +++ b/gossipd/routing.c @@ -241,7 +241,6 @@ struct routing_state *new_routing_state(const tal_t *ctx, struct routing_state *rstate = tal(ctx, struct routing_state); rstate->daemon = daemon; rstate->nodes = new_node_map(rstate); - rstate->gs = gossip_store_new(daemon); rstate->dying_channels = tal_arr(rstate, struct dying_channel, 0); rstate->pending_cannouncements = tal(rstate, struct pending_cannouncement_map); @@ -371,13 +370,13 @@ static void force_node_announce_rexmit(struct routing_state *rstate, struct node *node) { const u8 *announce; - announce = gossip_store_get(tmpctx, rstate->gs, node->bcast.index); + announce = gossip_store_get(tmpctx, rstate->daemon->gs, node->bcast.index); u32 initial_bcast_index = node->bcast.index; - gossip_store_delete(rstate->gs, + gossip_store_delete(rstate->daemon->gs, &node->bcast, WIRE_NODE_ANNOUNCEMENT); - node->bcast.index = gossip_store_add(rstate->gs, + node->bcast.index = gossip_store_add(rstate->daemon->gs, announce, node->bcast.timestamp, false, @@ -387,11 +386,11 @@ static void force_node_announce_rexmit(struct routing_state *rstate, if (node->rgraph.index == initial_bcast_index){ node->rgraph.index = node->bcast.index; } else { - announce = gossip_store_get(tmpctx, rstate->gs, node->rgraph.index); - gossip_store_delete(rstate->gs, + announce = gossip_store_get(tmpctx, rstate->daemon->gs, node->rgraph.index); + gossip_store_delete(rstate->daemon->gs, &node->rgraph, WIRE_NODE_ANNOUNCEMENT); - node->rgraph.index = gossip_store_add(rstate->gs, + node->rgraph.index = gossip_store_add(rstate->daemon->gs, announce, node->rgraph.timestamp, false, @@ -423,10 +422,10 @@ static void remove_chan_from_node(struct routing_state *rstate, /* Last channel? Simply delete node (and associated announce) */ if (num_chans == 0) { if (node->rgraph.index != node->bcast.index) - gossip_store_delete(rstate->gs, + gossip_store_delete(rstate->daemon->gs, &node->rgraph, WIRE_NODE_ANNOUNCEMENT); - gossip_store_delete(rstate->gs, + gossip_store_delete(rstate->daemon->gs, &node->bcast, WIRE_NODE_ANNOUNCEMENT); tal_free(node); @@ -440,10 +439,10 @@ static void remove_chan_from_node(struct routing_state *rstate, /* Removed only public channel? Remove node announcement. */ if (!node_has_broadcastable_channels(node)) { if (node->rgraph.index != node->bcast.index) - gossip_store_delete(rstate->gs, + gossip_store_delete(rstate->daemon->gs, &node->rgraph, WIRE_NODE_ANNOUNCEMENT); - gossip_store_delete(rstate->gs, + gossip_store_delete(rstate->daemon->gs, &node->bcast, WIRE_NODE_ANNOUNCEMENT); node->rgraph.index = node->bcast.index = 0; @@ -673,7 +672,7 @@ static void add_channel_announce_to_broadcast(struct routing_state *rstate, if (index) chan->bcast.index = index; else - chan->bcast.index = gossip_store_add(rstate->gs, + chan->bcast.index = gossip_store_add(rstate->daemon->gs, channel_announce, chan->bcast.timestamp, false, @@ -686,17 +685,17 @@ static void delete_chan_messages_from_store(struct routing_state *rstate, struct chan *chan) { /* If these aren't in the store, these are noops. */ - gossip_store_delete(rstate->gs, + gossip_store_delete(rstate->daemon->gs, &chan->bcast, WIRE_CHANNEL_ANNOUNCEMENT); if (chan->half[0].rgraph.index != chan->half[0].bcast.index) - gossip_store_delete(rstate->gs, + gossip_store_delete(rstate->daemon->gs, &chan->half[0].rgraph, WIRE_CHANNEL_UPDATE); - gossip_store_delete(rstate->gs, + gossip_store_delete(rstate->daemon->gs, &chan->half[0].bcast, WIRE_CHANNEL_UPDATE); if (chan->half[1].rgraph.index != chan->half[1].bcast.index) - gossip_store_delete(rstate->gs, + gossip_store_delete(rstate->daemon->gs, &chan->half[1].rgraph, WIRE_CHANNEL_UPDATE); - gossip_store_delete(rstate->gs, + gossip_store_delete(rstate->daemon->gs, &chan->half[1].bcast, WIRE_CHANNEL_UPDATE); } @@ -705,7 +704,7 @@ static void remove_channel_from_store(struct routing_state *rstate, { /* Put in tombstone marker. Zombie channels will have one already. */ if (!is_chan_zombie(chan)) - gossip_store_mark_channel_deleted(rstate->gs, &chan->scid); + gossip_store_mark_channel_deleted(rstate->daemon->gs, &chan->scid); /* Now delete old entries. */ delete_chan_messages_from_store(rstate, chan); @@ -1076,7 +1075,7 @@ static void delete_spam_update(struct routing_state *rstate, /* Spam updates will have a unique rgraph index */ if (hc->rgraph.index == hc->bcast.index) return; - gossip_store_delete(rstate->gs, &hc->rgraph, + gossip_store_delete(rstate->daemon->gs, &hc->rgraph, WIRE_CHANNEL_UPDATE); hc->rgraph.index = hc->bcast.index; hc->rgraph.timestamp = hc->bcast.timestamp; @@ -1231,7 +1230,7 @@ bool routing_add_channel_update(struct routing_state *rstate, /* Allow redundant updates once every 7 days */ if (timestamp < hc->bcast.timestamp + GOSSIP_PRUNE_INTERVAL(rstate->daemon->dev_fast_gossip_prune) / 2 - && !cupdate_different(rstate->gs, hc, update)) { + && !cupdate_different(rstate->daemon->gs, hc, update)) { SUPERVERBOSE("Ignoring redundant update for %s/%u" " (last %u, now %u)", type_to_string(tmpctx, @@ -1267,7 +1266,7 @@ bool routing_add_channel_update(struct routing_state *rstate, /* Delete any prior entries (noop if they don't exist) */ delete_spam_update(rstate, hc); if (!spam) - gossip_store_delete(rstate->gs, &hc->bcast, + gossip_store_delete(rstate->daemon->gs, &hc->bcast, WIRE_CHANNEL_UPDATE); /* Update timestamp(s) */ @@ -1319,41 +1318,41 @@ bool routing_add_channel_update(struct routing_state *rstate, /* Resurrection is a careful process. First delete the zombie- * flagged channel_announcement which has already been * tombstoned, and re-add to the store without zombie flag. */ - zombie_announcement = gossip_store_get(tmpctx, rstate->gs, + zombie_announcement = gossip_store_get(tmpctx, rstate->daemon->gs, chan->bcast.index); u32 offset = tal_count(zombie_announcement) + sizeof(struct gossip_hdr); /* The channel_announcement addendum reminds us of its size. */ - zombie_addendum = gossip_store_get(tmpctx, rstate->gs, + zombie_addendum = gossip_store_get(tmpctx, rstate->daemon->gs, chan->bcast.index + offset); - gossip_store_delete(rstate->gs, &chan->bcast, + gossip_store_delete(rstate->daemon->gs, &chan->bcast, WIRE_CHANNEL_ANNOUNCEMENT); chan->bcast.index = - gossip_store_add(rstate->gs, zombie_announcement, + gossip_store_add(rstate->daemon->gs, zombie_announcement, chan->bcast.timestamp, false, false, false, zombie_addendum); /* Deletion of the old addendum is optional. */ /* This opposing channel_update has been stashed away. Now that * there are two valid updates, this one gets restored. */ /* FIXME: Handle spam case probably needs a helper f'n */ - zombie_update[0] = gossip_store_get(tmpctx, rstate->gs, + zombie_update[0] = gossip_store_get(tmpctx, rstate->daemon->gs, chan->half[!direction].bcast.index); if (chan->half[!direction].bcast.index != chan->half[!direction].rgraph.index) { /* Don't forget the spam channel_update */ - zombie_update[1] = gossip_store_get(tmpctx, rstate->gs, + zombie_update[1] = gossip_store_get(tmpctx, rstate->daemon->gs, chan->half[!direction].rgraph.index); - gossip_store_delete(rstate->gs, &chan->half[!direction].rgraph, + gossip_store_delete(rstate->daemon->gs, &chan->half[!direction].rgraph, WIRE_CHANNEL_UPDATE); } - gossip_store_delete(rstate->gs, &chan->half[!direction].bcast, + gossip_store_delete(rstate->daemon->gs, &chan->half[!direction].bcast, WIRE_CHANNEL_UPDATE); chan->half[!direction].bcast.index = - gossip_store_add(rstate->gs, zombie_update[0], + gossip_store_add(rstate->daemon->gs, zombie_update[0], chan->half[!direction].bcast.timestamp, false, false, false, NULL); if (zombie_update[1]) chan->half[!direction].rgraph.index = - gossip_store_add(rstate->gs, zombie_update[1], + gossip_store_add(rstate->daemon->gs, zombie_update[1], chan->half[!direction].rgraph.timestamp, false, true, false, NULL); else @@ -1372,7 +1371,7 @@ bool routing_add_channel_update(struct routing_state *rstate, hc->rgraph.index = index; } else { hc->rgraph.index - = gossip_store_add(rstate->gs, update, timestamp, + = gossip_store_add(rstate->daemon->gs, update, timestamp, zombie, spam, dying, NULL); if (!spam) hc->bcast.index = hc->rgraph.index; @@ -1733,7 +1732,7 @@ bool routing_add_node_announcement(struct routing_state *rstate, /* Allow redundant updates once a day (faster in dev-fast-gossip-prune mode) */ redundant_time = GOSSIP_PRUNE_INTERVAL(rstate->daemon->dev_fast_gossip_prune) / 14; if (timestamp < node->bcast.timestamp + redundant_time - && !nannounce_different(rstate->gs, node, msg)) { + && !nannounce_different(rstate->daemon->gs, node, msg)) { SUPERVERBOSE( "Ignoring redundant nannounce for %s" " (last %u, now %u)", @@ -1769,15 +1768,15 @@ bool routing_add_node_announcement(struct routing_state *rstate, node->bcast.timestamp = timestamp; /* remove prior spam update if one exists */ if (node->rgraph.index != node->bcast.index) { - gossip_store_delete(rstate->gs, &node->rgraph, + gossip_store_delete(rstate->daemon->gs, &node->rgraph, WIRE_NODE_ANNOUNCEMENT); } /* Harmless if it was never added */ - gossip_store_delete(rstate->gs, &node->bcast, + gossip_store_delete(rstate->daemon->gs, &node->bcast, WIRE_NODE_ANNOUNCEMENT); /* Remove prior spam update. */ } else if (node->rgraph.index != node->bcast.index) { - gossip_store_delete(rstate->gs, &node->rgraph, + gossip_store_delete(rstate->daemon->gs, &node->rgraph, WIRE_NODE_ANNOUNCEMENT); } @@ -1788,7 +1787,7 @@ bool routing_add_node_announcement(struct routing_state *rstate, node->bcast.index = index; } else { node->rgraph.index - = gossip_store_add(rstate->gs, msg, timestamp, + = gossip_store_add(rstate->daemon->gs, msg, timestamp, false, spam, false, NULL); if (!spam) node->bcast.index = node->rgraph.index; @@ -2020,7 +2019,7 @@ void routing_expire_channels(struct routing_state *rstate, u32 blockheight) if (chan) channel_spent(rstate, chan); /* Delete dying marker itself */ - gossip_store_delete(rstate->gs, + gossip_store_delete(rstate->daemon->gs, &d->marker, WIRE_GOSSIP_STORE_CHAN_DYING); tal_arr_remove(&rstate->dying_channels, i); i--; @@ -2062,14 +2061,14 @@ void routing_channel_spent(struct routing_state *rstate, /* Save to gossip_store in case we restart */ msg = towire_gossip_store_chan_dying(tmpctx, &chan->scid, deadline); - index = gossip_store_add(rstate->gs, msg, 0, false, false, false, NULL); + index = gossip_store_add(rstate->daemon->gs, msg, 0, false, false, false, NULL); /* Mark it dying, so we don't gossip it */ - gossip_store_mark_dying(rstate->gs, &chan->bcast, + gossip_store_mark_dying(rstate->daemon->gs, &chan->bcast, WIRE_CHANNEL_ANNOUNCEMENT); for (int dir = 0; dir < ARRAY_SIZE(chan->half); dir++) { if (is_halfchan_defined(&chan->half[dir])) { - gossip_store_mark_dying(rstate->gs, + gossip_store_mark_dying(rstate->daemon->gs, &chan->half[dir].bcast, WIRE_CHANNEL_UPDATE); } diff --git a/gossipd/routing.h b/gossipd/routing.h index 63ff8d1bda62..c44eef3d3b07 100644 --- a/gossipd/routing.h +++ b/gossipd/routing.h @@ -186,9 +186,6 @@ struct routing_state { /* channel_announcement which are pending short_channel_id lookup */ struct pending_cannouncement_map *pending_cannouncements; - /* Gossip store */ - struct gossip_store *gs; - /* A map of channels indexed by short_channel_ids */ UINTMAP(struct chan *) chanmap; From a7e41bc2c252b573efaf800b50b5134154653538 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 31 Jan 2024 15:03:11 +1030 Subject: [PATCH 16/33] gossipd: remove zombie handling. We never enabled it, because we seemed to be eliminating valid channels. We discard zombie-marked records on loading. Signed-off-by: Rusty Russell --- common/gossip_store.h | 5 - common/gossmap.c | 3 - contrib/pyln-client/pyln/client/gossmap.py | 4 - devtools/dump-gossipstore.c | 6 +- gossipd/gossip_store.c | 82 ++++---------- gossipd/gossip_store.h | 14 +-- gossipd/routing.c | 121 ++------------------- gossipd/routing.h | 8 +- tests/test_gossip.py | 70 +----------- 9 files changed, 37 insertions(+), 276 deletions(-) diff --git a/common/gossip_store.h b/common/gossip_store.h index f1a8525df79a..200c1be6f115 100644 --- a/common/gossip_store.h +++ b/common/gossip_store.h @@ -39,11 +39,6 @@ struct gossip_rcvd_filter; */ #define GOSSIP_STORE_RATELIMIT_BIT 0x2000U -/** - * Bit of flags used to mark a channel announcement as inactive (needs channel updates.) - */ -#define GOSSIP_STORE_ZOMBIE_BIT 0x1000U - /** * Bit of flags used to mark a channel announcement closed (not deleted for 12 blocks) */ diff --git a/common/gossmap.c b/common/gossmap.c index d8e7e3a6b631..0a93e6614553 100644 --- a/common/gossmap.c +++ b/common/gossmap.c @@ -651,9 +651,6 @@ static bool map_catchup(struct gossmap *map, bool *changed) if (flags & GOSSIP_STORE_DELETED_BIT) continue; - if (flags & GOSSIP_STORE_ZOMBIE_BIT) - continue; - /* Partial write, this can happen. */ if (map->map_end + reclen > map->map_size) break; diff --git a/contrib/pyln-client/pyln/client/gossmap.py b/contrib/pyln-client/pyln/client/gossmap.py index 88641cbba617..27567596075a 100755 --- a/contrib/pyln-client/pyln/client/gossmap.py +++ b/contrib/pyln-client/pyln/client/gossmap.py @@ -17,7 +17,6 @@ GOSSIP_STORE_LEN_DELETED_BIT = 0x8000 GOSSIP_STORE_LEN_PUSH_BIT = 0x4000 GOSSIP_STORE_LEN_RATELIMIT_BIT = 0x2000 -GOSSIP_STORE_ZOMBIE_BIT = 0x1000 # These duplicate constants in lightning/gossipd/gossip_store_wiregen.h WIRE_GOSSIP_STORE_PRIVATE_CHANNEL = 4104 @@ -92,7 +91,6 @@ def __init__(self, buf: bytes, off: int): self.off = off self.deleted = (self.flags & GOSSIP_STORE_LEN_DELETED_BIT) != 0 self.ratelimit = (self.flags & GOSSIP_STORE_LEN_RATELIMIT_BIT) != 0 - self.zombie = (self.flags & GOSSIP_STORE_ZOMBIE_BIT) != 0 class GossmapHalfchannel(object): @@ -624,8 +622,6 @@ def refresh(self): break if hdr.deleted: # Skip deleted records continue - if hdr.zombie: - continue rectype, = struct.unpack(">H", rec[:2]) if rectype == channel_announcement.number: diff --git a/devtools/dump-gossipstore.c b/devtools/dump-gossipstore.c index 09c6f3a1e428..dcb5576b48c5 100644 --- a/devtools/dump-gossipstore.c +++ b/devtools/dump-gossipstore.c @@ -68,13 +68,12 @@ int main(int argc, char *argv[]) u16 flags = be16_to_cpu(hdr.flags); u16 msglen = be16_to_cpu(hdr.len); u8 *msg, *inner; - bool deleted, push, ratelimit, zombie, dying; + bool deleted, push, ratelimit, dying; u32 blockheight; deleted = (flags & GOSSIP_STORE_DELETED_BIT); push = (flags & GOSSIP_STORE_PUSH_BIT); ratelimit = (flags & GOSSIP_STORE_RATELIMIT_BIT); - zombie = (flags & GOSSIP_STORE_ZOMBIE_BIT); dying = (flags & GOSSIP_STORE_DYING_BIT); msg = tal_arr(NULL, u8, msglen); @@ -85,11 +84,10 @@ int main(int argc, char *argv[]) != crc32c(be32_to_cpu(hdr.timestamp), msg, msglen)) warnx("Checksum verification failed"); - printf("%zu: %s%s%s%s%s", off, + printf("%zu: %s%s%s%s", off, deleted ? "DELETED " : "", push ? "PUSH " : "", ratelimit ? "RATE-LIMITED " : "", - zombie ? "ZOMBIE " : "", dying ? "DYING " : ""); if (print_timestamp) printf("T=%u ", be32_to_cpu(hdr.timestamp)); diff --git a/gossipd/gossip_store.c b/gossipd/gossip_store.c index 89ad5ba3424d..c344df08f7a9 100644 --- a/gossipd/gossip_store.c +++ b/gossipd/gossip_store.c @@ -15,9 +15,12 @@ #include #include +/* Obsolete ZOMBIE bit */ +#define GOSSIP_STORE_ZOMBIE_BIT_V13 0x1000U + #define GOSSIP_STORE_TEMP_FILENAME "gossip_store.tmp" -/* We write it as major version 0, minor version 13 */ -#define GOSSIP_STORE_VER ((0 << 5) | 13) +/* We write it as major version 0, minor version 14 */ +#define GOSSIP_STORE_VER ((0 << 5) | 14) struct gossip_store { /* Back pointer. */ @@ -59,7 +62,7 @@ static ssize_t gossip_pwritev(int fd, const struct iovec *iov, int iovcnt, #endif /* !HAVE_PWRITEV */ static bool append_msg(int fd, const u8 *msg, u32 timestamp, - bool zombie, bool spam, bool dying, u64 *len) + bool spam, bool dying, u64 *len) { struct gossip_hdr hdr; u32 msglen; @@ -73,8 +76,6 @@ static bool append_msg(int fd, const u8 *msg, u32 timestamp, hdr.flags = 0; if (spam) hdr.flags |= CPU_TO_BE16(GOSSIP_STORE_RATELIMIT_BIT); - if (zombie) - hdr.flags |= CPU_TO_BE16(GOSSIP_STORE_ZOMBIE_BIT); if (dying) hdr.flags |= CPU_TO_BE16(GOSSIP_STORE_DYING_BIT); hdr.crc = cpu_to_be32(crc32c(timestamp, msg, msglen)); @@ -97,10 +98,11 @@ static bool append_msg(int fd, const u8 *msg, u32 timestamp, * v11 mandated channel_updates use the htlc_maximum_msat field * v12 added the zombie flag for expired channel updates * v13 removed private gossip entries + * v14 removed zombie flags */ static bool can_upgrade(u8 oldversion) { - return oldversion >= 9 && oldversion <= 12; + return oldversion >= 9 && oldversion <= 13; } /* On upgrade, do best effort on private channels: hand them to @@ -154,6 +156,7 @@ static void give_lightningd_canned_private_update(struct daemon *daemon, static bool upgrade_field(u8 oldversion, struct daemon *daemon, + u16 hdr_flags, u8 **msg) { int type = fromwire_peektype(*msg); @@ -175,6 +178,12 @@ static bool upgrade_field(u8 oldversion, *msg = tal_free(*msg); } } + if (oldversion <= 13) { + /* Discard any zombies */ + if (hdr_flags & GOSSIP_STORE_ZOMBIE_BIT_V13) { + *msg = tal_free(*msg); + } + } return true; } @@ -250,7 +259,8 @@ static u32 gossip_store_compact_offline(struct daemon *daemon) } if (oldversion != version) { - if (!upgrade_field(oldversion, daemon, &msg)) { + if (!upgrade_field(oldversion, daemon, + be16_to_cpu(hdr.flags), &msg)) { tal_free(msg); goto close_and_delete; } @@ -299,7 +309,7 @@ static u32 gossip_store_compact_offline(struct daemon *daemon) oldlen = lseek(old_fd, SEEK_END, 0); newlen = lseek(new_fd, SEEK_END, 0); append_msg(old_fd, towire_gossip_store_ended(tmpctx, newlen), - 0, false, false, false, &oldlen); + 0, false, false, &oldlen); close(old_fd); status_debug("gossip_store_compact_offline: %zu deleted, %zu copied", deleted, count); @@ -357,7 +367,7 @@ struct gossip_store *gossip_store_new(struct daemon *daemon) } u64 gossip_store_add(struct gossip_store *gs, const u8 *gossip_msg, - u32 timestamp, bool zombie, + u32 timestamp, bool spam, bool dying, const u8 *addendum) { u64 off = gs->len; @@ -365,12 +375,12 @@ u64 gossip_store_add(struct gossip_store *gs, const u8 *gossip_msg, /* Should never get here during loading! */ assert(gs->writable); - if (!append_msg(gs->fd, gossip_msg, timestamp, zombie, spam, dying, &gs->len)) { + if (!append_msg(gs->fd, gossip_msg, timestamp, spam, dying, &gs->len)) { status_broken("Failed writing to gossip store: %s", strerror(errno)); return 0; } - if (addendum && !append_msg(gs->fd, addendum, 0, false, false, false, &gs->len)) { + if (addendum && !append_msg(gs->fd, addendum, 0, false, false, &gs->len)) { status_broken("Failed writing addendum to gossip store: %s", strerror(errno)); return 0; @@ -504,53 +514,7 @@ void gossip_store_mark_channel_deleted(struct gossip_store *gs, const struct short_channel_id *scid) { gossip_store_add(gs, towire_gossip_store_delete_chan(tmpctx, scid), - 0, false, false, false, NULL); -} - -static void mark_zombie(struct gossip_store *gs, - const struct broadcastable *bcast, - enum peer_wire expected_type) -{ - beint16_t beflags; - u32 index = bcast->index; - - /* We assume flags is the first field! */ - BUILD_ASSERT(offsetof(struct gossip_hdr, flags) == 0); - - /* Should never get here during loading! */ - assert(gs->writable); - assert(index); - - const u8 *msg = gossip_store_get(tmpctx, gs, index); - assert(fromwire_peektype(msg) == expected_type); - - if (pread(gs->fd, &beflags, sizeof(beflags), index) != sizeof(beflags)) - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "Failed reading flags to zombie %s @%u: %s", - peer_wire_name(expected_type), - index, strerror(errno)); - - assert((be16_to_cpu(beflags) & GOSSIP_STORE_DELETED_BIT) == 0); - beflags |= cpu_to_be16(GOSSIP_STORE_ZOMBIE_BIT); - if (pwrite(gs->fd, &beflags, sizeof(beflags), index) != sizeof(beflags)) - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "Failed writing flags to zombie %s @%u: %s", - peer_wire_name(expected_type), - index, strerror(errno)); -} - -/* Marks the length field of a channel_announcement with the zombie flag bit */ -void gossip_store_mark_channel_zombie(struct gossip_store *gs, - struct broadcastable *bcast) -{ - mark_zombie(gs, bcast, WIRE_CHANNEL_ANNOUNCEMENT); -} - -/* Marks the length field of a channel_update with the zombie flag bit */ -void gossip_store_mark_cupdate_zombie(struct gossip_store *gs, - struct broadcastable *bcast) -{ - mark_zombie(gs, bcast, WIRE_CHANNEL_UPDATE); + 0, false, false, NULL); } u32 gossip_store_get_timestamp(struct gossip_store *gs, u64 offset) @@ -737,7 +701,7 @@ u32 gossip_store_load(struct gossip_store *gs) if (!routing_add_channel_update(gs->daemon->rstate, take(msg), gs->len, NULL, false, - spam, false)) { + spam)) { bad = "Bad channel_update"; goto badmsg; } diff --git a/gossipd/gossip_store.h b/gossipd/gossip_store.h index f0d95cf0f642..b6e83dce53a4 100644 --- a/gossipd/gossip_store.h +++ b/gossipd/gossip_store.h @@ -32,14 +32,13 @@ u32 gossip_store_load(struct gossip_store *gs); * @gs: gossip store * @gossip_msg: the gossip message to insert. * @timestamp: the timestamp for filtering of this messsage. - * @zombie: true if this channel is missing a current channel_update. * @spam: true if this message is rate-limited and squelched to peers. * @dying: true if this message is for a dying channel. * @addendum: another message to append immediately after this * (for appending amounts to channel_announcements for internal use). */ u64 gossip_store_add(struct gossip_store *gs, const u8 *gossip_msg, - u32 timestamp, bool zombie, bool spam, bool dying, + u32 timestamp, bool spam, bool dying, const u8 *addendum); @@ -80,17 +79,6 @@ void gossip_store_flag(struct gossip_store *gs, void gossip_store_mark_channel_deleted(struct gossip_store *gs, const struct short_channel_id *scid); -/* - * Marks the length field of a channel announcement with a zombie flag bit. - * This allows the channel_announcement to be retained in the store while - * waiting for channel updates to reactivate it. - */ -void gossip_store_mark_channel_zombie(struct gossip_store *gs, - struct broadcastable *bcast); - -void gossip_store_mark_cupdate_zombie(struct gossip_store *gs, - struct broadcastable *bcast); - /** * Mark this channel_announcement/channel_update as dying. * diff --git a/gossipd/routing.c b/gossipd/routing.c index 0779fa5c6d49..7d729e173301 100644 --- a/gossipd/routing.c +++ b/gossipd/routing.c @@ -312,25 +312,6 @@ static struct node *new_node(struct routing_state *rstate, return n; } -static bool is_chan_zombie(struct chan *chan) -{ - if (chan->half[0].zombie || chan->half[1].zombie) - return true; - return false; -} - -static bool is_node_zombie(struct node* node) -{ - struct chan_map_iter i; - struct chan *c; - - for (c = first_chan(node, &i); c; c = next_chan(node, &i)) { - if (!is_chan_zombie(c)) - return false; - } - return true; -} - /* We can *send* a channel_announce for a channel attached to this node: * we only send once we have a channel_update. */ bool node_has_broadcastable_channels(const struct node *node) @@ -339,8 +320,6 @@ bool node_has_broadcastable_channels(const struct node *node) struct chan *c; for (c = first_chan(node, &i); c; c = next_chan(node, &i)) { - if (is_chan_zombie(c)) - continue; if (is_halfchan_defined(&c->half[0]) || is_halfchan_defined(&c->half[1])) return true; @@ -354,10 +333,6 @@ static bool node_announce_predates_channels(const struct node *node) struct chan *c; for (c = first_chan(node, &i); c; c = next_chan(node, &i)) { - /* Zombies don't count! */ - if (is_chan_zombie(c)) - continue; - if (c->bcast.index < node->bcast.index) return false; } @@ -381,7 +356,6 @@ static void force_node_announce_rexmit(struct routing_state *rstate, node->bcast.timestamp, false, false, - false, NULL); if (node->rgraph.index == initial_bcast_index){ node->rgraph.index = node->bcast.index; @@ -393,7 +367,6 @@ static void force_node_announce_rexmit(struct routing_state *rstate, node->rgraph.index = gossip_store_add(rstate->daemon->gs, announce, node->rgraph.timestamp, - false, true, false, NULL); @@ -490,7 +463,6 @@ static void init_half_chan(struct routing_state *rstate, broadcastable_init(&c->bcast); broadcastable_init(&c->rgraph); c->tokens = TOKEN_MAX; - c->zombie = false; } static void bad_gossip_order(const u8 *msg, @@ -677,7 +649,6 @@ static void add_channel_announce_to_broadcast(struct routing_state *rstate, chan->bcast.timestamp, false, false, - false, addendum); } @@ -702,9 +673,8 @@ static void delete_chan_messages_from_store(struct routing_state *rstate, static void remove_channel_from_store(struct routing_state *rstate, struct chan *chan) { - /* Put in tombstone marker. Zombie channels will have one already. */ - if (!is_chan_zombie(chan)) - gossip_store_mark_channel_deleted(rstate->daemon->gs, &chan->scid); + /* Put in tombstone marker. */ + gossip_store_mark_channel_deleted(rstate->daemon->gs, &chan->scid); /* Now delete old entries. */ delete_chan_messages_from_store(rstate, chan); @@ -1115,8 +1085,7 @@ bool routing_add_channel_update(struct routing_state *rstate, /* NULL if it's us */ const struct node_id *source_peer, bool ignore_timestamp, - bool force_spam_flag, - bool force_zombie_flag) + bool force_spam_flag) { secp256k1_ecdsa_signature signature; struct short_channel_id short_channel_id; @@ -1133,7 +1102,6 @@ bool routing_add_channel_update(struct routing_state *rstate, u8 direction; struct amount_sat sat; bool spam; - bool zombie; bool dying; /* Make sure we own msg, even if we don't save it. */ @@ -1155,7 +1123,6 @@ bool routing_add_channel_update(struct routing_state *rstate, if (chan) { uc = NULL; sat = chan->sat; - zombie = is_chan_zombie(chan); dying = is_chan_dying(rstate, &short_channel_id); } else { /* Maybe announcement was waiting for this update? */ @@ -1172,8 +1139,6 @@ bool routing_add_channel_update(struct routing_state *rstate, return false; } sat = uc->sat; - /* When loading zombies from the store. */ - zombie = force_zombie_flag; dying = false; } @@ -1197,9 +1162,6 @@ bool routing_add_channel_update(struct routing_state *rstate, assert(!chan); chan = new_chan(rstate, &short_channel_id, &uc->id[0], &uc->id[1], sat); - /* Assign zombie flag if loading zombie from store */ - if (force_zombie_flag) - chan->half[direction].zombie = true; } /* Discard older updates */ @@ -1302,68 +1264,6 @@ bool routing_add_channel_update(struct routing_state *rstate, uc->index); } - /* Handle resurrection of zombie channels if the other side of the - * zombie channel has a recent timestamp. */ - if (zombie && timestamp_reasonable(rstate->daemon, - chan->half[!direction].bcast.timestamp) && - chan->half[!direction].bcast.index && !index) { - status_peer_debug(source_peer, - "Resurrecting zombie channel %s.", - type_to_string(tmpctx, - struct short_channel_id, - &chan->scid)); - const u8 *zombie_announcement = NULL; - const u8 *zombie_addendum = NULL; - const u8 *zombie_update[2] = {NULL, NULL}; - /* Resurrection is a careful process. First delete the zombie- - * flagged channel_announcement which has already been - * tombstoned, and re-add to the store without zombie flag. */ - zombie_announcement = gossip_store_get(tmpctx, rstate->daemon->gs, - chan->bcast.index); - u32 offset = tal_count(zombie_announcement) + - sizeof(struct gossip_hdr); - /* The channel_announcement addendum reminds us of its size. */ - zombie_addendum = gossip_store_get(tmpctx, rstate->daemon->gs, - chan->bcast.index + offset); - gossip_store_delete(rstate->daemon->gs, &chan->bcast, - WIRE_CHANNEL_ANNOUNCEMENT); - chan->bcast.index = - gossip_store_add(rstate->daemon->gs, zombie_announcement, - chan->bcast.timestamp, - false, false, false, zombie_addendum); - /* Deletion of the old addendum is optional. */ - /* This opposing channel_update has been stashed away. Now that - * there are two valid updates, this one gets restored. */ - /* FIXME: Handle spam case probably needs a helper f'n */ - zombie_update[0] = gossip_store_get(tmpctx, rstate->daemon->gs, - chan->half[!direction].bcast.index); - if (chan->half[!direction].bcast.index != chan->half[!direction].rgraph.index) { - /* Don't forget the spam channel_update */ - zombie_update[1] = gossip_store_get(tmpctx, rstate->daemon->gs, - chan->half[!direction].rgraph.index); - gossip_store_delete(rstate->daemon->gs, &chan->half[!direction].rgraph, - WIRE_CHANNEL_UPDATE); - } - gossip_store_delete(rstate->daemon->gs, &chan->half[!direction].bcast, - WIRE_CHANNEL_UPDATE); - chan->half[!direction].bcast.index = - gossip_store_add(rstate->daemon->gs, zombie_update[0], - chan->half[!direction].bcast.timestamp, - false, false, false, NULL); - if (zombie_update[1]) - chan->half[!direction].rgraph.index = - gossip_store_add(rstate->daemon->gs, zombie_update[1], - chan->half[!direction].rgraph.timestamp, - false, true, false, NULL); - else - chan->half[!direction].rgraph.index = chan->half[!direction].bcast.index; - - /* It's a miracle! */ - chan->half[0].zombie = false; - chan->half[1].zombie = false; - zombie = false; - } - /* If we're loading from store, this means we don't re-add to store. */ if (index) { if (!spam) @@ -1372,7 +1272,7 @@ bool routing_add_channel_update(struct routing_state *rstate, } else { hc->rgraph.index = gossip_store_add(rstate->daemon->gs, update, timestamp, - zombie, spam, dying, NULL); + spam, dying, NULL); if (!spam) hc->bcast.index = hc->rgraph.index; @@ -1540,7 +1440,7 @@ u8 *handle_channel_update(struct routing_state *rstate, const u8 *update TAKES, } routing_add_channel_update(rstate, take(serialized), 0, source_peer, force, - false, false); + false); return NULL; } @@ -1674,8 +1574,7 @@ bool routing_add_node_announcement(struct routing_state *rstate, if (!pna) { if (was_unknown) *was_unknown = true; - /* Don't complain if it's a zombie node! */ - if (!node || !is_node_zombie(node)) { + if (!node) { bad_gossip_order(msg, source_peer, type_to_string(tmpctx, struct node_id, &node_id)); @@ -1788,7 +1687,7 @@ bool routing_add_node_announcement(struct routing_state *rstate, } else { node->rgraph.index = gossip_store_add(rstate->daemon->gs, msg, timestamp, - false, spam, false, NULL); + spam, false, NULL); if (!spam) node->bcast.index = node->rgraph.index; @@ -1882,10 +1781,6 @@ void route_prune(struct routing_state *rstate) for (struct chan *chan = uintmap_first(&rstate->chanmap, &idx); chan; chan = uintmap_after(&rstate->chanmap, &idx)) { - /* These have been pruned already */ - if (is_chan_zombie(chan)) - continue; - /* BOLT #7: * - if the `timestamp` of the latest `channel_update` in * either direction is older than two weeks (1209600 seconds): @@ -2061,7 +1956,7 @@ void routing_channel_spent(struct routing_state *rstate, /* Save to gossip_store in case we restart */ msg = towire_gossip_store_chan_dying(tmpctx, &chan->scid, deadline); - index = gossip_store_add(rstate->daemon->gs, msg, 0, false, false, false, NULL); + index = gossip_store_add(rstate->daemon->gs, msg, 0, false, false, NULL); /* Mark it dying, so we don't gossip it */ gossip_store_mark_dying(rstate->daemon->gs, &chan->bcast, diff --git a/gossipd/routing.h b/gossipd/routing.h index c44eef3d3b07..85329c6f8597 100644 --- a/gossipd/routing.h +++ b/gossipd/routing.h @@ -30,9 +30,6 @@ struct half_chan { /* Token bucket */ u8 tokens; - - /* Disabled channel waiting for a channel_update from both sides. */ - bool zombie; }; struct chan { @@ -319,8 +316,7 @@ bool routing_add_channel_update(struct routing_state *rstate, u32 index, const struct node_id *source_peer TAKES, bool ignore_timestamp, - bool force_spam_flag, - bool force_zombie_flag); + bool force_spam_flag); /** * Add a node_announcement to the network view without checking it * @@ -364,7 +360,7 @@ bool would_ratelimit_cupdate(struct routing_state *rstate, const struct half_chan *hc, u32 timestamp); -/* Does this node have public, non-zombie channels? */ +/* Does this node have public channels? */ bool node_has_broadcastable_channels(const struct node *node); /* Returns an error string if there are unfinalized entries after load */ diff --git a/tests/test_gossip.py b/tests/test_gossip.py index a11fd86ed1d2..26a24fa9039c 100644 --- a/tests/test_gossip.py +++ b/tests/test_gossip.py @@ -1616,7 +1616,7 @@ def test_gossip_store_load_no_channel_update(node_factory): # This should actually result in an empty store. with open(os.path.join(l1.daemon.lightning_dir, TEST_NETWORK, 'gossip_store'), "rb") as f: - assert bytearray(f.read()) == bytearray.fromhex("0d") + assert bytearray(f.read()) == bytearray.fromhex("0e") def test_gossip_store_compact_on_load(node_factory, bitcoind): @@ -2094,74 +2094,6 @@ def get_gossip(node): assert len(get_gossip(l1)) == 2 -@pytest.mark.skip("Zombie research had unexpected side effects") -def test_channel_resurrection(node_factory, bitcoind): - """When a node goes offline long enough to prune a channel, the - channel_announcement should be retained in case the node comes back online. - """ - opts = {'dev-fast-gossip-prune': None, - 'may_reconnect': True} - l1, l2 = node_factory.get_nodes(2, opts=opts) - opts.update({'log-level': 'debug'}) - l3, = node_factory.get_nodes(1, opts=opts) - l1.rpc.connect(l2.info['id'], 'localhost', l2.port) - l3.rpc.connect(l2.info['id'], 'localhost', l2.port) - scid, _ = l1.fundchannel(l2, 10**6, True, True) - bitcoind.generate_block(6) - sync_blockheight(bitcoind, [l1, l2, l3]) - l3.wait_channel_active(scid) - start_time = int(time.time()) - # Channel_update should now be refreshed. - refresh_due = start_time + 44 - prune_due = start_time + 61 - l2.rpc.call('dev-gossip-set-time', [refresh_due]) - l3.rpc.call('dev-gossip-set-time', [refresh_due]) - # Automatic reconnect is too fast, so shutdown l1 instead of disconnecting - l1.stop() - l2.daemon.wait_for_log('Sending keepalive channel_update') - l3.daemon.wait_for_log('Received channel_update for channel 103x1') - # Wait for the next pruning cycle - l2.rpc.call('dev-gossip-set-time', [prune_due]) - l3.rpc.call('dev-gossip-set-time', [prune_due]) - # Make sure l1 is recognized as disconnected - wait_for(lambda: only_one(l2.rpc.listpeers(l1.info['id'])['peers'])['connected'] is False) - # Wait for the channel to be pruned. - l3.daemon.wait_for_log("Pruning channel") - assert l3.rpc.listchannels()['channels'] == [] - l1.start() - time.sleep(1) - l1.rpc.call('dev-gossip-set-time', [prune_due]) - time.sleep(1) - l1.rpc.call('dev-gossip-set-time', [prune_due]) - wait_for(lambda: [c['active'] for c in l2.rpc.listchannels()['channels']] == [True, True]) - l1.rpc.call('dev-gossip-set-time', [prune_due + 30]) - l2.rpc.call('dev-gossip-set-time', [prune_due + 30]) - l3.rpc.call('dev-gossip-set-time', [prune_due + 30]) - # l2 should recognize its own channel as announceable - wait_for(lambda: [[c['public'], c['active']] for c in l2.rpc.listchannels()['channels']] == [[True, True], [True, True]], timeout=30) - # l3 should be able to recover the zombie channel - wait_for(lambda: [c['active'] for c in l3.rpc.listchannels()['channels']] == [True, True], timeout=30) - - # Now test spending the outpoint and removing a zombie channel from the store. - l2.stop() - prune_again = prune_due + 91 - l1.rpc.call('dev-gossip-set-time', [prune_again]) - l3.rpc.call('dev-gossip-set-time', [prune_again]) - l3.daemon.wait_for_log("Pruning channel") - txid = l1.rpc.close(l2.info['id'], 1)['txid'] - bitcoind.generate_block(13, txid) - l3.daemon.wait_for_log(f"Deleting channel {scid} due to the funding " - "outpoint being spent", 30) - # gossip_store is cleaned of zombie channels once outpoint is spent. - gs_path = os.path.join(l3.daemon.lightning_dir, TEST_NETWORK, 'gossip_store') - gs = subprocess.run(['devtools/dump-gossipstore', '--print-deleted', gs_path], - check=True, timeout=TIMEOUT, stdout=subprocess.PIPE) - print(gs.stdout.decode()) - for l in gs.stdout.decode().splitlines(): - if "ZOMBIE" in l: - assert ("DELETED" in l) - - def test_dump_own_gossip(node_factory): """We *should* send all self-related gossip unsolicited, if we have any""" l1, l2 = node_factory.line_graph(2, wait_for_announce=True) From 0f9845be015c7d9c29698402af496f357d3c8c60 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 31 Jan 2024 15:03:11 +1030 Subject: [PATCH 17/33] gossipd: remove spam handling. We weakened this progressively over time, and gossip v1.5 makes spam impossible by protocol, so we can wait until then. Removing this code simplifies things a great deal! Signed-off-by: Rusty Russell Changelog-Removed: Protocol: we no longer ratelimit gossip messages by channel, making our code far simpler. --- common/gossip_store.h | 5 - connectd/gossip_store.c | 5 - connectd/gossip_store.h | 1 - connectd/multiplex.c | 1 - contrib/pyln-client/pyln/client/gossmap.py | 1 - .../pyln-client/pyln/client/gossmapstats.py | 8 - devtools/dump-gossipstore.c | 6 +- .../contribute-to-core-lightning.md | 6 +- gossipd/gossip_store.c | 24 +- gossipd/gossip_store.h | 3 +- gossipd/routing.c | 190 +++------------ gossipd/routing.h | 6 +- tests/test_gossip.py | 218 ------------------ 13 files changed, 53 insertions(+), 421 deletions(-) diff --git a/common/gossip_store.h b/common/gossip_store.h index 200c1be6f115..eb7e9c25473b 100644 --- a/common/gossip_store.h +++ b/common/gossip_store.h @@ -34,11 +34,6 @@ struct gossip_rcvd_filter; */ #define GOSSIP_STORE_PUSH_BIT 0x4000U -/** - * Bit of flags used to define a rate-limited record (do not rebroadcast) - */ -#define GOSSIP_STORE_RATELIMIT_BIT 0x2000U - /** * Bit of flags used to mark a channel announcement closed (not deleted for 12 blocks) */ diff --git a/connectd/gossip_store.c b/connectd/gossip_store.c index ca1fdd4deec5..c3e94dbf7bd1 100644 --- a/connectd/gossip_store.c +++ b/connectd/gossip_store.c @@ -112,7 +112,6 @@ static bool public_msg_type(enum peer_wire type) u8 *gossip_store_next(const tal_t *ctx, int *gossip_store_fd, u32 timestamp_min, u32 timestamp_max, - bool with_spam, size_t *off, size_t *end) { u8 *msg = NULL; @@ -122,7 +121,6 @@ u8 *gossip_store_next(const tal_t *ctx, struct gossip_hdr hdr; u16 msglen, flags; u32 checksum, timestamp; - bool ratelimited; int type, r; r = pread(*gossip_store_fd, &hdr, sizeof(hdr), *off); @@ -131,7 +129,6 @@ u8 *gossip_store_next(const tal_t *ctx, msglen = be16_to_cpu(hdr.len); flags = be16_to_cpu(hdr.flags); - ratelimited = (flags & GOSSIP_STORE_RATELIMIT_BIT); /* Skip any deleted/dying entries. */ if (flags & (GOSSIP_STORE_DELETED_BIT|GOSSIP_STORE_DYING_BIT)) { @@ -172,8 +169,6 @@ u8 *gossip_store_next(const tal_t *ctx, /* Ignore gossipd internal messages. */ } else if (!public_msg_type(type)) { msg = tal_free(msg); - } else if (!with_spam && ratelimited) { - msg = tal_free(msg); } } diff --git a/connectd/gossip_store.h b/connectd/gossip_store.h index 12cd1d6c19eb..4067d414148f 100644 --- a/connectd/gossip_store.h +++ b/connectd/gossip_store.h @@ -13,7 +13,6 @@ u8 *gossip_store_next(const tal_t *ctx, int *gossip_store_fd, u32 timestamp_min, u32 timestamp_max, - bool with_spam, size_t *off, size_t *end); /** diff --git a/connectd/multiplex.c b/connectd/multiplex.c index 3c50a2117ddd..72b160353059 100644 --- a/connectd/multiplex.c +++ b/connectd/multiplex.c @@ -509,7 +509,6 @@ static u8 *maybe_from_gossip_store(const tal_t *ctx, struct peer *peer) msg = gossip_store_next(ctx, &peer->daemon->gossip_store_fd, peer->gs.timestamp_min, peer->gs.timestamp_max, - false, &peer->gs.off, &peer->daemon->gossip_store_end); /* Don't send back gossip they sent to us! */ diff --git a/contrib/pyln-client/pyln/client/gossmap.py b/contrib/pyln-client/pyln/client/gossmap.py index 27567596075a..634a42d63603 100755 --- a/contrib/pyln-client/pyln/client/gossmap.py +++ b/contrib/pyln-client/pyln/client/gossmap.py @@ -16,7 +16,6 @@ GOSSIP_STORE_MAJOR_VERSION_MASK = 0xE0 GOSSIP_STORE_LEN_DELETED_BIT = 0x8000 GOSSIP_STORE_LEN_PUSH_BIT = 0x4000 -GOSSIP_STORE_LEN_RATELIMIT_BIT = 0x2000 # These duplicate constants in lightning/gossipd/gossip_store_wiregen.h WIRE_GOSSIP_STORE_PRIVATE_CHANNEL = 4104 diff --git a/contrib/pyln-client/pyln/client/gossmapstats.py b/contrib/pyln-client/pyln/client/gossmapstats.py index 5479e2dc908b..18ea04286f9a 100644 --- a/contrib/pyln-client/pyln/client/gossmapstats.py +++ b/contrib/pyln-client/pyln/client/gossmapstats.py @@ -31,10 +31,6 @@ def filter_halfchannels(self, predicate: Callable[[GossmapHalfchannel], bool], c return hc0 + hc1 # Now a bunch of predefined specific filter methods - def filter_nodes_ratelimited(self, nodes: Optional[Iterable[GossmapNode]] = None) -> List[GossmapNode]: - """ Filters nodes being marked by cln as ratelimited, when they send out too many updates. """ - return self.filter_nodes(lambda n: n.hdr is not None and n.hdr.ratelimit, nodes) - def filter_nodes_unannounced(self, nodes: Optional[Iterable[GossmapNode]] = None) -> List[GossmapNode]: """ Filters nodes that are only known by a channel, i.e. missing a node_announcement. Usually happens when a peer has been offline for a while. """ @@ -124,10 +120,6 @@ def filter_halfchannels_disabled(self, channels: Optional[Iterable[GossmapChanne """ Filters half-channels that are disabled. """ return self.filter_halfchannels(lambda hc: hc.disabled, channels) - def filter_halfchannels_ratelimited(self, channels: Optional[Iterable[GossmapChannel]] = None) -> List[GossmapHalfchannel]: - """ Filters half-channels that are being marked as ratelimited for sending out too many updates. """ - return self.filter_halfchannels(lambda hc: hc.hdr.ratelimit, channels) - def quantiles_nodes_channel_count(self, tiles=100, nodes: Optional[Iterable[GossmapNode]] = None) -> List[float]: if nodes is None: nodes = self.g.nodes.values() diff --git a/devtools/dump-gossipstore.c b/devtools/dump-gossipstore.c index dcb5576b48c5..a7fae9145d8e 100644 --- a/devtools/dump-gossipstore.c +++ b/devtools/dump-gossipstore.c @@ -68,12 +68,11 @@ int main(int argc, char *argv[]) u16 flags = be16_to_cpu(hdr.flags); u16 msglen = be16_to_cpu(hdr.len); u8 *msg, *inner; - bool deleted, push, ratelimit, dying; + bool deleted, push, dying; u32 blockheight; deleted = (flags & GOSSIP_STORE_DELETED_BIT); push = (flags & GOSSIP_STORE_PUSH_BIT); - ratelimit = (flags & GOSSIP_STORE_RATELIMIT_BIT); dying = (flags & GOSSIP_STORE_DYING_BIT); msg = tal_arr(NULL, u8, msglen); @@ -84,10 +83,9 @@ int main(int argc, char *argv[]) != crc32c(be32_to_cpu(hdr.timestamp), msg, msglen)) warnx("Checksum verification failed"); - printf("%zu: %s%s%s%s", off, + printf("%zu: %s%s%s", off, deleted ? "DELETED " : "", push ? "PUSH " : "", - ratelimit ? "RATE-LIMITED " : "", dying ? "DYING " : ""); if (print_timestamp) printf("T=%u ", be32_to_cpu(hdr.timestamp)); diff --git a/doc/contribute-to-core-lightning/contribute-to-core-lightning.md b/doc/contribute-to-core-lightning/contribute-to-core-lightning.md index c5506b4b374c..5e83adc3428a 100644 --- a/doc/contribute-to-core-lightning/contribute-to-core-lightning.md +++ b/doc/contribute-to-core-lightning/contribute-to-core-lightning.md @@ -160,7 +160,7 @@ The flags currently defined are: ``` #define DELETED 0x8000 #define PUSH 0x4000 -#define RATELIMIT 0x2000 +#define DYING 0x0800 ``` @@ -169,7 +169,7 @@ Deleted fields should be ignored: on restart, they will be removed as the gossip The push flag indicates gossip which is generated locally: this is important for gossip timestamp filtering, where peers request gossip and we always send our own gossip messages even if the timestamp wasn't within their request. -The ratelimit flag indicates that this gossip message came too fast: we record it, but don't relay it to peers. +The dying flag indicates that this channel has been spent, but we keep it around for 12 blocks in case it's actually a splice. Other flags should be ignored. @@ -234,4 +234,4 @@ This is placed in the gossip_store file when a funding transaction is spent. `b If you are keeping the file open to watch for changes: - The file is append-only, so you can simply try reading more records using inotify (or equivalent) or simply checking every few seconds. -- If you see a `gossip_store_ended` message, reopen the file. \ No newline at end of file +- If you see a `gossip_store_ended` message, reopen the file. diff --git a/gossipd/gossip_store.c b/gossipd/gossip_store.c index c344df08f7a9..1c34d3e95140 100644 --- a/gossipd/gossip_store.c +++ b/gossipd/gossip_store.c @@ -62,7 +62,7 @@ static ssize_t gossip_pwritev(int fd, const struct iovec *iov, int iovcnt, #endif /* !HAVE_PWRITEV */ static bool append_msg(int fd, const u8 *msg, u32 timestamp, - bool spam, bool dying, u64 *len) + bool dying, u64 *len) { struct gossip_hdr hdr; u32 msglen; @@ -74,8 +74,6 @@ static bool append_msg(int fd, const u8 *msg, u32 timestamp, msglen = tal_count(msg); hdr.len = cpu_to_be16(msglen); hdr.flags = 0; - if (spam) - hdr.flags |= CPU_TO_BE16(GOSSIP_STORE_RATELIMIT_BIT); if (dying) hdr.flags |= CPU_TO_BE16(GOSSIP_STORE_DYING_BIT); hdr.crc = cpu_to_be32(crc32c(timestamp, msg, msglen)); @@ -98,7 +96,7 @@ static bool append_msg(int fd, const u8 *msg, u32 timestamp, * v11 mandated channel_updates use the htlc_maximum_msat field * v12 added the zombie flag for expired channel updates * v13 removed private gossip entries - * v14 removed zombie flags + * v14 removed zombie and spam flags */ static bool can_upgrade(u8 oldversion) { @@ -309,7 +307,7 @@ static u32 gossip_store_compact_offline(struct daemon *daemon) oldlen = lseek(old_fd, SEEK_END, 0); newlen = lseek(new_fd, SEEK_END, 0); append_msg(old_fd, towire_gossip_store_ended(tmpctx, newlen), - 0, false, false, &oldlen); + 0, false, &oldlen); close(old_fd); status_debug("gossip_store_compact_offline: %zu deleted, %zu copied", deleted, count); @@ -368,19 +366,19 @@ struct gossip_store *gossip_store_new(struct daemon *daemon) u64 gossip_store_add(struct gossip_store *gs, const u8 *gossip_msg, u32 timestamp, - bool spam, bool dying, const u8 *addendum) + bool dying, const u8 *addendum) { u64 off = gs->len; /* Should never get here during loading! */ assert(gs->writable); - if (!append_msg(gs->fd, gossip_msg, timestamp, spam, dying, &gs->len)) { + if (!append_msg(gs->fd, gossip_msg, timestamp, dying, &gs->len)) { status_broken("Failed writing to gossip store: %s", strerror(errno)); return 0; } - if (addendum && !append_msg(gs->fd, addendum, 0, false, false, &gs->len)) { + if (addendum && !append_msg(gs->fd, addendum, 0, false, &gs->len)) { status_broken("Failed writing addendum to gossip store: %s", strerror(errno)); return 0; @@ -514,7 +512,7 @@ void gossip_store_mark_channel_deleted(struct gossip_store *gs, const struct short_channel_id *scid) { gossip_store_add(gs, towire_gossip_store_delete_chan(tmpctx, scid), - 0, false, false, NULL); + 0, false, NULL); } u32 gossip_store_get_timestamp(struct gossip_store *gs, u64 offset) @@ -632,8 +630,6 @@ u32 gossip_store_load(struct gossip_store *gs) gs->writable = false; while (pread(gs->fd, &hdr, sizeof(hdr), gs->len) == sizeof(hdr)) { - bool spam; - msglen = be16_to_cpu(hdr.len); checksum = be32_to_cpu(hdr.crc); msg = tal_arr(tmpctx, u8, msglen); @@ -654,7 +650,6 @@ u32 gossip_store_load(struct gossip_store *gs) deleted++; goto next; } - spam = (be16_to_cpu(hdr.flags) & GOSSIP_STORE_RATELIMIT_BIT); switch (fromwire_peektype(msg)) { case WIRE_GOSSIP_STORE_CHANNEL_AMOUNT: @@ -700,8 +695,7 @@ u32 gossip_store_load(struct gossip_store *gs) case WIRE_CHANNEL_UPDATE: if (!routing_add_channel_update(gs->daemon->rstate, take(msg), gs->len, - NULL, false, - spam)) { + NULL, false)) { bad = "Bad channel_update"; goto badmsg; } @@ -710,7 +704,7 @@ u32 gossip_store_load(struct gossip_store *gs) case WIRE_NODE_ANNOUNCEMENT: if (!routing_add_node_announcement(gs->daemon->rstate, take(msg), gs->len, - NULL, NULL, spam)) { + NULL, NULL)) { /* FIXME: This has been reported: routing.c * has logged, so ignore. */ break; diff --git a/gossipd/gossip_store.h b/gossipd/gossip_store.h index b6e83dce53a4..0f1c9060429a 100644 --- a/gossipd/gossip_store.h +++ b/gossipd/gossip_store.h @@ -32,13 +32,12 @@ u32 gossip_store_load(struct gossip_store *gs); * @gs: gossip store * @gossip_msg: the gossip message to insert. * @timestamp: the timestamp for filtering of this messsage. - * @spam: true if this message is rate-limited and squelched to peers. * @dying: true if this message is for a dying channel. * @addendum: another message to append immediately after this * (for appending amounts to channel_announcements for internal use). */ u64 gossip_store_add(struct gossip_store *gs, const u8 *gossip_msg, - u32 timestamp, bool spam, bool dying, + u32 timestamp, bool dying, const u8 *addendum); diff --git a/gossipd/routing.c b/gossipd/routing.c index 7d729e173301..526e38a12445 100644 --- a/gossipd/routing.c +++ b/gossipd/routing.c @@ -73,19 +73,6 @@ static u8 update_tokens(const struct routing_state *rstate, return num_tokens; } -static bool ratelimit(const struct routing_state *rstate, - u8 *tokens, u32 prev_timestamp, u32 new_timestamp) -{ - *tokens = update_tokens(rstate, *tokens, prev_timestamp, new_timestamp); - - /* Now, if we can afford it, pass this message. */ - if (*tokens >= TOKENS_PER_MSG) { - *tokens -= TOKENS_PER_MSG; - return true; - } - return false; -} - static const struct node_id * pending_node_announce_keyof(const struct pending_node_announce *a) { @@ -355,7 +342,6 @@ static void force_node_announce_rexmit(struct routing_state *rstate, announce, node->bcast.timestamp, false, - false, NULL); if (node->rgraph.index == initial_bcast_index){ node->rgraph.index = node->bcast.index; @@ -367,7 +353,6 @@ static void force_node_announce_rexmit(struct routing_state *rstate, node->rgraph.index = gossip_store_add(rstate->daemon->gs, announce, node->rgraph.timestamp, - true, false, NULL); } @@ -583,29 +568,12 @@ static void process_pending_node_announcement(struct routing_state *rstate, if (!routing_add_node_announcement(rstate, pna->node_announcement, pna->index, - pna->source_peer, NULL, - false)) + pna->source_peer, NULL)) status_unusual("pending node_announcement %s too old?", tal_hex(tmpctx, pna->node_announcement)); /* Never send this again. */ pna->node_announcement = tal_free(pna->node_announcement); } - if (pna->spam.node_announcement) { - SUPERVERBOSE( - "Processing deferred node_announcement for node %s", - type_to_string(pna, struct node_id, nodeid)); - - /* Can fail it timestamp is now too old */ - if (!routing_add_node_announcement(rstate, - pna->spam.node_announcement, - pna->spam.index, - NULL, NULL, - true)) - status_unusual("pending node_announcement %s too old?", - tal_hex(tmpctx, pna->spam.node_announcement)); - /* Never send this again. */ - pna->spam.node_announcement = tal_free(pna->spam.node_announcement); - } /* We don't need to catch any more node_announcements, since we've * accepted the public channel now. But other pending announcements @@ -648,7 +616,6 @@ static void add_channel_announce_to_broadcast(struct routing_state *rstate, channel_announce, chan->bcast.timestamp, false, - false, addendum); } @@ -1084,8 +1051,7 @@ bool routing_add_channel_update(struct routing_state *rstate, u32 index, /* NULL if it's us */ const struct node_id *source_peer, - bool ignore_timestamp, - bool force_spam_flag) + bool ignore_timestamp) { secp256k1_ecdsa_signature signature; struct short_channel_id short_channel_id; @@ -1101,7 +1067,6 @@ bool routing_add_channel_update(struct routing_state *rstate, struct unupdated_channel *uc; u8 direction; struct amount_sat sat; - bool spam; bool dying; /* Make sure we own msg, even if we don't save it. */ @@ -1168,20 +1133,12 @@ bool routing_add_channel_update(struct routing_state *rstate, hc = &chan->half[direction]; if (is_halfchan_defined(hc) && !ignore_timestamp) { - /* The gossip_store should contain a single broadcastable entry - * and potentially one rate-limited entry. Any more is a bug */ - if (index){ - if (!force_spam_flag){ - status_broken("gossip_store broadcastable " + /* The gossip_store should contain a single broadcastable entry: any more is a bug */ + if (index) { + status_broken("gossip_store broadcastable " "channel_update %u replaces %u!", index, hc->bcast.index); - return false; - } else if (hc->bcast.index != hc->rgraph.index){ - status_broken("gossip_store rate-limited " - "channel_update %u replaces %u!", - index, hc->rgraph.index); - return false; - } + return false; } if (timestamp <= hc->rgraph.timestamp) { @@ -1202,39 +1159,16 @@ bool routing_add_channel_update(struct routing_state *rstate, /* Ignoring != failing */ return true; } - - /* Make sure it's not spamming us */ - if (!local_direction(rstate, chan, NULL) - && !ratelimit(rstate, - &hc->tokens, hc->bcast.timestamp, timestamp)) { - status_peer_debug(source_peer, - "Spammy update for %s/%u flagged" - " (last %u, now %u)", - type_to_string(tmpctx, - struct short_channel_id, - &short_channel_id), - direction, - hc->bcast.timestamp, timestamp); - spam = true; - } else { - spam = false; - } - } else { - spam = false; } - if (force_spam_flag) - spam = true; /* Delete any prior entries (noop if they don't exist) */ delete_spam_update(rstate, hc); - if (!spam) - gossip_store_delete(rstate->daemon->gs, &hc->bcast, - WIRE_CHANNEL_UPDATE); + gossip_store_delete(rstate->daemon->gs, &hc->bcast, + WIRE_CHANNEL_UPDATE); /* Update timestamp(s) */ hc->rgraph.timestamp = timestamp; - if (!spam) - hc->bcast.timestamp = timestamp; + hc->bcast.timestamp = timestamp; /* If this is a peer's update to one of our local channels, tell lightningd. */ if (node_id_eq(&chan->nodes[!direction]->id, &rstate->daemon->id)) { @@ -1266,15 +1200,13 @@ bool routing_add_channel_update(struct routing_state *rstate, /* If we're loading from store, this means we don't re-add to store. */ if (index) { - if (!spam) - hc->bcast.index = index; + hc->bcast.index = index; hc->rgraph.index = index; } else { hc->rgraph.index = gossip_store_add(rstate->daemon->gs, update, timestamp, - spam, dying, NULL); - if (!spam) - hc->bcast.index = hc->rgraph.index; + dying, NULL); + hc->bcast.index = hc->rgraph.index; peer_supplied_good_gossip(rstate->daemon, source_peer, 1); } @@ -1439,8 +1371,7 @@ u8 *handle_channel_update(struct routing_state *rstate, const u8 *update TAKES, return towire_warningfmt(rstate, NULL, "%s", err); } - routing_add_channel_update(rstate, take(serialized), 0, source_peer, force, - false); + routing_add_channel_update(rstate, take(serialized), 0, source_peer, force); return NULL; } @@ -1526,8 +1457,7 @@ bool routing_add_node_announcement(struct routing_state *rstate, const u8 *msg TAKES, u32 index, const struct node_id *source_peer TAKES, - bool *was_unknown, - bool force_spam_flag) + bool *was_unknown) { struct node *node; secp256k1_ecdsa_signature signature; @@ -1537,7 +1467,6 @@ bool routing_add_node_announcement(struct routing_state *rstate, u8 alias[32]; u8 *features, *addresses; struct tlv_node_ann_tlvs *na_tlv; - bool spam; if (was_unknown) *was_unknown = false; @@ -1586,40 +1515,24 @@ bool routing_add_node_announcement(struct routing_state *rstate, SUPERVERBOSE("Deferring node_announcement for node %s", type_to_string(tmpctx, struct node_id, &node_id)); - /* a pending spam node announcement is possible when loading - * from the store */ - if (index && force_spam_flag) { - tal_free(pna->spam.node_announcement); - pna->spam.node_announcement = tal_dup_talarr(pna, u8, msg); - pna->spam.index = index; - } else { - tal_free(pna->node_announcement); - tal_free(pna->source_peer); - pna->node_announcement = tal_dup_talarr(pna, u8, msg); - pna->source_peer = tal_dup_or_null(pna, struct node_id, source_peer); - pna->timestamp = timestamp; - pna->index = index; - } + tal_free(pna->node_announcement); + tal_free(pna->source_peer); + pna->node_announcement = tal_dup_talarr(pna, u8, msg); + pna->source_peer = tal_dup_or_null(pna, struct node_id, source_peer); + pna->timestamp = timestamp; + pna->index = index; return true; } if (node->bcast.index) { u32 redundant_time; - /* The gossip_store should contain a single broadcastable entry - * and potentially one rate-limited entry. Any more is a bug */ - if (index){ - if (!force_spam_flag){ - status_broken("gossip_store broadcastable " - "node_announcement %u replaces %u!", - index, node->bcast.index); - return false; - } else if (node->bcast.index != node->rgraph.index){ - status_broken("gossip_store rate-limited " - "node_announcement %u replaces %u!", - index, node->rgraph.index); - return false; - } + /* The gossip_store should contain a single broadcastable entry: Any more is a bug */ + if (index) { + status_broken("gossip_store broadcastable " + "node_announcement %u replaces %u!", + index, node->bcast.index); + return false; } if (node->rgraph.timestamp >= timestamp) { @@ -1640,56 +1553,25 @@ bool routing_add_node_announcement(struct routing_state *rstate, /* Ignoring != failing */ return true; } - - /* Make sure it's not spamming us. */ - if (!ratelimit(rstate, - &node->tokens, node->bcast.timestamp, timestamp)) { - status_peer_debug(source_peer, - "Spammy nannounce for %s flagged" - " (last %u, now %u)", - type_to_string(tmpctx, - struct node_id, - &node_id), - node->bcast.timestamp, timestamp); - spam = true; - } else { - spam = false; - } - } else { - spam = false; } - if (force_spam_flag) - spam = true; /* Routing graph always references the latest message. */ node->rgraph.timestamp = timestamp; - if (!spam) { - node->bcast.timestamp = timestamp; - /* remove prior spam update if one exists */ - if (node->rgraph.index != node->bcast.index) { - gossip_store_delete(rstate->daemon->gs, &node->rgraph, - WIRE_NODE_ANNOUNCEMENT); - } - /* Harmless if it was never added */ - gossip_store_delete(rstate->daemon->gs, &node->bcast, - WIRE_NODE_ANNOUNCEMENT); - /* Remove prior spam update. */ - } else if (node->rgraph.index != node->bcast.index) { - gossip_store_delete(rstate->daemon->gs, &node->rgraph, - WIRE_NODE_ANNOUNCEMENT); - } + node->bcast.timestamp = timestamp; + + /* Harmless if it was never added */ + gossip_store_delete(rstate->daemon->gs, &node->bcast, + WIRE_NODE_ANNOUNCEMENT); /* Don't add to the store if it was loaded from the store. */ if (index) { node->rgraph.index = index; - if (!spam) - node->bcast.index = index; + node->bcast.index = index; } else { node->rgraph.index = gossip_store_add(rstate->daemon->gs, msg, timestamp, - spam, false, NULL); - if (!spam) - node->bcast.index = node->rgraph.index; + false, NULL); + node->bcast.index = node->rgraph.index; peer_supplied_good_gossip(rstate->daemon, source_peer, 1); } @@ -1765,7 +1647,7 @@ u8 *handle_node_announcement(struct routing_state *rstate, const u8 *node_ann, } /* May still fail, if we don't know the node. */ - routing_add_node_announcement(rstate, serialized, 0, source_peer, was_unknown, false); + routing_add_node_announcement(rstate, serialized, 0, source_peer, was_unknown); return NULL; } @@ -1956,7 +1838,7 @@ void routing_channel_spent(struct routing_state *rstate, /* Save to gossip_store in case we restart */ msg = towire_gossip_store_chan_dying(tmpctx, &chan->scid, deadline); - index = gossip_store_add(rstate->daemon->gs, msg, 0, false, false, NULL); + index = gossip_store_add(rstate->daemon->gs, msg, 0, false, NULL); /* Mark it dying, so we don't gossip it */ gossip_store_mark_dying(rstate->daemon->gs, &chan->bcast, diff --git a/gossipd/routing.h b/gossipd/routing.h index 85329c6f8597..92bb336361b6 100644 --- a/gossipd/routing.h +++ b/gossipd/routing.h @@ -315,8 +315,7 @@ bool routing_add_channel_update(struct routing_state *rstate, const u8 *update TAKES, u32 index, const struct node_id *source_peer TAKES, - bool ignore_timestamp, - bool force_spam_flag); + bool ignore_timestamp); /** * Add a node_announcement to the network view without checking it * @@ -328,8 +327,7 @@ bool routing_add_node_announcement(struct routing_state *rstate, const u8 *msg TAKES, u32 index, const struct node_id *source_peer TAKES, - bool *was_unknown, - bool force_spam_flag); + bool *was_unknown); /** * Add to rstate->dying_channels diff --git a/tests/test_gossip.py b/tests/test_gossip.py index 26a24fa9039c..66cf6621323c 100644 --- a/tests/test_gossip.py +++ b/tests/test_gossip.py @@ -1710,132 +1710,6 @@ def test_gossip_no_backtalk(node_factory): TEST_NETWORK != 'regtest', "Channel announcement contains genesis hash, receiving node discards on mismatch" ) -def test_gossip_ratelimit(node_factory, bitcoind): - """Check that we ratelimit incoming gossip. - - We create a partitioned network, in which the first partition consisting - of l1 and l2 is used to create an on-chain footprint and we then feed - canned gossip to the other partition consisting of l3. l3 should ratelimit - the incoming gossip. - - We get BROKEN logs because gossipd talks about non-existent channels to - lightningd ("**BROKEN** lightningd: Local update for bad scid 103x1x1"). - """ - l3 = node_factory.get_node(node_id=3, - allow_broken_log=True, - options={'dev-gossip-time': 1568096251}) - - # Bump to block 102, so the following tx ends up in 103x1: - bitcoind.generate_block(1) - - # We don't actually need to start l1 and l2, they're just there to create - # an unspent outpoint matching the expected script. This is also more - # stable against output ordering issues. - tx = bitcoind.rpc.createrawtransaction( - [], - [ - # Fundrawtransaction will fill in the first output with the change - {"bcrt1qtwxd8wg5eanumk86vfeujvp48hfkgannf77evggzct048wggsrxsum2pmm": 0.01000000} - ] - ) - tx = bitcoind.rpc.fundrawtransaction(tx, {'changePosition': 0})['hex'] - tx = bitcoind.rpc.signrawtransactionwithwallet(tx)['hex'] - txid = bitcoind.rpc.sendrawtransaction(tx) - wait_for(lambda: txid in bitcoind.rpc.getrawmempool()) - - # Make the tx gossipable: - bitcoind.generate_block(6) - sync_blockheight(bitcoind, [l3, ]) - - def channel_fees(node): - channels = node.rpc.listchannels()['channels'] - return [c['fee_per_millionth'] for c in channels] - - # Here are some ones I generated earlier (by removing gossip - # ratelimiting) - subprocess.check_call( - [ - 'devtools/gossipwith', - '--max-messages=0', - '{}@localhost:{}'.format(l3.info['id'], l3.port), - # announcement - '0100987b271fc95a37dbed78e6159e0ab792cda64603780454ce80832b4e31f63a6760abc8fdc53be35bb7cfccd125ee3d15b4fbdfb42165098970c19c7822bb413f46390e0c043c777226927eacd2186a03f064e4bdc30f891cb6e4990af49967d34b338755e99d728987e3d49227815e17f3ab40092434a59e33548e870071176db7d44d8c8f4c4cac27ae6554eb9350e97d47617e3a1355296c78e8234446fa2f138ad1b03439f18520227fb9e9eb92689b3a0ed36e6764f5a41777e9a2a4ce1026d19a4e4d8f7715c13ac2d6bf3238608a1ccf9afd91f774d84d170d9edddebf7460c54d49bd6cd81410bc3eeeba2b7278b1b5f7e748d77d793f31086847d582000006226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f0000670000010001022d223620a359a47ff7f7ac447c85c46c923da53389221a0054c11c1e3ca31d590266e4598d1d3c415f572a8488830b60f7e744ed9235eb0b1ba93283b315c0351802e3bd38009866c9da8ec4aa99cc4ea9c6c0dd46df15c61ef0ce1f271291714e5702324266de8403b3ab157a09f1f784d587af61831c998c151bcc21bb74c2b2314b', - # first update is free - '010225bfd9c5e2c5660188a14deb4002cd645ee67f00ad3b82146e46711ec460cb0c6819fdd1c680cb6d24e3906679ef071f13243a04a123e4b83310ebf0518ffd4206226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f00006700000100015d773ffb010100060000000000000000000000010000000a000000003b023380' - ], - timeout=TIMEOUT - ) - - # Wait for it to process channel. - wait_for(lambda: channel_fees(l3) == [10]) - - subprocess.check_call( - [ - 'devtools/gossipwith', - '--max-messages=0', - '{}@localhost:{}'.format(l3.info['id'], l3.port), - # next 4 are let through... - '01023a892ad9c9953a54ad3b8e2e03a93d1c973241b62f9a5cd1f17d5cdf08de0e8b4fcd24aa8bd45a48b788fe9dab3d416f28dfa390bc900ec0176ec5bd1afd435706226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f00006700000100015d77400001010006000000000000000000000014000003e9000000003b023380', - '010245966763623ebc16796165263d4b21711ef04ebf3929491e695ff89ed2b8ccc0668ceb9e35e0ff5b8901d95732a119c1ed84ac99861daa2de462118f7b70049f06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f00006700000100015d77400101010006000000000000000000000014000003ea000000003b023380', - '0102c479b7684b9db496b844f6925f4ffd8a27c5840a020d1b537623c1545dcd8e195776381bbf51213e541a853a4a49a0faf84316e7ccca5e7074901a96bbabe04e06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f00006700000100015d77400201010006000000000000000000000014000003eb000000003b023380', - # timestamp=1568096259, fee_proportional_millionths=1004 - '01024b866012d995d3d7aec7b7218a283de2d03492dbfa21e71dd546ec2e36c3d4200453420aa02f476f99c73fe1e223ea192f5fa544b70a8319f2a216f1513d503d06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f00006700000100015d77400301010006000000000000000000000014000003ec000000003b023380', - # update 5 marks you as a nasty spammer, but the routing graph is - # updated with this even though the gossip is not broadcast. - '01025b5b5a0daed874ab02bd3356d38190ff46bbaf5f10db5067da70f3ca203480ca78059e6621c6143f3da4e454d0adda6d01a9980ed48e71ccd0c613af73570a7106226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f00006700000100015d77400401010006000000000000000000000014000003ed000000003b023380' - ], - timeout=TIMEOUT - ) - # Rate limited channel_update okay to use in routing graph. - wait_for(lambda: channel_fees(l3) == [1005]) - # but should be flagged so we don't propagate to the network. - assert(l3.daemon.is_in_log("Spammy update for 103x1x1/1 flagged")) - - # ask for a gossip sync - raw = subprocess.run(['devtools/gossipwith', - '--initial-sync', - '--timeout-after={}'.format(1), - '--hex', - '{}@localhost:{}'.format(l3.info['id'], l3.port)], - check=True, - timeout=TIMEOUT, stdout=subprocess.PIPE).stdout - # The last message is the most recent channel update. - message = raw.decode('utf-8').split()[-1] - decoded = subprocess.run(['devtools/decodemsg', message], - check=True, - timeout=TIMEOUT, - stdout=subprocess.PIPE).stdout.decode('utf8') - assert("fee_proportional_millionths=1004" in decoded) - # Used in routing graph, but not passed to gossip peers. - assert("fee_proportional_millionths=1005" not in decoded) - - # 24 seconds later, it will accept another. - l3.rpc.call('dev-gossip-set-time', [1568096251 + 24]) - - subprocess.run(['devtools/gossipwith', - '--max-messages=0', - '{}@localhost:{}'.format(l3.info['id'], l3.port), - # update 6: timestamp=1568096284 fee_proportional_millionths=1006 - '010282d24bcd984956bd9b891848404ee59d89643923b21641d2c2c0770a51b8f5da00cef82458add970f0b654aa4c8d54f68a9a1cc6470a35810303b09437f1f73d06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f00006700000100015d77401c01010006000000000000000000000014000003ee000000003b023380'], - check=True, timeout=TIMEOUT) - - wait_for(lambda: channel_fees(l3) == [1006]) - raw = subprocess.run(['devtools/gossipwith', - '--initial-sync', - '--timeout-after={}'.format(1), - '--hex', - '{}@localhost:{}'.format(l3.info['id'], l3.port)], - check=True, - timeout=TIMEOUT, stdout=subprocess.PIPE).stdout - message = raw.decode('utf-8').split()[-1] - decoded = subprocess.run(['devtools/decodemsg', message], - check=True, - timeout=TIMEOUT, - stdout=subprocess.PIPE).stdout.decode('utf8') - - assert("fee_proportional_millionths=1006" in decoded) - - def check_socket(ip_addr, port): result = True sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) @@ -2131,98 +2005,6 @@ def test_dump_own_gossip(node_factory): assert expect == [] -@unittest.skipIf( - TEST_NETWORK != 'regtest', - "Channel announcement contains genesis hash, receiving node discards on mismatch" -) -def test_read_spam_nannounce(node_factory, bitcoind): - """issue #6531 lead to a node announcement not being deleted from - the gossip_store.""" - """broadcastable and spam node announcements should be loaded properly when - reading the gossip_store - even when they are both pending a channel - update.""" - opts = {'dev-gossip-time': 1691773540} - l1 = node_factory.get_node(start=False, opts=opts) - canned_store = ( - "0c" # Gossip store version byte - "0000" # length flags - "01b0" # length - "d163af25" # checksum - "64d66a3a" # timestamp - # Channel announcement - "010000335733f5942df5d950eb8766dee3a9d6626922844ed6ae7a110dd7e7edc32e3f6f3d9ac5cdea23ce25bb8dbf761fd3d5fc56c05b6856316d12e9d32ca0f08c69ca0306fe716e7b5151317d6440b7373d9fbc646ead48f2163f2b6d511afe6a79c75551c2620fc80974f2f864329d9778a08cdfbc9f2c9c1344c432702c66807cfb4db69b80fae8c33c70143d948b36614d620891bee2df99c86bc62207c17e3b9186214c0ccff2ded5598accc90eb1d5b2f7a83cd7f68d712ea047d8019f343063b0a236356a387146f58fa832ddc13c4714522cbb503f5f3ca8fcec6602be2438ad3f98fa0ceed58fe3a066d385fcacd98c704b2a8e98a3e20bf76b35b736000006226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f0000670000010000022d223620a359a47ff7f7ac447c85c46c923da53389221a0054c11c1e3ca31d59035d2b1192dfba134e10e540875d366ebc8bc353d5aa766b80c090b39c3a5d885d029053521d6ea7a52cdd55f733d0fb2d077c0373b0053b5b810d927244061b757302d6063d022691b2490ab454dee73a57c6ff5d308352b461ece69f3c284f2c2412" - # Channel Amount - "0000000a911183f600000000" - "100500000000000f4240" - # broadcastable node announcement (rgb=000001) - "0000009533d9cf8c64d66a44" - "010108f4e25debdd74d0f52b7f1da5dbd82f429d057f48e3d2ed49fcc65cfe3e185c086c1a83d8f3bb15dc0cc852d80390c24cd1fe6d288b91eb55cf98c4a9baf7c0000788a0000a0269a264d66a44022d223620a359a47ff7f7ac447c85c46c923da53389221a0054c11c1e3ca31d5900000153494c454e544152544953542d3536392d67303030643963302d6d6f646465640000" - # Rate-limited node announcment (rgb=000002) - "2000009519aa897a64d66a49" - "0101685f67556cd0a87e04c6d1e9daa4e31dcf14f881d6f1231b1bee8adf6666977b6b9baa497e91d2b6daae726cde69e3faf924d9c95d0ca6b374d6693d4fc0d648000788a0000a0269a264d66a49022d223620a359a47ff7f7ac447c85c46c923da53389221a0054c11c1e3ca31d5900000253494c454e544152544953542d3536392d67303030643963302d6d6f646465640000" - # Channel Update - "0000008a0234021c64d66a61" - "010242ce9d9e79f939399ea1291c04fffcafdfa911246464a4b48c16b7b816dd57b4168562a6c519eb31c37718b68bdfc6345d7c2db663b8b04a3558ce7736c5b61706226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f000067000001000064d66a6101000006000000000000000000000015000003e8000000003b023380" - # Channel Update - "0000008acd55fcb264d66a61" - "01023f5dd1f69f675a71d2a7a34956b26f12c4fe9ee287a8449a3eb7b756c583e3bb1334a8eb7c3e148d0f43e08b95c50017ba62da9a7843fe4850a3cb3c74dc5e2c06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f000067000001000064d66a6101010006000000000000000000000015000003e8000000003b023380" - ) - with open(os.path.join(l1.daemon.lightning_dir, TEST_NETWORK, 'gossip_store'), 'wb') as f: - f.write(bytearray.fromhex(canned_store)) - - bitcoind.generate_block(1) - tx = bitcoind.rpc.createrawtransaction( - [], - [ - # Fundrawtransaction will fill in the first output with the change - {"bcrt1qpd7nwe3jrt07st89uy82nn2xrmqtxyzqpty5ygt6w546lf6n0wcskswjvh": 0.01000000} - ] - ) - tx = bitcoind.rpc.fundrawtransaction(tx, {'changePosition': 0})['hex'] - tx = bitcoind.rpc.signrawtransactionwithwallet(tx)['hex'] - txid = bitcoind.rpc.sendrawtransaction(tx) - wait_for(lambda: txid in bitcoind.rpc.getrawmempool()) - bitcoind.generate_block(6) - l1.start() - # retrieves node info originating from the spam announcement - node_info = l1.rpc.listnodes('022d223620a359a47ff7f7ac447c85c46c923da53389221a0054c11c1e3ca31d59') - assert only_one(node_info['nodes'])['color'] == '000002' - - out = subprocess.run(['devtools/gossipwith', - '--initial-sync', - '--timeout-after={}'.format(int(math.sqrt(TIMEOUT) + 1)), - '--hex', - '{}@localhost:{}'.format(l1.info['id'], l1.port)], - check=True, - timeout=TIMEOUT, stdout=subprocess.PIPE).stdout.decode() - - received_broadcastable = False - received_spam = False - for message in out.splitlines(): - gos = subprocess.run(['devtools/decodemsg', message], check=True, - timeout=TIMEOUT, - stdout=subprocess.PIPE).stdout.decode() - - for line in gos.splitlines(): - if 'rgb_color=[000001]' in line: - received_broadcastable = True - if 'rgb_color=[000002]' in line: - received_spam = True - - assert received_broadcastable - assert not received_spam - # Send a new node announcement. It should replace both. - subprocess.run(['devtools/gossipwith', - '--max-messages=0', - '{}@localhost:{}'.format(l1.info['id'], l1.port), - # color=000003 - '0101273fd2c58deb4c3bd98610079657219a5c8291d6a85c3607eae895f25e08babd6e45edd1e62f719b20526ed1c8fc3c7d9e7e3fafa4f24e4cb64872d041a13503000788a0000a0269a264d66a4e022d223620a359a47ff7f7ac447c85c46c923da53389221a0054c11c1e3ca31d5900000353494c454e544152544953542d3536392d67303030643963302d6d6f646465640000'], - check=True, timeout=TIMEOUT) - l1.daemon.wait_for_log('Received node_announcement') - l1.restart() - assert not l1.daemon.is_in_log('BROKEN') - - def test_listchannels_deprecated_local(node_factory, bitcoind): """Test listchannels shows local/private channels only in deprecated mode""" l1, l2, l3 = node_factory.get_nodes(3, From 30214de30e2085272093a6bd8ce2436193dae61f Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 31 Jan 2024 15:03:11 +1030 Subject: [PATCH 18/33] gossip_store: remove infratructure and bits for marking ratelimited entries. Signed-off-by: Rusty Russell --- contrib/pyln-client/pyln/client/gossmap.py | 1 - .../pyln-client/pyln/client/gossmapstats.py | 2 - gossipd/routing.c | 109 +----------------- gossipd/routing.h | 19 --- gossipd/seeker.c | 2 +- gossipd/test/run-next_block_range.c | 5 - 6 files changed, 7 insertions(+), 131 deletions(-) diff --git a/contrib/pyln-client/pyln/client/gossmap.py b/contrib/pyln-client/pyln/client/gossmap.py index 634a42d63603..b7a27bcfadfb 100755 --- a/contrib/pyln-client/pyln/client/gossmap.py +++ b/contrib/pyln-client/pyln/client/gossmap.py @@ -89,7 +89,6 @@ def __init__(self, buf: bytes, off: int): self.flags, self.length, self.crc, self.timestamp = struct.unpack('>HHII', buf) self.off = off self.deleted = (self.flags & GOSSIP_STORE_LEN_DELETED_BIT) != 0 - self.ratelimit = (self.flags & GOSSIP_STORE_LEN_RATELIMIT_BIT) != 0 class GossmapHalfchannel(object): diff --git a/contrib/pyln-client/pyln/client/gossmapstats.py b/contrib/pyln-client/pyln/client/gossmapstats.py index 18ea04286f9a..7c6458696d20 100644 --- a/contrib/pyln-client/pyln/client/gossmapstats.py +++ b/contrib/pyln-client/pyln/client/gossmapstats.py @@ -153,8 +153,6 @@ def print_stats(self): print("CONSISTENCY") print(f" - {len(self.filter_nodes_unannounced())} orphan nodes without a node_announcement, only known from a channel_announcement.") print(f" - {len(self.g.orphan_channel_updates)} orphan channel_updates without a prior channel_announcement.") - print(f" - {len(self.filter_nodes_ratelimited())} nodes marked as ratelimited. (sending too many updates).") - print(f" - {len(self.filter_halfchannels_ratelimited())} half-channels marked as ratelimited. (sending too many updates).") print(f" - {len(self.filter_channels_nosatoshis())} channels without capacity (missing WIRE_GOSSIP_STORE_CHANNEL_AMOUNT). Should be 0.") print("") diff --git a/gossipd/routing.c b/gossipd/routing.c index 526e38a12445..3695f551c833 100644 --- a/gossipd/routing.c +++ b/gossipd/routing.c @@ -23,9 +23,6 @@ #define SUPERVERBOSE(...) #endif -/* 365.25 * 24 * 60 / 10 */ -#define BLOCKS_PER_YEAR 52596 - struct pending_spam_node_announce { u8 *node_announcement; u32 index; @@ -40,8 +37,6 @@ struct pending_node_announce { u32 index; /* If non-NULL this is peer to credit it with */ struct node_id *source_peer; - /* required for loading gossip store */ - struct pending_spam_node_announce spam; }; /* As per the below BOLT #7 quote, we delay forgetting a channel until 12 @@ -54,25 +49,6 @@ struct dying_channel { struct broadcastable marker; }; -/* We consider a reasonable gossip rate to be 2 per day, with burst of - * 4 per day. So we use a granularity of one hour. */ -#define TOKENS_PER_MSG 12 -#define TOKEN_MAX (12 * 4) - -static u8 update_tokens(const struct routing_state *rstate, - u8 tokens, u32 prev_timestamp, u32 new_timestamp) -{ - u64 num_tokens = tokens; - - assert(new_timestamp >= prev_timestamp); - - num_tokens += ((new_timestamp - prev_timestamp) - / GOSSIP_TOKEN_TIME(rstate->daemon->dev_fast_gossip)); - if (num_tokens > TOKEN_MAX) - num_tokens = TOKEN_MAX; - return num_tokens; -} - static const struct node_id * pending_node_announce_keyof(const struct pending_node_announce *a) { @@ -291,8 +267,6 @@ static struct node *new_node(struct routing_state *rstate, memset(n->chan_arr, 0, sizeof(n->chan_arr)); n->chan_map = NULL; broadcastable_init(&n->bcast); - broadcastable_init(&n->rgraph); - n->tokens = TOKEN_MAX; node_map_add(rstate->nodes, n); tal_add_destructor2(n, destroy_node, rstate); @@ -334,7 +308,6 @@ static void force_node_announce_rexmit(struct routing_state *rstate, const u8 *announce; announce = gossip_store_get(tmpctx, rstate->daemon->gs, node->bcast.index); - u32 initial_bcast_index = node->bcast.index; gossip_store_delete(rstate->daemon->gs, &node->bcast, WIRE_NODE_ANNOUNCEMENT); @@ -343,19 +316,6 @@ static void force_node_announce_rexmit(struct routing_state *rstate, node->bcast.timestamp, false, NULL); - if (node->rgraph.index == initial_bcast_index){ - node->rgraph.index = node->bcast.index; - } else { - announce = gossip_store_get(tmpctx, rstate->daemon->gs, node->rgraph.index); - gossip_store_delete(rstate->daemon->gs, - &node->rgraph, - WIRE_NODE_ANNOUNCEMENT); - node->rgraph.index = gossip_store_add(rstate->daemon->gs, - announce, - node->rgraph.timestamp, - false, - NULL); - } } static void remove_chan_from_node(struct routing_state *rstate, @@ -379,10 +339,6 @@ static void remove_chan_from_node(struct routing_state *rstate, /* Last channel? Simply delete node (and associated announce) */ if (num_chans == 0) { - if (node->rgraph.index != node->bcast.index) - gossip_store_delete(rstate->daemon->gs, - &node->rgraph, - WIRE_NODE_ANNOUNCEMENT); gossip_store_delete(rstate->daemon->gs, &node->bcast, WIRE_NODE_ANNOUNCEMENT); @@ -396,15 +352,11 @@ static void remove_chan_from_node(struct routing_state *rstate, /* Removed only public channel? Remove node announcement. */ if (!node_has_broadcastable_channels(node)) { - if (node->rgraph.index != node->bcast.index) - gossip_store_delete(rstate->daemon->gs, - &node->rgraph, - WIRE_NODE_ANNOUNCEMENT); gossip_store_delete(rstate->daemon->gs, &node->bcast, WIRE_NODE_ANNOUNCEMENT); - node->rgraph.index = node->bcast.index = 0; - node->rgraph.timestamp = node->bcast.timestamp = 0; + node->bcast.index = 0; + node->bcast.timestamp = 0; } else if (node_announce_predates_channels(node)) { /* node announcement predates all channel announcements? * Move to end (we could, in theory, move to just past next @@ -446,8 +398,6 @@ static void init_half_chan(struct routing_state *rstate, struct half_chan *c = &chan->half[channel_idx]; broadcastable_init(&c->bcast); - broadcastable_init(&c->rgraph); - c->tokens = TOKEN_MAX; } static void bad_gossip_order(const u8 *msg, @@ -544,8 +494,6 @@ static void catch_node_announcement(const tal_t *ctx, pna->index = 0; pna->refcount = 0; pna->source_peer = NULL; - pna->spam.node_announcement = NULL; - pna->spam.index = 0; pending_node_map_add(rstate->pending_node_map, pna); } pna->refcount++; @@ -625,14 +573,8 @@ static void delete_chan_messages_from_store(struct routing_state *rstate, /* If these aren't in the store, these are noops. */ gossip_store_delete(rstate->daemon->gs, &chan->bcast, WIRE_CHANNEL_ANNOUNCEMENT); - if (chan->half[0].rgraph.index != chan->half[0].bcast.index) - gossip_store_delete(rstate->daemon->gs, - &chan->half[0].rgraph, WIRE_CHANNEL_UPDATE); gossip_store_delete(rstate->daemon->gs, &chan->half[0].bcast, WIRE_CHANNEL_UPDATE); - if (chan->half[1].rgraph.index != chan->half[1].bcast.index) - gossip_store_delete(rstate->daemon->gs, - &chan->half[1].rgraph, WIRE_CHANNEL_UPDATE); gossip_store_delete(rstate->daemon->gs, &chan->half[1].bcast, WIRE_CHANNEL_UPDATE); } @@ -1006,18 +948,6 @@ static void update_pending(struct pending_cannouncement *pending, } } -static void delete_spam_update(struct routing_state *rstate, - struct half_chan *hc) -{ - /* Spam updates will have a unique rgraph index */ - if (hc->rgraph.index == hc->bcast.index) - return; - gossip_store_delete(rstate->daemon->gs, &hc->rgraph, - WIRE_CHANNEL_UPDATE); - hc->rgraph.index = hc->bcast.index; - hc->rgraph.timestamp = hc->bcast.timestamp; -} - static bool is_chan_dying(struct routing_state *rstate, const struct short_channel_id *scid) { @@ -1141,12 +1071,6 @@ bool routing_add_channel_update(struct routing_state *rstate, return false; } - if (timestamp <= hc->rgraph.timestamp) { - SUPERVERBOSE("Ignoring outdated update."); - /* Ignoring != failing */ - return true; - } - /* Allow redundant updates once every 7 days */ if (timestamp < hc->bcast.timestamp + GOSSIP_PRUNE_INTERVAL(rstate->daemon->dev_fast_gossip_prune) / 2 && !cupdate_different(rstate->daemon->gs, hc, update)) { @@ -1161,13 +1085,11 @@ bool routing_add_channel_update(struct routing_state *rstate, } } - /* Delete any prior entries (noop if they don't exist) */ - delete_spam_update(rstate, hc); + /* Delete any prior entry (noop if doesn't exist) */ gossip_store_delete(rstate->daemon->gs, &hc->bcast, WIRE_CHANNEL_UPDATE); - /* Update timestamp(s) */ - hc->rgraph.timestamp = timestamp; + /* Update timestamp */ hc->bcast.timestamp = timestamp; /* If this is a peer's update to one of our local channels, tell lightningd. */ @@ -1201,12 +1123,10 @@ bool routing_add_channel_update(struct routing_state *rstate, /* If we're loading from store, this means we don't re-add to store. */ if (index) { hc->bcast.index = index; - hc->rgraph.index = index; } else { - hc->rgraph.index + hc->bcast.index = gossip_store_add(rstate->daemon->gs, update, timestamp, dying, NULL); - hc->bcast.index = hc->rgraph.index; peer_supplied_good_gossip(rstate->daemon, source_peer, 1); } @@ -1231,14 +1151,6 @@ bool routing_add_channel_update(struct routing_state *rstate, return true; } -bool would_ratelimit_cupdate(struct routing_state *rstate, - const struct half_chan *hc, - u32 timestamp) -{ - return update_tokens(rstate, hc->tokens, hc->bcast.timestamp, timestamp) - >= TOKENS_PER_MSG; -} - static const struct node_id *get_channel_owner(struct routing_state *rstate, const struct short_channel_id *scid, int direction) @@ -1535,12 +1447,6 @@ bool routing_add_node_announcement(struct routing_state *rstate, return false; } - if (node->rgraph.timestamp >= timestamp) { - SUPERVERBOSE("Ignoring node announcement, it's outdated."); - /* OK unless we're loading from store */ - return index == 0; - } - /* Allow redundant updates once a day (faster in dev-fast-gossip-prune mode) */ redundant_time = GOSSIP_PRUNE_INTERVAL(rstate->daemon->dev_fast_gossip_prune) / 14; if (timestamp < node->bcast.timestamp + redundant_time @@ -1556,7 +1462,6 @@ bool routing_add_node_announcement(struct routing_state *rstate, } /* Routing graph always references the latest message. */ - node->rgraph.timestamp = timestamp; node->bcast.timestamp = timestamp; /* Harmless if it was never added */ @@ -1565,13 +1470,11 @@ bool routing_add_node_announcement(struct routing_state *rstate, /* Don't add to the store if it was loaded from the store. */ if (index) { - node->rgraph.index = index; node->bcast.index = index; } else { - node->rgraph.index + node->bcast.index = gossip_store_add(rstate->daemon->gs, msg, timestamp, false, NULL); - node->bcast.index = node->rgraph.index; peer_supplied_good_gossip(rstate->daemon, source_peer, 1); } diff --git a/gossipd/routing.h b/gossipd/routing.h index 92bb336361b6..739f2968f80a 100644 --- a/gossipd/routing.h +++ b/gossipd/routing.h @@ -23,13 +23,6 @@ struct routing_state; struct half_chan { /* Timestamp and index into store file - safe to broadcast */ struct broadcastable bcast; - - /* Most recent gossip for the routing graph - may be rate-limited and - * non-broadcastable. If there is no spam, rgraph == bcast. */ - struct broadcastable rgraph; - - /* Token bucket */ - u8 tokens; }; struct chan { @@ -84,13 +77,6 @@ struct node { /* Timestamp and index into store file */ struct broadcastable bcast; - /* Possibly spam flagged. Nonbroadcastable, but used for routing graph. - * If there is no current spam, rgraph == bcast. */ - struct broadcastable rgraph; - - /* Token bucket */ - u8 tokens; - /* Channels connecting us to other nodes */ /* For a small number of channels (by far the most common) we * use a simple array, with empty buckets NULL. For larger, we use a @@ -353,11 +339,6 @@ void routing_channel_spent(struct routing_state *rstate, */ void routing_expire_channels(struct routing_state *rstate, u32 blockheight); -/* Would we ratelimit a channel_update with this timestamp? */ -bool would_ratelimit_cupdate(struct routing_state *rstate, - const struct half_chan *hc, - u32 timestamp); - /* Does this node have public channels? */ bool node_has_broadcastable_channels(const struct node *node); diff --git a/gossipd/seeker.c b/gossipd/seeker.c index bd1ec17b6882..9f2cc5218a53 100644 --- a/gossipd/seeker.c +++ b/gossipd/seeker.c @@ -592,7 +592,7 @@ static bool want_update(struct seeker *seeker, if (timestamp <= hc->bcast.timestamp) return false; - return !would_ratelimit_cupdate(seeker->daemon->rstate, hc, timestamp); + return true; } /* They gave us timestamps. Do we want updated versions? */ diff --git a/gossipd/test/run-next_block_range.c b/gossipd/test/run-next_block_range.c index 3599c3949da5..5f18d02b4d2a 100644 --- a/gossipd/test/run-next_block_range.c +++ b/gossipd/test/run-next_block_range.c @@ -82,11 +82,6 @@ void status_fmt(enum log_level level UNNEEDED, const char *fmt UNNEEDED, ...) { fprintf(stderr, "status_fmt called!\n"); abort(); } -/* Generated stub for would_ratelimit_cupdate */ -bool would_ratelimit_cupdate(struct routing_state *rstate UNNEEDED, - const struct half_chan *hc UNNEEDED, - u32 timestamp UNNEEDED) -{ fprintf(stderr, "would_ratelimit_cupdate called!\n"); abort(); } /* AUTOGENERATED MOCKS END */ static void test_block_range(struct seeker *seeker, From 7b1ecb5bbc150b74f19515816c89b41f60ff26c8 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 31 Jan 2024 15:03:11 +1030 Subject: [PATCH 19/33] gossmap_manage: new file for managing the gossip store. This is a fair amount of code, but much is taken from the old routing.c, with the difference that this uses common/gossmap instead of our own structures. The interfaces are fairly clear: 1. gossmap_manage_new - allocator 2. gossmap_manage_channel_announcement - handle new channel announcement msg - if too early, keeps it in early map - queues it, asks lightingd about UTXO. 3. gossmap_manage_handle_get_txout_reply - handle response from lightningd for above. 4. gossmap_manage_channel_update - handle channel_update message - may have to wait on pending channel_announcement 5. gossmap_manage_node_announcement - handle node_announcement msg - may have to wait on pending channel_announcement 6. gossmap_manage_new_block - see if early announces can now be processed. 7. gossmap_manage_channel_spent - lightningd tells us UTXO is spent - may prepare channel for closing in 12 blocks. 8. gossmap_manage_channel_dying - gossip_store load tells us channel was spent earlier. - like gossmap_manage_channel_spent, but maybe < 12. 9. gossmap_manage_get_gossmap - gossmap accessor: seeker and queries will need this. 10. gossmap_manage_new_peer - a new peer has connected, give them all our gossip. Signed-off-by: Rusty Russell --- gossipd/gossip_store.c | 1 + gossipd/gossip_store.h | 3 +- gossipd/gossmap_manage.c | 1077 ++++++++++++++++++++++++++++++++++++++ gossipd/gossmap_manage.h | 112 ++++ 4 files changed, 1192 insertions(+), 1 deletion(-) create mode 100644 gossipd/gossmap_manage.c create mode 100644 gossipd/gossmap_manage.h diff --git a/gossipd/gossip_store.c b/gossipd/gossip_store.c index 1c34d3e95140..6c759a389fb5 100644 --- a/gossipd/gossip_store.c +++ b/gossipd/gossip_store.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include diff --git a/gossipd/gossip_store.h b/gossipd/gossip_store.h index 0f1c9060429a..21c0cdbe3593 100644 --- a/gossipd/gossip_store.h +++ b/gossipd/gossip_store.h @@ -7,13 +7,14 @@ #include #include #include -#include /** * gossip_store -- On-disk storage related information */ struct gossip_store; +struct broadcastable; +struct daemon; struct routing_state; struct gossip_store *gossip_store_new(struct daemon *daemon); diff --git a/gossipd/gossmap_manage.c b/gossipd/gossmap_manage.c new file mode 100644 index 000000000000..4bd314bd728d --- /dev/null +++ b/gossipd/gossmap_manage.c @@ -0,0 +1,1077 @@ +#include "config.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct pending_cannounce { + const u8 *scriptpubkey; + const u8 *channel_announcement; + const struct node_id *source_peer; +}; + +struct pending_cupdate { + struct short_channel_id scid; + secp256k1_ecdsa_signature signature; + u8 message_flags; + u8 channel_flags; + u16 cltv_expiry_delta; + struct amount_msat htlc_minimum_msat, htlc_maximum_msat; + u32 fee_base_msat, fee_proportional_millionths; + u32 timestamp; + const u8 *update; + const struct node_id *source_peer; +}; + +struct pending_nannounce { + struct node_id node_id; + u32 timestamp; + const u8 *nannounce; + const struct node_id *source_peer; +}; + +struct dying_channel { + struct short_channel_id scid; + /* Offset of dying marker in the gossip_store */ + u64 gossmap_offset; + u32 deadline_blockheight; +}; + +struct gossmap_manage { + struct daemon *daemon; + + /* For us to write to gossip_store */ + int fd; + + /* gossip map itself (access via gossmap_manage_get_gossmap, so it's fresh!) */ + struct gossmap *raw_gossmap; + + /* Announcements we're checking, indexed by scid */ + UINTMAP(struct pending_cannounce *) pending_ann_map; + + /* Updates we've deferred for above */ + struct pending_cupdate **pending_cupdates; + + /* Announcements which are too early to check. */ + UINTMAP(struct pending_cannounce *) early_ann_map; + struct pending_cupdate **early_cupdates; + + /* Node announcements (waiting for a pending_cannounce maybe) */ + struct pending_nannounce **pending_nannounces; + + /* Lookups we've failed recently */ + struct txout_failures *txf; + + /* Blockheights of scids to remove */ + struct dying_channel *dying_channels; +}; + +static void enqueue_cupdate(struct pending_cupdate ***queue, + struct short_channel_id scid, + const secp256k1_ecdsa_signature *signature, + u8 message_flags, + u8 channel_flags, + u16 cltv_expiry_delta, + struct amount_msat htlc_minimum_msat, + struct amount_msat htlc_maximum_msat, + u32 fee_base_msat, + u32 fee_proportional_millionths, + u32 timestamp, + const u8 *update TAKES, + const struct node_id *source_peer TAKES) +{ + struct pending_cupdate *pcu = tal(*queue, struct pending_cupdate); + + pcu->scid = scid; + pcu->signature = *signature; + pcu->message_flags = message_flags; + pcu->channel_flags = channel_flags; + pcu->cltv_expiry_delta = cltv_expiry_delta; + pcu->htlc_minimum_msat = htlc_minimum_msat; + pcu->htlc_maximum_msat = htlc_maximum_msat; + pcu->fee_base_msat = fee_base_msat; + pcu->fee_proportional_millionths = fee_proportional_millionths; + pcu->timestamp = timestamp; + pcu->update = tal_dup_talarr(pcu, u8, update); + pcu->source_peer = tal_dup_or_null(pcu, struct node_id, source_peer); + + tal_arr_expand(queue, pcu); +} + +static void enqueue_nannounce(struct pending_nannounce ***queue, + const struct node_id *node_id, + u32 timestamp, + const u8 *nannounce TAKES, + const struct node_id *source_peer TAKES) +{ + struct pending_nannounce *pna = tal(*queue, struct pending_nannounce); + + pna->node_id = *node_id; + pna->timestamp = timestamp; + pna->nannounce = tal_dup_talarr(pna, u8, nannounce); + pna->source_peer = tal_dup_or_null(pna, struct node_id, source_peer); + + tal_arr_expand(queue, pna); +} + +/* Does any channel_announcement preceed this offset in the gossip_store? */ +static bool any_cannounce_preceeds_offset(struct gossmap *gossmap, + const struct gossmap_node *node, + const struct gossmap_chan *exclude_chan, + u64 offset) +{ + for (size_t i = 0; i < node->num_chans; i++) { + struct gossmap_chan *chan = gossmap_nth_chan(gossmap, node, i, NULL); + + if (chan == exclude_chan) + continue; + if (chan->cann_off < offset) + return true; + } + return false; +} + +/* To actually remove a channel: + * - Suppress future lookups in case we receive another channel_update. + * - Put deleted tombstone in gossip_store. + * - Mark records deleted in gossip_store. + * - See if node_announcement(s) need to be removed, or moved. + */ +static void remove_channel(struct gossmap_manage *gm, + struct gossmap *gossmap, + struct gossmap_chan *chan, + struct short_channel_id scid) +{ + /* Suppress any now-obsolete updates/announcements */ + txout_failures_add(gm->txf, scid); + + /* Cover race where we were looking up this UTXO as it was spent. */ + tal_free(uintmap_del(&gm->pending_ann_map, scid.u64)); + tal_free(uintmap_del(&gm->early_ann_map, scid.u64)); + + /* Put in tombstone marker. */ + gossip_store_mark_channel_deleted(gm->daemon->gs, &scid); + + /* Delete from store */ + gossip_store_del(gm->daemon->gs, chan->cann_off, WIRE_CHANNEL_ANNOUNCEMENT); + for (int dir = 0; dir < 2; dir++) { + if (gossmap_chan_set(chan, dir)) + gossip_store_del(gm->daemon->gs, chan->cupdate_off[dir], WIRE_CHANNEL_UPDATE); + } + + /* Check for node_announcements which should no longer be there */ + for (int dir = 0; dir < 2; dir++) { + struct gossmap_node *node; + const u8 *nannounce; + u32 timestamp; + + node = gossmap_nth_node(gossmap, chan, dir); + + /* If there was a node announcement, we might need to fix things up. */ + if (!gossmap_node_announced(node)) + continue; + + /* Last channel? Delete node announce */ + if (node->num_chans == 1) { + gossip_store_del(gm->daemon->gs, node->nann_off, WIRE_NODE_ANNOUNCEMENT); + continue; + } + + /* Maybe this was the last channel_announcement which preceeded node_announcement? */ + if (chan->cann_off > node->nann_off) + continue; + + if (any_cannounce_preceeds_offset(gossmap, node, chan, node->nann_off)) + continue; + + /* To maintain order, delete and re-add node_announcement */ + nannounce = gossmap_node_get_announce(tmpctx, gossmap, node); + timestamp = gossip_store_get_timestamp(gm->daemon->gs, node->nann_off); + gossip_store_del(gm->daemon->gs, node->nann_off, WIRE_NODE_ANNOUNCEMENT); + gossip_store_add(gm->daemon->gs, nannounce, timestamp, false, NULL); + } +} + +static void reprocess_queued_msgs(struct gossmap_manage *gm); + +static void report_bad_update(struct gossmap *map, + const struct short_channel_id_dir *scidd, + u16 cltv_expiry_delta, + u32 fee_base_msat, + u32 fee_proportional_millionths, + struct gossmap_manage *gm) +{ + status_debug("Update for %s has silly values, disabling (cltv=%u, fee=%u+%u)", + type_to_string(tmpctx, struct short_channel_id_dir, scidd), + cltv_expiry_delta, fee_base_msat, fee_proportional_millionths); +} + +struct gossmap_manage *gossmap_manage_new(const tal_t *ctx, + struct daemon *daemon) +{ + struct gossmap_manage *gm = tal(ctx, struct gossmap_manage); + + gm->fd = open(GOSSIP_STORE_FILENAME, O_RDWR); + if (gm->fd < 0) + status_failed(STATUS_FAIL_INTERNAL_ERROR, + "Opening gossip_store store: %s", + strerror(errno)); + gm->raw_gossmap = gossmap_load_fd(gm, gm->fd, report_bad_update, NULL, gm); + assert(gm->raw_gossmap); + gm->daemon = daemon; + + uintmap_init(&gm->pending_ann_map); + gm->pending_cupdates = tal_arr(gm, struct pending_cupdate *, 0); + uintmap_init(&gm->early_ann_map); + gm->early_cupdates = tal_arr(gm, struct pending_cupdate *, 0); + gm->pending_nannounces = tal_arr(gm, struct pending_nannounce *, 0); + gm->txf = txout_failures_new(gm, daemon); + gm->dying_channels = tal_arr(gm, struct dying_channel, 0); + + return gm; +} + +/* Catch CI giving out-of-order gossip: definitely happens IRL though */ +static void bad_gossip(const struct node_id *source_peer, const char *str) +{ + status_peer_debug(source_peer, "Bad gossip order: %s", str); +} + +/* Send peer a warning message, if non-NULL. */ +static void peer_warning(struct gossmap_manage *gm, + const struct node_id *source_peer, + const char *fmt, ...) +{ + va_list ap; + char *formatted; + + va_start(ap, fmt); + formatted = tal_vfmt(tmpctx, fmt, ap); + va_end(ap); + + bad_gossip(source_peer, formatted); + if (!source_peer) + return; + + queue_peer_msg(gm->daemon, source_peer, + take(towire_warningfmt(NULL, NULL, "%s", formatted))); +} + +const char *gossmap_manage_channel_announcement(const tal_t *ctx, + struct gossmap_manage *gm, + const u8 *announce TAKES, + const struct node_id *source_peer TAKES) +{ + secp256k1_ecdsa_signature node_signature_1, node_signature_2; + secp256k1_ecdsa_signature bitcoin_signature_1, bitcoin_signature_2; + u8 *features; + struct bitcoin_blkid chain_hash; + struct short_channel_id scid; + struct node_id node_id_1; + struct node_id node_id_2; + struct pubkey bitcoin_key_1; + struct pubkey bitcoin_key_2; + struct pending_cannounce *pca; + const char *warn; + u32 blockheight = gm->daemon->current_blockheight; + struct gossmap *gossmap = gossmap_manage_get_gossmap(gm); + + /* Make sure we own msg, even if we don't save it. */ + if (taken(announce)) + tal_steal(tmpctx, announce); + + if (!fromwire_channel_announcement(tmpctx, announce, &node_signature_1, &node_signature_2, + &bitcoin_signature_1, &bitcoin_signature_2, &features, &chain_hash, + &scid, &node_id_1, &node_id_2, &bitcoin_key_1, &bitcoin_key_2)) { + return tal_fmt(ctx, "Malformed channel_announcement %s", + tal_hex(tmpctx, announce)); + } + + /* If a prior txout lookup failed there is little point it trying + * again. Just drop the announcement and walk away whistling. + * + * Happens quite a lot in CI on just-closed channels. + */ + if (in_txout_failures(gm->txf, scid)) { + return NULL; + } + + warn = sigcheck_channel_announcement(ctx, &node_id_1, &node_id_2, + &bitcoin_key_1, &bitcoin_key_2, + &node_signature_1, &node_signature_2, + &bitcoin_signature_1, &bitcoin_signature_2, + announce); + if (warn) + return warn; + + /* Already known? */ + if (gossmap_find_chan(gossmap, &scid)) + return NULL; + + pca = tal(gm, struct pending_cannounce); + pca->scriptpubkey = scriptpubkey_p2wsh(pca, + bitcoin_redeem_2of2(tmpctx, + &bitcoin_key_1, + &bitcoin_key_2)); + pca->channel_announcement = tal_dup_talarr(pca, u8, announce); + pca->source_peer = tal_dup_or_null(pca, struct node_id, source_peer); + + /* FIXME: Flood protection! */ + /* Don't know blockheight yet, or not yet deep enough? Don't even ask */ + if (!is_scid_depth_announceable(&scid, blockheight)) { + /* Don't expect to be more than 12 blocks behind! */ + if (blockheight != 0 + && short_channel_id_blocknum(&scid) > blockheight + 12) { + return tal_fmt(ctx, + "Bad gossip order: ignoring channel_announcement %s at blockheight %u", + short_channel_id_to_str(tmpctx, &scid), + blockheight); + } + + if (!uintmap_add(&gm->early_ann_map, scid.u64, pca)) { + /* Already pending? Ignore */ + tal_free(pca); + return NULL; + } + + /* We will retry in gossip_manage_new_block */ + return NULL; + } + + status_debug("channel_announcement: Adding %s to pending...", + short_channel_id_to_str(tmpctx, &scid)); + if (!uintmap_add(&gm->pending_ann_map, scid.u64, pca)) { + /* Already pending? Ignore */ + tal_free(pca); + return NULL; + } + + /* Ask lightningd about this scid: see + * gossmap_manage_handle_get_txout_reply */ + daemon_conn_send(gm->daemon->master, + take(towire_gossipd_get_txout(NULL, &scid))); + return NULL; +} + +/*~ We queue incoming channel_announcement pending confirmation from lightningd + * that it really is an unspent output. Here's its reply. */ +void gossmap_manage_handle_get_txout_reply(struct gossmap_manage *gm, const u8 *msg) +{ + struct short_channel_id scid; + u8 *outscript; + struct amount_sat sat; + struct pending_cannounce *pca; + + if (!fromwire_gossipd_get_txout_reply(msg, msg, &scid, &sat, &outscript)) + master_badmsg(WIRE_GOSSIPD_GET_TXOUT_REPLY, msg); + + status_debug("channel_announcement: got reply for %s...", + short_channel_id_to_str(tmpctx, &scid)); + + pca = uintmap_del(&gm->pending_ann_map, scid.u64); + if (!pca) { + /* If we looking specifically for this, we no longer + * are (but don't penalize sender: we don't know if it was + * good or bad). */ + remove_unknown_scid(gm->daemon->seeker, &scid, true); + /* Was it deleted because we saw channel close? */ + if (!in_txout_failures(gm->txf, scid)) + status_broken("get_txout_reply with unknown scid %s?", + short_channel_id_to_str(tmpctx, &scid)); + return; + } + + /* BOLT #7: + * + * The receiving node: + *... + * - if the `short_channel_id`'s output... is spent: + * - MUST ignore the message. + */ + if (tal_count(outscript) == 0) { + peer_warning(gm, pca->source_peer, + "channel_announcement: no unspent txout %s", + short_channel_id_to_str(tmpctx, &scid)); + goto bad; + } + + if (!memeq(outscript, tal_bytelen(outscript), + pca->scriptpubkey, tal_bytelen(pca->scriptpubkey))) { + peer_warning(gm, pca->source_peer, + "channel_announcement: txout %s expected %s, got %s", + short_channel_id_to_str(tmpctx, &scid), + tal_hex(tmpctx, pca->scriptpubkey), + tal_hex(tmpctx, outscript)); + goto bad; + } + + /* Set with timestamp 0 (we will update once we have a channel_update) */ + gossip_store_add(gm->daemon->gs, pca->channel_announcement, 0, false, + towire_gossip_store_channel_amount(tmpctx, sat)); + tal_free(pca); + + /* If we looking specifically for this, we no longer are. */ + remove_unknown_scid(gm->daemon->seeker, &scid, true); + + /* When all pending requests are done, we reconsider queued messages */ + reprocess_queued_msgs(gm); + + return; + +bad: + tal_free(pca); + txout_failures_add(gm->txf, scid); + /* If we looking specifically for this, we no longer are. */ + remove_unknown_scid(gm->daemon->seeker, &scid, false); +} + +/* This is called both from when we receive the channel update, and if + * we had to defer. */ +static const char *process_channel_update(const tal_t *ctx, + struct gossmap_manage *gm, + struct short_channel_id scid, + const secp256k1_ecdsa_signature *signature, + u8 message_flags, + u8 channel_flags, + u16 cltv_expiry_delta, + struct amount_msat htlc_minimum_msat, + struct amount_msat htlc_maximum_msat, + u32 fee_base_msat, + u32 fee_proportional_millionths, + u32 timestamp, + const u8 *update, + const struct node_id *source_peer) +{ + struct gossmap_chan *chan; + struct node_id node_id, remote_id; + const char *err; + int dir = (channel_flags & ROUTING_FLAGS_DIRECTION); + struct gossmap *gossmap = gossmap_manage_get_gossmap(gm); + + chan = gossmap_find_chan(gossmap, &scid); + if (!chan) { + /* Did we explicitly reject announce? Ignore completely. */ + if (in_txout_failures(gm->txf, scid)) + return NULL; + + /* Seeker may want to ask about this. */ + query_unknown_channel(gm->daemon, source_peer, scid); + + /* Don't send them warning, it can happen. */ + bad_gossip(source_peer, + tal_fmt(tmpctx, "Unknown channel %s", + short_channel_id_to_str(tmpctx, &scid))); + return NULL; + } + + /* Now we know node, we can check signature. */ + gossmap_node_get_id(gossmap, + gossmap_nth_node(gossmap, chan, dir), + &node_id); + + err = sigcheck_channel_update(ctx, &node_id, signature, update); + if (err) + return err; + + /* Don't allow private updates on public channels! */ + if (message_flags & ROUTING_OPT_DONT_FORWARD) { + return tal_fmt(ctx, "Do not set DONT_FORWARD on public channel_updates (%s)", + short_channel_id_to_str(tmpctx, &scid)); + } + + /* Do we have same or earlier update? */ + if (gossmap_chan_set(chan, dir)) { + u32 prev_timestamp + = gossip_store_get_timestamp(gm->daemon->gs, chan->cupdate_off[dir]); + if (prev_timestamp >= timestamp) { + /* Too old, ignore */ + return NULL; + } + } else { + /* Is this the first update in either direction? If so, + * rewrite channel_announcement so timestamp is correct. */ + if (!gossmap_chan_set(chan, dir)) + gossip_store_set_timestamp(gm->daemon->gs, chan->cann_off, timestamp); + } + + /* OK, apply the new one */ + gossip_store_add(gm->daemon->gs, update, timestamp, false, NULL); + + /* Now delete old */ + if (gossmap_chan_set(chan, dir)) + gossip_store_del(gm->daemon->gs, chan->cupdate_off[dir], WIRE_CHANNEL_UPDATE); + + /* Is this an update for an incoming channel? If so, keep lightningd updated */ + gossmap_node_get_id(gossmap, + gossmap_nth_node(gossmap, chan, !dir), + &remote_id); + if (node_id_eq(&remote_id, &gm->daemon->id)) { + tell_lightningd_peer_update(gm->daemon, source_peer, + scid, fee_base_msat, + fee_proportional_millionths, + cltv_expiry_delta, htlc_minimum_msat, + htlc_maximum_msat); + } + + status_peer_debug(source_peer, + "Received channel_update for channel %s/%d now %s", + type_to_string(tmpctx, struct short_channel_id, + &scid), + dir, + channel_flags & ROUTING_FLAGS_DISABLED ? "DISABLED" : "ACTIVE"); + return NULL; +} + +const char *gossmap_manage_channel_update(const tal_t *ctx, + struct gossmap_manage *gm, + const u8 *update TAKES, + const struct node_id *source_peer TAKES) +{ + secp256k1_ecdsa_signature signature; + struct short_channel_id scid; + u32 timestamp; + u8 message_flags, channel_flags; + u16 cltv_expiry_delta; + struct amount_msat htlc_minimum_msat, htlc_maximum_msat; + u32 fee_base_msat; + u32 fee_proportional_millionths; + struct bitcoin_blkid chain_hash; + struct gossmap *gossmap = gossmap_manage_get_gossmap(gm); + + if (taken(update)) + tal_steal(tmpctx, update); + + if (taken(source_peer)) + tal_steal(tmpctx, source_peer); + + if (!fromwire_channel_update(update, &signature, + &chain_hash, &scid, + ×tamp, &message_flags, + &channel_flags, &cltv_expiry_delta, + &htlc_minimum_msat, &fee_base_msat, + &fee_proportional_millionths, + &htlc_maximum_msat)) { + return tal_fmt(ctx, "channel_update: malformed %s", + tal_hex(tmpctx, update)); + } + + /* Don't accept ancient or far-future timestamps. */ + if (!timestamp_reasonable(gm->daemon, timestamp)) + return NULL; + + /* Still waiting? */ + if (uintmap_get(&gm->pending_ann_map, scid.u64)) { + enqueue_cupdate(&gm->pending_cupdates, + scid, + &signature, + message_flags, + channel_flags, + cltv_expiry_delta, + htlc_minimum_msat, + htlc_maximum_msat, + fee_base_msat, + fee_proportional_millionths, + timestamp, + take(update), + source_peer); + return NULL; + } + + /* Too early? */ + if (uintmap_get(&gm->early_ann_map, scid.u64)) { + enqueue_cupdate(&gm->early_cupdates, + scid, + &signature, + message_flags, + channel_flags, + cltv_expiry_delta, + htlc_minimum_msat, + htlc_maximum_msat, + fee_base_msat, + fee_proportional_millionths, + timestamp, + take(update), + source_peer); + return NULL; + } + + /* Private channel_updates are not always marked as such. So check if it's an unknown + * channel, and signed by the peer itself. */ + if (!gossmap_find_chan(gossmap, &scid) + && source_peer + && sigcheck_channel_update(tmpctx, source_peer, &signature, update) == NULL) { + tell_lightningd_peer_update(gm->daemon, source_peer, + scid, fee_base_msat, + fee_proportional_millionths, + cltv_expiry_delta, htlc_minimum_msat, + htlc_maximum_msat); + return NULL; + } + + return process_channel_update(ctx, gm, scid, &signature, + message_flags, channel_flags, + cltv_expiry_delta, + htlc_minimum_msat, + htlc_maximum_msat, + fee_base_msat, + fee_proportional_millionths, + timestamp, update, source_peer); +} + +static void process_node_announcement(struct gossmap_manage *gm, + const struct gossmap_node *node, + u32 timestamp, + const struct node_id *node_id, + const u8 *nannounce, + const struct node_id *source_peer) +{ + /* Do we have a later one? If so, ignore */ + if (gossmap_node_announced(node)) { + u32 prev_timestamp + = gossip_store_get_timestamp(gm->daemon->gs, node->nann_off); + if (prev_timestamp >= timestamp) { + /* Too old, ignore */ + return; + } + } + + /* OK, apply the new one */ + gossip_store_add(gm->daemon->gs, nannounce, timestamp, false, NULL); + + /* Now delete old */ + if (gossmap_node_announced(node)) + gossip_store_del(gm->daemon->gs, node->nann_off, WIRE_NODE_ANNOUNCEMENT); + + status_peer_debug(source_peer, + "Received node_announcement for node %s", + type_to_string(tmpctx, struct node_id, node_id)); +} + +const char *gossmap_manage_node_announcement(const tal_t *ctx, + struct gossmap_manage *gm, + const u8 *nannounce TAKES, + const struct node_id *source_peer TAKES) +{ + secp256k1_ecdsa_signature signature; + u32 timestamp; + struct node_id node_id; + u8 rgb_color[3]; + u8 alias[32]; + u8 *features, *addresses; + struct wireaddr *wireaddrs; + struct tlv_node_ann_tlvs *na_tlv; + struct gossmap_node *node; + const char *err; + struct gossmap *gossmap = gossmap_manage_get_gossmap(gm); + + if (taken(nannounce)) + tal_steal(tmpctx, nannounce); + + if (taken(source_peer)) + tal_steal(tmpctx, source_peer); + + if (!fromwire_node_announcement(tmpctx, nannounce, + &signature, &features, ×tamp, + &node_id, rgb_color, alias, + &addresses, + &na_tlv)) { + /* BOLT #7: + * + * - if `node_id` is NOT a valid compressed public key: + * - SHOULD send a `warning`. + * - MAY close the connection. + * - MUST NOT process the message further. + */ + return tal_fmt(ctx, "node_announcement: malformed %s", + tal_hex(tmpctx, nannounce)); + } + + wireaddrs = fromwire_wireaddr_array(tmpctx, addresses); + if (!wireaddrs) { + /* BOLT #7: + * + * - if `addrlen` is insufficient to hold the address + * descriptors of the known types: + * - SHOULD send a `warning`. + * - MAY close the connection. + */ + return tal_fmt(ctx, + "node_announcement: malformed wireaddrs %s in %s", + tal_hex(tmpctx, wireaddrs), + tal_hex(tmpctx, nannounce)); + } + + err = sigcheck_node_announcement(ctx, &node_id, &signature, + nannounce); + if (err) + return err; + + node = gossmap_find_node(gossmap, &node_id); + if (!node) { + /* Still waiting for some channel_announcement? */ + if (!uintmap_empty(&gm->pending_ann_map) + || !uintmap_empty(&gm->early_ann_map)) { + enqueue_nannounce(&gm->pending_nannounces, + &node_id, + timestamp, + take(nannounce), + source_peer); + return NULL; + } + + /* Seeker may want to ask about this. */ + query_unknown_node(gm->daemon, source_peer, &node_id); + + /* Don't complain to them: this can happen. */ + bad_gossip(source_peer, + tal_fmt(tmpctx, + "node_announcement: unknown node %s", + node_id_to_hexstr(tmpctx, &node_id))); + return NULL; + } + + process_node_announcement(gm, node, timestamp, &node_id, nannounce, source_peer); + return NULL; +} + +static void process_pending_cupdate(struct gossmap_manage *gm, + struct pending_cupdate *pcu) +{ + const char *err; + + err = process_channel_update(tmpctx, gm, + pcu->scid, + &pcu->signature, + pcu->message_flags, + pcu->channel_flags, + pcu->cltv_expiry_delta, + pcu->htlc_minimum_msat, + pcu->htlc_maximum_msat, + pcu->fee_base_msat, + pcu->fee_proportional_millionths, + pcu->timestamp, + pcu->update, + pcu->source_peer); + if (err) + peer_warning(gm, pcu->source_peer, + "channel_update: %s", err); +} + +/* No channel_announcement now pending, so process every update which was waiting. */ +static void reprocess_pending_cupdates(struct gossmap_manage *gm) +{ + /* Grab current array and reset to empty */ + struct pending_cupdate **pcus = gm->pending_cupdates; + + gm->pending_cupdates = tal_arr(gm, struct pending_cupdate *, 0); + + /* Now we can canonically process any pending channel_updates */ + for (size_t i = 0; i < tal_count(pcus); i++) + process_pending_cupdate(gm, pcus[i]); + + tal_free(pcus); +} + +/* No channel_announcement are early, so process every update which was for those. */ +static void reprocess_early_cupdates(struct gossmap_manage *gm) +{ + /* Grab current array and reset to empty */ + struct pending_cupdate **pcus = gm->early_cupdates; + + gm->early_cupdates = tal_arr(gm, struct pending_cupdate *, 0); + + for (size_t i = 0; i < tal_count(pcus); i++) { + /* Is announcement now pending? Add directly to pending queue. */ + if (uintmap_get(&gm->pending_ann_map, pcus[i]->scid.u64)) { + tal_arr_expand(&gm->pending_cupdates, + tal_steal(gm->pending_cupdates, pcus[i])); + continue; + } + + process_pending_cupdate(gm, pcus[i]); + } + tal_free(pcus); +} + +static void reprocess_queued_msgs(struct gossmap_manage *gm) +{ + bool pending_ann_empty, early_ann_empty; + struct gossmap *gossmap = gossmap_manage_get_gossmap(gm); + + pending_ann_empty = uintmap_empty(&gm->pending_ann_map); + early_ann_empty = uintmap_empty(&gm->early_ann_map); + + if (pending_ann_empty) { + reprocess_pending_cupdates(gm); + /* This should have been final! */ + assert(uintmap_empty(&gm->pending_ann_map)); + } + + if (early_ann_empty) { + /* reprocess_pending_cupdates should not have added any! */ + assert(uintmap_empty(&gm->early_ann_map)); + reprocess_early_cupdates(gm); + /* Won't add any more */ + assert(uintmap_empty(&gm->early_ann_map)); + } + + /* Nothing at all outstanding? All node_announcements can now be processed */ + if (early_ann_empty && pending_ann_empty) { + struct pending_nannounce **pnas = gm->pending_nannounces; + + gm->pending_nannounces = tal_arr(gm, struct pending_nannounce *, 0); + + for (size_t i = 0; i < tal_count(pnas); i++) { + struct gossmap_node *node; + + node = gossmap_find_node(gossmap, &pnas[i]->node_id); + if (!node) { + /* Seeker may want to ask about this. */ + query_unknown_node(gm->daemon, + pnas[i]->source_peer, &pnas[i]->node_id); + + /* Don't complain to them: this can happen. */ + bad_gossip(pnas[i]->source_peer, + tal_fmt(tmpctx, + "node_announcement: unknown node %s", + node_id_to_hexstr(tmpctx, &pnas[i]->node_id))); + continue; + } + + process_node_announcement(gm, node, + pnas[i]->timestamp, + &pnas[i]->node_id, + pnas[i]->nannounce, + pnas[i]->source_peer); + } + + /* Won't add any new ones */ + assert(uintmap_empty(&gm->pending_ann_map)); + assert(uintmap_empty(&gm->early_ann_map)); + + tal_free(pnas); + } +} + +static void kill_spent_channel(struct gossmap_manage *gm, + struct gossmap *gossmap, + struct short_channel_id scid) +{ + struct gossmap_chan *chan; + + chan = gossmap_find_chan(gossmap, &scid); + if (!chan) { + status_broken("Dying channel %s already deleted?", + type_to_string(tmpctx, struct short_channel_id, &scid)); + return; + } + + status_debug("Deleting channel %s due to the funding outpoint being " + "spent", + type_to_string(tmpctx, struct short_channel_id, &scid)); + + remove_channel(gm, gossmap, chan, scid); +} + +void gossmap_manage_new_block(struct gossmap_manage *gm, u32 new_blockheight) +{ + u64 idx; + struct gossmap *gossmap = gossmap_manage_get_gossmap(gm); + + for (struct pending_cannounce *pca = uintmap_first(&gm->early_ann_map, &idx); + pca != NULL; + pca = uintmap_after(&gm->early_ann_map, &idx)) { + struct short_channel_id scid; + scid.u64 = idx; + + /* Stop when we are at unreachable heights */ + if (!is_scid_depth_announceable(&scid, new_blockheight)) + break; + + uintmap_del(&gm->early_ann_map, idx); + + if (!uintmap_add(&gm->pending_ann_map, idx, pca)) { + /* Already pending? Ignore */ + tal_free(pca); + continue; + } + + status_debug("gossmap_manage: new block, adding %s to pending...", + short_channel_id_to_str(tmpctx, &scid)); + + /* Ask lightningd about this scid: see + * gossmap_manage_handle_get_txout_reply */ + daemon_conn_send(gm->daemon->master, + take(towire_gossipd_get_txout(NULL, &scid))); + } + + for (size_t i = 0; i < tal_count(gm->dying_channels); i++) { + if (gm->dying_channels[i].deadline_blockheight > new_blockheight) + continue; + + kill_spent_channel(gm, gossmap, gm->dying_channels[i].scid); + gossip_store_del(gm->daemon->gs, + /* FIXME: fix API to give us pre-hdr offsets! */ + gm->dying_channels[i].gossmap_offset + + sizeof(struct gossip_hdr), + WIRE_GOSSIP_STORE_CHAN_DYING); + tal_arr_remove(&gm->dying_channels, i); + } +} + +void gossmap_manage_channel_spent(struct gossmap_manage *gm, + u32 blockheight, + struct short_channel_id scid) +{ + struct gossmap_chan *chan; + const struct gossmap_node *me; + const u8 *msg; + u32 deadline; + u64 off; + struct gossmap *gossmap = gossmap_manage_get_gossmap(gm); + + chan = gossmap_find_chan(gossmap, &scid); + if (!chan) + return; + + me = gossmap_find_node(gossmap, &gm->daemon->id); + /* We delete our own channels immediately, since we have local knowledge */ + if (gossmap_nth_node(gossmap, chan, 0) == me + || gossmap_nth_node(gossmap, chan, 1) == me) { + kill_spent_channel(gm, gossmap, scid); + return; + } + + /* BOLT #7: + * - once its funding output has been spent OR reorganized out: + * - SHOULD forget a channel after a 12-block delay. + */ + deadline = blockheight + 12; + + /* Remember locally so we can kill it in 12 blocks */ + status_debug("channel %s closing soon due" + " to the funding outpoint being spent", + type_to_string(tmpctx, struct short_channel_id, &scid)); + + /* Save to gossip_store in case we restart */ + msg = towire_gossip_store_chan_dying(tmpctx, &scid, deadline); + off = gossip_store_add(gm->daemon->gs, msg, 0, false, NULL); + gossmap_manage_channel_dying(gm, off, deadline, scid); + + /* Mark it dying, so we don't gossip it */ + gossip_store_flag(gm->daemon->gs, chan->cann_off, + GOSSIP_STORE_DYING_BIT, + WIRE_CHANNEL_ANNOUNCEMENT); + /* Channel updates too! */ + for (int dir = 0; dir < 2; dir++) { + if (!gossmap_chan_set(chan, dir)) + continue; + + gossip_store_flag(gm->daemon->gs, + chan->cupdate_off[dir], + GOSSIP_STORE_DYING_BIT, + WIRE_CHANNEL_UPDATE); + } +} + +struct gossmap *gossmap_manage_get_gossmap(struct gossmap_manage *gm) +{ + gossmap_refresh(gm->raw_gossmap, NULL); + return gm->raw_gossmap; +} + +bool gossmap_manage_channel_dying(struct gossmap_manage *gm, + u64 gossmap_offset, + u32 deadline, + struct short_channel_id scid) +{ + struct dying_channel dead; + struct gossmap *gossmap = gossmap_manage_get_gossmap(gm); + + /* Can't kill missing channels! */ + if (!gossmap_find_chan(gossmap, &scid)) + return false; + + dead.deadline_blockheight = deadline; + dead.gossmap_offset = gossmap_offset; + dead.scid = scid; + + tal_arr_expand(&gm->dying_channels, dead); + return true; +} + +/* BOLT #7: + * - if the `gossip_queries` feature is negotiated: + * - MUST NOT relay any gossip messages it did not generate itself, + * unless explicitly requested. + */ +/* i.e. the strong implication is that we spam our own gossip aggressively! + * "Look at me!" "Look at me!!!!". + */ +/* Statistically, how many peers to we tell about each channel? */ +#define GOSSIP_SPAM_REDUNDANCY 5 + +void gossmap_manage_new_peer(struct gossmap_manage *gm, + const struct node_id *peer) +{ + struct gossmap_node *me; + const u8 *msg; + u64 send_threshold; + struct gossmap *gossmap = gossmap_manage_get_gossmap(gm); + + /* Find ourselves; if no channels, nothing to send */ + me = gossmap_find_node(gossmap, &gm->daemon->id); + if (!me) + return; + + send_threshold = -1ULL; + + /* Just in case we have many peers and not all are connecting or + * some other corner case, send everything to first few. */ + if (peer_node_id_map_count(gm->daemon->peers) > GOSSIP_SPAM_REDUNDANCY + && me->num_chans > GOSSIP_SPAM_REDUNDANCY) { + send_threshold = -1ULL / me->num_chans * GOSSIP_SPAM_REDUNDANCY; + } + + for (size_t i = 0; i < me->num_chans; i++) { + struct gossmap_chan *chan = gossmap_nth_chan(gossmap, me, i, NULL); + + /* We set this so we'll send a fraction of all our channels */ + if (pseudorand_u64() > send_threshold) + continue; + + /* Send channel_announce */ + msg = gossmap_chan_get_announce(NULL, gossmap, chan); + queue_peer_msg(gm->daemon, peer, take(msg)); + + /* Send both channel_updates (if they exist): both help people + * use our channel, so we care! */ + for (int dir = 0; dir < 2; dir++) { + if (!gossmap_chan_set(chan, dir)) + continue; + msg = gossmap_chan_get_update(NULL, gossmap, chan, dir); + queue_peer_msg(gm->daemon, peer, take(msg)); + } + } + + /* If we have one, we should send our own node_announcement */ + msg = gossmap_node_get_announce(NULL, gossmap, me); + if (msg) + queue_peer_msg(gm->daemon, peer, take(msg)); +} diff --git a/gossipd/gossmap_manage.h b/gossipd/gossmap_manage.h new file mode 100644 index 000000000000..a49dc38eed51 --- /dev/null +++ b/gossipd/gossmap_manage.h @@ -0,0 +1,112 @@ +#ifndef LIGHTNING_GOSSIPD_GOSSMAP_MANAGE_H +#define LIGHTNING_GOSSIPD_GOSSMAP_MANAGE_H +#include "config.h" + +struct daemon; +struct gossmap_manage; + +struct gossmap_manage *gossmap_manage_new(const tal_t *ctx, + struct daemon *daemon); + +/** + * gossmap_manage_channel_announcement: process an incoming channel_announcement + * @ctx: tal context for return string + * @gm: the gossmap_manage context + * @announce: the channel_announcement message + * @source_peer: peer who sent this (NULL if it's from lightningd) + * + * Returns an error string if it wasn't redundant or included. + */ +const char *gossmap_manage_channel_announcement(const tal_t *ctx, + struct gossmap_manage *gm, + const u8 *announce TAKES, + const struct node_id *source_peer TAKES); + + +/** + * gossmap_manage_handle_get_txout_reply: process a txout reply from lightningd + * @gm: the gossmap_manage context + * @msg: the message + * + * Since handle_channel_announcement asks lightning for utxos, + * it gets called back here. + */ +void gossmap_manage_handle_get_txout_reply(struct gossmap_manage *gm, const u8 *msg); + +/** + * gossmap_manage_channel_update: process an incoming channel_update + * @ctx: tal context for return string + * @gm: the gossmap_manage context + * @update: the channel_update message + * @source_peer: optional peer who sent this + * + * Returns an error string if it wasn't redundant or included. + */ +const char *gossmap_manage_channel_update(const tal_t *ctx, + struct gossmap_manage *gm, + const u8 *update TAKES, + const struct node_id *source_peer TAKES); + +/** + * gossmap_manage_node_announcement: process an incoming node_announcement + * @ctx: tal context for return string allocation + * @gm: the gossmap_manage context + * @node_announcement: the node_announcement message + * @source_peer: optional peer who sent this + * + * Returns an error string if it wasn't redundant or included. + */ +const char *gossmap_manage_node_announcement(const tal_t *ctx, + struct gossmap_manage *gm, + const u8 *node_announcement TAKES, + const struct node_id *source_peer TAKES); + +/** + * gossmap_manage_new_block: handle block height update. + * @gm: the gossmap_manage context + * @new_blockheight: the new blockheight + */ +void gossmap_manage_new_block(struct gossmap_manage *gm, u32 new_blockheight); + +/** + * gossmap_manage_channel_spent: handle an UTXO being spent + * @gm: the gossmap_manage context + * @blockheight: the blockheight it was spent at + * @scid: the short_channel_id + * + * lightningd tells us all the possible UTXOs spent every block: most + * don't match channels. + */ +void gossmap_manage_channel_spent(struct gossmap_manage *gm, + u32 blockheight, + struct short_channel_id scid); + +/** + * gossmap_manage_channel_dying: dying channel loaded from store. + * @gm: the gossmap_manage context + * @offset: the offset of the dying marker in the store. + * @deadline: the blockheight it is to expire + * @scid: the short_channel_id + * + * Returns false if this channel does not exist (already dead!). + */ +bool gossmap_manage_channel_dying(struct gossmap_manage *gm, + u64 gossmap_offset, + u32 deadline, + struct short_channel_id scid); + +/** + * gossmap_manage_get_gossmap: get the (refreshed!) gossmap + * @gm: the gossmap_manage context + */ +struct gossmap *gossmap_manage_get_gossmap(struct gossmap_manage *gm); + +/** + * gossmap_manage_new_peer: send all our own gossip to this peer. + * @gm: the gossmap_manage context + * @peer: the node_id of the peer. + */ +void gossmap_manage_new_peer(struct gossmap_manage *gm, + const struct node_id *peer); + +#endif /* LIGHTNING_GOSSIPD_GOSSMAP_MANAGE_H */ From 7bd42c6022ca0cc9fc5be9ed5084578c8442099a Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 31 Jan 2024 15:03:11 +1030 Subject: [PATCH 20/33] gossipd: gossmap_manage helper to get a node's address, if any. Signed-off-by: Rusty Russell --- gossipd/gossmap_manage.c | 46 ++++++++++++++++++++++++++++++++++++++++ gossipd/gossmap_manage.h | 11 ++++++++++ 2 files changed, 57 insertions(+) diff --git a/gossipd/gossmap_manage.c b/gossipd/gossmap_manage.c index 4bd314bd728d..27f0e6ceead0 100644 --- a/gossipd/gossmap_manage.c +++ b/gossipd/gossmap_manage.c @@ -1075,3 +1075,49 @@ void gossmap_manage_new_peer(struct gossmap_manage *gm, if (msg) queue_peer_msg(gm->daemon, peer, take(msg)); } + +struct wireaddr *gossmap_manage_get_node_addresses(const tal_t *ctx, + struct gossmap_manage *gm, + const struct node_id *node_id) +{ + struct gossmap_node *node; + u8 *nannounce; + struct node_id id; + secp256k1_ecdsa_signature signature; + u32 timestamp; + u8 *addresses, *features; + u8 rgb_color[3], alias[32]; + struct tlv_node_ann_tlvs *na_tlvs; + struct wireaddr *wireaddrs; + struct gossmap *gossmap = gossmap_manage_get_gossmap(gm); + + node = gossmap_find_node(gossmap, node_id); + if (!node) + return NULL; + + nannounce = gossmap_node_get_announce(tmpctx, gossmap, + node); + if (!nannounce) + return NULL; + + if (!fromwire_node_announcement(tmpctx, nannounce, + &signature, &features, + ×tamp, + &id, rgb_color, alias, + &addresses, + &na_tlvs)) { + status_broken("Bad node_announcement for %s in gossip_store: %s", + node_id_to_hexstr(tmpctx, node_id), + tal_hex(tmpctx, nannounce)); + return NULL; + } + + wireaddrs = fromwire_wireaddr_array(ctx, addresses); + if (!wireaddrs) { + status_broken("Bad wireaddrs in node_announcement in gossip_store: %s", + tal_hex(tmpctx, nannounce)); + return NULL; + } + + return wireaddrs; +} diff --git a/gossipd/gossmap_manage.h b/gossipd/gossmap_manage.h index a49dc38eed51..66d6f9e0fdd1 100644 --- a/gossipd/gossmap_manage.h +++ b/gossipd/gossmap_manage.h @@ -109,4 +109,15 @@ struct gossmap *gossmap_manage_get_gossmap(struct gossmap_manage *gm); void gossmap_manage_new_peer(struct gossmap_manage *gm, const struct node_id *peer); +/** + * gossmap_manage_get_node_addresses: get addresses for this node. + * @ctx: the allocation context + * @gm: the gossmap_manage context + * @node_id: the node_id to look up + * + * Returns NULL if we don't have node_announcement for it. + */ +struct wireaddr *gossmap_manage_get_node_addresses(const tal_t *ctx, + struct gossmap_manage *gm, + const struct node_id *node_id); #endif /* LIGHTNING_GOSSIPD_GOSSMAP_MANAGE_H */ From 62ead87269b7882dfc7338985854a62ad2e2f3b0 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 31 Jan 2024 15:03:11 +1030 Subject: [PATCH 21/33] gossipd: implement pruning timer inside gossmap_manage. No external interfaces, we start the timer on allocation. Signed-off-by: Rusty Russell --- gossipd/gossmap_manage.c | 95 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/gossipd/gossmap_manage.c b/gossipd/gossmap_manage.c index 27f0e6ceead0..5b443dd296ed 100644 --- a/gossipd/gossmap_manage.c +++ b/gossipd/gossmap_manage.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -80,8 +81,14 @@ struct gossmap_manage { /* Blockheights of scids to remove */ struct dying_channel *dying_channels; + + /* Occasional check for dead channels */ + struct oneshot *prune_timer; }; +/* Timer recursion */ +static void start_prune_timer(struct gossmap_manage *gm); + static void enqueue_cupdate(struct pending_cupdate ***queue, struct short_channel_id scid, const secp256k1_ecdsa_signature *signature, @@ -208,6 +215,93 @@ static void remove_channel(struct gossmap_manage *gm, } } +static u32 get_timestamp(struct gossmap *gossmap, + const struct gossmap_chan *chan, + int dir) +{ + u32 timestamp; + + /* 0 is sufficient for our needs */ + if (!gossmap_chan_set(chan, dir)) + return 0; + + gossmap_chan_get_update_details(gossmap, chan, dir, + ×tamp, + NULL, NULL, NULL, NULL, NULL, NULL); + return timestamp; +} + +/* Every half a week we look for dead channels (faster in dev) */ +static void prune_network(struct gossmap_manage *gm) +{ + u64 now = gossip_time_now(gm->daemon).ts.tv_sec; + /* Anything below this highwater mark ought to be pruned */ + const s64 highwater = now - GOSSIP_PRUNE_INTERVAL(gm->daemon->dev_fast_gossip_prune); + const struct gossmap_node *me; + struct gossmap *gossmap; + + /* We reload this every time we delete a channel: that way we can tell if it's + * time to remove a node! */ + gossmap = gossmap_manage_get_gossmap(gm); + me = gossmap_find_node(gossmap, &gm->daemon->id); + + /* Now iterate through all channels and see if it is still alive */ + for (size_t i = 0; i < gossmap_max_chan_idx(gossmap); i++) { + struct gossmap_chan *chan = gossmap_chan_byidx(gossmap, i); + u32 timestamp[2]; + struct short_channel_id scid; + + if (!chan) + continue; + + /* BOLT #7: + * - if the `timestamp` of the latest `channel_update` in + * either direction is older than two weeks (1209600 seconds): + * - MAY prune the channel. + */ + /* This is a fancy way of saying "both ends must refresh!" */ + timestamp[0] = get_timestamp(gossmap, chan, 0); + timestamp[1] = get_timestamp(gossmap, chan, 1); + + if (timestamp[0] >= highwater && timestamp[1] >= highwater) + continue; + + scid = gossmap_chan_scid(gossmap, chan); + + /* Is it one of mine? */ + if (gossmap_nth_node(gossmap, chan, 0) == me + || gossmap_nth_node(gossmap, chan, 1) == me) { + int local = (gossmap_nth_node(gossmap, chan, 1) == me); + status_unusual("Pruning local channel %s from gossip_store: local channel_update time %u, remote %u", + type_to_string(tmpctx, struct short_channel_id, + &scid), + timestamp[local], timestamp[!local]); + } + + status_debug("Pruning channel %s from network view (ages %u and %u)", + type_to_string(tmpctx, struct short_channel_id, + &scid), + timestamp[0], timestamp[1]); + + remove_channel(gm, gossmap, chan, scid); + + gossmap = gossmap_manage_get_gossmap(gm); + me = gossmap_find_node(gossmap, &gm->daemon->id); + } + + /* Note: some nodes may have been left with no channels! Gossmap will + * remove them on next refresh. */ + start_prune_timer(gm); +} + +static void start_prune_timer(struct gossmap_manage *gm) +{ + /* Schedule next run now */ + gm->prune_timer = new_reltimer(&gm->daemon->timers, gm, + time_from_sec(GOSSIP_PRUNE_INTERVAL(gm->daemon->dev_fast_gossip_prune)/4), + prune_network, gm); +} + static void reprocess_queued_msgs(struct gossmap_manage *gm); static void report_bad_update(struct gossmap *map, @@ -244,6 +338,7 @@ struct gossmap_manage *gossmap_manage_new(const tal_t *ctx, gm->txf = txout_failures_new(gm, daemon); gm->dying_channels = tal_arr(gm, struct dying_channel, 0); + start_prune_timer(gm); return gm; } From 8bca9f9693ef9fa910eafcfe6fe54bc3b4dd8bba Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 31 Jan 2024 15:03:12 +1030 Subject: [PATCH 22/33] gossipd: interface to have gossmap_manage send updates on init. At initialization, gossipd is supposed to send all the local channel_updates and any node_announcement it knows, so lightningd doesn't generate fresh ones unnecessarily. Signed-off-by: Rusty Russell --- gossipd/gossmap_manage.c | 40 ++++++++++++++++++++++++++++++++++++++++ gossipd/gossmap_manage.h | 10 ++++++++++ 2 files changed, 50 insertions(+) diff --git a/gossipd/gossmap_manage.c b/gossipd/gossmap_manage.c index 5b443dd296ed..9ff939b8a155 100644 --- a/gossipd/gossmap_manage.c +++ b/gossipd/gossmap_manage.c @@ -1171,6 +1171,46 @@ void gossmap_manage_new_peer(struct gossmap_manage *gm, queue_peer_msg(gm->daemon, peer, take(msg)); } +void gossmap_manage_tell_lightningd_locals(struct daemon *daemon, + struct gossmap_manage *gm) +{ + struct gossmap_node *me; + const u8 *nannounce; + struct gossmap *gossmap = gossmap_manage_get_gossmap(gm); + + /* Find ourselves; if no channels, nothing to send */ + me = gossmap_find_node(gossmap, &gm->daemon->id); + if (!me) + return; + + for (size_t i = 0; i < me->num_chans; i++) { + struct gossmap_chan *chan = gossmap_nth_chan(gossmap, me, i, NULL); + struct short_channel_id scid; + const u8 *cupdate; + + scid = gossmap_chan_scid(gossmap, chan); + cupdate = gossmap_chan_get_update(tmpctx, gossmap, chan, 0); + if (cupdate) + daemon_conn_send(daemon->master, + take(towire_gossipd_init_cupdate(NULL, + &scid, + cupdate))); + cupdate = gossmap_chan_get_update(tmpctx, gossmap, chan, 1); + if (cupdate) + daemon_conn_send(daemon->master, + take(towire_gossipd_init_cupdate(NULL, + &scid, + cupdate))); + } + + /* Tell lightningd about our current node_announcement, if any */ + nannounce = gossmap_node_get_announce(tmpctx, gossmap, me); + if (nannounce) + daemon_conn_send(daemon->master, + take(towire_gossipd_init_nannounce(NULL, + nannounce))); +} + struct wireaddr *gossmap_manage_get_node_addresses(const tal_t *ctx, struct gossmap_manage *gm, const struct node_id *node_id) diff --git a/gossipd/gossmap_manage.h b/gossipd/gossmap_manage.h index 66d6f9e0fdd1..96470fd09582 100644 --- a/gossipd/gossmap_manage.h +++ b/gossipd/gossmap_manage.h @@ -120,4 +120,14 @@ void gossmap_manage_new_peer(struct gossmap_manage *gm, struct wireaddr *gossmap_manage_get_node_addresses(const tal_t *ctx, struct gossmap_manage *gm, const struct node_id *node_id); + +/** + * gossmap_manage_tell_lightningd_locals: tell lightningd our latest updates. + * @daemon: the gossip daemon + * @gm: the gossmap_manage context + * + * Done before we reply to gossipd_init. + */ +void gossmap_manage_tell_lightningd_locals(struct daemon *daemon, + struct gossmap_manage *gm); #endif /* LIGHTNING_GOSSIPD_GOSSMAP_MANAGE_H */ From b26e94518043ec25c4d1f5b12e3f037700d342d8 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 31 Jan 2024 15:03:12 +1030 Subject: [PATCH 23/33] gossipd: don't validate UTXOs on our own channels. It's an unnecessary round-trip, and can cause us to complain in CI, in the case where the channel has been closed by the time we ask. Signed-off-by: Rusty Russell --- gossipd/gossipd.c | 3 ++- gossipd/gossipd_wire.csv | 1 + gossipd/gossmap_manage.c | 16 +++++++++++++++- gossipd/gossmap_manage.h | 9 +++++++-- lightningd/channel_gossip.c | 6 +++--- lightningd/gossip_control.c | 2 +- wallet/test/run-wallet.c | 2 +- 7 files changed, 30 insertions(+), 9 deletions(-) diff --git a/gossipd/gossipd.c b/gossipd/gossipd.c index 7ec69907db8b..2f7ebbd1139e 100644 --- a/gossipd/gossipd.c +++ b/gossipd/gossipd.c @@ -812,8 +812,9 @@ static void inject_gossip(struct daemon *daemon, const u8 *msg) u8 *goss; const u8 *errmsg; const char *err; + struct amount_sat *known_amount; - if (!fromwire_gossipd_addgossip(msg, msg, &goss)) + if (!fromwire_gossipd_addgossip(msg, msg, &goss, &known_amount)) master_badmsg(WIRE_GOSSIPD_ADDGOSSIP, msg); switch (fromwire_peektype(goss)) { diff --git a/gossipd/gossipd_wire.csv b/gossipd/gossipd_wire.csv index 6a0cc5d088d4..9ffabb791446 100644 --- a/gossipd/gossipd_wire.csv +++ b/gossipd/gossipd_wire.csv @@ -68,6 +68,7 @@ msgtype,gossipd_new_blockheight_reply,3126 msgtype,gossipd_addgossip,3044 msgdata,gossipd_addgossip,len,u16, msgdata,gossipd_addgossip,msg,u8,len +msgdata,gossipd_addgossip,known_channel,?amount_sat, # Empty string means no problem. msgtype,gossipd_addgossip_reply,3144 diff --git a/gossipd/gossmap_manage.c b/gossipd/gossmap_manage.c index 9ff939b8a155..470490329781 100644 --- a/gossipd/gossmap_manage.c +++ b/gossipd/gossmap_manage.c @@ -371,7 +371,8 @@ static void peer_warning(struct gossmap_manage *gm, const char *gossmap_manage_channel_announcement(const tal_t *ctx, struct gossmap_manage *gm, const u8 *announce TAKES, - const struct node_id *source_peer TAKES) + const struct node_id *source_peer TAKES, + const struct amount_sat *known_amount) { secp256k1_ecdsa_signature node_signature_1, node_signature_2; secp256k1_ecdsa_signature bitcoin_signature_1, bitcoin_signature_2; @@ -427,6 +428,19 @@ const char *gossmap_manage_channel_announcement(const tal_t *ctx, pca->channel_announcement = tal_dup_talarr(pca, u8, announce); pca->source_peer = tal_dup_or_null(pca, struct node_id, source_peer); + /* Are we supposed to add immediately without checking with lightningd? + * Unless we already got it from a peer and we're processing now! + */ + if (known_amount + && !uintmap_get(&gm->pending_ann_map, scid.u64) + && !uintmap_get(&gm->early_ann_map, scid.u64)) { + /* Set with timestamp 0 (we will update once we have a channel_update) */ + gossip_store_add(gm->daemon->gs, announce, 0, false, + towire_gossip_store_channel_amount(tmpctx, *known_amount)); + tal_free(pca); + return NULL; + } + /* FIXME: Flood protection! */ /* Don't know blockheight yet, or not yet deep enough? Don't even ask */ if (!is_scid_depth_announceable(&scid, blockheight)) { diff --git a/gossipd/gossmap_manage.h b/gossipd/gossmap_manage.h index 96470fd09582..6396778aae55 100644 --- a/gossipd/gossmap_manage.h +++ b/gossipd/gossmap_manage.h @@ -14,13 +14,18 @@ struct gossmap_manage *gossmap_manage_new(const tal_t *ctx, * @gm: the gossmap_manage context * @announce: the channel_announcement message * @source_peer: peer who sent this (NULL if it's from lightningd) + * @known_amount: if non-NULL, do not ask lightningd to look up UTXO. * - * Returns an error string if it wasn't redundant or included. + * Returns an error string if it wasn't redundant or included. Lightningd + * suppresses lookups if it generated the announcement, partially because it's + * redundant, but also because in our tests the UTXO is often spent by the time + * it processes the lookup! */ const char *gossmap_manage_channel_announcement(const tal_t *ctx, struct gossmap_manage *gm, const u8 *announce TAKES, - const struct node_id *source_peer TAKES); + const struct node_id *source_peer TAKES, + const struct amount_sat *known_amount); /** diff --git a/lightningd/channel_gossip.c b/lightningd/channel_gossip.c index 97a9d81d73e8..a7f123d1f415 100644 --- a/lightningd/channel_gossip.c +++ b/lightningd/channel_gossip.c @@ -324,7 +324,7 @@ static void broadcast_public_cupdate(struct channel *channel, take(sign_update(NULL, channel->peer->ld, cupdate))); subd_req(ld->gossip, ld->gossip, - take(towire_gossipd_addgossip(NULL, cg->cupdate)), + take(towire_gossipd_addgossip(NULL, cg->cupdate, NULL)), -1, 0, broadcast_public_cupdate_addgossip_reply, channel); } @@ -496,7 +496,7 @@ static void send_channel_announcement(struct channel *channel) &cg->remote_sigs->bitcoin_sig); subd_req(ld->gossip, ld->gossip, - take(towire_gossipd_addgossip(NULL, ca)), + take(towire_gossipd_addgossip(NULL, ca, &channel->funding_sats)), -1, 0, send_channel_announce_addgossip_reply, channel); /* We can also send our first public channel_update now */ broadcast_public_cupdate(channel, true); @@ -1088,6 +1088,6 @@ void channel_gossip_node_announce(struct lightningd *ld) /* Tell gossipd. */ subd_req(ld->gossip, ld->gossip, - take(towire_gossipd_addgossip(NULL, nannounce)), + take(towire_gossipd_addgossip(NULL, nannounce, NULL)), -1, 0, node_announce_addgossip_reply, NULL); } diff --git a/lightningd/gossip_control.c b/lightningd/gossip_control.c index 362a132f0a91..37bdb48df4f7 100644 --- a/lightningd/gossip_control.c +++ b/lightningd/gossip_control.c @@ -410,7 +410,7 @@ static struct command_result *json_addgossip(struct command *cmd, NULL)) return command_param_failed(); - req = towire_gossipd_addgossip(cmd, gossip_msg); + req = towire_gossipd_addgossip(cmd, gossip_msg, NULL); subd_req(cmd->ld->gossip, cmd->ld->gossip, req, -1, 0, json_addgossip_reply, cmd); diff --git a/wallet/test/run-wallet.c b/wallet/test/run-wallet.c index 04160f03b1c9..863c5678906b 100644 --- a/wallet/test/run-wallet.c +++ b/wallet/test/run-wallet.c @@ -1063,7 +1063,7 @@ u8 *towire_final_incorrect_cltv_expiry(const tal_t *ctx UNNEEDED, u32 cltv_expir u8 *towire_final_incorrect_htlc_amount(const tal_t *ctx UNNEEDED, struct amount_msat incoming_htlc_amt UNNEEDED) { fprintf(stderr, "towire_final_incorrect_htlc_amount called!\n"); abort(); } /* Generated stub for towire_gossipd_addgossip */ -u8 *towire_gossipd_addgossip(const tal_t *ctx UNNEEDED, const u8 *msg UNNEEDED) +u8 *towire_gossipd_addgossip(const tal_t *ctx UNNEEDED, const u8 *msg UNNEEDED, struct amount_sat *known_channel UNNEEDED) { fprintf(stderr, "towire_gossipd_addgossip called!\n"); abort(); } /* Generated stub for towire_hsmd_check_pubkey */ u8 *towire_hsmd_check_pubkey(const tal_t *ctx UNNEEDED, u32 index UNNEEDED, const struct pubkey *pubkey UNNEEDED) From 8f935704c1cc8ef1dca73891e6241b4f0fce6893 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 31 Jan 2024 15:03:12 +1030 Subject: [PATCH 24/33] gossipd: re-implement flood protection. If we've got more than 10000 pending channel_announcements, complain and stop processing any more. If this becomes a problem, we can limit individual peers. Signed-off-by: Rusty Russell --- gossipd/gossmap_manage.c | 128 ++++++++++++++++++++++++++++++--------- 1 file changed, 100 insertions(+), 28 deletions(-) diff --git a/gossipd/gossmap_manage.c b/gossipd/gossmap_manage.c index 470490329781..4d38cf900edc 100644 --- a/gossipd/gossmap_manage.c +++ b/gossipd/gossmap_manage.c @@ -54,6 +54,15 @@ struct dying_channel { u32 deadline_blockheight; }; +struct cannounce_map { + UINTMAP(struct pending_cannounce *) map; + size_t count; + + /* Name, for flood reporting */ + const char *name; + bool flood_reported; +}; + struct gossmap_manage { struct daemon *daemon; @@ -64,13 +73,13 @@ struct gossmap_manage { struct gossmap *raw_gossmap; /* Announcements we're checking, indexed by scid */ - UINTMAP(struct pending_cannounce *) pending_ann_map; + struct cannounce_map pending_ann_map; /* Updates we've deferred for above */ struct pending_cupdate **pending_cupdates; /* Announcements which are too early to check. */ - UINTMAP(struct pending_cannounce *) early_ann_map; + struct cannounce_map early_ann_map; struct pending_cupdate **early_cupdates; /* Node announcements (waiting for a pending_cannounce maybe) */ @@ -137,6 +146,70 @@ static void enqueue_nannounce(struct pending_nannounce ***queue, tal_arr_expand(queue, pna); } +/* Helpers to keep counters in sync with maps! */ +static void map_init(struct cannounce_map *map, const char *name) +{ + uintmap_init(&map->map); + map->count = 0; + map->name = name; + map->flood_reported = false; +} + +static bool map_add(struct cannounce_map *map, + struct short_channel_id scid, + struct pending_cannounce *pca) +{ + /* More than 10000 pending things? Stop! */ + if (map->count > 10000) { + if (!map->flood_reported) { + status_unusual("%s being flooded by %s: dropping some", + map->name, + pca->source_peer + ? node_id_to_hexstr(tmpctx, pca->source_peer) + : "unknown"); + map->flood_reported = true; + } + return false; + } + + if (uintmap_add(&map->map, scid.u64, pca)) { + map->count++; + return true; + } + return false; +} + +static struct pending_cannounce *map_del(struct cannounce_map *map, + struct short_channel_id scid) +{ + struct pending_cannounce *pca = uintmap_del(&map->map, scid.u64); + if (pca) { + assert(map->count); + map->count--; + if (map->flood_reported && uintmap_empty(&map->map)) { + status_unusual("%s flood has subsided", map->name); + map->flood_reported = false; + } + } + return pca; +} + +static bool map_empty(const struct cannounce_map *map) +{ + if (uintmap_empty(&map->map)) { + assert(map->count == 0); + return true; + } + assert(map->count != 0); + return false; +} + +static struct pending_cannounce *map_get(struct cannounce_map *map, + struct short_channel_id scid) +{ + return uintmap_get(&map->map, scid.u64); +} + /* Does any channel_announcement preceed this offset in the gossip_store? */ static bool any_cannounce_preceeds_offset(struct gossmap *gossmap, const struct gossmap_node *node, @@ -169,8 +242,8 @@ static void remove_channel(struct gossmap_manage *gm, txout_failures_add(gm->txf, scid); /* Cover race where we were looking up this UTXO as it was spent. */ - tal_free(uintmap_del(&gm->pending_ann_map, scid.u64)); - tal_free(uintmap_del(&gm->early_ann_map, scid.u64)); + tal_free(map_del(&gm->pending_ann_map, scid)); + tal_free(map_del(&gm->early_ann_map, scid)); /* Put in tombstone marker. */ gossip_store_mark_channel_deleted(gm->daemon->gs, &scid); @@ -330,9 +403,9 @@ struct gossmap_manage *gossmap_manage_new(const tal_t *ctx, assert(gm->raw_gossmap); gm->daemon = daemon; - uintmap_init(&gm->pending_ann_map); + map_init(&gm->pending_ann_map, "pending announcements"); gm->pending_cupdates = tal_arr(gm, struct pending_cupdate *, 0); - uintmap_init(&gm->early_ann_map); + map_init(&gm->early_ann_map, "too-early announcements"); gm->early_cupdates = tal_arr(gm, struct pending_cupdate *, 0); gm->pending_nannounces = tal_arr(gm, struct pending_nannounce *, 0); gm->txf = txout_failures_new(gm, daemon); @@ -432,8 +505,8 @@ const char *gossmap_manage_channel_announcement(const tal_t *ctx, * Unless we already got it from a peer and we're processing now! */ if (known_amount - && !uintmap_get(&gm->pending_ann_map, scid.u64) - && !uintmap_get(&gm->early_ann_map, scid.u64)) { + && !map_get(&gm->pending_ann_map, scid) + && !map_get(&gm->early_ann_map, scid)) { /* Set with timestamp 0 (we will update once we have a channel_update) */ gossip_store_add(gm->daemon->gs, announce, 0, false, towire_gossip_store_channel_amount(tmpctx, *known_amount)); @@ -441,7 +514,6 @@ const char *gossmap_manage_channel_announcement(const tal_t *ctx, return NULL; } - /* FIXME: Flood protection! */ /* Don't know blockheight yet, or not yet deep enough? Don't even ask */ if (!is_scid_depth_announceable(&scid, blockheight)) { /* Don't expect to be more than 12 blocks behind! */ @@ -453,7 +525,7 @@ const char *gossmap_manage_channel_announcement(const tal_t *ctx, blockheight); } - if (!uintmap_add(&gm->early_ann_map, scid.u64, pca)) { + if (!map_add(&gm->early_ann_map, scid, pca)) { /* Already pending? Ignore */ tal_free(pca); return NULL; @@ -465,7 +537,7 @@ const char *gossmap_manage_channel_announcement(const tal_t *ctx, status_debug("channel_announcement: Adding %s to pending...", short_channel_id_to_str(tmpctx, &scid)); - if (!uintmap_add(&gm->pending_ann_map, scid.u64, pca)) { + if (!map_add(&gm->pending_ann_map, scid, pca)) { /* Already pending? Ignore */ tal_free(pca); return NULL; @@ -493,7 +565,7 @@ void gossmap_manage_handle_get_txout_reply(struct gossmap_manage *gm, const u8 * status_debug("channel_announcement: got reply for %s...", short_channel_id_to_str(tmpctx, &scid)); - pca = uintmap_del(&gm->pending_ann_map, scid.u64); + pca = map_del(&gm->pending_ann_map, scid); if (!pca) { /* If we looking specifically for this, we no longer * are (but don't penalize sender: we don't know if it was @@ -685,7 +757,7 @@ const char *gossmap_manage_channel_update(const tal_t *ctx, return NULL; /* Still waiting? */ - if (uintmap_get(&gm->pending_ann_map, scid.u64)) { + if (map_get(&gm->pending_ann_map, scid)) { enqueue_cupdate(&gm->pending_cupdates, scid, &signature, @@ -703,7 +775,7 @@ const char *gossmap_manage_channel_update(const tal_t *ctx, } /* Too early? */ - if (uintmap_get(&gm->early_ann_map, scid.u64)) { + if (map_get(&gm->early_ann_map, scid)) { enqueue_cupdate(&gm->early_cupdates, scid, &signature, @@ -834,8 +906,8 @@ const char *gossmap_manage_node_announcement(const tal_t *ctx, node = gossmap_find_node(gossmap, &node_id); if (!node) { /* Still waiting for some channel_announcement? */ - if (!uintmap_empty(&gm->pending_ann_map) - || !uintmap_empty(&gm->early_ann_map)) { + if (!map_empty(&gm->pending_ann_map) + || !map_empty(&gm->early_ann_map)) { enqueue_nannounce(&gm->pending_nannounces, &node_id, timestamp, @@ -907,7 +979,7 @@ static void reprocess_early_cupdates(struct gossmap_manage *gm) for (size_t i = 0; i < tal_count(pcus); i++) { /* Is announcement now pending? Add directly to pending queue. */ - if (uintmap_get(&gm->pending_ann_map, pcus[i]->scid.u64)) { + if (map_get(&gm->pending_ann_map, pcus[i]->scid)) { tal_arr_expand(&gm->pending_cupdates, tal_steal(gm->pending_cupdates, pcus[i])); continue; @@ -923,21 +995,21 @@ static void reprocess_queued_msgs(struct gossmap_manage *gm) bool pending_ann_empty, early_ann_empty; struct gossmap *gossmap = gossmap_manage_get_gossmap(gm); - pending_ann_empty = uintmap_empty(&gm->pending_ann_map); - early_ann_empty = uintmap_empty(&gm->early_ann_map); + pending_ann_empty = map_empty(&gm->pending_ann_map); + early_ann_empty = map_empty(&gm->early_ann_map); if (pending_ann_empty) { reprocess_pending_cupdates(gm); /* This should have been final! */ - assert(uintmap_empty(&gm->pending_ann_map)); + assert(map_empty(&gm->pending_ann_map)); } if (early_ann_empty) { /* reprocess_pending_cupdates should not have added any! */ - assert(uintmap_empty(&gm->early_ann_map)); + assert(map_empty(&gm->early_ann_map)); reprocess_early_cupdates(gm); /* Won't add any more */ - assert(uintmap_empty(&gm->early_ann_map)); + assert(map_empty(&gm->early_ann_map)); } /* Nothing at all outstanding? All node_announcements can now be processed */ @@ -971,8 +1043,8 @@ static void reprocess_queued_msgs(struct gossmap_manage *gm) } /* Won't add any new ones */ - assert(uintmap_empty(&gm->pending_ann_map)); - assert(uintmap_empty(&gm->early_ann_map)); + assert(map_empty(&gm->pending_ann_map)); + assert(map_empty(&gm->early_ann_map)); tal_free(pnas); } @@ -1003,9 +1075,9 @@ void gossmap_manage_new_block(struct gossmap_manage *gm, u32 new_blockheight) u64 idx; struct gossmap *gossmap = gossmap_manage_get_gossmap(gm); - for (struct pending_cannounce *pca = uintmap_first(&gm->early_ann_map, &idx); + for (struct pending_cannounce *pca = uintmap_first(&gm->early_ann_map.map, &idx); pca != NULL; - pca = uintmap_after(&gm->early_ann_map, &idx)) { + pca = uintmap_after(&gm->early_ann_map.map, &idx)) { struct short_channel_id scid; scid.u64 = idx; @@ -1013,9 +1085,9 @@ void gossmap_manage_new_block(struct gossmap_manage *gm, u32 new_blockheight) if (!is_scid_depth_announceable(&scid, new_blockheight)) break; - uintmap_del(&gm->early_ann_map, idx); + map_del(&gm->early_ann_map, scid); - if (!uintmap_add(&gm->pending_ann_map, idx, pca)) { + if (!map_add(&gm->pending_ann_map, scid, pca)) { /* Already pending? Ignore */ tal_free(pca); continue; From fda49fa33dbc5cef53bfb68e4457805ba16d9073 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 31 Jan 2024 16:02:25 +1030 Subject: [PATCH 25/33] gossipd: make the seeker code use gossmap_manage. We add a temporary stub gossmap_manage constructor, which simply opens the gossmap and doesn't do anything else. Then seeker uses this, rather than routing.c, to probe. We optimize our "get random node announcements" a bit by traversing a random set of nodes directly, and seeing if we have no node_announcement, then querying their first channel. Signed-off-by: Rusty Russell --- gossipd/Makefile | 3 + gossipd/gossipd.c | 3 + gossipd/gossipd.h | 3 + gossipd/gossmap_manage.c | 26 ++++++- gossipd/gossmap_manage.h | 4 + gossipd/seeker.c | 115 +++++++++++++++++----------- gossipd/test/run-next_block_range.c | 43 ++++++++++- 7 files changed, 145 insertions(+), 52 deletions(-) diff --git a/gossipd/Makefile b/gossipd/Makefile index 5a12f93c1a57..28237330bac8 100644 --- a/gossipd/Makefile +++ b/gossipd/Makefile @@ -5,6 +5,7 @@ GOSSIPD_HEADERS_WSRC := gossipd/gossipd_wiregen.h \ gossipd/gossip_store_wiregen.h \ gossipd/gossipd.h \ gossipd/gossip_store.h \ + gossipd/gossmap_manage.h \ gossipd/queries.h \ gossipd/routing.h \ gossipd/txout_failures.h \ @@ -44,6 +45,8 @@ GOSSIPD_COMMON_OBJS := \ common/dev_disconnect.o \ common/ecdh_hsmd.o \ common/features.o \ + common/gossmap.o \ + common/fp16.o \ common/status_wiregen.o \ common/key_derive.o \ common/lease_rates.o \ diff --git a/gossipd/gossipd.c b/gossipd/gossipd.c index 2f7ebbd1139e..fa998ce5f3fe 100644 --- a/gossipd/gossipd.c +++ b/gossipd/gossipd.c @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -699,6 +700,8 @@ static void gossip_init(struct daemon *daemon, const u8 *msg) daemon->gs = gossip_store_new(daemon); daemon->rstate = new_routing_state(daemon, daemon); + daemon->gm = gossmap_manage_new_gossmap_only(daemon, daemon); + /* Load stored gossip messages (FIXME: API sucks)*/ daemon->gossip_store_populated = (gossip_store_load(daemon->gs) != 0); diff --git a/gossipd/gossipd.h b/gossipd/gossipd.h index 5b61043d0f40..372194808768 100644 --- a/gossipd/gossipd.h +++ b/gossipd/gossipd.h @@ -53,6 +53,9 @@ struct daemon { /* Routing information */ struct routing_state *rstate; + /* Manager of writing to the gossip_store */ + struct gossmap_manage *gm; + /* Timers: we batch gossip, and also refresh announcements */ struct timers timers; diff --git a/gossipd/gossmap_manage.c b/gossipd/gossmap_manage.c index 4d38cf900edc..31fc392056dd 100644 --- a/gossipd/gossmap_manage.c +++ b/gossipd/gossmap_manage.c @@ -3,19 +3,19 @@ #include #include #include -#include #include +#include #include #include #include #include #include #include -#include +#include +#include #include #include -#include -#include +#include #include #include #include @@ -421,6 +421,24 @@ static void bad_gossip(const struct node_id *source_peer, const char *str) status_peer_debug(source_peer, "Bad gossip order: %s", str); } +/* Minimal gossmap-only transition constructor */ +struct gossmap_manage *gossmap_manage_new_gossmap_only(const tal_t *ctx, + struct daemon *daemon) +{ + struct gossmap_manage *gm = tal(ctx, struct gossmap_manage); + + gm->fd = open(GOSSIP_STORE_FILENAME, O_RDWR); + if (gm->fd < 0) + status_failed(STATUS_FAIL_INTERNAL_ERROR, + "Opening gossip_store store: %s", + strerror(errno)); + gm->raw_gossmap = gossmap_load_fd(gm, gm->fd, report_bad_update, NULL, gm); + assert(gm->raw_gossmap); + gm->daemon = daemon; + + return gm; +} + /* Send peer a warning message, if non-NULL. */ static void peer_warning(struct gossmap_manage *gm, const struct node_id *source_peer, diff --git a/gossipd/gossmap_manage.h b/gossipd/gossmap_manage.h index 6396778aae55..191a989d81d1 100644 --- a/gossipd/gossmap_manage.h +++ b/gossipd/gossmap_manage.h @@ -8,6 +8,10 @@ struct gossmap_manage; struct gossmap_manage *gossmap_manage_new(const tal_t *ctx, struct daemon *daemon); +/* Minimal gossmap-only transition constructor */ +struct gossmap_manage *gossmap_manage_new_gossmap_only(const tal_t *ctx, + struct daemon *daemon); + /** * gossmap_manage_channel_announcement: process an incoming channel_announcement * @ctx: tal context for return string diff --git a/gossipd/seeker.c b/gossipd/seeker.c index 9f2cc5218a53..f23bc1268010 100644 --- a/gossipd/seeker.c +++ b/gossipd/seeker.c @@ -3,16 +3,18 @@ #include #include #include +#include #include #include +#include #include #include #include #include #include #include +#include #include -#include #include #define GOSSIP_SEEKER_INTERVAL(seeker) \ @@ -441,33 +443,38 @@ static int cmp_scid(const struct short_channel_id *a, /* We can't ask for channels by node_id, so probe at random */ static bool get_unannounced_nodes(const tal_t *ctx, - struct routing_state *rstate, + struct daemon *daemon, size_t max, struct short_channel_id **scids, u8 **query_flags) { - size_t num = 0; - u64 offset; - double total_weight = 0.0; + size_t num = 0, off, max_idx; + struct gossmap *gossmap = gossmap_manage_get_gossmap(daemon->gm); - /* Pick an example short_channel_id at random to query. As a - * side-effect this gets the node. */ + /* No nodes? Nothing to ask for */ + max_idx = gossmap_max_node_idx(gossmap); + if (max_idx == 0 || max == 0) + return false; + + /* Start at random index */ + off = pseudorand(max_idx); *scids = tal_arr(ctx, struct short_channel_id, max); - /* FIXME: This is inefficient! Reuse next_block_range here! */ - for (struct chan *c = uintmap_first(&rstate->chanmap, &offset); - c; - c = uintmap_after(&rstate->chanmap, &offset)) { - if (c->nodes[0]->bcast.index && c->nodes[1]->bcast.index) + for (size_t i = 0; i < max_idx; i++) { + const struct gossmap_node *node = gossmap_node_byidx(gossmap, (off + i) % max_idx); + const struct gossmap_chan *chan; + + if (!node) + continue; + if (gossmap_node_announced(node)) continue; - if (num < max) { - (*scids)[num++] = c->scid; - } else { - /* Maybe replace one: approx. reservoir sampling */ - if (random_select(1.0, &total_weight)) - (*scids)[pseudorand(max)] = c->scid; - } + /* Query first chan. */ + chan = gossmap_nth_chan(gossmap, node, 0, NULL); + (*scids)[num++] = gossmap_chan_scid(gossmap, chan); + + if (num == max) + break; } if (num == 0) { @@ -478,18 +485,23 @@ static bool get_unannounced_nodes(const tal_t *ctx, if (num < max) tal_resize(scids, num); - /* Sort them into order. */ + /* Sort them into order, and remove duplicates! */ asort(*scids, num, cmp_scid, NULL); + for (size_t i = 1; i < tal_count(*scids); i++) { + if (short_channel_id_eq(&(*scids)[i], &(*scids)[i-1])) { + tal_arr_remove(scids, i); + } + } /* Now get flags. */ - *query_flags = tal_arr(ctx, u8, num); + *query_flags = tal_arr(ctx, u8, tal_count(*scids)); for (size_t i = 0; i < tal_count(*scids); i++) { - struct chan *c = get_channel(rstate, &(*scids)[i]); + const struct gossmap_chan *chan = gossmap_find_chan(gossmap, &(*scids)[i]); (*query_flags)[i] = 0; - if (!c->nodes[0]->bcast.index) + if (!gossmap_node_announced(gossmap_nth_node(gossmap, chan, 0))) (*query_flags)[i] |= SCID_QF_NODE1; - if (!c->nodes[1]->bcast.index) + if (!gossmap_node_announced(gossmap_nth_node(gossmap, chan, 1))) (*query_flags)[i] |= SCID_QF_NODE2; } return true; @@ -500,9 +512,10 @@ static void peer_gossip_probe_nannounces(struct seeker *seeker); static void nodeannounce_query_done(struct peer *peer, bool complete) { - struct seeker *seeker = peer->daemon->seeker; - struct routing_state *rstate = seeker->daemon->rstate; + struct daemon *daemon = peer->daemon; + struct seeker *seeker = daemon->seeker; size_t new_nannounce = 0, num_scids; + struct gossmap *gossmap = gossmap_manage_get_gossmap(daemon->gm); /* We might have given up on them, then they replied. */ if (seeker->random_peer != peer) { @@ -514,16 +527,16 @@ static void nodeannounce_query_done(struct peer *peer, bool complete) num_scids = tal_count(seeker->nannounce_scids); for (size_t i = 0; i < num_scids; i++) { - struct chan *c = get_channel(rstate, - &seeker->nannounce_scids[i]); + struct gossmap_chan *c = gossmap_find_chan(gossmap, + &seeker->nannounce_scids[i]); /* Could have closed since we asked. */ if (!c) continue; if ((seeker->nannounce_query_flags[i] & SCID_QF_NODE1) - && c->nodes[0]->bcast.index) + && gossmap_node_announced(gossmap_nth_node(gossmap, c, 0))) new_nannounce++; if ((seeker->nannounce_query_flags[i] & SCID_QF_NODE2) - && c->nodes[1]->bcast.index) + && gossmap_node_announced(gossmap_nth_node(gossmap, c, 1))) new_nannounce++; } @@ -550,7 +563,7 @@ static void nodeannounce_query_done(struct peer *peer, bool complete) if (num_scids > 7000) num_scids = 7000; - if (!get_unannounced_nodes(seeker, seeker->daemon->rstate, num_scids, + if (!get_unannounced_nodes(seeker, seeker->daemon, num_scids, &seeker->nannounce_scids, &seeker->nannounce_query_flags)) { /* Nothing unknown at all? Great, we're done */ @@ -583,13 +596,19 @@ static void peer_gossip_probe_nannounces(struct seeker *seeker) } /* They have update with this timestamp: do we want it? */ -static bool want_update(struct seeker *seeker, - u32 timestamp, const struct half_chan *hc) +static bool want_update(struct gossmap *gossmap, + u32 timestamp, + const struct gossmap_chan *chan, + int dir) { - if (!is_halfchan_defined(hc)) + u32 our_timestamp; + + if (!gossmap_chan_set(chan, dir)) return timestamp != 0; - if (timestamp <= hc->bcast.timestamp) + gossmap_chan_get_update_details(gossmap, chan, dir, &our_timestamp, + NULL, NULL, NULL, NULL, NULL, NULL); + if (timestamp <= our_timestamp) return false; return true; @@ -597,12 +616,14 @@ static bool want_update(struct seeker *seeker, /* They gave us timestamps. Do we want updated versions? */ static void check_timestamps(struct seeker *seeker, - struct chan *c, + struct gossmap *gossmap, + struct gossmap_chan *c, const struct channel_update_timestamps *ts, struct peer *peer) { u8 *stale; u8 query_flag = 0; + struct short_channel_id scid; /* BOLT #7: * * `timestamp_node_id_1` is the timestamp of the `channel_update` @@ -612,19 +633,20 @@ static void check_timestamps(struct seeker *seeker, * for `node_id_2`, or 0 if there was no `channel_update` from that * node. */ - if (want_update(seeker, ts->timestamp_node_id_1, &c->half[0])) + if (want_update(gossmap, ts->timestamp_node_id_1, c, 0)) query_flag |= SCID_QF_UPDATE1; - if (want_update(seeker, ts->timestamp_node_id_2, &c->half[1])) + if (want_update(gossmap, ts->timestamp_node_id_2, c, 1)) query_flag |= SCID_QF_UPDATE2; if (!query_flag) return; /* Add in flags if we're already getting it. */ - stale = uintmap_get(&seeker->stale_scids, c->scid.u64); + scid = gossmap_chan_scid(gossmap, c); + stale = uintmap_get(&seeker->stale_scids, scid.u64); if (!stale) { stale = talz(seeker, u8); - uintmap_add(&seeker->stale_scids, c->scid.u64, stale); + uintmap_add(&seeker->stale_scids, scid.u64, stale); set_preferred_peer(seeker, peer); } *stale |= query_flag; @@ -646,20 +668,23 @@ static void process_scid_probe(struct peer *peer, u32 first_blocknum, u32 number_of_blocks, const struct range_query_reply *replies) { - struct seeker *seeker = peer->daemon->seeker; + struct daemon *daemon = peer->daemon; + struct seeker *seeker = daemon->seeker; bool new_unknown_scids = false; + struct gossmap *gossmap; /* We might have given up on them, then they replied. */ if (seeker->random_peer != peer) return; seeker->random_peer = NULL; + gossmap = gossmap_manage_get_gossmap(daemon->gm); for (size_t i = 0; i < tal_count(replies); i++) { - struct chan *c = get_channel(seeker->daemon->rstate, - &replies[i].scid); + struct gossmap_chan *c = gossmap_find_chan(gossmap, + &replies[i].scid); if (c) { - check_timestamps(seeker, c, &replies[i].ts, peer); + check_timestamps(seeker, gossmap, c, &replies[i].ts, peer); continue; } @@ -686,7 +711,7 @@ static void process_scid_probe(struct peer *peer, } /* Channel probe finished, try asking for 128 unannounced nodes. */ - if (!get_unannounced_nodes(seeker, seeker->daemon->rstate, 128, + if (!get_unannounced_nodes(seeker, seeker->daemon, 128, &seeker->nannounce_scids, &seeker->nannounce_query_flags)) { /* No unknown nodes. Great! */ diff --git a/gossipd/test/run-next_block_range.c b/gossipd/test/run-next_block_range.c index 5f18d02b4d2a..4e38a60b9f02 100644 --- a/gossipd/test/run-next_block_range.c +++ b/gossipd/test/run-next_block_range.c @@ -33,6 +33,46 @@ struct peer *find_peer(struct daemon *daemon UNNEEDED, const struct node_id *id struct peer *first_random_peer(struct daemon *daemon UNNEEDED, struct peer_node_id_map_iter *it UNNEEDED) { fprintf(stderr, "first_random_peer called!\n"); abort(); } +/* Generated stub for gossmap_chan_get_update_details */ +void gossmap_chan_get_update_details(const struct gossmap *map UNNEEDED, + const struct gossmap_chan *chan UNNEEDED, + int dir UNNEEDED, + u32 *timestamp UNNEEDED, + u8 *message_flags UNNEEDED, + u8 *channel_flags UNNEEDED, + u32 *fee_base_msat UNNEEDED, + u32 *fee_proportional_millionths UNNEEDED, + struct amount_msat *htlc_minimum_msat UNNEEDED, + struct amount_msat *htlc_maximum_msat UNNEEDED) +{ fprintf(stderr, "gossmap_chan_get_update_details called!\n"); abort(); } +/* Generated stub for gossmap_chan_scid */ +struct short_channel_id gossmap_chan_scid(const struct gossmap *map UNNEEDED, + const struct gossmap_chan *c UNNEEDED) +{ fprintf(stderr, "gossmap_chan_scid called!\n"); abort(); } +/* Generated stub for gossmap_find_chan */ +struct gossmap_chan *gossmap_find_chan(const struct gossmap *map UNNEEDED, + const struct short_channel_id *scid UNNEEDED) +{ fprintf(stderr, "gossmap_find_chan called!\n"); abort(); } +/* Generated stub for gossmap_manage_get_gossmap */ +struct gossmap *gossmap_manage_get_gossmap(struct gossmap_manage *gm UNNEEDED) +{ fprintf(stderr, "gossmap_manage_get_gossmap called!\n"); abort(); } +/* Generated stub for gossmap_max_node_idx */ +u32 gossmap_max_node_idx(const struct gossmap *map UNNEEDED) +{ fprintf(stderr, "gossmap_max_node_idx called!\n"); abort(); } +/* Generated stub for gossmap_node_byidx */ +struct gossmap_node *gossmap_node_byidx(const struct gossmap *map UNNEEDED, u32 idx UNNEEDED) +{ fprintf(stderr, "gossmap_node_byidx called!\n"); abort(); } +/* Generated stub for gossmap_nth_chan */ +struct gossmap_chan *gossmap_nth_chan(const struct gossmap *map UNNEEDED, + const struct gossmap_node *node UNNEEDED, + u32 n UNNEEDED, + int *which_half UNNEEDED) +{ fprintf(stderr, "gossmap_nth_chan called!\n"); abort(); } +/* Generated stub for gossmap_nth_node */ +struct gossmap_node *gossmap_nth_node(const struct gossmap *map UNNEEDED, + const struct gossmap_chan *chan UNNEEDED, + int n UNNEEDED) +{ fprintf(stderr, "gossmap_nth_node called!\n"); abort(); } /* Generated stub for memleak_scan_intmap_ */ void memleak_scan_intmap_(struct htable *memtable UNNEEDED, const struct intmap *m UNNEEDED) { fprintf(stderr, "memleak_scan_intmap_ called!\n"); abort(); } @@ -69,9 +109,6 @@ void queue_peer_msg(struct daemon *daemon UNNEEDED, const struct node_id *peer UNNEEDED, const u8 *msg TAKES UNNEEDED) { fprintf(stderr, "queue_peer_msg called!\n"); abort(); } -/* Generated stub for random_select */ -bool random_select(double weight UNNEEDED, double *tot_weight UNNEEDED) -{ fprintf(stderr, "random_select called!\n"); abort(); } /* Generated stub for status_failed */ void status_failed(enum status_failreason code UNNEEDED, const char *fmt UNNEEDED, ...) From b3d77604de44489c1c356567fb5825af73e67662 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 31 Jan 2024 16:03:25 +1030 Subject: [PATCH 26/33] gossipd: make the query response code use gossmap_manage. This is a bit less optimal than before, where we had an ordered map of channels and could easily serve "channels between scids 800000x and 900000x". We now iterate all of them. The rest is fairly mechanical. Signed-off-by: Rusty Russell --- gossipd/queries.c | 178 +++++++++++++++++------------ gossipd/test/run-crc32_of_update.c | 72 +++++++++--- gossipd/test/run-extended-info.c | 72 +++++++++--- 3 files changed, 223 insertions(+), 99 deletions(-) diff --git a/gossipd/queries.c b/gossipd/queries.c index 68c0dfcb752d..4f11cc53d789 100644 --- a/gossipd/queries.c +++ b/gossipd/queries.c @@ -6,13 +6,15 @@ #include #include #include +#include #include #include #include +#include #include #include +#include #include -#include #include static u32 dev_max_encoding_bytes = -1U; @@ -367,23 +369,46 @@ static u32 crc32_of_update(const u8 *channel_update) return sum; } -static void get_checksum_and_timestamp(struct routing_state *rstate, - const struct chan *chan, - int direction, - u32 *tstamp, u32 *csum) +/* BOLT #7: + * Where: + * * `timestamp_node_id_1` is the timestamp of the `channel_update` for `node_id_1`, or 0 if there was no `channel_update` from that node. + * * `timestamp_node_id_2` is the timestamp of the `channel_update` for `node_id_2`, or 0 if there was no `channel_update` from that node. + */ +static u32 get_timestamp(struct gossmap *gossmap, + const struct gossmap_chan *chan, + int dir) { - const struct half_chan *hc = &chan->half[direction]; + u32 timestamp; + if (!gossmap_chan_set(chan, dir)) + return 0; - if (!is_halfchan_defined(hc)) { - *tstamp = *csum = 0; - } else { - const u8 *update = gossip_store_get(tmpctx, rstate->daemon->gs, - hc->bcast.index); - *tstamp = hc->bcast.timestamp; - *csum = crc32_of_update(update); - } + gossmap_chan_get_update_details(gossmap, chan, dir, ×tamp, + NULL, NULL, NULL, NULL, NULL, NULL); + return timestamp; +} + +/* BOLT #7: + * Where: + * * `checksum_node_id_1` is the checksum of the `channel_update` for + * `node_id_1`, or 0 if there was no `channel_update` from that + * node. + * * `checksum_node_id_2` is the checksum of the `channel_update` for + * `node_id_2`, or 0 if there was no `channel_update` from that + * node. + */ +static u32 get_checksum(struct gossmap *gossmap, + const struct gossmap_chan *chan, + int dir) +{ + u8 *cupdate; + + cupdate = gossmap_chan_get_update(tmpctx, gossmap, chan, dir); + if (!cupdate) + return 0; + return crc32_of_update(cupdate); } + /* FIXME: This assumes that the tlv type encodes into 1 byte! */ static size_t tlv_overhead(size_t num_entries, size_t size) { @@ -437,15 +462,15 @@ static size_t max_entries(enum query_option_flags query_option_flags) /* This gets all the scids they asked for, and optionally the timestamps and checksums */ static struct short_channel_id *gather_range(const tal_t *ctx, - struct routing_state *rstate, + struct daemon *daemon, u32 first_blocknum, u32 number_of_blocks, enum query_option_flags query_option_flags, struct channel_update_timestamps **tstamps, struct channel_update_checksums **csums) { - struct short_channel_id scid, *scids; + struct short_channel_id *scids; u32 end_block; - bool scid_ok; + struct gossmap *gossmap = gossmap_manage_get_gossmap(daemon->gm); scids = tal_arr(ctx, struct short_channel_id, 0); if (query_option_flags & QUERY_ADD_TIMESTAMPS) @@ -457,16 +482,6 @@ static struct short_channel_id *gather_range(const tal_t *ctx, else *csums = NULL; - /* Avoid underflow: we don't use block 0 anyway */ - if (first_blocknum == 0) - scid_ok = mk_short_channel_id(&scid, 1, 0, 0); - else - scid_ok = mk_short_channel_id(&scid, first_blocknum, 0, 0); - scid.u64--; - /* Out of range? No blocks then. */ - if (!scid_ok) - return NULL; - if (number_of_blocks == 0) return NULL; @@ -475,39 +490,44 @@ static struct short_channel_id *gather_range(const tal_t *ctx, if (end_block < first_blocknum) end_block = UINT_MAX; - /* We keep a `uintmap` of `short_channel_id` to `struct chan *`. - * Unlike a htable, it's efficient to iterate through, but it only - * works because each short_channel_id is basically a 64-bit unsigned - * integer. - * - * First we iterate and gather all the short channel ids. */ - while (uintmap_after(&rstate->chanmap, &scid.u64)) { - struct chan *chan; - struct channel_update_timestamps ts; - struct channel_update_checksums cs; + /* We used to maintain a uintmap of channels by scid, but + * we no longer do, making this more expensive. But still + * not too bad, since it's usually in-mem */ + for (size_t i = 0; i < gossmap_max_chan_idx(gossmap); i++) { + struct gossmap_chan *chan = gossmap_chan_byidx(gossmap, i); + struct short_channel_id scid; - if (short_channel_id_blocknum(&scid) > end_block) - break; + if (!chan) + continue; - /* FIXME: Store csum in header. */ - chan = get_channel(rstate, &scid); - tal_arr_expand(&scids, scid); + /* By policy, we don't give announcements here with no + * channel_updates */ + if (!gossmap_chan_set(chan, 0) && !gossmap_chan_set(chan, 1)) { + continue; + } - /* Don't calc csums if we don't even care */ - if (!(query_option_flags - & (QUERY_ADD_TIMESTAMPS|QUERY_ADD_CHECKSUMS))) + scid = gossmap_chan_scid(gossmap, chan); + if (short_channel_id_blocknum(&scid) < first_blocknum + || short_channel_id_blocknum(&scid) > end_block) { continue; + } - get_checksum_and_timestamp(rstate, chan, 0, - &ts.timestamp_node_id_1, - &cs.checksum_node_id_1); - get_checksum_and_timestamp(rstate, chan, 1, - &ts.timestamp_node_id_2, - &cs.checksum_node_id_2); - if (query_option_flags & QUERY_ADD_TIMESTAMPS) + tal_arr_expand(&scids, scid); + + if (*tstamps) { + struct channel_update_timestamps ts; + + ts.timestamp_node_id_1 = get_timestamp(gossmap, chan, 0); + ts.timestamp_node_id_2 = get_timestamp(gossmap, chan, 1); tal_arr_expand(tstamps, ts); - if (query_option_flags & QUERY_ADD_CHECKSUMS) + } + + if (*csums) { + struct channel_update_checksums cs; + cs.checksum_node_id_1 = get_checksum(gossmap, chan, 0); + cs.checksum_node_id_2 = get_checksum(gossmap, chan, 1); tal_arr_expand(csums, cs); + } } return scids; @@ -522,13 +542,13 @@ static void queue_channel_ranges(struct peer *peer, u32 first_blocknum, u32 number_of_blocks, enum query_option_flags query_option_flags) { - struct routing_state *rstate = peer->daemon->rstate; + struct daemon *daemon = peer->daemon; struct channel_update_timestamps *tstamps; struct channel_update_checksums *csums; struct short_channel_id *scids; size_t off, limit; - scids = gather_range(tmpctx, rstate, first_blocknum, number_of_blocks, + scids = gather_range(tmpctx, daemon, first_blocknum, number_of_blocks, query_option_flags, &tstamps, &csums); limit = max_entries(query_option_flags); @@ -929,9 +949,11 @@ static void uniquify_node_ids(struct node_id **ids) * it's finished all of them. */ static bool maybe_send_query_responses_peer(struct peer *peer) { - struct routing_state *rstate = peer->daemon->rstate; + struct daemon *daemon = peer->daemon; size_t i, num; bool sent = false; + const u8 *msg; + struct gossmap *gossmap = gossmap_manage_get_gossmap(daemon->gm); /* BOLT #7: * @@ -940,9 +962,11 @@ static bool maybe_send_query_responses_peer(struct peer *peer) /* Search for next short_channel_id we know about. */ num = tal_count(peer->scid_queries); for (i = peer->scid_query_idx; !sent && i < num; i++) { - struct chan *chan; + struct gossmap_chan *chan; + struct gossmap_node *node; + struct node_id node_id; - chan = get_channel(rstate, &peer->scid_queries[i]); + chan = gossmap_find_chan(gossmap, &peer->scid_queries[i]); if (!chan) continue; @@ -951,7 +975,8 @@ static bool maybe_send_query_responses_peer(struct peer *peer) * - MUST reply with a `channel_announcement` */ if (peer->scid_query_flags[i] & SCID_QF_ANNOUNCE) { - queue_peer_from_store(peer, &chan->bcast); + msg = gossmap_chan_get_announce(NULL, gossmap, chan); + queue_peer_msg(daemon, &peer->id, take(msg)); sent = true; } @@ -965,13 +990,15 @@ static bool maybe_send_query_responses_peer(struct peer *peer) * - MUST reply with the latest `channel_update` for * `node_id_2` */ if ((peer->scid_query_flags[i] & SCID_QF_UPDATE1) - && is_halfchan_defined(&chan->half[0])) { - queue_peer_from_store(peer, &chan->half[0].bcast); + && gossmap_chan_set(chan, 0)) { + msg = gossmap_chan_get_update(NULL, gossmap, chan, 0); + queue_peer_msg(daemon, &peer->id, take(msg)); sent = true; } if ((peer->scid_query_flags[i] & SCID_QF_UPDATE2) - && is_halfchan_defined(&chan->half[1])) { - queue_peer_from_store(peer, &chan->half[1].bcast); + && gossmap_chan_set(chan, 1)) { + msg = gossmap_chan_get_update(NULL, gossmap, chan, 1); + queue_peer_msg(daemon, &peer->id, take(msg)); sent = true; } @@ -985,12 +1012,16 @@ static bool maybe_send_query_responses_peer(struct peer *peer) * - MUST reply with the latest `node_announcement` for * `node_id_2` */ /* Save node ids for later transmission of node_announcement */ - if (peer->scid_query_flags[i] & SCID_QF_NODE1) - tal_arr_expand(&peer->scid_query_nodes, - chan->nodes[0]->id); - if (peer->scid_query_flags[i] & SCID_QF_NODE2) - tal_arr_expand(&peer->scid_query_nodes, - chan->nodes[1]->id); + if (peer->scid_query_flags[i] & SCID_QF_NODE1) { + node = gossmap_nth_node(gossmap, chan, 0); + gossmap_node_get_id(gossmap, node, &node_id); + tal_arr_expand(&peer->scid_query_nodes, node_id); + } + if (peer->scid_query_flags[i] & SCID_QF_NODE2) { + node = gossmap_nth_node(gossmap, chan, 1); + gossmap_node_get_id(gossmap, node, &node_id); + tal_arr_expand(&peer->scid_query_nodes, node_id); + } } /* Just finished channels? Remove duplicate nodes. */ @@ -1021,15 +1052,16 @@ static bool maybe_send_query_responses_peer(struct peer *peer) * node_announcement to send. */ num = tal_count(peer->scid_query_nodes); for (i = peer->scid_query_nodes_idx; !sent && i < num; i++) { - const struct node *n; + const struct gossmap_node *n; /* Not every node announces itself (we know it exists because * of a channel_announcement, however) */ - n = get_node(rstate, &peer->scid_query_nodes[i]); - if (!n || !n->bcast.index) + n = gossmap_find_node(gossmap, &peer->scid_query_nodes[i]); + if (!n || !gossmap_node_announced(n)) continue; - queue_peer_from_store(peer, &n->bcast); + msg = gossmap_node_get_announce(NULL, gossmap, n); + queue_peer_msg(daemon, &peer->id, take(msg)); sent = true; } peer->scid_query_nodes_idx = i; diff --git a/gossipd/test/run-crc32_of_update.c b/gossipd/test/run-crc32_of_update.c index 980274279101..0f0a05028917 100644 --- a/gossipd/test/run-crc32_of_update.c +++ b/gossipd/test/run-crc32_of_update.c @@ -48,15 +48,65 @@ struct peer *first_random_peer(struct daemon *daemon UNNEEDED, /* Generated stub for fromwire_gossipd_dev_set_max_scids_encode_size */ bool fromwire_gossipd_dev_set_max_scids_encode_size(const void *p UNNEEDED, u32 *max UNNEEDED) { fprintf(stderr, "fromwire_gossipd_dev_set_max_scids_encode_size called!\n"); abort(); } -/* Generated stub for get_node */ -struct node *get_node(struct routing_state *rstate UNNEEDED, - const struct node_id *id UNNEEDED) -{ fprintf(stderr, "get_node called!\n"); abort(); } -/* Generated stub for gossip_store_get */ -const u8 *gossip_store_get(const tal_t *ctx UNNEEDED, - struct gossip_store *gs UNNEEDED, - u64 offset UNNEEDED) -{ fprintf(stderr, "gossip_store_get called!\n"); abort(); } +/* Generated stub for gossmap_chan_byidx */ +struct gossmap_chan *gossmap_chan_byidx(const struct gossmap *map UNNEEDED, u32 idx UNNEEDED) +{ fprintf(stderr, "gossmap_chan_byidx called!\n"); abort(); } +/* Generated stub for gossmap_chan_get_announce */ +u8 *gossmap_chan_get_announce(const tal_t *ctx UNNEEDED, + const struct gossmap *map UNNEEDED, + const struct gossmap_chan *c UNNEEDED) +{ fprintf(stderr, "gossmap_chan_get_announce called!\n"); abort(); } +/* Generated stub for gossmap_chan_get_update */ +u8 *gossmap_chan_get_update(const tal_t *ctx UNNEEDED, + const struct gossmap *map UNNEEDED, + const struct gossmap_chan *chan UNNEEDED, + int dir UNNEEDED) +{ fprintf(stderr, "gossmap_chan_get_update called!\n"); abort(); } +/* Generated stub for gossmap_chan_get_update_details */ +void gossmap_chan_get_update_details(const struct gossmap *map UNNEEDED, + const struct gossmap_chan *chan UNNEEDED, + int dir UNNEEDED, + u32 *timestamp UNNEEDED, + u8 *message_flags UNNEEDED, + u8 *channel_flags UNNEEDED, + u32 *fee_base_msat UNNEEDED, + u32 *fee_proportional_millionths UNNEEDED, + struct amount_msat *htlc_minimum_msat UNNEEDED, + struct amount_msat *htlc_maximum_msat UNNEEDED) +{ fprintf(stderr, "gossmap_chan_get_update_details called!\n"); abort(); } +/* Generated stub for gossmap_chan_scid */ +struct short_channel_id gossmap_chan_scid(const struct gossmap *map UNNEEDED, + const struct gossmap_chan *c UNNEEDED) +{ fprintf(stderr, "gossmap_chan_scid called!\n"); abort(); } +/* Generated stub for gossmap_find_chan */ +struct gossmap_chan *gossmap_find_chan(const struct gossmap *map UNNEEDED, + const struct short_channel_id *scid UNNEEDED) +{ fprintf(stderr, "gossmap_find_chan called!\n"); abort(); } +/* Generated stub for gossmap_find_node */ +struct gossmap_node *gossmap_find_node(const struct gossmap *map UNNEEDED, + const struct node_id *id UNNEEDED) +{ fprintf(stderr, "gossmap_find_node called!\n"); abort(); } +/* Generated stub for gossmap_manage_get_gossmap */ +struct gossmap *gossmap_manage_get_gossmap(struct gossmap_manage *gm UNNEEDED) +{ fprintf(stderr, "gossmap_manage_get_gossmap called!\n"); abort(); } +/* Generated stub for gossmap_max_chan_idx */ +u32 gossmap_max_chan_idx(const struct gossmap *map UNNEEDED) +{ fprintf(stderr, "gossmap_max_chan_idx called!\n"); abort(); } +/* Generated stub for gossmap_node_get_announce */ +u8 *gossmap_node_get_announce(const tal_t *ctx UNNEEDED, + const struct gossmap *map UNNEEDED, + const struct gossmap_node *n UNNEEDED) +{ fprintf(stderr, "gossmap_node_get_announce called!\n"); abort(); } +/* Generated stub for gossmap_node_get_id */ +void gossmap_node_get_id(const struct gossmap *map UNNEEDED, + const struct gossmap_node *node UNNEEDED, + struct node_id *id UNNEEDED) +{ fprintf(stderr, "gossmap_node_get_id called!\n"); abort(); } +/* Generated stub for gossmap_nth_node */ +struct gossmap_node *gossmap_nth_node(const struct gossmap *map UNNEEDED, + const struct gossmap_chan *chan UNNEEDED, + int n UNNEEDED) +{ fprintf(stderr, "gossmap_nth_node called!\n"); abort(); } /* Generated stub for master_badmsg */ void master_badmsg(u32 type_expected UNNEEDED, const u8 *msg) { fprintf(stderr, "master_badmsg called!\n"); abort(); } @@ -70,10 +120,6 @@ void peer_supplied_good_gossip(struct daemon *daemon UNNEEDED, const struct node_id *source_peer UNNEEDED, size_t amount UNNEEDED) { fprintf(stderr, "peer_supplied_good_gossip called!\n"); abort(); } -/* Generated stub for queue_peer_from_store */ -void queue_peer_from_store(struct peer *peer UNNEEDED, - const struct broadcastable *bcast UNNEEDED) -{ fprintf(stderr, "queue_peer_from_store called!\n"); abort(); } /* Generated stub for queue_peer_msg */ void queue_peer_msg(struct daemon *daemon UNNEEDED, const struct node_id *peer UNNEEDED, diff --git a/gossipd/test/run-extended-info.c b/gossipd/test/run-extended-info.c index 11d53e86107d..4b14e108012c 100644 --- a/gossipd/test/run-extended-info.c +++ b/gossipd/test/run-extended-info.c @@ -52,15 +52,65 @@ struct peer *first_random_peer(struct daemon *daemon UNNEEDED, /* Generated stub for fromwire_gossipd_dev_set_max_scids_encode_size */ bool fromwire_gossipd_dev_set_max_scids_encode_size(const void *p UNNEEDED, u32 *max UNNEEDED) { fprintf(stderr, "fromwire_gossipd_dev_set_max_scids_encode_size called!\n"); abort(); } -/* Generated stub for get_node */ -struct node *get_node(struct routing_state *rstate UNNEEDED, - const struct node_id *id UNNEEDED) -{ fprintf(stderr, "get_node called!\n"); abort(); } -/* Generated stub for gossip_store_get */ -const u8 *gossip_store_get(const tal_t *ctx UNNEEDED, - struct gossip_store *gs UNNEEDED, - u64 offset UNNEEDED) -{ fprintf(stderr, "gossip_store_get called!\n"); abort(); } +/* Generated stub for gossmap_chan_byidx */ +struct gossmap_chan *gossmap_chan_byidx(const struct gossmap *map UNNEEDED, u32 idx UNNEEDED) +{ fprintf(stderr, "gossmap_chan_byidx called!\n"); abort(); } +/* Generated stub for gossmap_chan_get_announce */ +u8 *gossmap_chan_get_announce(const tal_t *ctx UNNEEDED, + const struct gossmap *map UNNEEDED, + const struct gossmap_chan *c UNNEEDED) +{ fprintf(stderr, "gossmap_chan_get_announce called!\n"); abort(); } +/* Generated stub for gossmap_chan_get_update */ +u8 *gossmap_chan_get_update(const tal_t *ctx UNNEEDED, + const struct gossmap *map UNNEEDED, + const struct gossmap_chan *chan UNNEEDED, + int dir UNNEEDED) +{ fprintf(stderr, "gossmap_chan_get_update called!\n"); abort(); } +/* Generated stub for gossmap_chan_get_update_details */ +void gossmap_chan_get_update_details(const struct gossmap *map UNNEEDED, + const struct gossmap_chan *chan UNNEEDED, + int dir UNNEEDED, + u32 *timestamp UNNEEDED, + u8 *message_flags UNNEEDED, + u8 *channel_flags UNNEEDED, + u32 *fee_base_msat UNNEEDED, + u32 *fee_proportional_millionths UNNEEDED, + struct amount_msat *htlc_minimum_msat UNNEEDED, + struct amount_msat *htlc_maximum_msat UNNEEDED) +{ fprintf(stderr, "gossmap_chan_get_update_details called!\n"); abort(); } +/* Generated stub for gossmap_chan_scid */ +struct short_channel_id gossmap_chan_scid(const struct gossmap *map UNNEEDED, + const struct gossmap_chan *c UNNEEDED) +{ fprintf(stderr, "gossmap_chan_scid called!\n"); abort(); } +/* Generated stub for gossmap_find_chan */ +struct gossmap_chan *gossmap_find_chan(const struct gossmap *map UNNEEDED, + const struct short_channel_id *scid UNNEEDED) +{ fprintf(stderr, "gossmap_find_chan called!\n"); abort(); } +/* Generated stub for gossmap_find_node */ +struct gossmap_node *gossmap_find_node(const struct gossmap *map UNNEEDED, + const struct node_id *id UNNEEDED) +{ fprintf(stderr, "gossmap_find_node called!\n"); abort(); } +/* Generated stub for gossmap_manage_get_gossmap */ +struct gossmap *gossmap_manage_get_gossmap(struct gossmap_manage *gm UNNEEDED) +{ fprintf(stderr, "gossmap_manage_get_gossmap called!\n"); abort(); } +/* Generated stub for gossmap_max_chan_idx */ +u32 gossmap_max_chan_idx(const struct gossmap *map UNNEEDED) +{ fprintf(stderr, "gossmap_max_chan_idx called!\n"); abort(); } +/* Generated stub for gossmap_node_get_announce */ +u8 *gossmap_node_get_announce(const tal_t *ctx UNNEEDED, + const struct gossmap *map UNNEEDED, + const struct gossmap_node *n UNNEEDED) +{ fprintf(stderr, "gossmap_node_get_announce called!\n"); abort(); } +/* Generated stub for gossmap_node_get_id */ +void gossmap_node_get_id(const struct gossmap *map UNNEEDED, + const struct gossmap_node *node UNNEEDED, + struct node_id *id UNNEEDED) +{ fprintf(stderr, "gossmap_node_get_id called!\n"); abort(); } +/* Generated stub for gossmap_nth_node */ +struct gossmap_node *gossmap_nth_node(const struct gossmap *map UNNEEDED, + const struct gossmap_chan *chan UNNEEDED, + int n UNNEEDED) +{ fprintf(stderr, "gossmap_nth_node called!\n"); abort(); } /* Generated stub for master_badmsg */ void master_badmsg(u32 type_expected UNNEEDED, const u8 *msg) { fprintf(stderr, "master_badmsg called!\n"); abort(); } @@ -74,10 +124,6 @@ void peer_supplied_good_gossip(struct daemon *daemon UNNEEDED, const struct node_id *source_peer UNNEEDED, size_t amount UNNEEDED) { fprintf(stderr, "peer_supplied_good_gossip called!\n"); abort(); } -/* Generated stub for queue_peer_from_store */ -void queue_peer_from_store(struct peer *peer UNNEEDED, - const struct broadcastable *bcast UNNEEDED) -{ fprintf(stderr, "queue_peer_from_store called!\n"); abort(); } /* Generated stub for queue_peer_msg */ void queue_peer_msg(struct daemon *daemon UNNEEDED, const struct node_id *peer UNNEEDED, From 7115b05ca34ff8aebbfdb9dc7833b8cef60e71c0 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 31 Jan 2024 16:14:06 +1030 Subject: [PATCH 27/33] gossipd: switch over to using gossmap_manage, not routing.c. The gossip_store_load is now basically a noop, since gossmap does that. gossipd removes a pile of routines dealing with messages, in favor of just handing them to gossmap_manage. The stub gossmap_manage constructor is removed entirely. We simplified behaviour around channel_announcements with no channel update: we now add them to the store, and go back to fix the timestamp later. This changes a test, which explicitly tests for the old behaviour. Signed-off-by: Rusty Russell --- gossipd/gossip_store.c | 41 +--- gossipd/gossipd.c | 413 +++++---------------------------------- gossipd/gossipd.h | 3 - gossipd/gossmap_manage.c | 18 -- gossipd/gossmap_manage.h | 4 - tests/test_gossip.py | 15 +- 6 files changed, 71 insertions(+), 423 deletions(-) diff --git a/gossipd/gossip_store.c b/gossipd/gossip_store.c index 6c759a389fb5..41999a8f97af 100644 --- a/gossipd/gossip_store.c +++ b/gossipd/gossip_store.c @@ -8,9 +8,12 @@ #include #include #include +#include #include #include -#include +#include +#include +#include #include #include #include @@ -627,8 +630,8 @@ u32 gossip_store_load(struct gossip_store *gs) struct timeabs start = time_now(); size_t deleted = 0; u8 *chan_ann = NULL; - u64 chan_ann_off = 0; /* Spurious gcc-9 (Ubuntu 9-20190402-1ubuntu1) 9.0.1 20190402 (experimental) warning */ + /* FIXME: Do compaction here, and check checksums, etc then.. */ gs->writable = false; while (pread(gs->fd, &hdr, sizeof(hdr), gs->len) == sizeof(hdr)) { msglen = be16_to_cpu(hdr.len); @@ -662,14 +665,6 @@ u32 gossip_store_load(struct gossip_store *gs) /* Previous channel_announcement may have been deleted */ if (!chan_ann) break; - if (!routing_add_channel_announcement(gs->daemon->rstate, - take(chan_ann), - satoshis, - chan_ann_off, - NULL)) { - bad = "Bad channel_announcement"; - goto badmsg; - } chan_ann = NULL; stats[0]++; break; @@ -678,9 +673,8 @@ u32 gossip_store_load(struct gossip_store *gs) bad = "channel_announcement without amount"; goto badmsg; } - /* Save for channel_amount (next msg) */ + /* Save for channel_amount (next msg) (not tmpctx, it gets cleaned!) */ chan_ann = tal_steal(gs, msg); - chan_ann_off = gs->len; break; case WIRE_GOSSIP_STORE_CHAN_DYING: { struct short_channel_id scid; @@ -690,26 +684,16 @@ u32 gossip_store_load(struct gossip_store *gs) bad = "Bad gossip_store_chan_dying"; goto badmsg; } - remember_chan_dying(gs->daemon->rstate, &scid, deadline, gs->len); + if (!gossmap_manage_channel_dying(gs->daemon->gm, gs->len, deadline, scid)) { + bad = "Invalid gossip_store_chan_dying"; + goto badmsg; + } break; } case WIRE_CHANNEL_UPDATE: - if (!routing_add_channel_update(gs->daemon->rstate, - take(msg), gs->len, - NULL, false)) { - bad = "Bad channel_update"; - goto badmsg; - } stats[1]++; break; case WIRE_NODE_ANNOUNCEMENT: - if (!routing_add_node_announcement(gs->daemon->rstate, - take(msg), gs->len, - NULL, NULL)) { - /* FIXME: This has been reported: routing.c - * has logged, so ignore. */ - break; - } stats[2]++; break; default: @@ -728,10 +712,6 @@ u32 gossip_store_load(struct gossip_store *gs) goto corrupt; } - bad = unfinalized_entries(tmpctx, gs->daemon->rstate); - if (bad) - goto corrupt; - goto out; badmsg: @@ -748,7 +728,6 @@ u32 gossip_store_load(struct gossip_store *gs) if (gs->fd < 0 || !write_all(gs->fd, &gs->version, sizeof(gs->version))) status_failed(STATUS_FAIL_INTERNAL_ERROR, "Truncating new store file: %s", strerror(errno)); - remove_all_gossip(gs->daemon->rstate); gs->len = 1; gs->timestamp = 0; out: diff --git a/gossipd/gossipd.c b/gossipd/gossipd.c index fa998ce5f3fe..5f40a01d4beb 100644 --- a/gossipd/gossipd.c +++ b/gossipd/gossipd.c @@ -98,225 +98,12 @@ void queue_peer_msg(struct daemon *daemon, tal_free(msg); } -/*~ We have a helper for messages from the store. */ -void queue_peer_from_store(struct peer *peer, - const struct broadcastable *bcast) -{ - struct gossip_store *gs = peer->daemon->gs; - queue_peer_msg(peer->daemon, &peer->id, - take(gossip_store_get(NULL, gs, bcast->index))); -} - -/*~ We don't actually keep node_announcements in memory; we keep them in - * a file called `gossip_store`. If we need some node details, we reload - * and reparse. It's slow, but generally rare. */ -static bool get_node_announcement(const tal_t *ctx, - struct daemon *daemon, - const struct node *n, - u8 rgb_color[3], - u8 alias[32], - u8 **features, - struct wireaddr **wireaddrs, - struct lease_rates **rates) -{ - const u8 *msg; - struct node_id id; - secp256k1_ecdsa_signature signature; - u32 timestamp; - u8 *addresses; - struct tlv_node_ann_tlvs *na_tlvs; - - if (!n->bcast.index) - return false; - - msg = gossip_store_get(tmpctx, daemon->gs, n->bcast.index); - - /* Note: validity of node_id is already checked. */ - if (!fromwire_node_announcement(ctx, msg, - &signature, features, - ×tamp, - &id, rgb_color, alias, - &addresses, - &na_tlvs)) { - status_broken("Bad local node_announcement @%u: %s", - n->bcast.index, tal_hex(tmpctx, msg)); - return false; - } - - if (!node_id_eq(&id, &n->id) || timestamp != n->bcast.timestamp) { - status_broken("Wrong node_announcement @%u:" - " expected %s timestamp %u " - " got %s timestamp %u", - n->bcast.index, - type_to_string(tmpctx, struct node_id, &n->id), - timestamp, - type_to_string(tmpctx, struct node_id, &id), - n->bcast.timestamp); - return false; - } - - *wireaddrs = fromwire_wireaddr_array(ctx, addresses); - *rates = tal_steal(ctx, na_tlvs->option_will_fund); - - tal_free(addresses); - return true; -} - -/* Version which also does nodeid lookup */ -static bool get_node_announcement_by_id(const tal_t *ctx, - struct daemon *daemon, - const struct node_id *node_id, - u8 rgb_color[3], - u8 alias[32], - u8 **features, - struct wireaddr **wireaddrs, - struct lease_rates **rates) -{ - struct node *n = get_node(daemon->rstate, node_id); - if (!n) - return false; - - return get_node_announcement(ctx, daemon, n, rgb_color, alias, - features, wireaddrs, rates); -} - -/*~Routines to handle gossip messages from peer, forwarded by subdaemons. +/*~Routines to handle gossip messages from peer, forwarded by connectd. *----------------------------------------------------------------------- * - * It's not the subdaemon's fault if they're malformed or invalid; so these - * all return an error packet which gets sent back to the subdaemon in that - * case. + * We send back a warning if they send us something bogus. */ -/* The routing code checks that it's basically valid, returning an - * error message for the peer or NULL. NULL means it's OK, but the - * message might be redundant, in which case scid is also NULL. - * Otherwise `scid` gives us the short_channel_id claimed by the - * message, and puts the announcemnt on an internal 'pending' - * queue. We'll send a request to lightningd to look it up, and continue - * processing in `handle_txout_reply`. */ -static const u8 *handle_channel_announcement_msg(struct daemon *daemon, - const struct node_id *source_peer, - const u8 *msg) -{ - const struct short_channel_id *scid; - const u8 *err; - - /* If it's OK, tells us the short_channel_id to lookup; it notes - * if this is the unknown channel the peer was looking for (in - * which case, it frees and NULLs that ptr) */ - err = handle_channel_announcement(daemon->rstate, msg, - daemon->current_blockheight, - &scid, source_peer); - if (err) - return err; - else if (scid) { - /* We give them some grace period, in case we don't know about - * block yet. */ - if (daemon->current_blockheight == 0 - || !is_scid_depth_announceable(scid, - daemon->current_blockheight)) { - tal_arr_expand(&daemon->deferred_txouts, *scid); - } else { - daemon_conn_send(daemon->master, - take(towire_gossipd_get_txout(NULL, - scid))); - } - } - return NULL; -} - -static u8 *handle_channel_update_msg(struct peer *peer, const u8 *msg) -{ - struct short_channel_id unknown_scid; - /* Hand the channel_update to the routing code */ - u8 *err; - - unknown_scid.u64 = 0; - err = handle_channel_update(peer->daemon->rstate, msg, &peer->id, - &unknown_scid, false); - if (err) - return err; - - /* If it's an unknown channel, ask someone about it */ - if (unknown_scid.u64 != 0) - query_unknown_channel(peer->daemon, &peer->id, unknown_scid); - - return NULL; -} - -static u8 *handle_node_announce(struct peer *peer, const u8 *msg) -{ - bool was_unknown = false; - u8 *err; - - err = handle_node_announcement(peer->daemon->rstate, msg, &peer->id, - &was_unknown); - if (was_unknown) - query_unknown_node(peer->daemon, &peer->id, NULL); - return err; -} - -/* Statistically, how many peers to we tell about each channel? */ -#define GOSSIP_SPAM_REDUNDANCY 5 - -/* BOLT #7: - * - if the `gossip_queries` feature is negotiated: - * - MUST NOT relay any gossip messages it did not generate itself, - * unless explicitly requested. - */ -/* i.e. the strong implication is that we spam our own gossip aggressively! - * "Look at me!" "Look at me!!!!". - */ -static void dump_our_gossip(struct daemon *daemon, struct peer *peer) -{ - struct node *me; - struct chan_map_iter it; - const struct chan *chan, **chans = tal_arr(tmpctx, const struct chan *, 0); - size_t num_to_send; - - /* Find ourselves; if no channels, nothing to send */ - me = get_node(daemon->rstate, &daemon->id); - if (!me) - return; - - for (chan = first_chan(me, &it); chan; chan = next_chan(me, &it)) { - tal_arr_expand(&chans, chan); - } - - /* Just in case we have many peers and not all are connecting or - * some other corner case, send everything to first few. */ - if (peer_node_id_map_count(daemon->peers) <= GOSSIP_SPAM_REDUNDANCY) - num_to_send = tal_count(chans); - else { - if (tal_count(chans) < GOSSIP_SPAM_REDUNDANCY) - num_to_send = tal_count(chans); - else { - /* Pick victims at random */ - tal_arr_randomize(chans, const struct chan *); - num_to_send = GOSSIP_SPAM_REDUNDANCY; - } - } - - for (size_t i = 0; i < num_to_send; i++) { - chan = chans[i]; - - /* Send channel_announce */ - queue_peer_from_store(peer, &chan->bcast); - - /* Send both channel_updates (if they exist): both help people - * use our channel, so we care! */ - for (int dir = 0; dir < 2; dir++) { - if (is_halfchan_defined(&chan->half[dir])) - queue_peer_from_store(peer, &chan->half[dir].bcast); - } - } - - /* If we have one, we should send our own node_announcement */ - if (me->bcast.index) - queue_peer_from_store(peer, &me->bcast); -} - /*~ This is where connectd tells us about a new peer we might want to * gossip with. */ static void connectd_new_peer(struct daemon *daemon, const u8 *msg) @@ -352,7 +139,7 @@ static void connectd_new_peer(struct daemon *daemon, const u8 *msg) tal_add_destructor(peer, destroy_peer); /* Send everything we know about our own channels */ - dump_our_gossip(daemon, peer); + gossmap_manage_new_peer(daemon->gm, &peer->id); /* This sends the initial timestamp filter. */ seeker_setup_peer_gossip(daemon->seeker, peer); @@ -382,19 +169,14 @@ static struct io_plan *handle_get_address(struct io_conn *conn, const u8 *msg) { struct node_id id; - u8 rgb_color[3]; - u8 alias[32]; - u8 *features; struct wireaddr *addrs; - struct lease_rates *rates; if (!fromwire_gossipd_get_addrs(msg, &id)) master_badmsg(WIRE_GOSSIPD_GET_ADDRS, msg); - if (!get_node_announcement_by_id(tmpctx, daemon, &id, - rgb_color, alias, &features, &addrs, - &rates)) - addrs = NULL; + addrs = gossmap_manage_get_node_addresses(tmpctx, + daemon->gm, + &id); daemon_conn_send(daemon->master, take(towire_gossipd_get_addrs_reply(NULL, addrs))); @@ -403,36 +185,44 @@ static struct io_plan *handle_get_address(struct io_conn *conn, static void handle_recv_gossip(struct daemon *daemon, const u8 *outermsg) { - struct node_id id; + struct node_id source; u8 *msg; const u8 *err; + const char *errmsg; struct peer *peer; - if (!fromwire_gossipd_recv_gossip(outermsg, outermsg, &id, &msg)) { + if (!fromwire_gossipd_recv_gossip(outermsg, outermsg, &source, &msg)) { status_failed(STATUS_FAIL_INTERNAL_ERROR, "Bad gossipd_recv_gossip msg from connectd: %s", tal_hex(tmpctx, outermsg)); } - peer = find_peer(daemon, &id); + peer = find_peer(daemon, &source); if (!peer) { status_broken("connectd sent gossip msg %s from unknown peer %s", peer_wire_name(fromwire_peektype(msg)), - type_to_string(tmpctx, struct node_id, &id)); + type_to_string(tmpctx, struct node_id, &source)); return; } + status_peer_debug(&source, "handle_recv_gossip: %s", peer_wire_name(fromwire_peektype(msg))); /* These are messages relayed from peer */ switch ((enum peer_wire)fromwire_peektype(msg)) { case WIRE_CHANNEL_ANNOUNCEMENT: - err = handle_channel_announcement_msg(peer->daemon, &id, msg); - goto handled_msg; + errmsg = gossmap_manage_channel_announcement(tmpctx, + daemon->gm, + msg, &source, NULL); + goto handled_msg_errmsg; case WIRE_CHANNEL_UPDATE: - err = handle_channel_update_msg(peer, msg); - goto handled_msg; + errmsg = gossmap_manage_channel_update(tmpctx, + daemon->gm, + msg, &source); + goto handled_msg_errmsg; case WIRE_NODE_ANNOUNCEMENT: - err = handle_node_announce(peer, msg); - goto handled_msg; + errmsg = gossmap_manage_node_announcement(tmpctx, + daemon->gm, + msg, &source); + goto handled_msg_errmsg; case WIRE_QUERY_CHANNEL_RANGE: err = handle_query_channel_range(peer, msg); goto handled_msg; @@ -496,12 +286,19 @@ static void handle_recv_gossip(struct daemon *daemon, const u8 *outermsg) peer_wire_name(fromwire_peektype(msg)), type_to_string(tmpctx, struct node_id, &peer->id)); -handled_msg: - if (err) - queue_peer_msg(peer->daemon, &peer->id, take(err)); +handled_msg_errmsg: + if (errmsg) + err = towire_warningfmt(NULL, NULL, "%s", errmsg); else + err = NULL; + +handled_msg: + if (err) { + queue_peer_msg(daemon, &source, take(err)); + } else { /* Some peer gave us gossip, so we're not at zero. */ peer->daemon->gossip_store_populated = true; + } } /*~ connectd's input handler is very simple. */ @@ -536,26 +333,6 @@ static struct io_plan *connectd_req(struct io_conn *conn, return daemon_conn_read_next(conn, daemon->connectd); } -/* BOLT #7: - * - * A node: - * - if the `timestamp` of the latest `channel_update` in - * either direction is older than two weeks (1209600 seconds): - * - MAY prune the channel. - * - MAY ignore the channel. - */ -static void gossip_refresh_network(struct daemon *daemon) -{ - /* Schedule next run now */ - notleak(new_reltimer(&daemon->timers, daemon, - time_from_sec(GOSSIP_PRUNE_INTERVAL(daemon->dev_fast_gossip_prune)/4), - gossip_refresh_network, daemon)); - - /* Prune: I hope lightningd is keeping up with our own channel - * refreshes! */ - route_prune(daemon->rstate); -} - void tell_lightningd_peer_update(struct daemon *daemon, const struct node_id *source_peer, struct short_channel_id scid, @@ -577,49 +354,6 @@ void tell_lightningd_peer_update(struct daemon *daemon, daemon_conn_send(daemon->master, take(msg)); } -static void tell_master_local_cupdates(struct daemon *daemon) -{ - struct chan_map_iter i; - struct chan *c; - struct node *me; - - me = get_node(daemon->rstate, &daemon->id); - if (!me) - return; - - for (c = first_chan(me, &i); c; c = next_chan(me, &i)) { - struct half_chan *hc; - int direction; - const u8 *cupdate; - - if (!local_direction(daemon->rstate, c, &direction)) - continue; - - hc = &c->half[direction]; - if (!is_halfchan_defined(hc)) - continue; - - cupdate = gossip_store_get(tmpctx, - daemon->gs, - hc->bcast.index); - daemon_conn_send(daemon->master, - take(towire_gossipd_init_cupdate(NULL, - &c->scid, - cupdate))); - } - - /* Tell lightningd about our current node_announcement, if any */ - if (me->bcast.index) { - const u8 *nannounce; - nannounce = gossip_store_get(tmpctx, - daemon->gs, - me->bcast.index); - daemon_conn_send(daemon->master, - take(towire_gossipd_init_nannounce(NULL, - nannounce))); - } -} - struct peer *first_random_peer(struct daemon *daemon, struct peer_node_id_map_iter *it) { @@ -698,19 +432,13 @@ static void gossip_init(struct daemon *daemon, const u8 *msg) } daemon->gs = gossip_store_new(daemon); - daemon->rstate = new_routing_state(daemon, daemon); - - daemon->gm = gossmap_manage_new_gossmap_only(daemon, daemon); + /* Gossmap manager starts up */ + daemon->gm = gossmap_manage_new(daemon, daemon); /* Load stored gossip messages (FIXME: API sucks)*/ daemon->gossip_store_populated = (gossip_store_load(daemon->gs) != 0); - /* Start the twice- weekly refresh timer. */ - notleak(new_reltimer(&daemon->timers, daemon, - time_from_sec(GOSSIP_PRUNE_INTERVAL(daemon->dev_fast_gossip_prune) / 4), - gossip_refresh_network, daemon)); - /* Fire up the seeker! */ daemon->seeker = new_seeker(daemon); @@ -722,7 +450,7 @@ static void gossip_init(struct daemon *daemon, const u8 *msg) /* Tell it about all our local (public) channel_update messages, * and node_announcement, so it doesn't unnecessarily regenerate them. */ - tell_master_local_cupdates(daemon); + gossmap_manage_tell_lightningd_locals(daemon, daemon->gm); /* OK, we are ready. */ daemon_conn_send(daemon->master, @@ -751,7 +479,7 @@ static void new_blockheight(struct daemon *daemon, const u8 *msg) i--; } - routing_expire_channels(daemon->rstate, daemon->current_blockheight); + gossmap_manage_new_block(daemon->gm, daemon->current_blockheight); daemon_conn_send(daemon->master, take(towire_gossipd_new_blockheight_reply(NULL))); @@ -787,69 +515,44 @@ static void dev_gossip_set_time(struct daemon *daemon, const u8 *msg) daemon->dev_gossip_time->ts.tv_nsec = 0; } -/*~ We queue incoming channel_announcement pending confirmation from lightningd - * that it really is an unspent output. Here's its reply. */ -static void handle_txout_reply(struct daemon *daemon, const u8 *msg) -{ - struct short_channel_id scid; - u8 *outscript; - struct amount_sat sat; - bool good; - - if (!fromwire_gossipd_get_txout_reply(msg, msg, &scid, &sat, &outscript)) - master_badmsg(WIRE_GOSSIPD_GET_TXOUT_REPLY, msg); - - /* Outscript is NULL if it's not an unspent output */ - good = handle_pending_cannouncement(daemon, daemon->rstate, - &scid, sat, outscript); - - /* If we looking specifically for this, we no longer are. */ - remove_unknown_scid(daemon->seeker, &scid, good); -} - /*~ lightningd tells us when about a gossip message directly, when told to by * the addgossip RPC call. That's usually used when a plugin gets an update * returned in an payment error. */ static void inject_gossip(struct daemon *daemon, const u8 *msg) { u8 *goss; - const u8 *errmsg; const char *err; struct amount_sat *known_amount; if (!fromwire_gossipd_addgossip(msg, msg, &goss, &known_amount)) master_badmsg(WIRE_GOSSIPD_ADDGOSSIP, msg); + status_debug("inject_gossip: %s", peer_wire_name(fromwire_peektype(goss))); switch (fromwire_peektype(goss)) { case WIRE_CHANNEL_ANNOUNCEMENT: - errmsg = handle_channel_announcement_msg(daemon, NULL, goss); + err = gossmap_manage_channel_announcement(tmpctx, + daemon->gm, + take(goss), NULL, + known_amount); break; case WIRE_NODE_ANNOUNCEMENT: - errmsg = handle_node_announcement(daemon->rstate, goss, - NULL, NULL); + err = gossmap_manage_node_announcement(tmpctx, + daemon->gm, + take(goss), NULL); break; case WIRE_CHANNEL_UPDATE: - errmsg = handle_channel_update(daemon->rstate, goss, - NULL, NULL, true); + err = gossmap_manage_channel_update(tmpctx, + daemon->gm, + take(goss), NULL); break; default: err = tal_fmt(tmpctx, "unknown gossip type %i", fromwire_peektype(goss)); - goto err_extracted; } - /* The APIs above are designed to send error messages back to peers: - * we extract the raw string instead. */ - if (errmsg) { - err = sanitize_error(tmpctx, errmsg, NULL); - tal_free(errmsg); - } else - /* Send empty string if no error. */ - err = ""; - -err_extracted: + /* FIXME: Make this an optional string in gossipd_addgossip_reply */ daemon_conn_send(daemon->master, - take(towire_gossipd_addgossip_reply(NULL, err))); + take(towire_gossipd_addgossip_reply(NULL, err ? err : ""))); } /*~ This is where lightningd tells us that a channel's funding transaction has @@ -862,16 +565,8 @@ static void handle_outpoints_spent(struct daemon *daemon, const u8 *msg) if (!fromwire_gossipd_outpoints_spent(msg, msg, &blockheight, &scids)) master_badmsg(WIRE_GOSSIPD_OUTPOINTS_SPENT, msg); - for (size_t i = 0; i < tal_count(scids); i++) { - struct chan *chan = get_channel(daemon->rstate, &scids[i]); - - if (!chan) - continue; - - /* We have a current_blockheight, but it's not necessarily - * updated first. */ - routing_channel_spent(daemon->rstate, blockheight, chan); - } + for (size_t i = 0; i < tal_count(scids); i++) + gossmap_manage_channel_spent(daemon->gm, blockheight, scids[i]); } /*~ This routine handles all the commands from lightningd. */ @@ -887,7 +582,7 @@ static struct io_plan *recv_req(struct io_conn *conn, goto done; case WIRE_GOSSIPD_GET_TXOUT_REPLY: - handle_txout_reply(daemon, msg); + gossmap_manage_handle_get_txout_reply(daemon->gm, msg); goto done; case WIRE_GOSSIPD_OUTPOINTS_SPENT: diff --git a/gossipd/gossipd.h b/gossipd/gossipd.h index 372194808768..727d3cdb350a 100644 --- a/gossipd/gossipd.h +++ b/gossipd/gossipd.h @@ -50,9 +50,6 @@ struct daemon { /* Connection to connect daemon. */ struct daemon_conn *connectd; - /* Routing information */ - struct routing_state *rstate; - /* Manager of writing to the gossip_store */ struct gossmap_manage *gm; diff --git a/gossipd/gossmap_manage.c b/gossipd/gossmap_manage.c index 31fc392056dd..af0303b772bb 100644 --- a/gossipd/gossmap_manage.c +++ b/gossipd/gossmap_manage.c @@ -421,24 +421,6 @@ static void bad_gossip(const struct node_id *source_peer, const char *str) status_peer_debug(source_peer, "Bad gossip order: %s", str); } -/* Minimal gossmap-only transition constructor */ -struct gossmap_manage *gossmap_manage_new_gossmap_only(const tal_t *ctx, - struct daemon *daemon) -{ - struct gossmap_manage *gm = tal(ctx, struct gossmap_manage); - - gm->fd = open(GOSSIP_STORE_FILENAME, O_RDWR); - if (gm->fd < 0) - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "Opening gossip_store store: %s", - strerror(errno)); - gm->raw_gossmap = gossmap_load_fd(gm, gm->fd, report_bad_update, NULL, gm); - assert(gm->raw_gossmap); - gm->daemon = daemon; - - return gm; -} - /* Send peer a warning message, if non-NULL. */ static void peer_warning(struct gossmap_manage *gm, const struct node_id *source_peer, diff --git a/gossipd/gossmap_manage.h b/gossipd/gossmap_manage.h index 191a989d81d1..6396778aae55 100644 --- a/gossipd/gossmap_manage.h +++ b/gossipd/gossmap_manage.h @@ -8,10 +8,6 @@ struct gossmap_manage; struct gossmap_manage *gossmap_manage_new(const tal_t *ctx, struct daemon *daemon); -/* Minimal gossmap-only transition constructor */ -struct gossmap_manage *gossmap_manage_new_gossmap_only(const tal_t *ctx, - struct daemon *daemon); - /** * gossmap_manage_channel_announcement: process an incoming channel_announcement * @ctx: tal context for return string diff --git a/tests/test_gossip.py b/tests/test_gossip.py index 66cf6621323c..a80a185f9c79 100644 --- a/tests/test_gossip.py +++ b/tests/test_gossip.py @@ -1194,8 +1194,11 @@ def test_gossip_store_v10_upgrade(node_factory): "1ea7c2eadf8a29eb8690511a519b5656e29aa0a853771c4e38e65c5abf43d907295a915e69e451f4c7a0c3dc13dd943cfbe3ae88c0b96667cd7d58955dbfedcf43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea33090000000013a63c0000b500015b8d9b440000009000000000000003e8000003e800000001")) l1.start() - # May preceed the Started msg waited for in 'start'. - wait_for(lambda: l1.daemon.is_in_log(r'gossip_store: Unupdated channel_announcement at 1.')) + # Channel "exists" but doesn't show in listchannels, as it has no updates. + assert l1.rpc.listchannels() == {'channels': []} + assert only_one(l1.rpc.listnodes('021bf3de4e84e3d52f9a3e36fbdcd2c4e8dbf203b9ce4fc07c2f03be6c21d0c675')['nodes']) + assert only_one(l1.rpc.listnodes('03f113414ebdc6c1fb0f33c99cd5a1d09dd79e7fdf2468cf1fe1af6674361695d2')['nodes']) + assert len(l1.rpc.listnodes()['nodes']) == 2 def test_gossip_store_load_announce_before_update(node_factory): @@ -1611,12 +1614,8 @@ def test_gossip_store_load_no_channel_update(node_factory): l1.start() # May preceed the Started msg waited for in 'start'. - wait_for(lambda: l1.daemon.is_in_log('gossip_store: Unupdated channel_announcement at 1. Moving to gossip_store.corrupt and truncating')) - assert os.path.exists(os.path.join(l1.daemon.lightning_dir, TEST_NETWORK, 'gossip_store.corrupt')) - - # This should actually result in an empty store. - with open(os.path.join(l1.daemon.lightning_dir, TEST_NETWORK, 'gossip_store'), "rb") as f: - assert bytearray(f.read()) == bytearray.fromhex("0e") + wait_for(lambda: l1.daemon.is_in_log(r'gossipd: gossip_store: Read 1/0/1/0 cannounce/cupdate/nannounce/cdelete from store \(0 deleted\) in 628 bytes')) + assert not os.path.exists(os.path.join(l1.daemon.lightning_dir, TEST_NETWORK, 'gossip_store.corrupt')) def test_gossip_store_compact_on_load(node_factory, bitcoind): From c8cf272827fca8ab021fc97dc623f74691d8f7c0 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 31 Jan 2024 16:14:13 +1030 Subject: [PATCH 28/33] gossipd: remove routing.c and other unused functions. Signed-off-by: Rusty Russell --- gossipd/Makefile | 3 +- gossipd/broadcast.h | 20 - gossipd/gossip_store.c | 156 ++-- gossipd/gossip_store.h | 37 - gossipd/gossipd.c | 2 +- gossipd/gossipd.h | 5 - gossipd/queries.h | 4 - gossipd/routing.c | 1762 ---------------------------------------- gossipd/routing.h | 350 -------- 9 files changed, 50 insertions(+), 2289 deletions(-) delete mode 100644 gossipd/broadcast.h delete mode 100644 gossipd/routing.c delete mode 100644 gossipd/routing.h diff --git a/gossipd/Makefile b/gossipd/Makefile index 28237330bac8..c18a5d32574a 100644 --- a/gossipd/Makefile +++ b/gossipd/Makefile @@ -7,11 +7,10 @@ GOSSIPD_HEADERS_WSRC := gossipd/gossipd_wiregen.h \ gossipd/gossip_store.h \ gossipd/gossmap_manage.h \ gossipd/queries.h \ - gossipd/routing.h \ gossipd/txout_failures.h \ gossipd/sigcheck.h \ gossipd/seeker.h -GOSSIPD_HEADERS := $(GOSSIPD_HEADERS_WSRC) gossipd/broadcast.h +GOSSIPD_HEADERS := $(GOSSIPD_HEADERS_WSRC) GOSSIPD_SRC := $(GOSSIPD_HEADERS_WSRC:.h=.c) GOSSIPD_OBJS := $(GOSSIPD_SRC:.c=.o) diff --git a/gossipd/broadcast.h b/gossipd/broadcast.h deleted file mode 100644 index d07f98b38ff2..000000000000 --- a/gossipd/broadcast.h +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef LIGHTNING_GOSSIPD_BROADCAST_H -#define LIGHTNING_GOSSIPD_BROADCAST_H -#include "config.h" - -#include - -/* This is nested inside a node, chan or half_chan; rewriting the store can - * cause it to change! */ -struct broadcastable { - /* This is also the offset within the gossip_store; even with 1M - * channels we still have a factor of 8 before this wraps. */ - u32 index; - u32 timestamp; -}; - -static inline void broadcastable_init(struct broadcastable *bcast) -{ - bcast->index = 0; -} -#endif /* LIGHTNING_GOSSIPD_BROADCAST_H */ diff --git a/gossipd/gossip_store.c b/gossipd/gossip_store.c index 41999a8f97af..03a13ab0c767 100644 --- a/gossipd/gossip_store.c +++ b/gossipd/gossip_store.c @@ -8,7 +8,6 @@ #include #include #include -#include #include #include #include @@ -391,38 +390,60 @@ u64 gossip_store_add(struct gossip_store *gs, const u8 *gossip_msg, return off; } -void gossip_store_mark_dying(struct gossip_store *gs, - const struct broadcastable *bcast, - int type) +static const u8 *gossip_store_get_with_hdr(const tal_t *ctx, + struct gossip_store *gs, + u64 offset, + struct gossip_hdr *hdr) { - const u8 *msg; - be16 flags; - - /* Should never get here during loading! */ - assert(gs->writable); - - /* Should never try to overwrite version */ - assert(bcast->index); + u32 msglen, checksum; + u8 *msg; - /* Sanity check, that this is a channel announcement */ - msg = gossip_store_get(tmpctx, gs, bcast->index); - if (fromwire_peektype(msg) != type) { - status_broken("gossip_store incorrect dying msg not %u @%u of %"PRIu64": %s", - type, bcast->index, gs->len, tal_hex(tmpctx, msg)); - return; + if (offset == 0) + status_failed(STATUS_FAIL_INTERNAL_ERROR, + "gossip_store: can't access offset %"PRIu64, + offset); + if (pread(gs->fd, hdr, sizeof(*hdr), offset) != sizeof(*hdr)) { + status_failed(STATUS_FAIL_INTERNAL_ERROR, + "gossip_store: can't read hdr offset %"PRIu64 + "/%"PRIu64": %s", + offset, gs->len, strerror(errno)); } - if (pread(gs->fd, &flags, sizeof(flags), bcast->index) != sizeof(flags)) { + if (be16_to_cpu(hdr->flags) & GOSSIP_STORE_DELETED_BIT) status_failed(STATUS_FAIL_INTERNAL_ERROR, - "Could not read to mark dying at %u/%"PRIu64": %s", - bcast->index, gs->len, strerror(errno)); - } + "gossip_store: get delete entry offset %"PRIu64 + "/%"PRIu64"", + offset, gs->len); - flags |= cpu_to_be16(GOSSIP_STORE_DYING_BIT); - if (pwrite(gs->fd, &flags, sizeof(flags), bcast->index) != sizeof(flags)) + msglen = be16_to_cpu(hdr->len); + checksum = be32_to_cpu(hdr->crc); + msg = tal_arr(ctx, u8, msglen); + if (pread(gs->fd, msg, msglen, offset + sizeof(*hdr)) != msglen) status_failed(STATUS_FAIL_INTERNAL_ERROR, - "Failed writing flags to dying @%u: %s", - bcast->index, strerror(errno)); + "gossip_store: can't read len %u offset %"PRIu64 + "/%"PRIu64, msglen, offset, gs->len); + + if (checksum != crc32c(be32_to_cpu(hdr->timestamp), msg, msglen)) + status_failed(STATUS_FAIL_INTERNAL_ERROR, + "gossip_store: bad checksum offset %"PRIu64": %s", + offset, tal_hex(tmpctx, msg)); + + return msg; +} + +static bool check_msg_type(struct gossip_store *gs, u32 index, int flag, int type) +{ + struct gossip_hdr hdr; + const u8 *msg = gossip_store_get_with_hdr(tmpctx, gs, index, &hdr); + + if (fromwire_peektype(msg) == type) + return true; + + status_broken("asked to flag-%u type %i @%u but store contains " + "%i (gs->len=%"PRIu64"): %s", + flag, type, index, fromwire_peektype(msg), + gs->len, tal_hex(tmpctx, msg)); + return false; } /* Returns index of following entry. */ @@ -452,14 +473,8 @@ static u32 flag_by_index(struct gossip_store *gs, u32 index, int flag, int type) return index; } - const u8 *msg = gossip_store_get(tmpctx, gs, index); - if(fromwire_peektype(msg) != type) { - status_broken("asked to flag-%u type %i @%u but store contains " - "%i (gs->len=%"PRIu64"): %s", - flag, type, index, fromwire_peektype(msg), - gs->len, tal_hex(tmpctx, msg)); + if (!check_msg_type(gs, index, flag, type)) return index; - } assert((be16_to_cpu(hdr.beflags) & flag) == 0); hdr.beflags |= cpu_to_be16(flag); @@ -499,19 +514,6 @@ void gossip_store_flag(struct gossip_store *gs, flag_by_index(gs, offset - sizeof(struct gossip_hdr), flag, type); } -void gossip_store_delete(struct gossip_store *gs, - struct broadcastable *bcast, - int type) -{ - if (!bcast->index) - return; - - gossip_store_del(gs, bcast->index + sizeof(struct gossip_hdr), type); - - /* Reset index. */ - bcast->index = 0; -} - void gossip_store_mark_channel_deleted(struct gossip_store *gs, const struct short_channel_id *scid) { @@ -534,47 +536,6 @@ u32 gossip_store_get_timestamp(struct gossip_store *gs, u64 offset) return be32_to_cpu(hdr.timestamp); } -static const u8 *gossip_store_get_with_hdr(const tal_t *ctx, - struct gossip_store *gs, - u64 offset, - struct gossip_hdr *hdr) -{ - u32 msglen, checksum; - u8 *msg; - - if (offset == 0) - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "gossip_store: can't access offset %"PRIu64, - offset); - if (pread(gs->fd, hdr, sizeof(*hdr), offset) != sizeof(*hdr)) { - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "gossip_store: can't read hdr offset %"PRIu64 - "/%"PRIu64": %s", - offset, gs->len, strerror(errno)); - } - - if (be16_to_cpu(hdr->flags) & GOSSIP_STORE_DELETED_BIT) - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "gossip_store: get delete entry offset %"PRIu64 - "/%"PRIu64"", - offset, gs->len); - - msglen = be16_to_cpu(hdr->len); - checksum = be32_to_cpu(hdr->crc); - msg = tal_arr(ctx, u8, msglen); - if (pread(gs->fd, msg, msglen, offset + sizeof(*hdr)) != msglen) - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "gossip_store: can't read len %u offset %"PRIu64 - "/%"PRIu64, msglen, offset, gs->len); - - if (checksum != crc32c(be32_to_cpu(hdr->timestamp), msg, msglen)) - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "gossip_store: bad checksum offset %"PRIu64": %s", - offset, tal_hex(tmpctx, msg)); - - return msg; -} - void gossip_store_set_timestamp(struct gossip_store *gs, u64 offset, u32 timestamp) { struct gossip_hdr hdr; @@ -598,27 +559,6 @@ void gossip_store_set_timestamp(struct gossip_store *gs, u64 offset, u32 timesta offset, strerror(errno)); } -const u8 *gossip_store_get(const tal_t *ctx, - struct gossip_store *gs, - u64 offset) -{ - struct gossip_hdr hdr; - - return gossip_store_get_with_hdr(ctx, gs, offset, &hdr); -} - -int gossip_store_readonly_fd(struct gossip_store *gs) -{ - int fd = open(GOSSIP_STORE_FILENAME, O_RDONLY); - - /* Skip over version header */ - if (fd != -1 && lseek(fd, 1, SEEK_SET) != 1) { - close_noerr(fd); - fd = -1; - } - return fd; -} - u32 gossip_store_load(struct gossip_store *gs) { struct gossip_hdr hdr; diff --git a/gossipd/gossip_store.h b/gossipd/gossip_store.h index 21c0cdbe3593..6bf86c34ce6e 100644 --- a/gossipd/gossip_store.h +++ b/gossipd/gossip_store.h @@ -42,15 +42,6 @@ u64 gossip_store_add(struct gossip_store *gs, const u8 *gossip_msg, const u8 *addendum); -/** - * Delete the broadcast associated with this (if any). - * - * In developer mode, checks that type is correct. - */ -void gossip_store_delete(struct gossip_store *gs, - struct broadcastable *bcast, - int type); - /** * Delete the record at this offset (offset is that of * record, not header, unlike bcast->index!). @@ -79,26 +70,6 @@ void gossip_store_flag(struct gossip_store *gs, void gossip_store_mark_channel_deleted(struct gossip_store *gs, const struct short_channel_id *scid); -/** - * Mark this channel_announcement/channel_update as dying. - * - * We'll clean it up in 12 blocks, but this tells connectd not to gossip - * about it. - */ -void gossip_store_mark_dying(struct gossip_store *gs, - const struct broadcastable *bcast, - int type); - - -/** - * Direct store accessor: loads gossip msg back from store. - * - * Caller must ensure offset != 0. Never returns NULL. - */ -const u8 *gossip_store_get(const tal_t *ctx, - struct gossip_store *gs, - u64 offset); - /** * Direct store accessor: get timestamp header for a record. * @@ -113,12 +84,4 @@ u32 gossip_store_get_timestamp(struct gossip_store *gs, u64 offset); */ void gossip_store_set_timestamp(struct gossip_store *gs, u64 offset, u32 timestamp); -/** - * Get a readonly fd for the gossip_store. - * @gs: the gossip store. - * - * Returns -1 on failure, and sets errno. - */ -int gossip_store_readonly_fd(struct gossip_store *gs); - #endif /* LIGHTNING_GOSSIPD_GOSSIP_STORE_H */ diff --git a/gossipd/gossipd.c b/gossipd/gossipd.c index 5f40a01d4beb..247d7bdb3296 100644 --- a/gossipd/gossipd.c +++ b/gossipd/gossipd.c @@ -26,13 +26,13 @@ #include #include #include +#include #include #include #include #include #include #include -#include #include #include diff --git a/gossipd/gossipd.h b/gossipd/gossipd.h index 727d3cdb350a..2c1216bea7cb 100644 --- a/gossipd/gossipd.h +++ b/gossipd/gossipd.h @@ -148,11 +148,6 @@ void queue_peer_msg(struct daemon *daemon, const struct node_id *peer, const u8 *msg TAKES); -/* Queue a gossip_store message for the peer: the subdaemon on the - * other end simply forwards it to the peer. */ -void queue_peer_from_store(struct peer *peer, - const struct broadcastable *bcast); - /* We have an update for one of our channels (or unknown). */ void tell_lightningd_peer_update(struct daemon *daemon, const struct node_id *source_peer, diff --git a/gossipd/queries.h b/gossipd/queries.h index 727f6ef1023f..b6748d6d7b1d 100644 --- a/gossipd/queries.h +++ b/gossipd/queries.h @@ -53,10 +53,6 @@ bool query_short_channel_ids(struct daemon *daemon, const u8 *query_flags, void (*cb)(struct peer *peer_, bool complete)); -struct io_plan *dev_query_channel_range(struct io_conn *conn, - struct daemon *daemon, - const u8 *msg); - /* This is a testing hack to allow us to artificially lower the maximum bytes * of short_channel_ids we'll encode, using dev_set_max_scids_encode_size. */ void dev_set_max_scids_encode_size(struct daemon *daemon, const u8 *msg); diff --git a/gossipd/routing.c b/gossipd/routing.c deleted file mode 100644 index 3695f551c833..000000000000 --- a/gossipd/routing.c +++ /dev/null @@ -1,1762 +0,0 @@ -#include "config.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifndef SUPERVERBOSE -#define SUPERVERBOSE(...) -#endif - -struct pending_spam_node_announce { - u8 *node_announcement; - u32 index; -}; - -struct pending_node_announce { - struct routing_state *rstate; - struct node_id nodeid; - size_t refcount; - u8 *node_announcement; - u32 timestamp; - u32 index; - /* If non-NULL this is peer to credit it with */ - struct node_id *source_peer; -}; - -/* As per the below BOLT #7 quote, we delay forgetting a channel until 12 - * blocks after we see it close. This gives time for splicing (or even other - * opens) to replace the channel, and broadcast it after 6 blocks. */ -struct dying_channel { - struct short_channel_id scid; - u32 deadline_blockheight; - /* Where the dying_channel marker is in the store. */ - struct broadcastable marker; -}; - -static const struct node_id * -pending_node_announce_keyof(const struct pending_node_announce *a) -{ - return &a->nodeid; -} - -static bool pending_node_announce_eq(const struct pending_node_announce *pna, - const struct node_id *pc) -{ - return node_id_eq(&pna->nodeid, pc); -} - -HTABLE_DEFINE_TYPE(struct pending_node_announce, pending_node_announce_keyof, - node_map_hash_key, pending_node_announce_eq, - pending_node_map); - -/* We keep around announcements for channels until we have an - * update for them (which gives us their timestamp) */ -struct unupdated_channel { - /* The channel_announcement message */ - const u8 *channel_announce; - /* The short_channel_id */ - struct short_channel_id scid; - /* The ids of the nodes */ - struct node_id id[2]; - /* When we added, so we can discard old ones */ - struct timeabs added; - /* If we loaded from the store, this is where. */ - u32 index; - /* Channel capacity */ - struct amount_sat sat; - /* If non-NULL this is peer to credit it with */ - struct node_id *source_peer; -}; - -static struct unupdated_channel * -get_unupdated_channel(const struct routing_state *rstate, - const struct short_channel_id *scid) -{ - return uintmap_get(&rstate->unupdated_chanmap, scid->u64); -} - -static void destroy_unupdated_channel(struct unupdated_channel *uc, - struct routing_state *rstate) -{ - uintmap_del(&rstate->unupdated_chanmap, uc->scid.u64); -} - -static struct node_map *new_node_map(const tal_t *ctx) -{ - struct node_map *map = tal(ctx, struct node_map); - node_map_init(map); - return map; -} - -/* We use a simple array (with NULL entries) until we have too many. */ -static bool node_uses_chan_map(const struct node *node) -{ - return node->chan_map; -} - -/* When simple array fills, use a htable. */ -static void convert_node_to_chan_map(struct node *node) -{ - assert(!node_uses_chan_map(node)); - node->chan_map = tal(node, struct chan_map); - chan_map_init_sized(node->chan_map, ARRAY_SIZE(node->chan_arr) + 1); - assert(node_uses_chan_map(node)); - for (size_t i = 0; i < ARRAY_SIZE(node->chan_arr); i++) { - chan_map_add(node->chan_map, node->chan_arr[i]); - node->chan_arr[i] = NULL; - } -} - -static void add_chan(struct node *node, struct chan *chan) -{ - if (!node_uses_chan_map(node)) { - for (size_t i = 0; i < ARRAY_SIZE(node->chan_arr); i++) { - if (node->chan_arr[i] == NULL) { - node->chan_arr[i] = chan; - return; - } - } - convert_node_to_chan_map(node); - } - - chan_map_add(node->chan_map, chan); -} - -static struct chan *next_chan_arr(const struct node *node, - struct chan_map_iter *i) -{ - while (i->i.off < ARRAY_SIZE(node->chan_arr)) { - if (node->chan_arr[i->i.off]) - return node->chan_arr[i->i.off]; - i->i.off++; - } - return NULL; -} - -struct chan *first_chan(const struct node *node, struct chan_map_iter *i) -{ - if (!node_uses_chan_map(node)) { - i->i.off = 0; - return next_chan_arr(node, i); - } - - return chan_map_first(node->chan_map, i); -} - -struct chan *next_chan(const struct node *node, struct chan_map_iter *i) -{ - if (!node_uses_chan_map(node)) { - i->i.off++; - return next_chan_arr(node, i); - } - - return chan_map_next(node->chan_map, i); -} - -static void destroy_routing_state(struct routing_state *rstate) -{ - /* Since we omitted destructors on these, clean up manually */ - u64 idx; - for (struct chan *chan = uintmap_first(&rstate->chanmap, &idx); - chan; - chan = uintmap_after(&rstate->chanmap, &idx)) - free_chan(rstate, chan); -} - -static void memleak_help_routing_tables(struct htable *memtable, - struct routing_state *rstate) -{ - struct node *n; - struct node_map_iter nit; - - memleak_scan_htable(memtable, &rstate->nodes->raw); - memleak_scan_htable(memtable, &rstate->pending_node_map->raw); - memleak_scan_htable(memtable, &rstate->pending_cannouncements->raw); - memleak_scan_uintmap(memtable, &rstate->unupdated_chanmap); - - for (n = node_map_first(rstate->nodes, &nit); - n; - n = node_map_next(rstate->nodes, &nit)) { - if (node_uses_chan_map(n)) - memleak_scan_htable(memtable, &n->chan_map->raw); - } -} - -struct routing_state *new_routing_state(const tal_t *ctx, - struct daemon *daemon) -{ - struct routing_state *rstate = tal(ctx, struct routing_state); - rstate->daemon = daemon; - rstate->nodes = new_node_map(rstate); - rstate->dying_channels = tal_arr(rstate, struct dying_channel, 0); - - rstate->pending_cannouncements = tal(rstate, struct pending_cannouncement_map); - pending_cannouncement_map_init(rstate->pending_cannouncements); - - uintmap_init(&rstate->chanmap); - uintmap_init(&rstate->unupdated_chanmap); - rstate->txf = txout_failures_new(rstate, rstate->daemon); - rstate->pending_node_map = tal(ctx, struct pending_node_map); - pending_node_map_init(rstate->pending_node_map); - - tal_add_destructor(rstate, destroy_routing_state); - memleak_add_helper(rstate, memleak_help_routing_tables); - - return rstate; -} - - -const struct node_id *node_map_keyof_node(const struct node *n) -{ - return &n->id; -} - -size_t node_map_hash_key(const struct node_id *pc) -{ - return siphash24(siphash_seed(), pc->k, sizeof(pc->k)); -} - -bool node_map_node_eq(const struct node *n, const struct node_id *pc) -{ - return node_id_eq(&n->id, pc); -} - - -static void destroy_node(struct node *node, struct routing_state *rstate) -{ - struct chan_map_iter i; - struct chan *c; - node_map_del(rstate->nodes, node); - - /* These remove themselves from chans[]. */ - while ((c = first_chan(node, &i)) != NULL) - free_chan(rstate, c); -} - -struct node *get_node(struct routing_state *rstate, - const struct node_id *id) -{ - return node_map_get(rstate->nodes, id); -} - -static struct node *new_node(struct routing_state *rstate, - const struct node_id *id) -{ - struct node *n; - - assert(!get_node(rstate, id)); - - n = tal(rstate, struct node); - n->id = *id; - memset(n->chan_arr, 0, sizeof(n->chan_arr)); - n->chan_map = NULL; - broadcastable_init(&n->bcast); - node_map_add(rstate->nodes, n); - tal_add_destructor2(n, destroy_node, rstate); - - return n; -} - -/* We can *send* a channel_announce for a channel attached to this node: - * we only send once we have a channel_update. */ -bool node_has_broadcastable_channels(const struct node *node) -{ - struct chan_map_iter i; - struct chan *c; - - for (c = first_chan(node, &i); c; c = next_chan(node, &i)) { - if (is_halfchan_defined(&c->half[0]) - || is_halfchan_defined(&c->half[1])) - return true; - } - return false; -} - -static bool node_announce_predates_channels(const struct node *node) -{ - struct chan_map_iter i; - struct chan *c; - - for (c = first_chan(node, &i); c; c = next_chan(node, &i)) { - if (c->bcast.index < node->bcast.index) - return false; - } - return true; -} - -/* Move this node's announcement to the tail of the gossip_store, to - * make everyone send it again. */ -static void force_node_announce_rexmit(struct routing_state *rstate, - struct node *node) -{ - const u8 *announce; - announce = gossip_store_get(tmpctx, rstate->daemon->gs, node->bcast.index); - - gossip_store_delete(rstate->daemon->gs, - &node->bcast, - WIRE_NODE_ANNOUNCEMENT); - node->bcast.index = gossip_store_add(rstate->daemon->gs, - announce, - node->bcast.timestamp, - false, - NULL); -} - -static void remove_chan_from_node(struct routing_state *rstate, - struct node *node, const struct chan *chan) -{ - size_t num_chans; - - if (!node_uses_chan_map(node)) { - num_chans = 0; - for (size_t i = 0; i < ARRAY_SIZE(node->chan_arr); i++) { - if (node->chan_arr[i] == chan) - node->chan_arr[i] = NULL; - else if (node->chan_arr[i] != NULL) - num_chans++; - } - } else { - if (!chan_map_del(node->chan_map, chan)) - abort(); - num_chans = chan_map_count(node->chan_map); - } - - /* Last channel? Simply delete node (and associated announce) */ - if (num_chans == 0) { - gossip_store_delete(rstate->daemon->gs, - &node->bcast, - WIRE_NODE_ANNOUNCEMENT); - tal_free(node); - return; - } - - /* Don't bother if there's no node_announcement */ - if (!node->bcast.index) - return; - - /* Removed only public channel? Remove node announcement. */ - if (!node_has_broadcastable_channels(node)) { - gossip_store_delete(rstate->daemon->gs, - &node->bcast, - WIRE_NODE_ANNOUNCEMENT); - node->bcast.index = 0; - node->bcast.timestamp = 0; - } else if (node_announce_predates_channels(node)) { - /* node announcement predates all channel announcements? - * Move to end (we could, in theory, move to just past next - * channel_announce, but we don't care that much about spurious - * retransmissions in this corner case */ - force_node_announce_rexmit(rstate, node); - } -} - -/* With --developer, we make sure that free_chan is called on this chan! */ -static void destroy_chan_check(struct chan *chan) -{ - assert(chan->sat.satoshis == (unsigned long)chan); /* Raw: dev-hack */ -} - -static void free_chans_from_node(struct routing_state *rstate, struct chan *chan) -{ - remove_chan_from_node(rstate, chan->nodes[0], chan); - remove_chan_from_node(rstate, chan->nodes[1], chan); - - if (rstate->daemon->developer) - chan->sat.satoshis = (unsigned long)chan; /* Raw: dev-hack */ -} - -/* We used to make this a tal_add_destructor2, but that costs 40 bytes per - * chan, and we only ever explicitly free it anyway. */ -void free_chan(struct routing_state *rstate, struct chan *chan) -{ - free_chans_from_node(rstate, chan); - uintmap_del(&rstate->chanmap, chan->scid.u64); - - tal_free(chan); -} - -static void init_half_chan(struct routing_state *rstate, - struct chan *chan, - int channel_idx) -{ - struct half_chan *c = &chan->half[channel_idx]; - - broadcastable_init(&c->bcast); -} - -static void bad_gossip_order(const u8 *msg, - const struct node_id *source_peer, - const char *details) -{ - status_peer_debug(source_peer, - "Bad gossip order: %s before announcement %s from %s", - peer_wire_name(fromwire_peektype(msg)), - details, - source_peer ? type_to_string(tmpctx, struct node_id, source_peer) : "local"); -} - -struct chan *new_chan(struct routing_state *rstate, - const struct short_channel_id *scid, - const struct node_id *id1, - const struct node_id *id2, - struct amount_sat satoshis) -{ - struct chan *chan = tal(rstate, struct chan); - int n1idx = node_id_idx(id1, id2); - struct node *n1, *n2; - - if (rstate->daemon->developer) - tal_add_destructor(chan, destroy_chan_check); - - /* We should never add a channel twice */ - assert(!uintmap_get(&rstate->chanmap, scid->u64)); - - /* Create nodes on demand */ - n1 = get_node(rstate, id1); - if (!n1) - n1 = new_node(rstate, id1); - n2 = get_node(rstate, id2); - if (!n2) - n2 = new_node(rstate, id2); - - chan->scid = *scid; - chan->nodes[n1idx] = n1; - chan->nodes[!n1idx] = n2; - broadcastable_init(&chan->bcast); - /* This is how we indicate it's not public yet. */ - chan->bcast.timestamp = 0; - chan->sat = satoshis; - - add_chan(n2, chan); - add_chan(n1, chan); - - /* Populate with (inactive) connections */ - init_half_chan(rstate, chan, n1idx); - init_half_chan(rstate, chan, !n1idx); - - uintmap_add(&rstate->chanmap, scid->u64, chan); - - return chan; -} - -/* We allow node announcements for this node if it doesn't otherwise exist, so - * we can process them once it does exist (a channel_announce is being - * validated right now). - * - * If we attach one, remove it on destruction of @ctx. - */ -static void del_pending_node_announcement(const tal_t *ctx UNUSED, - struct pending_node_announce *pna) -{ - if (--pna->refcount == 0) { - pending_node_map_del(pna->rstate->pending_node_map, pna); - tal_free(pna); - } -} - -static void catch_node_announcement(const tal_t *ctx, - struct routing_state *rstate, - struct node_id *nodeid) -{ - struct pending_node_announce *pna; - struct node *node; - - /* No need if we already know about the node. */ - node = get_node(rstate, nodeid); - if (node) - return; - - /* We can have multiple channels announced at same time for nodes; - * but we can only have one of these in the map. */ - pna = pending_node_map_get(rstate->pending_node_map, nodeid); - if (!pna) { - pna = tal(rstate, struct pending_node_announce); - pna->rstate = rstate; - pna->nodeid = *nodeid; - pna->node_announcement = NULL; - pna->timestamp = 0; - pna->index = 0; - pna->refcount = 0; - pna->source_peer = NULL; - pending_node_map_add(rstate->pending_node_map, pna); - } - pna->refcount++; - tal_add_destructor2(ctx, del_pending_node_announcement, pna); -} - -static void process_pending_node_announcement(struct routing_state *rstate, - struct node_id *nodeid) -{ - struct pending_node_announce *pna = pending_node_map_get(rstate->pending_node_map, nodeid); - if (!pna) - return; - - if (pna->node_announcement) { - SUPERVERBOSE( - "Processing deferred node_announcement for node %s", - type_to_string(pna, struct node_id, nodeid)); - - /* Can fail it timestamp is now too old */ - if (!routing_add_node_announcement(rstate, - pna->node_announcement, - pna->index, - pna->source_peer, NULL)) - status_unusual("pending node_announcement %s too old?", - tal_hex(tmpctx, pna->node_announcement)); - /* Never send this again. */ - pna->node_announcement = tal_free(pna->node_announcement); - } - - /* We don't need to catch any more node_announcements, since we've - * accepted the public channel now. But other pending announcements - * may still hold a reference they use in - * del_pending_node_announcement, so simply delete it from the map. */ - pending_node_map_del(rstate->pending_node_map, notleak(pna)); -} - -static struct pending_cannouncement * -find_pending_cannouncement(struct routing_state *rstate, - const struct short_channel_id *scid) -{ - struct pending_cannouncement *pann; - - pann = pending_cannouncement_map_get(rstate->pending_cannouncements, scid); - - return pann; -} - -static void destroy_pending_cannouncement(struct pending_cannouncement *pending, - struct routing_state *rstate) -{ - pending_cannouncement_map_del(rstate->pending_cannouncements, pending); -} - -static void add_channel_announce_to_broadcast(struct routing_state *rstate, - struct chan *chan, - const u8 *channel_announce, - u32 timestamp, - u32 index) -{ - u8 *addendum = towire_gossip_store_channel_amount(tmpctx, chan->sat); - - chan->bcast.timestamp = timestamp; - /* 0, unless we're loading from store */ - if (index) - chan->bcast.index = index; - else - chan->bcast.index = gossip_store_add(rstate->daemon->gs, - channel_announce, - chan->bcast.timestamp, - false, - addendum); -} - -static void delete_chan_messages_from_store(struct routing_state *rstate, - struct chan *chan) -{ - /* If these aren't in the store, these are noops. */ - gossip_store_delete(rstate->daemon->gs, - &chan->bcast, WIRE_CHANNEL_ANNOUNCEMENT); - gossip_store_delete(rstate->daemon->gs, - &chan->half[0].bcast, WIRE_CHANNEL_UPDATE); - gossip_store_delete(rstate->daemon->gs, - &chan->half[1].bcast, WIRE_CHANNEL_UPDATE); -} - -static void remove_channel_from_store(struct routing_state *rstate, - struct chan *chan) -{ - /* Put in tombstone marker. */ - gossip_store_mark_channel_deleted(rstate->daemon->gs, &chan->scid); - - /* Now delete old entries. */ - delete_chan_messages_from_store(rstate, chan); -} - -bool routing_add_channel_announcement(struct routing_state *rstate, - const u8 *msg TAKES, - struct amount_sat sat, - u32 index, - const struct node_id *source_peer) -{ - secp256k1_ecdsa_signature node_signature_1, node_signature_2; - secp256k1_ecdsa_signature bitcoin_signature_1, bitcoin_signature_2; - u8 *features; - struct bitcoin_blkid chain_hash; - struct short_channel_id scid; - struct node_id node_id_1; - struct node_id node_id_2; - struct pubkey bitcoin_key_1; - struct pubkey bitcoin_key_2; - struct unupdated_channel *uc; - - /* Make sure we own msg, even if we don't save it. */ - if (taken(msg)) - tal_steal(tmpctx, msg); - - if (!fromwire_channel_announcement( - tmpctx, msg, &node_signature_1, &node_signature_2, - &bitcoin_signature_1, &bitcoin_signature_2, &features, &chain_hash, - &scid, &node_id_1, &node_id_2, &bitcoin_key_1, &bitcoin_key_2)) - return false; - - uc = tal(rstate, struct unupdated_channel); - uc->channel_announce = tal_dup_talarr(uc, u8, msg); - uc->added = gossip_time_now(rstate->daemon); - uc->index = index; - uc->sat = sat; - uc->scid = scid; - uc->id[0] = node_id_1; - uc->id[1] = node_id_2; - uc->source_peer = tal_dup_or_null(uc, struct node_id, source_peer); - uintmap_add(&rstate->unupdated_chanmap, scid.u64, uc); - tal_add_destructor2(uc, destroy_unupdated_channel, rstate); - - /* If a node_announcement comes along, save it for once we're updated */ - catch_node_announcement(uc, rstate, &node_id_1); - catch_node_announcement(uc, rstate, &node_id_2); - - return true; -} - -u8 *handle_channel_announcement(struct routing_state *rstate, - const u8 *announce TAKES, - u32 current_blockheight, - const struct short_channel_id **scid, - const struct node_id *source_peer TAKES) -{ - struct pending_cannouncement *pending; - struct bitcoin_blkid chain_hash; - u8 *features, *warn; - secp256k1_ecdsa_signature node_signature_1, node_signature_2; - secp256k1_ecdsa_signature bitcoin_signature_1, bitcoin_signature_2; - struct chan *chan; - const char *err; - - pending = tal(rstate, struct pending_cannouncement); - pending->source_peer = tal_dup_or_null(pending, struct node_id, source_peer); - pending->updates[0] = NULL; - pending->updates[1] = NULL; - pending->update_source_peer[0] = pending->update_source_peer[1] = NULL; - pending->announce = tal_dup_talarr(pending, u8, announce); - pending->update_timestamps[0] = pending->update_timestamps[1] = 0; - - if (!fromwire_channel_announcement(pending, pending->announce, - &node_signature_1, - &node_signature_2, - &bitcoin_signature_1, - &bitcoin_signature_2, - &features, - &chain_hash, - &pending->short_channel_id, - &pending->node_id_1, - &pending->node_id_2, - &pending->bitcoin_key_1, - &pending->bitcoin_key_2)) { - warn = towire_warningfmt(rstate, NULL, - "Malformed channel_announcement %s", - tal_hex(pending, pending->announce)); - goto malformed; - } - - /* We don't use features */ - tal_free(features); - - /* If we know the blockheight, and it's in the future, reject - * out-of-hand. Remember, it should be 6 deep before they tell us - * anyway. */ - if (current_blockheight != 0 - && short_channel_id_blocknum(&pending->short_channel_id) > current_blockheight) { - status_peer_debug(pending->source_peer, - "Ignoring future channel_announcment for %s" - " (current block %u)", - type_to_string(tmpctx, struct short_channel_id, - &pending->short_channel_id), - current_blockheight); - goto ignored; - } - - /* If a prior txout lookup failed there is little point it trying - * again. Just drop the announcement and walk away whistling. */ - if (in_txout_failures(rstate->txf, pending->short_channel_id)) { - SUPERVERBOSE( - "Ignoring channel_announcement of %s due to a prior txout " - "query failure. The channel was likely closed on-chain.", - type_to_string(tmpctx, struct short_channel_id, - &pending->short_channel_id)); - goto ignored; - } - - /* Check if we know the channel already (no matter in what - * state, we stop here if yes). */ - chan = get_channel(rstate, &pending->short_channel_id); - if (chan != NULL) { - SUPERVERBOSE("%s: %s already has public channel", - __func__, - type_to_string(tmpctx, struct short_channel_id, - &pending->short_channel_id)); - goto ignored; - } - if (get_unupdated_channel(rstate, &pending->short_channel_id)) { - SUPERVERBOSE("%s: %s already has unupdated channel", - __func__, - type_to_string(tmpctx, struct short_channel_id, - &pending->short_channel_id)); - goto ignored; - } - - /* We don't replace previous ones, since we might validate that and - * think this one is OK! */ - if (find_pending_cannouncement(rstate, &pending->short_channel_id)) { - SUPERVERBOSE("%s: %s already has pending cannouncement", - __func__, - type_to_string(tmpctx, struct short_channel_id, - &pending->short_channel_id)); - goto ignored; - } - - /* FIXME: Handle duplicates as per BOLT #7 */ - - /* BOLT #7: - * The receiving node: - *... - * - if the specified `chain_hash` is unknown to the receiver: - * - MUST ignore the message. - */ - if (!bitcoin_blkid_eq(&chain_hash, &chainparams->genesis_blockhash)) { - status_peer_debug(pending->source_peer, - "Received channel_announcement %s for unknown chain %s", - type_to_string(pending, struct short_channel_id, - &pending->short_channel_id), - type_to_string(pending, struct bitcoin_blkid, &chain_hash)); - goto ignored; - } - - /* Note that if node_id_1 or node_id_2 are malformed, it's caught here */ - err = sigcheck_channel_announcement(tmpctx, - &pending->node_id_1, - &pending->node_id_2, - &pending->bitcoin_key_1, - &pending->bitcoin_key_2, - &node_signature_1, - &node_signature_2, - &bitcoin_signature_1, - &bitcoin_signature_2, - pending->announce); - if (err) { - /* BOLT #7: - * - * - if `bitcoin_signature_1`, `bitcoin_signature_2`, - * `node_signature_1` OR `node_signature_2` are invalid OR NOT - * correct: - * - SHOULD send a `warning`. - * - MAY close the connection. - * - MUST ignore the message. - */ - warn = towire_warningfmt(rstate, NULL, "%s", err); - goto malformed; - } - - /* Don't add an infinite number of pending announcements. If we're - * catching up with the bitcoin chain, though, they can definitely - * pile up. */ - if (pending_cannouncement_map_count(rstate->pending_cannouncements) - > 100000) { - static bool warned = false; - if (!warned) { - status_peer_unusual(pending->source_peer, - "Flooded by channel_announcements:" - " ignoring some"); - warned = true; - } - goto ignored; - } - - status_peer_debug(pending->source_peer, - "Received channel_announcement for channel %s", - type_to_string(tmpctx, struct short_channel_id, - &pending->short_channel_id)); - - /* Add both endpoints to the pending_node_map so we can stash - * node_announcements while we wait for the txout check */ - catch_node_announcement(pending, rstate, &pending->node_id_1); - catch_node_announcement(pending, rstate, &pending->node_id_2); - - pending_cannouncement_map_add(rstate->pending_cannouncements, pending); - tal_add_destructor2(pending, destroy_pending_cannouncement, rstate); - - /* Success */ - // MSC: Cppcheck 1.86 gets this false positive - // cppcheck-suppress autoVariables - *scid = &pending->short_channel_id; - return NULL; - -malformed: - tal_free(pending); - *scid = NULL; - return warn; - -ignored: - tal_free(pending); - *scid = NULL; - return NULL; -} - -static void process_pending_channel_update(struct daemon *daemon, - struct routing_state *rstate, - const struct short_channel_id *scid, - const u8 *cupdate, - const struct node_id *source_peer) -{ - u8 *err; - - if (!cupdate) - return; - - err = handle_channel_update(rstate, cupdate, source_peer, NULL, false); - if (err) { - /* FIXME: We could send this error back to peer if != NULL */ - status_peer_debug(source_peer, - "Pending channel_update for %s: %s", - type_to_string(tmpctx, struct short_channel_id, - scid), - sanitize_error(tmpctx, err, NULL)); - tal_free(err); - } -} - -bool handle_pending_cannouncement(struct daemon *daemon, - struct routing_state *rstate, - const struct short_channel_id *scid, - struct amount_sat sat, - const u8 *outscript) -{ - const u8 *s; - struct pending_cannouncement *pending; - - pending = find_pending_cannouncement(rstate, scid); - if (!pending) - return false; - - /* BOLT #7: - * - * The receiving node: - *... - * - if the `short_channel_id`'s output... is spent: - * - MUST ignore the message. - */ - if (tal_count(outscript) == 0) { - status_peer_debug(pending->source_peer, - "channel_announcement: no unspent txout %s", - type_to_string(pending, - struct short_channel_id, - scid)); - tal_free(pending); - txout_failures_add(rstate->txf, *scid); - return false; - } - - /* BOLT #7: - * - * The receiving node: - *... - * - if the `short_channel_id`'s output does NOT correspond to a P2WSH - * (using `bitcoin_key_1` and `bitcoin_key_2`, as specified in - * [BOLT #3](03-transactions.md#funding-transaction-output)) ... - * - MUST ignore the message. - */ - s = scriptpubkey_p2wsh(pending, - bitcoin_redeem_2of2(pending, - &pending->bitcoin_key_1, - &pending->bitcoin_key_2)); - - if (!scripteq(s, outscript)) { - status_peer_debug(pending->source_peer, - "channel_announcement: txout %s expected %s, got %s", - type_to_string( - pending, struct short_channel_id, - scid), - tal_hex(tmpctx, s), - tal_hex(tmpctx, outscript)); - tal_free(pending); - return false; - } - - /* Remove pending now, so below functions don't see it. */ - pending_cannouncement_map_del(rstate->pending_cannouncements, pending); - tal_del_destructor2(pending, destroy_pending_cannouncement, rstate); - - /* Can fail if channel_announcement too old */ - if (!routing_add_channel_announcement(rstate, pending->announce, sat, 0, - pending->source_peer)) - status_peer_unusual(pending->source_peer, - "Could not add channel_announcement %s: too old?", - tal_hex(tmpctx, pending->announce)); - else { - /* Did we have an update waiting? If so, apply now. */ - process_pending_channel_update(daemon, rstate, scid, pending->updates[0], - pending->update_source_peer[0]); - process_pending_channel_update(daemon, rstate, scid, pending->updates[1], - pending->update_source_peer[1]); - } - - tal_free(pending); - return true; -} - -static void update_pending(struct pending_cannouncement *pending, - u32 timestamp, const u8 *update, - const u8 direction, - const struct node_id *source_peer TAKES) -{ - SUPERVERBOSE("Deferring update for pending channel %s/%d", - type_to_string(tmpctx, struct short_channel_id, - &pending->short_channel_id), direction); - - if (pending->update_timestamps[direction] < timestamp) { - if (pending->updates[direction]) { - status_peer_debug(source_peer, - "Replacing existing update"); - tal_free(pending->updates[direction]); - } - pending->updates[direction] - = tal_dup_talarr(pending, u8, update); - pending->update_timestamps[direction] = timestamp; - tal_free(pending->update_source_peer[direction]); - pending->update_source_peer[direction] - = tal_dup_or_null(pending, struct node_id, source_peer); - } else { - /* Don't leak if we don't update! */ - if (taken(source_peer)) - tal_free(source_peer); - } -} - -static bool is_chan_dying(struct routing_state *rstate, - const struct short_channel_id *scid) -{ - for (size_t i = 0; i < tal_count(rstate->dying_channels); i++) { - if (short_channel_id_eq(&rstate->dying_channels[i].scid, scid)) - return true; - } - return false; -} - -/* Is this channel_update different from prev (not sigs and timestamps)? */ -static bool cupdate_different(struct gossip_store *gs, - const struct half_chan *hc, - const u8 *cupdate) -{ - const u8 *oparts[2], *nparts[2]; - size_t osizes[2], nsizes[2]; - const u8 *orig; - - /* Get last one we have. */ - orig = gossip_store_get(tmpctx, gs, hc->bcast.index); - get_cupdate_parts(orig, oparts, osizes); - get_cupdate_parts(cupdate, nparts, nsizes); - - return !memeq(oparts[0], osizes[0], nparts[0], nsizes[0]) - || !memeq(oparts[1], osizes[1], nparts[1], nsizes[1]); -} - -bool routing_add_channel_update(struct routing_state *rstate, - const u8 *update TAKES, - u32 index, - /* NULL if it's us */ - const struct node_id *source_peer, - bool ignore_timestamp) -{ - secp256k1_ecdsa_signature signature; - struct short_channel_id short_channel_id; - u32 timestamp; - u8 message_flags, channel_flags; - u16 expiry; - struct amount_msat htlc_minimum, htlc_maximum; - u32 fee_base_msat; - u32 fee_proportional_millionths; - struct bitcoin_blkid chain_hash; - struct chan *chan; - struct half_chan *hc; - struct unupdated_channel *uc; - u8 direction; - struct amount_sat sat; - bool dying; - - /* Make sure we own msg, even if we don't save it. */ - if (taken(update)) - tal_steal(tmpctx, update); - - if (!fromwire_channel_update( - update, &signature, &chain_hash, - &short_channel_id, ×tamp, - &message_flags, &channel_flags, - &expiry, &htlc_minimum, &fee_base_msat, - &fee_proportional_millionths, - &htlc_maximum)) - return false; - - direction = channel_flags & 0x1; - chan = get_channel(rstate, &short_channel_id); - - if (chan) { - uc = NULL; - sat = chan->sat; - dying = is_chan_dying(rstate, &short_channel_id); - } else { - /* Maybe announcement was waiting for this update? */ - uc = get_unupdated_channel(rstate, &short_channel_id); - if (!uc) { - if (index) - return false; - /* Allow ld to process a private channel update */ - tell_lightningd_peer_update(rstate->daemon, source_peer, - short_channel_id, fee_base_msat, - fee_proportional_millionths, - expiry, htlc_minimum, - htlc_maximum); - return false; - } - sat = uc->sat; - dying = false; - } - - /* Reject update if the `htlc_maximum_msat` is greater - * than the total available channel satoshis */ - if (amount_msat_greater_sat(htlc_maximum, sat)) - return false; - - /* Check timestamp is sane (unless from store). */ - if (!index && !timestamp_reasonable(rstate->daemon, timestamp)) { - SUPERVERBOSE("Ignoring update timestamp %u for %s/%u", - timestamp, - type_to_string(tmpctx, struct short_channel_id, - &short_channel_id), - direction); - return false; - } - - /* OK, we're going to accept this, so create chan if doesn't exist */ - if (uc) { - assert(!chan); - chan = new_chan(rstate, &short_channel_id, - &uc->id[0], &uc->id[1], sat); - } - - /* Discard older updates */ - hc = &chan->half[direction]; - - if (is_halfchan_defined(hc) && !ignore_timestamp) { - /* The gossip_store should contain a single broadcastable entry: any more is a bug */ - if (index) { - status_broken("gossip_store broadcastable " - "channel_update %u replaces %u!", - index, hc->bcast.index); - return false; - } - - /* Allow redundant updates once every 7 days */ - if (timestamp < hc->bcast.timestamp + GOSSIP_PRUNE_INTERVAL(rstate->daemon->dev_fast_gossip_prune) / 2 - && !cupdate_different(rstate->daemon->gs, hc, update)) { - SUPERVERBOSE("Ignoring redundant update for %s/%u" - " (last %u, now %u)", - type_to_string(tmpctx, - struct short_channel_id, - &short_channel_id), - direction, hc->bcast.timestamp, timestamp); - /* Ignoring != failing */ - return true; - } - } - - /* Delete any prior entry (noop if doesn't exist) */ - gossip_store_delete(rstate->daemon->gs, &hc->bcast, - WIRE_CHANNEL_UPDATE); - - /* Update timestamp */ - hc->bcast.timestamp = timestamp; - - /* If this is a peer's update to one of our local channels, tell lightningd. */ - if (node_id_eq(&chan->nodes[!direction]->id, &rstate->daemon->id)) { - /* give lightningd the channel's inbound info to store to db */ - tell_lightningd_peer_update(rstate->daemon, - /* Note: we can get public - * channel_updates from other than - * direct peer! So tell lightningd - * to trust us. */ - NULL, - short_channel_id, fee_base_msat, - fee_proportional_millionths, - expiry, htlc_minimum, - htlc_maximum); - } - - /* BOLT #7: - * - MUST consider the `timestamp` of the `channel_announcement` to be - * the `timestamp` of a corresponding `channel_update`. - * - MUST consider whether to send the `channel_announcement` after - * receiving the first corresponding `channel_update`. - */ - if (uc) { - add_channel_announce_to_broadcast(rstate, chan, - uc->channel_announce, - timestamp, - uc->index); - } - - /* If we're loading from store, this means we don't re-add to store. */ - if (index) { - hc->bcast.index = index; - } else { - hc->bcast.index - = gossip_store_add(rstate->daemon->gs, update, timestamp, - dying, NULL); - - peer_supplied_good_gossip(rstate->daemon, source_peer, 1); - } - - if (uc) { - /* If we were waiting for these nodes to appear (or gain a - public channel), process node_announcements now */ - process_pending_node_announcement(rstate, &chan->nodes[0]->id); - process_pending_node_announcement(rstate, &chan->nodes[1]->id); - tal_free(uc); - } - - status_peer_debug(source_peer, - "Received %schannel_update for %schannel %s/%d now %s", - ignore_timestamp ? "(forced) " : "", - dying ? "dying ": "", - type_to_string(tmpctx, struct short_channel_id, - &short_channel_id), - channel_flags & 0x01, - channel_flags & ROUTING_FLAGS_DISABLED ? "DISABLED" : "ACTIVE"); - - return true; -} - -static const struct node_id *get_channel_owner(struct routing_state *rstate, - const struct short_channel_id *scid, - int direction) -{ - struct chan *chan = get_channel(rstate, scid); - struct unupdated_channel *uc; - - if (chan) - return &chan->nodes[direction]->id; - - /* Might be unupdated channel */ - uc = get_unupdated_channel(rstate, scid); - if (uc) - return &uc->id[direction]; - return NULL; -} - -u8 *handle_channel_update(struct routing_state *rstate, const u8 *update TAKES, - const struct node_id *source_peer, - struct short_channel_id *unknown_scid, - bool force) -{ - u8 *serialized; - const struct node_id *owner; - secp256k1_ecdsa_signature signature; - struct short_channel_id short_channel_id; - u32 timestamp; - u8 message_flags, channel_flags; - u16 expiry; - struct amount_msat htlc_minimum, htlc_maximum; - u32 fee_base_msat; - u32 fee_proportional_millionths; - struct bitcoin_blkid chain_hash; - u8 direction; - struct pending_cannouncement *pending; - const char *err; - - serialized = tal_dup_talarr(tmpctx, u8, update); - if (!fromwire_channel_update(serialized, &signature, - &chain_hash, &short_channel_id, - ×tamp, &message_flags, - &channel_flags, &expiry, - &htlc_minimum, &fee_base_msat, - &fee_proportional_millionths, - &htlc_maximum)) { - /* FIXME: We removed a warning about the - * channel_update being malformed since the warning - * could cause lnd to disconnect (seems they treat - * channel-unrelated warnings as fatal?). This was - * caused by lnd not enforcing the `htlc_maximum`, - * thus the parsing would fail. We can re-add the - * warning once our assumption that `htlc_maximum` - * being set is valid. */ - return NULL; - } - direction = channel_flags & 0x1; - - /* BOLT #7: - * - * The receiving node: - *... - * - if the specified `chain_hash` value is unknown (meaning it isn't - * active on the specified chain): - * - MUST ignore the channel update. - */ - if (!bitcoin_blkid_eq(&chain_hash, &chainparams->genesis_blockhash)) { - status_peer_debug(source_peer, - "Received channel_update for unknown chain %s", - type_to_string(tmpctx, struct bitcoin_blkid, - &chain_hash)); - return NULL; - } - - /* If we dropped the matching announcement for this channel due to the - * txout query failing, don't report failure, it's just too noisy on - * mainnet */ - if (in_txout_failures(rstate->txf, short_channel_id)) - return NULL; - - /* If we have an unvalidated channel, just queue on that */ - pending = find_pending_cannouncement(rstate, &short_channel_id); - if (pending) { - status_peer_debug(source_peer, - "Updated pending announce with update %s/%u", - type_to_string(tmpctx, - struct short_channel_id, - &short_channel_id), - direction); - update_pending(pending, timestamp, serialized, direction, source_peer); - return NULL; - } - - owner = get_channel_owner(rstate, &short_channel_id, direction); - if (!owner) { - /* This may be a local channel we don't know about. If it's from a peer, - * check signature assuming it's from that peer, and if it's valid, hand to ld */ - if (source_peer - && sigcheck_channel_update(tmpctx, source_peer, &signature, serialized) == NULL) { - tell_lightningd_peer_update(rstate->daemon, source_peer, - short_channel_id, fee_base_msat, - fee_proportional_millionths, - expiry, htlc_minimum, - htlc_maximum); - return NULL; - } - - if (unknown_scid) - *unknown_scid = short_channel_id; - bad_gossip_order(serialized, - source_peer, - tal_fmt(tmpctx, "%s/%u", - type_to_string(tmpctx, - struct short_channel_id, - &short_channel_id), - direction)); - return NULL; - } - - err = sigcheck_channel_update(tmpctx, owner, &signature, serialized); - if (err) { - /* BOLT #7: - * - * - if `signature` is not a valid signature, using `node_id` - * of the double-SHA256 of the entire message following the - * `signature` field (including unknown fields following - * `fee_proportional_millionths`): - * - SHOULD send a `warning` and close the connection. - * - MUST NOT process the message further. - */ - return towire_warningfmt(rstate, NULL, "%s", err); - } - - routing_add_channel_update(rstate, take(serialized), 0, source_peer, force); - return NULL; -} - -/* Get non-signature, non-timestamp parts of (valid!) node_announcement, - * with TLV broken out separately */ -static void get_nannounce_parts(const u8 *node_announcement, - const u8 *parts[3], - size_t sizes[3]) -{ - size_t len, ad_len; - const u8 *flen, *ad_start; - - /* BOLT #7: - * - * 1. type: 257 (`node_announcement`) - * 2. data: - * * [`signature`:`signature`] - * * [`u16`:`flen`] - * * [`flen*byte`:`features`] - * * [`u32`:`timestamp`] - *... - */ - /* Note: 2 bytes for `type` field */ - /* We already checked it's valid before accepting */ - assert(tal_count(node_announcement) > 2 + 64); - parts[0] = node_announcement + 2 + 64; - - /* Read flen to get size */ - flen = parts[0]; - len = tal_count(node_announcement) - (2 + 64); - sizes[0] = 2 + fromwire_u16(&flen, &len); - assert(flen != NULL && len >= 4); - - /* BOLT-0fe3485a5320efaa2be8cfa0e570ad4d0259cec3 #7: - * - * * [`u32`:`timestamp`] - * * [`point`:`node_id`] - * * [`3*byte`:`rgb_color`] - * * [`32*byte`:`alias`] - * * [`u16`:`addrlen`] - * * [`addrlen*byte`:`addresses`] - * * [`node_ann_tlvs`:`tlvs`] - */ - parts[1] = node_announcement + 2 + 64 + sizes[0] + 4; - - /* Find the end of the addresses */ - ad_start = parts[1] + 33 + 3 + 32; - len = tal_count(node_announcement) - - (2 + 64 + sizes[0] + 4 + 33 + 3 + 32); - ad_len = fromwire_u16(&ad_start, &len); - assert(ad_start != NULL && len >= ad_len); - - sizes[1] = 33 + 3 + 32 + 2 + ad_len; - - /* Is there a TLV ? */ - sizes[2] = len - ad_len; - if (sizes[2] != 0) - parts[2] = parts[1] + sizes[1]; - else - parts[2] = NULL; -} - -/* Is this node_announcement different from prev (not sigs and timestamps)? */ -static bool nannounce_different(struct gossip_store *gs, - const struct node *node, - const u8 *nannounce) -{ - const u8 *oparts[3], *nparts[3]; - size_t osizes[3], nsizes[3]; - const u8 *orig; - - /* Get last one we have. */ - orig = gossip_store_get(tmpctx, gs, node->bcast.index); - get_nannounce_parts(orig, oparts, osizes); - get_nannounce_parts(nannounce, nparts, nsizes); - - return !memeq(oparts[0], osizes[0], nparts[0], nsizes[0]) - || !memeq(oparts[1], osizes[1], nparts[1], nsizes[1]) - || !memeq(oparts[2], osizes[2], nparts[2], nsizes[2]); -} - -bool routing_add_node_announcement(struct routing_state *rstate, - const u8 *msg TAKES, - u32 index, - const struct node_id *source_peer TAKES, - bool *was_unknown) -{ - struct node *node; - secp256k1_ecdsa_signature signature; - u32 timestamp; - struct node_id node_id; - u8 rgb_color[3]; - u8 alias[32]; - u8 *features, *addresses; - struct tlv_node_ann_tlvs *na_tlv; - - if (was_unknown) - *was_unknown = false; - - /* Make sure we own msg, even if we don't save it. */ - if (taken(msg)) - tal_steal(tmpctx, msg); - - /* Note: validity of node_id is already checked. */ - if (!fromwire_node_announcement(tmpctx, msg, - &signature, &features, ×tamp, - &node_id, rgb_color, alias, - &addresses, - &na_tlv)) { - return false; - } - - node = get_node(rstate, &node_id); - - if (node == NULL || !node_has_broadcastable_channels(node)) { - struct pending_node_announce *pna; - /* BOLT #7: - * - * - if `node_id` is NOT previously known from a - * `channel_announcement` message, OR if `timestamp` is NOT - * greater than the last-received `node_announcement` from - * this `node_id`: - * - SHOULD ignore the message. - */ - /* Check if we are currently verifying the txout for a - * matching channel */ - pna = pending_node_map_get(rstate->pending_node_map, - &node_id); - if (!pna) { - if (was_unknown) - *was_unknown = true; - if (!node) { - bad_gossip_order(msg, source_peer, - type_to_string(tmpctx, struct node_id, - &node_id)); - } - return false; - } else if (timestamp <= pna->timestamp) - /* Ignore old ones: they're OK (unless from store). */ - return index == 0; - - SUPERVERBOSE("Deferring node_announcement for node %s", - type_to_string(tmpctx, struct node_id, &node_id)); - tal_free(pna->node_announcement); - tal_free(pna->source_peer); - pna->node_announcement = tal_dup_talarr(pna, u8, msg); - pna->source_peer = tal_dup_or_null(pna, struct node_id, source_peer); - pna->timestamp = timestamp; - pna->index = index; - return true; - } - - if (node->bcast.index) { - u32 redundant_time; - - /* The gossip_store should contain a single broadcastable entry: Any more is a bug */ - if (index) { - status_broken("gossip_store broadcastable " - "node_announcement %u replaces %u!", - index, node->bcast.index); - return false; - } - - /* Allow redundant updates once a day (faster in dev-fast-gossip-prune mode) */ - redundant_time = GOSSIP_PRUNE_INTERVAL(rstate->daemon->dev_fast_gossip_prune) / 14; - if (timestamp < node->bcast.timestamp + redundant_time - && !nannounce_different(rstate->daemon->gs, node, msg)) { - SUPERVERBOSE( - "Ignoring redundant nannounce for %s" - " (last %u, now %u)", - type_to_string(tmpctx, struct node_id, &node_id), - node->bcast.timestamp, timestamp); - /* Ignoring != failing */ - return true; - } - } - - /* Routing graph always references the latest message. */ - node->bcast.timestamp = timestamp; - - /* Harmless if it was never added */ - gossip_store_delete(rstate->daemon->gs, &node->bcast, - WIRE_NODE_ANNOUNCEMENT); - - /* Don't add to the store if it was loaded from the store. */ - if (index) { - node->bcast.index = index; - } else { - node->bcast.index - = gossip_store_add(rstate->daemon->gs, msg, timestamp, - false, NULL); - - peer_supplied_good_gossip(rstate->daemon, source_peer, 1); - } - - /* Only log this if *not* loading from store. */ - if (!index) - status_peer_debug(source_peer, - "Received node_announcement for node %s", - type_to_string(tmpctx, struct node_id, - &node_id)); - - return true; -} - -u8 *handle_node_announcement(struct routing_state *rstate, const u8 *node_ann, - const struct node_id *source_peer TAKES, - bool *was_unknown) -{ - u8 *serialized; - secp256k1_ecdsa_signature signature; - u32 timestamp; - struct node_id node_id; - u8 rgb_color[3]; - u8 alias[32]; - u8 *features, *addresses; - struct wireaddr *wireaddrs; - size_t len = tal_count(node_ann); - struct tlv_node_ann_tlvs *na_tlv; - const char *err; - - if (was_unknown) - *was_unknown = false; - - serialized = tal_dup_arr(tmpctx, u8, node_ann, len, 0); - if (!fromwire_node_announcement(tmpctx, serialized, - &signature, &features, ×tamp, - &node_id, rgb_color, alias, - &addresses, - &na_tlv)) { - /* BOLT #7: - * - * - if `node_id` is NOT a valid compressed public key: - * - SHOULD send a `warning`. - * - MAY close the connection. - * - MUST NOT process the message further. - */ - /* FIXME: We removed a warning about the - * node_announcement being malformed since the warning - * could cause lnd to disconnect (seems they treat - * channel-unrelated warnings as fatal?). - */ - return NULL; - } - - err = sigcheck_node_announcement(tmpctx, &node_id, &signature, - serialized); - if (err) - return towire_warningfmt(rstate, NULL, "%s", err); - - wireaddrs = fromwire_wireaddr_array(tmpctx, addresses); - if (!wireaddrs) { - /* BOLT #7: - * - * - if `addrlen` is insufficient to hold the address - * descriptors of the known types: - * - SHOULD send a `warning`. - * - MAY close the connection. - */ - return towire_warningfmt(rstate, NULL, - "Malformed wireaddrs %s in %s.", - tal_hex(tmpctx, wireaddrs), - tal_hex(tmpctx, node_ann)); - } - - /* May still fail, if we don't know the node. */ - routing_add_node_announcement(rstate, serialized, 0, source_peer, was_unknown); - return NULL; -} - -void route_prune(struct routing_state *rstate) -{ - u64 now = gossip_time_now(rstate->daemon).ts.tv_sec; - /* Anything below this highwater mark ought to be pruned */ - const s64 highwater = now - GOSSIP_PRUNE_INTERVAL(rstate->daemon->dev_fast_gossip_prune); - struct chan **pruned = tal_arr(tmpctx, struct chan *, 0); - u64 idx; - - /* Now iterate through all channels and see if it is still alive */ - for (struct chan *chan = uintmap_first(&rstate->chanmap, &idx); - chan; - chan = uintmap_after(&rstate->chanmap, &idx)) { - /* BOLT #7: - * - if the `timestamp` of the latest `channel_update` in - * either direction is older than two weeks (1209600 seconds): - * - MAY prune the channel. - */ - /* This is a fancy way of saying "both ends must refresh!" */ - if (!is_halfchan_defined(&chan->half[0]) - || chan->half[0].bcast.timestamp < highwater - || !is_halfchan_defined(&chan->half[1]) - || chan->half[1].bcast.timestamp < highwater) { - if (local_direction(rstate, chan, NULL)) - status_unusual("Pruning local channel %s from gossip_store: not refreshed in over two weeks", - type_to_string(tmpctx, struct short_channel_id, - &chan->scid)); - - status_debug( - "Pruning channel %s from network view (ages %"PRIu64" and %"PRIu64"s)", - type_to_string(tmpctx, struct short_channel_id, - &chan->scid), - is_halfchan_defined(&chan->half[0]) - ? now - chan->half[0].bcast.timestamp : 0, - is_halfchan_defined(&chan->half[1]) - ? now - chan->half[1].bcast.timestamp : 0); - - /* This may perturb iteration so do outside loop. */ - tal_arr_expand(&pruned, chan); - } - } - - /* Look for channels we had an announcement for, but no update. */ - for (struct unupdated_channel *uc - = uintmap_first(&rstate->unupdated_chanmap, &idx); - uc; - uc = uintmap_after(&rstate->unupdated_chanmap, &idx)) { - if (uc->added.ts.tv_sec < highwater) { - tal_free(uc); - } - } - - /* Now free all the chans and maybe even nodes. */ - for (size_t i = 0; i < tal_count(pruned); i++) { - remove_channel_from_store(rstate, pruned[i]); - free_chan(rstate, pruned[i]); - } -} - -const char *unfinalized_entries(const tal_t *ctx, struct routing_state *rstate) -{ - struct unupdated_channel *uc; - u64 index; - struct pending_node_announce *pna; - struct pending_node_map_iter it; - - uc = uintmap_first(&rstate->unupdated_chanmap, &index); - if (uc) - return tal_fmt(ctx, "Unupdated channel_announcement at %u", - uc->index); - - pna = pending_node_map_first(rstate->pending_node_map, &it); - if (pna) - return tal_fmt(ctx, "Waiting node_announcement at %u", - pna->index); - - return NULL; -} - -/* Gossip store was corrupt, forget anything we loaded. */ -void remove_all_gossip(struct routing_state *rstate) -{ - struct node *n; - struct node_map_iter nit; - struct chan *c; - struct unupdated_channel *uc; - u64 index; - struct pending_cannouncement *pca; - struct pending_cannouncement_map_iter pit; - struct pending_node_map_iter pnait; - - /* We don't want them to try to delete from store, so do this - * manually. */ - while ((n = node_map_first(rstate->nodes, &nit)) != NULL) { - tal_del_destructor2(n, destroy_node, rstate); - node_map_del(rstate->nodes, n); - tal_free(n); - } - - /* Now free all the channels. */ - while ((c = uintmap_first(&rstate->chanmap, &index)) != NULL) { - uintmap_del(&rstate->chanmap, index); - if (rstate->daemon->developer) - c->sat = amount_sat((unsigned long)c); - tal_free(c); - } - - while ((uc = uintmap_first(&rstate->unupdated_chanmap, &index)) != NULL) - tal_free(uc); - - while ((pca = pending_cannouncement_map_first(rstate->pending_cannouncements, &pit)) != NULL) - tal_free(pca); - - /* Freeing unupdated chanmaps should empty this */ - assert(pending_node_map_first(rstate->pending_node_map, &pnait) == NULL); -} - -static void channel_spent(struct routing_state *rstate, - struct chan *chan STEALS) -{ - status_debug("Deleting channel %s due to the funding outpoint being " - "spent", - type_to_string(tmpctx, struct short_channel_id, - &chan->scid)); - /* Suppress any now-obsolete updates/announcements */ - txout_failures_add(rstate->txf, chan->scid); - remove_channel_from_store(rstate, chan); - /* Freeing is sufficient since everything else is allocated off - * of the channel and this takes care of unregistering - * the channel */ - free_chan(rstate, chan); -} - -void routing_expire_channels(struct routing_state *rstate, u32 blockheight) -{ - struct chan *chan; - - for (size_t i = 0; i < tal_count(rstate->dying_channels); i++) { - struct dying_channel *d = rstate->dying_channels + i; - - if (blockheight < d->deadline_blockheight) - continue; - chan = get_channel(rstate, &d->scid); - if (chan) - channel_spent(rstate, chan); - /* Delete dying marker itself */ - gossip_store_delete(rstate->daemon->gs, - &d->marker, WIRE_GOSSIP_STORE_CHAN_DYING); - tal_arr_remove(&rstate->dying_channels, i); - i--; - } -} - -void remember_chan_dying(struct routing_state *rstate, - const struct short_channel_id *scid, - u32 deadline_blockheight, - u64 index) -{ - struct dying_channel d; - d.scid = *scid; - d.deadline_blockheight = deadline_blockheight; - d.marker.index = index; - tal_arr_expand(&rstate->dying_channels, d); -} - -void routing_channel_spent(struct routing_state *rstate, - u32 current_blockheight, - struct chan *chan) -{ - u64 index; - u32 deadline; - u8 *msg; - - /* FIXME: We should note that delay is not necessary (or even - * sensible) for local channels! */ - if (local_direction(rstate, chan, NULL)) { - channel_spent(rstate, chan); - return; - } - - /* BOLT #7: - * - once its funding output has been spent OR reorganized out: - * - SHOULD forget a channel after a 12-block delay. - */ - deadline = current_blockheight + 12; - - /* Save to gossip_store in case we restart */ - msg = towire_gossip_store_chan_dying(tmpctx, &chan->scid, deadline); - index = gossip_store_add(rstate->daemon->gs, msg, 0, false, NULL); - - /* Mark it dying, so we don't gossip it */ - gossip_store_mark_dying(rstate->daemon->gs, &chan->bcast, - WIRE_CHANNEL_ANNOUNCEMENT); - for (int dir = 0; dir < ARRAY_SIZE(chan->half); dir++) { - if (is_halfchan_defined(&chan->half[dir])) { - gossip_store_mark_dying(rstate->daemon->gs, - &chan->half[dir].bcast, - WIRE_CHANNEL_UPDATE); - } - } - - /* Remember locally so we can kill it in 12 blocks */ - status_debug("channel %s closing soon due" - " to the funding outpoint being spent", - type_to_string(msg, struct short_channel_id, &chan->scid)); - remember_chan_dying(rstate, &chan->scid, deadline, index); -} diff --git a/gossipd/routing.h b/gossipd/routing.h deleted file mode 100644 index 739f2968f80a..000000000000 --- a/gossipd/routing.h +++ /dev/null @@ -1,350 +0,0 @@ -#ifndef LIGHTNING_GOSSIPD_ROUTING_H -#define LIGHTNING_GOSSIPD_ROUTING_H -#include "config.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -struct daemon; -struct peer; -struct routing_state; - -struct half_chan { - /* Timestamp and index into store file - safe to broadcast */ - struct broadcastable bcast; -}; - -struct chan { - struct short_channel_id scid; - - /* - * half[0]->src == nodes[0] half[0]->dst == nodes[1] - * half[1]->src == nodes[1] half[1]->dst == nodes[0] - */ - struct half_chan half[2]; - /* node[0].id < node[1].id */ - struct node *nodes[2]; - - /* Timestamp and index into store file */ - struct broadcastable bcast; - - struct amount_sat sat; -}; - -/* Use this instead of tal_free(chan)! */ -void free_chan(struct routing_state *rstate, struct chan *chan); - -static inline bool is_halfchan_defined(const struct half_chan *hc) -{ - return hc->bcast.index != 0; -} - -/* Container for per-node channel pointers. Better cache performance - * than uintmap, and we don't need ordering. */ -static inline const struct short_channel_id *chan_map_scid(const struct chan *c) -{ - return &c->scid; -} - -static inline size_t hash_scid(const struct short_channel_id *scid) -{ - /* scids cost money to generate, so simple hash works here */ - return (scid->u64 >> 32) ^ (scid->u64 >> 16) ^ scid->u64; -} - -static inline bool chan_eq_scid(const struct chan *c, - const struct short_channel_id *scid) -{ - return short_channel_id_eq(scid, &c->scid); -} - -HTABLE_DEFINE_TYPE(struct chan, chan_map_scid, hash_scid, chan_eq_scid, chan_map); - -struct node { - struct node_id id; - - /* Timestamp and index into store file */ - struct broadcastable bcast; - - /* Channels connecting us to other nodes */ - /* For a small number of channels (by far the most common) we - * use a simple array, with empty buckets NULL. For larger, we use a - * proper hash table, with the extra allocations that implies. - * - * As of November 2022, 5 or 6 gives the optimal size. - */ - struct chan *chan_arr[6]; - /* If we have more than that, we use a hash. */ - struct chan_map *chan_map; -}; - -const struct node_id *node_map_keyof_node(const struct node *n); -size_t node_map_hash_key(const struct node_id *pc); -bool node_map_node_eq(const struct node *n, const struct node_id *pc); -HTABLE_DEFINE_TYPE(struct node, node_map_keyof_node, node_map_hash_key, node_map_node_eq, node_map); - -/* We've unpacked and checked its signatures, now we wait for master to tell - * us the txout to check */ -struct pending_cannouncement { - /* Unpacked fields here */ - - /* also the key in routing_state->pending_cannouncements */ - struct short_channel_id short_channel_id; - struct node_id node_id_1; - struct node_id node_id_2; - struct pubkey bitcoin_key_1; - struct pubkey bitcoin_key_2; - - struct node_id *source_peer; - - /* The raw bits */ - const u8 *announce; - - /* Deferred updates, if we received them while waiting for - * this (one for each direction) */ - const u8 *updates[2]; - /* Peers responsible */ - struct node_id *update_source_peer[2]; - - /* Only ever replace with newer updates */ - u32 update_timestamps[2]; -}; - -static inline const struct short_channel_id *panding_cannouncement_map_scid( - const struct pending_cannouncement *pending_ann) -{ - return &pending_ann->short_channel_id; -} - -static inline size_t hash_pending_cannouncement_scid( - const struct short_channel_id *scid) -{ - /* like hash_scid() for struct chan above */ - return (scid->u64 >> 32) ^ (scid->u64 >> 16) ^ scid->u64; -} - -static inline bool pending_cannouncement_eq_scid( - const struct pending_cannouncement *pending_ann, - const struct short_channel_id *scid) -{ - return short_channel_id_eq(scid, &pending_ann->short_channel_id); -} - -HTABLE_DEFINE_TYPE(struct pending_cannouncement, panding_cannouncement_map_scid, - hash_pending_cannouncement_scid, pending_cannouncement_eq_scid, - pending_cannouncement_map); - -struct pending_node_map; -struct unupdated_channel; - -/* If you know n is one end of the channel, get index of src == n */ -static inline int half_chan_idx(const struct node *n, const struct chan *chan) -{ - int idx = (chan->nodes[1] == n); - - assert(chan->nodes[0] == n || chan->nodes[1] == n); - return idx; -} - -struct routing_state { - struct daemon *daemon; - - /* All known nodes. */ - struct node_map *nodes; - - /* node_announcements which are waiting on pending_cannouncement */ - struct pending_node_map *pending_node_map; - - /* channel_announcement which are pending short_channel_id lookup */ - struct pending_cannouncement_map *pending_cannouncements; - - /* A map of channels indexed by short_channel_ids */ - UINTMAP(struct chan *) chanmap; - - /* A map of channel_announcements indexed by short_channel_ids: - * we haven't got a channel_update for these yet. */ - UINTMAP(struct unupdated_channel *) unupdated_chanmap; - - /* Cache for txout queries that failed. Allows us to skip failed - * checks if we get another announcement for the same scid. */ - struct txout_failures *txf; - - /* Channels which are closed, but we're waiting 12 blocks */ - struct dying_channel *dying_channels; -}; - -/* Which direction are we? False if neither. */ -static inline bool local_direction(struct routing_state *rstate, - const struct chan *chan, - int *direction) -{ - for (int dir = 0; dir <= 1; (dir)++) { - if (node_id_eq(&chan->nodes[dir]->id, &rstate->daemon->id)) { - if (direction) - *direction = dir; - return true; - } - } - return false; -} - -static inline struct chan * -get_channel(const struct routing_state *rstate, - const struct short_channel_id *scid) -{ - return uintmap_get(&rstate->chanmap, scid->u64); -} - -struct routing_state *new_routing_state(const tal_t *ctx, - struct daemon *daemon); - -/** - * Add a new bidirectional channel from id1 to id2 with the given - * short_channel_id and capacity to the local network view. The channel may not - * already exist, and might create the node entries for the two endpoints, if - * they do not exist yet. - */ -struct chan *new_chan(struct routing_state *rstate, - const struct short_channel_id *scid, - const struct node_id *id1, - const struct node_id *id2, - struct amount_sat sat); - -/* Handlers for incoming messages */ - -/** - * handle_channel_announcement -- Check channel announcement is valid - * - * Returns error message if we should fail channel. Make *scid non-NULL - * (for checking) if we extracted a short_channel_id, otherwise ignore. - */ -u8 *handle_channel_announcement(struct routing_state *rstate, - const u8 *announce TAKES, - u32 current_blockheight, - const struct short_channel_id **scid, - const struct node_id *source_peer TAKES); - -/** - * handle_pending_cannouncement -- handle channel_announce once we've - * completed short_channel_id lookup. Returns true if handling created - * a new channel. - */ -bool handle_pending_cannouncement(struct daemon *daemon, - struct routing_state *rstate, - const struct short_channel_id *scid, - const struct amount_sat sat, - const u8 *txscript); - -/* Iterate through channels in a node */ -struct chan *first_chan(const struct node *node, struct chan_map_iter *i); -struct chan *next_chan(const struct node *node, struct chan_map_iter *i); - -/* Returns NULL if all OK, otherwise an error for the peer which sent. - * If the error is that the channel is unknown, fills in *unknown_scid - * (if not NULL). */ -u8 *handle_channel_update(struct routing_state *rstate, const u8 *update TAKES, - const struct node_id *source_peer TAKES, - struct short_channel_id *unknown_scid, - bool force); - -/* Returns NULL if all OK, otherwise an error for the peer which sent. - * If was_unknown is not NULL, sets it to true if that was the reason for - * the error: the node was unknown to us. */ -u8 *handle_node_announcement(struct routing_state *rstate, const u8 *node_ann, - const struct node_id *source_peer TAKES, - bool *was_unknown); - -/* Get a node: use this instead of node_map_get() */ -struct node *get_node(struct routing_state *rstate, - const struct node_id *id); - -void route_prune(struct routing_state *rstate); - -/** - * Add a channel_announcement to the network view without checking it - * - * Directly add the channel to the local network, without checking it first. Use - * this only for messages from trusted sources. Untrusted sources should use the - * @see{handle_channel_announcement} entrypoint to check before adding. - * - * index is usually 0, in which case it's set by insert_broadcast adding it - * to the store. - * - * source_peer is an optional peer responsible for this. - */ -bool routing_add_channel_announcement(struct routing_state *rstate, - const u8 *msg TAKES, - struct amount_sat sat, - u32 index, - const struct node_id *source_peer TAKES); - -/** - * Add a channel_update without checking for errors - * - * Used to actually insert the information in the channel update into the local - * network view. Only use this for messages that are known to be good. For - * untrusted source, requiring verification please use - * @see{handle_channel_update} - */ -bool routing_add_channel_update(struct routing_state *rstate, - const u8 *update TAKES, - u32 index, - const struct node_id *source_peer TAKES, - bool ignore_timestamp); -/** - * Add a node_announcement to the network view without checking it - * - * Directly add the node being announced to the network view, without verifying - * it. This must be from a trusted source, e.g., gossip_store. For untrusted - * sources (peers) please use @see{handle_node_announcement}. - */ -bool routing_add_node_announcement(struct routing_state *rstate, - const u8 *msg TAKES, - u32 index, - const struct node_id *source_peer TAKES, - bool *was_unknown); - -/** - * Add to rstate->dying_channels - * - * Exposed here for when we load the gossip_store. - */ -void remember_chan_dying(struct routing_state *rstate, - const struct short_channel_id *scid, - u32 deadline_blockheight, - u64 index); - -/** - * When a channel's funding has been spent. - */ -void routing_channel_spent(struct routing_state *rstate, - u32 current_blockheight, - struct chan *chan); - -/** - * Clean up any dying channels. - * - * This finally deletes channel past their deadline. - */ -void routing_expire_channels(struct routing_state *rstate, u32 blockheight); - -/* Does this node have public channels? */ -bool node_has_broadcastable_channels(const struct node *node); - -/* Returns an error string if there are unfinalized entries after load */ -const char *unfinalized_entries(const tal_t *ctx, struct routing_state *rstate); - -void remove_all_gossip(struct routing_state *rstate); - -#endif /* LIGHTNING_GOSSIPD_ROUTING_H */ From e3c1187da0035b2f417e51ef7da1d46301bd7e99 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 31 Jan 2024 16:14:14 +1030 Subject: [PATCH 29/33] gossipd: clean up gossip_store routines. We don't use the dying flag, and we can manually append the addendum rather than having gossip_store_add present a bizarre interface. Signed-off-by: Rusty Russell --- gossipd/gossip_store.c | 25 ++++--------------------- gossipd/gossip_store.h | 18 ++++-------------- gossipd/gossmap_manage.c | 22 +++++++++++++--------- 3 files changed, 21 insertions(+), 44 deletions(-) diff --git a/gossipd/gossip_store.c b/gossipd/gossip_store.c index 03a13ab0c767..aa2f4039e6eb 100644 --- a/gossipd/gossip_store.c +++ b/gossipd/gossip_store.c @@ -64,8 +64,7 @@ static ssize_t gossip_pwritev(int fd, const struct iovec *iov, int iovcnt, } #endif /* !HAVE_PWRITEV */ -static bool append_msg(int fd, const u8 *msg, u32 timestamp, - bool dying, u64 *len) +static bool append_msg(int fd, const u8 *msg, u32 timestamp, u64 *len) { struct gossip_hdr hdr; u32 msglen; @@ -77,8 +76,6 @@ static bool append_msg(int fd, const u8 *msg, u32 timestamp, msglen = tal_count(msg); hdr.len = cpu_to_be16(msglen); hdr.flags = 0; - if (dying) - hdr.flags |= CPU_TO_BE16(GOSSIP_STORE_DYING_BIT); hdr.crc = cpu_to_be32(crc32c(timestamp, msg, msglen)); hdr.timestamp = cpu_to_be32(timestamp); @@ -310,7 +307,7 @@ static u32 gossip_store_compact_offline(struct daemon *daemon) oldlen = lseek(old_fd, SEEK_END, 0); newlen = lseek(new_fd, SEEK_END, 0); append_msg(old_fd, towire_gossip_store_ended(tmpctx, newlen), - 0, false, &oldlen); + 0, &oldlen); close(old_fd); status_debug("gossip_store_compact_offline: %zu deleted, %zu copied", deleted, count); @@ -367,25 +364,18 @@ struct gossip_store *gossip_store_new(struct daemon *daemon) return gs; } -u64 gossip_store_add(struct gossip_store *gs, const u8 *gossip_msg, - u32 timestamp, - bool dying, const u8 *addendum) +u64 gossip_store_add(struct gossip_store *gs, const u8 *gossip_msg, u32 timestamp) { u64 off = gs->len; /* Should never get here during loading! */ assert(gs->writable); - if (!append_msg(gs->fd, gossip_msg, timestamp, dying, &gs->len)) { + if (!append_msg(gs->fd, gossip_msg, timestamp, &gs->len)) { status_broken("Failed writing to gossip store: %s", strerror(errno)); return 0; } - if (addendum && !append_msg(gs->fd, addendum, 0, false, &gs->len)) { - status_broken("Failed writing addendum to gossip store: %s", - strerror(errno)); - return 0; - } return off; } @@ -514,13 +504,6 @@ void gossip_store_flag(struct gossip_store *gs, flag_by_index(gs, offset - sizeof(struct gossip_hdr), flag, type); } -void gossip_store_mark_channel_deleted(struct gossip_store *gs, - const struct short_channel_id *scid) -{ - gossip_store_add(gs, towire_gossip_store_delete_chan(tmpctx, scid), - 0, false, NULL); -} - u32 gossip_store_get_timestamp(struct gossip_store *gs, u64 offset) { struct gossip_hdr hdr; diff --git a/gossipd/gossip_store.h b/gossipd/gossip_store.h index 6bf86c34ce6e..f0fbcc18e5ec 100644 --- a/gossipd/gossip_store.h +++ b/gossipd/gossip_store.h @@ -29,17 +29,14 @@ struct gossip_store *gossip_store_new(struct daemon *daemon); u32 gossip_store_load(struct gossip_store *gs); /** - * Add a gossip message to the gossip_store (and optional addendum) + * Append a gossip message to the gossip_store * @gs: gossip store * @gossip_msg: the gossip message to insert. * @timestamp: the timestamp for filtering of this messsage. - * @dying: true if this message is for a dying channel. - * @addendum: another message to append immediately after this - * (for appending amounts to channel_announcements for internal use). */ -u64 gossip_store_add(struct gossip_store *gs, const u8 *gossip_msg, - u32 timestamp, bool dying, - const u8 *addendum); +u64 gossip_store_add(struct gossip_store *gs, + const u8 *gossip_msg, + u32 timestamp); /** @@ -63,13 +60,6 @@ void gossip_store_flag(struct gossip_store *gs, u16 flag, int type); -/** - * Mark that the channel is about to be deleted, for convenience of - * others mapping the gossip_store. - */ -void gossip_store_mark_channel_deleted(struct gossip_store *gs, - const struct short_channel_id *scid); - /** * Direct store accessor: get timestamp header for a record. * diff --git a/gossipd/gossmap_manage.c b/gossipd/gossmap_manage.c index af0303b772bb..9f1b879ec2dd 100644 --- a/gossipd/gossmap_manage.c +++ b/gossipd/gossmap_manage.c @@ -246,7 +246,9 @@ static void remove_channel(struct gossmap_manage *gm, tal_free(map_del(&gm->early_ann_map, scid)); /* Put in tombstone marker. */ - gossip_store_mark_channel_deleted(gm->daemon->gs, &scid); + gossip_store_add(gm->daemon->gs, + towire_gossip_store_delete_chan(tmpctx, &scid), + 0); /* Delete from store */ gossip_store_del(gm->daemon->gs, chan->cann_off, WIRE_CHANNEL_ANNOUNCEMENT); @@ -284,7 +286,7 @@ static void remove_channel(struct gossmap_manage *gm, nannounce = gossmap_node_get_announce(tmpctx, gossmap, node); timestamp = gossip_store_get_timestamp(gm->daemon->gs, node->nann_off); gossip_store_del(gm->daemon->gs, node->nann_off, WIRE_NODE_ANNOUNCEMENT); - gossip_store_add(gm->daemon->gs, nannounce, timestamp, false, NULL); + gossip_store_add(gm->daemon->gs, nannounce, timestamp); } } @@ -508,8 +510,9 @@ const char *gossmap_manage_channel_announcement(const tal_t *ctx, && !map_get(&gm->pending_ann_map, scid) && !map_get(&gm->early_ann_map, scid)) { /* Set with timestamp 0 (we will update once we have a channel_update) */ - gossip_store_add(gm->daemon->gs, announce, 0, false, - towire_gossip_store_channel_amount(tmpctx, *known_amount)); + gossip_store_add(gm->daemon->gs, announce, 0); + gossip_store_add(gm->daemon->gs, + towire_gossip_store_channel_amount(tmpctx, *known_amount), 0); tal_free(pca); return NULL; } @@ -603,8 +606,9 @@ void gossmap_manage_handle_get_txout_reply(struct gossmap_manage *gm, const u8 * } /* Set with timestamp 0 (we will update once we have a channel_update) */ - gossip_store_add(gm->daemon->gs, pca->channel_announcement, 0, false, - towire_gossip_store_channel_amount(tmpctx, sat)); + gossip_store_add(gm->daemon->gs, pca->channel_announcement, 0); + gossip_store_add(gm->daemon->gs, + towire_gossip_store_channel_amount(tmpctx, sat), 0); tal_free(pca); /* If we looking specifically for this, we no longer are. */ @@ -692,7 +696,7 @@ static const char *process_channel_update(const tal_t *ctx, } /* OK, apply the new one */ - gossip_store_add(gm->daemon->gs, update, timestamp, false, NULL); + gossip_store_add(gm->daemon->gs, update, timestamp); /* Now delete old */ if (gossmap_chan_set(chan, dir)) @@ -833,7 +837,7 @@ static void process_node_announcement(struct gossmap_manage *gm, } /* OK, apply the new one */ - gossip_store_add(gm->daemon->gs, nannounce, timestamp, false, NULL); + gossip_store_add(gm->daemon->gs, nannounce, timestamp); /* Now delete old */ if (gossmap_node_announced(node)) @@ -1152,7 +1156,7 @@ void gossmap_manage_channel_spent(struct gossmap_manage *gm, /* Save to gossip_store in case we restart */ msg = towire_gossip_store_chan_dying(tmpctx, &scid, deadline); - off = gossip_store_add(gm->daemon->gs, msg, 0, false, NULL); + off = gossip_store_add(gm->daemon->gs, msg, 0); gossmap_manage_channel_dying(gm, off, deadline, scid); /* Mark it dying, so we don't gossip it */ From cdfbe983b30bda81d647bb7a0f6576a26a0705ba Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 31 Jan 2024 16:14:14 +1030 Subject: [PATCH 30/33] gossipd: clean up gossip_store offsets. gossmap offsets are to the beginning of the message, whereas the gossip_store uses the header offset. Convert the internals of gossip_store to use gossmap-style uniformly, even where it's a little less convenient. Signed-off-by: Rusty Russell --- gossipd/gossip_store.c | 91 +++++++++++++++++----------------------- gossipd/gossip_store.h | 8 ++-- gossipd/gossmap_manage.c | 18 ++++---- 3 files changed, 50 insertions(+), 67 deletions(-) diff --git a/gossipd/gossip_store.c b/gossipd/gossip_store.c index aa2f4039e6eb..c92d33ddc4ce 100644 --- a/gossipd/gossip_store.c +++ b/gossipd/gossip_store.c @@ -377,9 +377,11 @@ u64 gossip_store_add(struct gossip_store *gs, const u8 *gossip_msg, u32 timestam return 0; } - return off; + /* By gossmap convention, offset is *after* hdr */ + return off + sizeof(struct gossip_hdr); } +/* Offsets are all gossmap-style: *after* hdr! */ static const u8 *gossip_store_get_with_hdr(const tal_t *ctx, struct gossip_store *gs, u64 offset, @@ -388,27 +390,27 @@ static const u8 *gossip_store_get_with_hdr(const tal_t *ctx, u32 msglen, checksum; u8 *msg; - if (offset == 0) + if (offset <= sizeof(*hdr)) status_failed(STATUS_FAIL_INTERNAL_ERROR, "gossip_store: can't access offset %"PRIu64, offset); - if (pread(gs->fd, hdr, sizeof(*hdr), offset) != sizeof(*hdr)) { + if (pread(gs->fd, hdr, sizeof(*hdr), offset - sizeof(*hdr)) != sizeof(*hdr)) { status_failed(STATUS_FAIL_INTERNAL_ERROR, "gossip_store: can't read hdr offset %"PRIu64 "/%"PRIu64": %s", - offset, gs->len, strerror(errno)); + offset - sizeof(*hdr), gs->len, strerror(errno)); } if (be16_to_cpu(hdr->flags) & GOSSIP_STORE_DELETED_BIT) status_failed(STATUS_FAIL_INTERNAL_ERROR, "gossip_store: get delete entry offset %"PRIu64 "/%"PRIu64"", - offset, gs->len); + offset - sizeof(*hdr), gs->len); msglen = be16_to_cpu(hdr->len); checksum = be32_to_cpu(hdr->crc); msg = tal_arr(ctx, u8, msglen); - if (pread(gs->fd, msg, msglen, offset + sizeof(*hdr)) != msglen) + if (pread(gs->fd, msg, msglen, offset) != msglen) status_failed(STATUS_FAIL_INTERNAL_ERROR, "gossip_store: can't read len %u offset %"PRIu64 "/%"PRIu64, msglen, offset, gs->len); @@ -416,64 +418,65 @@ static const u8 *gossip_store_get_with_hdr(const tal_t *ctx, if (checksum != crc32c(be32_to_cpu(hdr->timestamp), msg, msglen)) status_failed(STATUS_FAIL_INTERNAL_ERROR, "gossip_store: bad checksum offset %"PRIu64": %s", - offset, tal_hex(tmpctx, msg)); + offset - sizeof(*hdr), tal_hex(tmpctx, msg)); return msg; } -static bool check_msg_type(struct gossip_store *gs, u32 index, int flag, int type) +static bool check_msg_type(struct gossip_store *gs, u64 offset, int flag, int type) { struct gossip_hdr hdr; - const u8 *msg = gossip_store_get_with_hdr(tmpctx, gs, index, &hdr); + const u8 *msg = gossip_store_get_with_hdr(tmpctx, gs, offset, &hdr); if (fromwire_peektype(msg) == type) return true; - status_broken("asked to flag-%u type %i @%u but store contains " + status_broken("asked to flag-%u type %i @%"PRIu64" but store contains " "%i (gs->len=%"PRIu64"): %s", - flag, type, index, fromwire_peektype(msg), + flag, type, offset, fromwire_peektype(msg), gs->len, tal_hex(tmpctx, msg)); return false; } -/* Returns index of following entry. */ -static u32 flag_by_index(struct gossip_store *gs, u32 index, int flag, int type) +/* Returns offset of following entry (i.e. after its header). */ +u64 gossip_store_set_flag(struct gossip_store *gs, + u64 offset, u16 flag, int type) { struct { beint16_t beflags; beint16_t belen; } hdr; + u64 hdr_off = offset - sizeof(struct gossip_hdr); /* Should never get here during loading! */ assert(gs->writable); /* Should never try to overwrite version */ - assert(index); + assert(offset > sizeof(struct gossip_hdr)); /* FIXME: debugging a gs->len overrun issue reported in #6270 */ - if (pread(gs->fd, &hdr, sizeof(hdr), index) != sizeof(hdr)) { - status_broken("gossip_store pread fail during flag %u @%u type: %i" - " gs->len: %"PRIu64, flag, index, type, gs->len); - return index; + if (pread(gs->fd, &hdr, sizeof(hdr), hdr_off) != sizeof(hdr)) { + status_broken("gossip_store pread fail during flag %u @%"PRIu64" type: %i" + " gs->len: %"PRIu64, flag, hdr_off, type, gs->len); + return offset; } - if (index + sizeof(struct gossip_hdr) + - be16_to_cpu(hdr.belen) > gs->len) { - status_broken("gossip_store overrun during flag-%u @%u type: %i" - " gs->len: %"PRIu64, flag, index, type, gs->len); - return index; + if (offset + be16_to_cpu(hdr.belen) > gs->len) { + status_broken("gossip_store overrun during flag-%u @%"PRIu64" type: %i" + " gs->len: %"PRIu64, flag, hdr_off, type, gs->len); + return offset; } - if (!check_msg_type(gs, index, flag, type)) - return index; + if (!check_msg_type(gs, offset, flag, type)) + return offset; assert((be16_to_cpu(hdr.beflags) & flag) == 0); hdr.beflags |= cpu_to_be16(flag); - if (pwrite(gs->fd, &hdr.beflags, sizeof(hdr.beflags), index) != sizeof(hdr.beflags)) + if (pwrite(gs->fd, &hdr.beflags, sizeof(hdr.beflags), hdr_off) != sizeof(hdr.beflags)) status_failed(STATUS_FAIL_INTERNAL_ERROR, - "Failed writing flags to delete @%u: %s", - index, strerror(errno)); + "Failed writing flags to delete @%"PRIu64": %s", + offset, strerror(errno)); - return index + sizeof(struct gossip_hdr) + be16_to_cpu(hdr.belen); + return offset + be16_to_cpu(hdr.belen) + sizeof(struct gossip_hdr); } void gossip_store_del(struct gossip_store *gs, @@ -483,25 +486,15 @@ void gossip_store_del(struct gossip_store *gs, u32 next_index; assert(offset > sizeof(struct gossip_hdr)); - next_index = flag_by_index(gs, offset - sizeof(struct gossip_hdr), - GOSSIP_STORE_DELETED_BIT, - type); + next_index = gossip_store_set_flag(gs, offset, + GOSSIP_STORE_DELETED_BIT, + type); /* For a channel_announcement, we need to delete amount too */ if (type == WIRE_CHANNEL_ANNOUNCEMENT) - flag_by_index(gs, next_index, - GOSSIP_STORE_DELETED_BIT, - WIRE_GOSSIP_STORE_CHANNEL_AMOUNT); -} - -void gossip_store_flag(struct gossip_store *gs, - u64 offset, - u16 flag, - int type) -{ - assert(offset > sizeof(struct gossip_hdr)); - - flag_by_index(gs, offset - sizeof(struct gossip_hdr), flag, type); + gossip_store_set_flag(gs, next_index, + GOSSIP_STORE_DELETED_BIT, + WIRE_GOSSIP_STORE_CHANNEL_AMOUNT); } u32 gossip_store_get_timestamp(struct gossip_store *gs, u64 offset) @@ -524,13 +517,7 @@ void gossip_store_set_timestamp(struct gossip_store *gs, u64 offset, u32 timesta struct gossip_hdr hdr; const u8 *msg; - assert(offset > sizeof(struct gossip_hdr)); - msg = gossip_store_get_with_hdr(tmpctx, gs, offset - sizeof(hdr), &hdr); - if (pread(gs->fd, &hdr, sizeof(hdr), offset - sizeof(hdr)) != sizeof(hdr)) { - status_broken("gossip_store overrun during set_timestamp @%"PRIu64 - " gs->len: %"PRIu64, offset, gs->len); - return; - } + msg = gossip_store_get_with_hdr(tmpctx, gs, offset, &hdr); /* Change timestamp and crc */ hdr.timestamp = cpu_to_be32(timestamp); diff --git a/gossipd/gossip_store.h b/gossipd/gossip_store.h index f0fbcc18e5ec..50ec5bd44fb4 100644 --- a/gossipd/gossip_store.h +++ b/gossipd/gossip_store.h @@ -51,14 +51,12 @@ void gossip_store_del(struct gossip_store *gs, /** * Add a flag the record at this offset (offset is that of - * record, not header, unlike bcast->index!). + * record!). Returns offset of *next* record. * * In developer mode, checks that type is correct. */ -void gossip_store_flag(struct gossip_store *gs, - u64 offset, - u16 flag, - int type); +u64 gossip_store_set_flag(struct gossip_store *gs, + u64 offset, u16 flag, int type); /** * Direct store accessor: get timestamp header for a record. diff --git a/gossipd/gossmap_manage.c b/gossipd/gossmap_manage.c index 9f1b879ec2dd..3857d9b01969 100644 --- a/gossipd/gossmap_manage.c +++ b/gossipd/gossmap_manage.c @@ -1112,9 +1112,7 @@ void gossmap_manage_new_block(struct gossmap_manage *gm, u32 new_blockheight) kill_spent_channel(gm, gossmap, gm->dying_channels[i].scid); gossip_store_del(gm->daemon->gs, - /* FIXME: fix API to give us pre-hdr offsets! */ - gm->dying_channels[i].gossmap_offset - + sizeof(struct gossip_hdr), + gm->dying_channels[i].gossmap_offset, WIRE_GOSSIP_STORE_CHAN_DYING); tal_arr_remove(&gm->dying_channels, i); } @@ -1160,18 +1158,18 @@ void gossmap_manage_channel_spent(struct gossmap_manage *gm, gossmap_manage_channel_dying(gm, off, deadline, scid); /* Mark it dying, so we don't gossip it */ - gossip_store_flag(gm->daemon->gs, chan->cann_off, - GOSSIP_STORE_DYING_BIT, - WIRE_CHANNEL_ANNOUNCEMENT); + gossip_store_set_flag(gm->daemon->gs, chan->cann_off, + GOSSIP_STORE_DYING_BIT, + WIRE_CHANNEL_ANNOUNCEMENT); /* Channel updates too! */ for (int dir = 0; dir < 2; dir++) { if (!gossmap_chan_set(chan, dir)) continue; - gossip_store_flag(gm->daemon->gs, - chan->cupdate_off[dir], - GOSSIP_STORE_DYING_BIT, - WIRE_CHANNEL_UPDATE); + gossip_store_set_flag(gm->daemon->gs, + chan->cupdate_off[dir], + GOSSIP_STORE_DYING_BIT, + WIRE_CHANNEL_UPDATE); } } From 207656f3c27397f4fdd3a4c4186e9989984e5096 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 31 Jan 2024 16:38:20 +1030 Subject: [PATCH 31/33] gossipd: simplify gossip store API. Instead of "new" and "load", we don't really need to "load" anything, so do everything in gossip_store_new. Have it do the compaction/rewrite, and collect the dying records --- gossipd/gossip_store.c | 378 +++++++++++++------------------ gossipd/gossip_store.h | 39 +++- gossipd/gossipd.c | 13 +- gossipd/gossmap_manage.c | 54 +---- gossipd/gossmap_manage.h | 18 +- gossipd/test/run-txout_failure.c | 6 - tests/test_gossip.py | 14 +- 7 files changed, 216 insertions(+), 306 deletions(-) diff --git a/gossipd/gossip_store.c b/gossipd/gossip_store.c index c92d33ddc4ce..d6b218e17c7e 100644 --- a/gossipd/gossip_store.c +++ b/gossipd/gossip_store.c @@ -29,9 +29,6 @@ struct gossip_store { /* Back pointer. */ struct daemon *daemon; - /* This is false when we're loading */ - bool writable; - int fd; u8 version; @@ -186,60 +183,88 @@ static bool upgrade_field(u8 oldversion, return true; } -/* Read gossip store entries, copy non-deleted ones. This code is written - * as simply and robustly as possible! */ -static u32 gossip_store_compact_offline(struct daemon *daemon) +/* Read gossip store entries, copy non-deleted ones. Check basic + * validity, but this code is written as simply and robustly as + * possible! + * + * Returns fd of new store. + */ +static int gossip_store_compact(struct daemon *daemon, + u64 *total_len, + bool *populated, + struct chan_dying **dying) { - size_t count = 0, deleted = 0; + size_t cannounces = 0, cupdates = 0, nannounces = 0, deleted = 0; int old_fd, new_fd; - u64 oldlen, newlen; + u64 old_len, cur_off; struct gossip_hdr hdr; u8 oldversion, version = GOSSIP_STORE_VER; struct stat st; + bool prev_chan_ann = false; + struct timeabs start = time_now(); + const char *bad; + + *populated = false; + old_len = 0; + new_fd = open(GOSSIP_STORE_TEMP_FILENAME, O_RDWR|O_TRUNC|O_CREAT, 0600); + if (new_fd < 0) { + status_failed(STATUS_FAIL_INTERNAL_ERROR, + "Opening new gossip_store file: %s", + strerror(errno)); + } + + if (!write_all(new_fd, &version, sizeof(version))) { + status_failed(STATUS_FAIL_INTERNAL_ERROR, + "Writing new gossip_store file: %s", + strerror(errno)); + } + *total_len = sizeof(version); + + /* RDWR since we add closed marker at end! */ old_fd = open(GOSSIP_STORE_FILENAME, O_RDWR); - if (old_fd == -1) - return 0; + if (old_fd == -1) { + if (errno == ENOENT) + goto rename_new; + + status_failed(STATUS_FAIL_INTERNAL_ERROR, + "Reading gossip_store file: %s", + strerror(errno)); + }; if (fstat(old_fd, &st) != 0) { status_broken("Could not stat gossip_store: %s", strerror(errno)); - goto close_old; - } - - new_fd = open(GOSSIP_STORE_TEMP_FILENAME, O_RDWR|O_TRUNC|O_CREAT, 0600); - if (new_fd < 0) { - status_broken( - "Could not open file for gossip_store compaction"); - goto close_old; + goto rename_new; } if (!read_all(old_fd, &oldversion, sizeof(oldversion)) || (oldversion != version && !can_upgrade(oldversion))) { status_broken("gossip_store_compact: bad version"); - goto close_and_delete; + goto rename_new; } - if (!write_all(new_fd, &version, sizeof(version))) { - status_broken("gossip_store_compact_offline: writing version to store: %s", - strerror(errno)); - goto close_and_delete; - } + cur_off = old_len = sizeof(oldversion); - /* Read everything, write non-deleted ones to new_fd */ + /* Read everything, write non-deleted ones to new_fd. If something goes wrong, + * we end up with truncated store. */ while (read_all(old_fd, &hdr, sizeof(hdr))) { size_t msglen; u8 *msg; + /* Partial writes can happen, and we simply truncate */ msglen = be16_to_cpu(hdr.len); msg = tal_arr(NULL, u8, msglen); if (!read_all(old_fd, msg, msglen)) { - status_broken("gossip_store_compact_offline: reading msg len %zu from store: %s", - msglen, strerror(errno)); + status_unusual("gossip_store_compact: store ends early at %"PRIu64, + old_len); tal_free(msg); - goto close_and_delete; + goto rename_new; } + cur_off = old_len; + old_len += sizeof(hdr) + msglen; + if (be16_to_cpu(hdr.flags) & GOSSIP_STORE_DELETED_BIT) { deleted++; tal_free(msg); @@ -249,18 +274,18 @@ static u32 gossip_store_compact_offline(struct daemon *daemon) /* Check checksum (upgrade would overwrite, so do it now) */ if (be32_to_cpu(hdr.crc) != crc32c(be32_to_cpu(hdr.timestamp), msg, msglen)) { - status_broken("gossip_store_compact_offline: checksum verification failed? %08x should be %08x", + bad = tal_fmt(tmpctx, "checksum verification failed? %08x should be %08x", be32_to_cpu(hdr.crc), crc32c(be32_to_cpu(hdr.timestamp), msg, msglen)); - tal_free(msg); - goto close_and_delete; + goto badmsg; } if (oldversion != version) { if (!upgrade_field(oldversion, daemon, be16_to_cpu(hdr.flags), &msg)) { tal_free(msg); - goto close_and_delete; + bad = "upgrade of store failed"; + goto badmsg; } /* It can tell us to delete record entirely. */ @@ -283,94 +308,130 @@ static u32 gossip_store_compact_offline(struct daemon *daemon) continue; } + switch (fromwire_peektype(msg)) { + case WIRE_GOSSIP_STORE_CHANNEL_AMOUNT: + /* Previous channel_announcement may have been deleted */ + if (prev_chan_ann) + cannounces++; + prev_chan_ann = false; + break; + case WIRE_CHANNEL_ANNOUNCEMENT: + if (prev_chan_ann) { + bad = "channel_announcement without amount"; + goto badmsg; + } + prev_chan_ann = true; + break; + case WIRE_GOSSIP_STORE_CHAN_DYING: { + struct chan_dying cd; + + if (!fromwire_gossip_store_chan_dying(msg, + &cd.scid, + &cd.deadline)) { + bad = "Bad gossip_store_chan_dying"; + goto badmsg; + } + /* By convention, these offsets are *after* header */ + cd.gossmap_offset = *total_len + sizeof(hdr); + tal_arr_expand(dying, cd); + break; + } + case WIRE_CHANNEL_UPDATE: + cupdates++; + break; + case WIRE_NODE_ANNOUNCEMENT: + nannounces++; + break; + default: + bad = "Unknown message"; + goto badmsg; + } + if (!write_all(new_fd, &hdr, sizeof(hdr)) || !write_all(new_fd, msg, msglen)) { - status_broken("gossip_store_compact_offline: writing msg len %zu to new store: %s", + status_failed(STATUS_FAIL_INTERNAL_ERROR, + "gossip_store_compact: writing msg len %zu to new store: %s", msglen, strerror(errno)); - tal_free(msg); - goto close_and_delete; } tal_free(msg); - count++; + *total_len += sizeof(hdr) + msglen; } - if (close(new_fd) != 0) { - status_broken("gossip_store_compact_offline: closing new store: %s", - strerror(errno)); - goto close_old; + + assert(*total_len == lseek(new_fd, 0, SEEK_END)); + + /* Unlikely, but a channel_announcement without an amount: we just truncate. */ + if (prev_chan_ann) { + bad = "channel_announcement without amount"; + goto badmsg; } + + /* If we have any contents, and the file is less than 1 hour + * old, say "seems good" */ + if (st.st_mtime > time_now().ts.tv_sec - 3600 && *total_len > 1) { + *populated = true; + } + +rename_new: if (rename(GOSSIP_STORE_TEMP_FILENAME, GOSSIP_STORE_FILENAME) != 0) { - status_broken("gossip_store_compact_offline: rename failed: %s", + status_failed(STATUS_FAIL_INTERNAL_ERROR, + "gossip_store_compact: rename failed: %s", strerror(errno)); } /* Create end marker now new file exists. */ - oldlen = lseek(old_fd, SEEK_END, 0); - newlen = lseek(new_fd, SEEK_END, 0); - append_msg(old_fd, towire_gossip_store_ended(tmpctx, newlen), - 0, &oldlen); - close(old_fd); - status_debug("gossip_store_compact_offline: %zu deleted, %zu copied", - deleted, count); - return st.st_mtime; - -close_and_delete: - close(new_fd); -close_old: - close(old_fd); - unlink(GOSSIP_STORE_TEMP_FILENAME); - return 0; -} + if (old_fd != -1) { + append_msg(old_fd, towire_gossip_store_ended(tmpctx, *total_len), + 0, &old_len); + close(old_fd); + } -struct gossip_store *gossip_store_new(struct daemon *daemon) -{ - struct gossip_store *gs = tal(daemon, struct gossip_store); - gs->writable = true; - gs->timestamp = gossip_store_compact_offline(daemon); - gs->fd = open(GOSSIP_STORE_FILENAME, O_RDWR|O_CREAT, 0600); - if (gs->fd < 0) + status_debug("Store compact time: %"PRIu64" msec", + time_to_msec(time_between(time_now(), start))); + status_debug("gossip_store: Read %zu/%zu/%zu/%zu cannounce/cupdate/nannounce/delete from store in %"PRIu64" bytes, now %"PRIu64" bytes (populated=%s)", + cannounces, cupdates, nannounces, deleted, + old_len, *total_len, + *populated ? "true": "false"); + return new_fd; + +badmsg: + /* We truncate */ + status_broken("gossip_store: %s (offset %"PRIu64"). Moving to %s.corrupt and truncating", + bad, cur_off, GOSSIP_STORE_FILENAME); + + rename(GOSSIP_STORE_FILENAME, GOSSIP_STORE_FILENAME ".corrupt"); + if (lseek(new_fd, 0, SEEK_SET) != 0 + || !write_all(new_fd, &version, sizeof(version))) { status_failed(STATUS_FAIL_INTERNAL_ERROR, - "Opening gossip_store store: %s", + "Overwriting new gossip_store file: %s", strerror(errno)); - gs->daemon = daemon; - gs->len = sizeof(gs->version); + } + *total_len = sizeof(version); + goto rename_new; +} - tal_add_destructor(gs, gossip_store_destroy); +struct gossip_store *gossip_store_new(const tal_t *ctx, + struct daemon *daemon, + bool *populated, + struct chan_dying **dying) +{ + struct gossip_store *gs = tal(ctx, struct gossip_store); - /* Try to read the version, write it if this is a new file, or truncate - * if the version doesn't match */ - if (read(gs->fd, &gs->version, sizeof(gs->version)) - == sizeof(gs->version)) { - /* Version match? All good */ - if (gs->version == GOSSIP_STORE_VER) - return gs; - - status_unusual("Gossip store version %u not %u: removing", - gs->version, GOSSIP_STORE_VER); - if (ftruncate(gs->fd, 0) != 0) - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "Truncating store: %s", strerror(errno)); - /* Subtle: we are at offset 1, move back to start! */ - if (lseek(gs->fd, 0, SEEK_SET) != 0) - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "Seeking to start of store: %s", - strerror(errno)); - } - /* Empty file, write version byte */ - gs->version = GOSSIP_STORE_VER; - if (write(gs->fd, &gs->version, sizeof(gs->version)) - != sizeof(gs->version)) - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "Writing version to store: %s", strerror(errno)); + gs->daemon = daemon; + *dying = tal_arr(ctx, struct chan_dying, 0); + gs->fd = gossip_store_compact(daemon, &gs->len, populated, dying); + tal_add_destructor(gs, gossip_store_destroy); return gs; } +int gossip_store_get_fd(const struct gossip_store *gs) +{ + return gs->fd; +} + u64 gossip_store_add(struct gossip_store *gs, const u8 *gossip_msg, u32 timestamp) { u64 off = gs->len; - /* Should never get here during loading! */ - assert(gs->writable); - if (!append_msg(gs->fd, gossip_msg, timestamp, &gs->len)) { status_broken("Failed writing to gossip store: %s", strerror(errno)); @@ -448,9 +509,6 @@ u64 gossip_store_set_flag(struct gossip_store *gs, } hdr; u64 hdr_off = offset - sizeof(struct gossip_hdr); - /* Should never get here during loading! */ - assert(gs->writable); - /* Should never try to overwrite version */ assert(offset > sizeof(struct gossip_hdr)); @@ -528,125 +586,3 @@ void gossip_store_set_timestamp(struct gossip_store *gs, u64 offset, u32 timesta "Failed writing header to re-timestamp @%"PRIu64": %s", offset, strerror(errno)); } - -u32 gossip_store_load(struct gossip_store *gs) -{ - struct gossip_hdr hdr; - u32 msglen, checksum; - u8 *msg; - struct amount_sat satoshis; - const char *bad; - size_t stats[] = {0, 0, 0, 0}; - struct timeabs start = time_now(); - size_t deleted = 0; - u8 *chan_ann = NULL; - - /* FIXME: Do compaction here, and check checksums, etc then.. */ - gs->writable = false; - while (pread(gs->fd, &hdr, sizeof(hdr), gs->len) == sizeof(hdr)) { - msglen = be16_to_cpu(hdr.len); - checksum = be32_to_cpu(hdr.crc); - msg = tal_arr(tmpctx, u8, msglen); - - if (pread(gs->fd, msg, msglen, gs->len+sizeof(hdr)) != msglen) { - bad = "gossip_store: truncated file?"; - goto corrupt; - } - - if (checksum != crc32c(be32_to_cpu(hdr.timestamp), msg, msglen)) { - bad = tal_fmt(tmpctx, "Checksum verification failed: %08x should be %08x", - checksum, crc32c(be32_to_cpu(hdr.timestamp), msg, msglen)); - goto badmsg; - } - - /* Skip deleted entries */ - if (be16_to_cpu(hdr.flags) & GOSSIP_STORE_DELETED_BIT) { - deleted++; - goto next; - } - - switch (fromwire_peektype(msg)) { - case WIRE_GOSSIP_STORE_CHANNEL_AMOUNT: - if (!fromwire_gossip_store_channel_amount(msg, - &satoshis)) { - bad = "Bad gossip_store_channel_amount"; - goto badmsg; - } - /* Previous channel_announcement may have been deleted */ - if (!chan_ann) - break; - chan_ann = NULL; - stats[0]++; - break; - case WIRE_CHANNEL_ANNOUNCEMENT: - if (chan_ann) { - bad = "channel_announcement without amount"; - goto badmsg; - } - /* Save for channel_amount (next msg) (not tmpctx, it gets cleaned!) */ - chan_ann = tal_steal(gs, msg); - break; - case WIRE_GOSSIP_STORE_CHAN_DYING: { - struct short_channel_id scid; - u32 deadline; - - if (!fromwire_gossip_store_chan_dying(msg, &scid, &deadline)) { - bad = "Bad gossip_store_chan_dying"; - goto badmsg; - } - if (!gossmap_manage_channel_dying(gs->daemon->gm, gs->len, deadline, scid)) { - bad = "Invalid gossip_store_chan_dying"; - goto badmsg; - } - break; - } - case WIRE_CHANNEL_UPDATE: - stats[1]++; - break; - case WIRE_NODE_ANNOUNCEMENT: - stats[2]++; - break; - default: - bad = "Unknown message"; - goto badmsg; - } - - next: - gs->len += sizeof(hdr) + msglen; - clean_tmpctx(); - } - - if (chan_ann) { - tal_free(chan_ann); - bad = "dangling channel_announcement"; - goto corrupt; - } - - goto out; - -badmsg: - bad = tal_fmt(tmpctx, "%s (%s)", bad, tal_hex(tmpctx, msg)); - -corrupt: - status_broken("gossip_store: %s. Moving to %s.corrupt and truncating", - bad, GOSSIP_STORE_FILENAME); - - /* FIXME: Debug partial truncate case. */ - rename(GOSSIP_STORE_FILENAME, GOSSIP_STORE_FILENAME ".corrupt"); - close(gs->fd); - gs->fd = open(GOSSIP_STORE_FILENAME, O_RDWR|O_TRUNC|O_CREAT, 0600); - if (gs->fd < 0 || !write_all(gs->fd, &gs->version, sizeof(gs->version))) - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "Truncating new store file: %s", strerror(errno)); - gs->len = 1; - gs->timestamp = 0; -out: - gs->writable = true; - status_debug("total store load time: %"PRIu64" msec", - time_to_msec(time_between(time_now(), start))); - status_debug("gossip_store: Read %zu/%zu/%zu/%zu cannounce/cupdate/nannounce/cdelete from store (%zu deleted) in %"PRIu64" bytes", - stats[0], stats[1], stats[2], stats[3], deleted, - gs->len); - - return gs->timestamp; -} diff --git a/gossipd/gossip_store.h b/gossipd/gossip_store.h index 50ec5bd44fb4..374a7181c22f 100644 --- a/gossipd/gossip_store.h +++ b/gossipd/gossip_store.h @@ -13,20 +13,43 @@ */ struct gossip_store; -struct broadcastable; struct daemon; -struct routing_state; -struct gossip_store *gossip_store_new(struct daemon *daemon); +/* For channels we find dying in the store, on load. They are < 12 + * blocks closed. */ +struct chan_dying { + struct short_channel_id scid; + /* Offset of dying marker in the gossip_store */ + u64 gossmap_offset; + /* Blockheight where it's supposed to be deleted. */ + u32 deadline; +}; /** - * Load the initial gossip store, if any. - * - * @param gs The `gossip_store` to read from + * Load the gossip_store + * @ctx: the context to allocate from + * @daemon: the daemon context + * @populated: set to false if store is empty/obviously partial. + * @dying: an array of channels we found dying markers for. + */ +struct gossip_store *gossip_store_new(const tal_t *ctx, + struct daemon *daemon, + bool *populated, + struct chan_dying **dying); + +/** + * Get the fd from the gossip_store. + * @gs: the gossip_store. * - * Returns the last-modified time of the store, or 0 if it was created new. + * Used by gossmap_manage to create a gossmap. + */ +int gossip_store_get_fd(const struct gossip_store *gs); + +/** + * Get the (refreshed!) gossmap from the gossip_store. + * @gs: gossip store */ -u32 gossip_store_load(struct gossip_store *gs); +struct gossmap *gossip_store_gossmap(struct gossip_store *gs); /** * Append a gossip message to the gossip_store diff --git a/gossipd/gossipd.c b/gossipd/gossipd.c index 247d7bdb3296..778e48cd696e 100644 --- a/gossipd/gossipd.c +++ b/gossipd/gossipd.c @@ -412,6 +412,7 @@ bool timestamp_reasonable(const struct daemon *daemon, u32 timestamp) static void gossip_init(struct daemon *daemon, const u8 *msg) { u32 *dev_gossip_time; + struct chan_dying *dying; if (!fromwire_gossipd_init(daemon, msg, &chainparams, @@ -431,13 +432,13 @@ static void gossip_init(struct daemon *daemon, const u8 *msg) tal_free(dev_gossip_time); } - daemon->gs = gossip_store_new(daemon); - /* Gossmap manager starts up */ - daemon->gm = gossmap_manage_new(daemon, daemon); + daemon->gs = gossip_store_new(daemon, + daemon, + &daemon->gossip_store_populated, + &dying); - /* Load stored gossip messages (FIXME: API sucks)*/ - daemon->gossip_store_populated = - (gossip_store_load(daemon->gs) != 0); + /* Gossmap manager starts up */ + daemon->gm = gossmap_manage_new(daemon, daemon, take(dying)); /* Fire up the seeker! */ daemon->seeker = new_seeker(daemon); diff --git a/gossipd/gossmap_manage.c b/gossipd/gossmap_manage.c index 3857d9b01969..e540eb0092a1 100644 --- a/gossipd/gossmap_manage.c +++ b/gossipd/gossmap_manage.c @@ -47,13 +47,6 @@ struct pending_nannounce { const struct node_id *source_peer; }; -struct dying_channel { - struct short_channel_id scid; - /* Offset of dying marker in the gossip_store */ - u64 gossmap_offset; - u32 deadline_blockheight; -}; - struct cannounce_map { UINTMAP(struct pending_cannounce *) map; size_t count; @@ -89,7 +82,7 @@ struct gossmap_manage { struct txout_failures *txf; /* Blockheights of scids to remove */ - struct dying_channel *dying_channels; + struct chan_dying *dying_channels; /* Occasional check for dead channels */ struct oneshot *prune_timer; @@ -392,15 +385,12 @@ static void report_bad_update(struct gossmap *map, } struct gossmap_manage *gossmap_manage_new(const tal_t *ctx, - struct daemon *daemon) + struct daemon *daemon, + struct chan_dying *dying_channels TAKES) { struct gossmap_manage *gm = tal(ctx, struct gossmap_manage); - gm->fd = open(GOSSIP_STORE_FILENAME, O_RDWR); - if (gm->fd < 0) - status_failed(STATUS_FAIL_INTERNAL_ERROR, - "Opening gossip_store store: %s", - strerror(errno)); + gm->fd = gossip_store_get_fd(daemon->gs); gm->raw_gossmap = gossmap_load_fd(gm, gm->fd, report_bad_update, NULL, gm); assert(gm->raw_gossmap); gm->daemon = daemon; @@ -411,7 +401,7 @@ struct gossmap_manage *gossmap_manage_new(const tal_t *ctx, gm->early_cupdates = tal_arr(gm, struct pending_cupdate *, 0); gm->pending_nannounces = tal_arr(gm, struct pending_nannounce *, 0); gm->txf = txout_failures_new(gm, daemon); - gm->dying_channels = tal_arr(gm, struct dying_channel, 0); + gm->dying_channels = tal_dup_talarr(gm, struct chan_dying, dying_channels); start_prune_timer(gm); return gm; @@ -1107,7 +1097,7 @@ void gossmap_manage_new_block(struct gossmap_manage *gm, u32 new_blockheight) } for (size_t i = 0; i < tal_count(gm->dying_channels); i++) { - if (gm->dying_channels[i].deadline_blockheight > new_blockheight) + if (gm->dying_channels[i].deadline > new_blockheight) continue; kill_spent_channel(gm, gossmap, gm->dying_channels[i].scid); @@ -1125,8 +1115,7 @@ void gossmap_manage_channel_spent(struct gossmap_manage *gm, struct gossmap_chan *chan; const struct gossmap_node *me; const u8 *msg; - u32 deadline; - u64 off; + struct chan_dying cd; struct gossmap *gossmap = gossmap_manage_get_gossmap(gm); chan = gossmap_find_chan(gossmap, &scid); @@ -1145,7 +1134,8 @@ void gossmap_manage_channel_spent(struct gossmap_manage *gm, * - once its funding output has been spent OR reorganized out: * - SHOULD forget a channel after a 12-block delay. */ - deadline = blockheight + 12; + cd.deadline = blockheight + 12; + cd.scid = scid; /* Remember locally so we can kill it in 12 blocks */ status_debug("channel %s closing soon due" @@ -1153,9 +1143,9 @@ void gossmap_manage_channel_spent(struct gossmap_manage *gm, type_to_string(tmpctx, struct short_channel_id, &scid)); /* Save to gossip_store in case we restart */ - msg = towire_gossip_store_chan_dying(tmpctx, &scid, deadline); - off = gossip_store_add(gm->daemon->gs, msg, 0); - gossmap_manage_channel_dying(gm, off, deadline, scid); + msg = towire_gossip_store_chan_dying(tmpctx, &cd.scid, cd.deadline); + cd.gossmap_offset = gossip_store_add(gm->daemon->gs, msg, 0); + tal_arr_expand(&gm->dying_channels, cd); /* Mark it dying, so we don't gossip it */ gossip_store_set_flag(gm->daemon->gs, chan->cann_off, @@ -1179,26 +1169,6 @@ struct gossmap *gossmap_manage_get_gossmap(struct gossmap_manage *gm) return gm->raw_gossmap; } -bool gossmap_manage_channel_dying(struct gossmap_manage *gm, - u64 gossmap_offset, - u32 deadline, - struct short_channel_id scid) -{ - struct dying_channel dead; - struct gossmap *gossmap = gossmap_manage_get_gossmap(gm); - - /* Can't kill missing channels! */ - if (!gossmap_find_chan(gossmap, &scid)) - return false; - - dead.deadline_blockheight = deadline; - dead.gossmap_offset = gossmap_offset; - dead.scid = scid; - - tal_arr_expand(&gm->dying_channels, dead); - return true; -} - /* BOLT #7: * - if the `gossip_queries` feature is negotiated: * - MUST NOT relay any gossip messages it did not generate itself, diff --git a/gossipd/gossmap_manage.h b/gossipd/gossmap_manage.h index 6396778aae55..1749f025a83a 100644 --- a/gossipd/gossmap_manage.h +++ b/gossipd/gossmap_manage.h @@ -4,9 +4,11 @@ struct daemon; struct gossmap_manage; +struct chan_dying; struct gossmap_manage *gossmap_manage_new(const tal_t *ctx, - struct daemon *daemon); + struct daemon *daemon, + struct chan_dying *dying_channels TAKES); /** * gossmap_manage_channel_announcement: process an incoming channel_announcement @@ -86,20 +88,6 @@ void gossmap_manage_channel_spent(struct gossmap_manage *gm, u32 blockheight, struct short_channel_id scid); -/** - * gossmap_manage_channel_dying: dying channel loaded from store. - * @gm: the gossmap_manage context - * @offset: the offset of the dying marker in the store. - * @deadline: the blockheight it is to expire - * @scid: the short_channel_id - * - * Returns false if this channel does not exist (already dead!). - */ -bool gossmap_manage_channel_dying(struct gossmap_manage *gm, - u64 gossmap_offset, - u32 deadline, - struct short_channel_id scid); - /** * gossmap_manage_get_gossmap: get the (refreshed!) gossmap * @gm: the gossmap_manage context diff --git a/gossipd/test/run-txout_failure.c b/gossipd/test/run-txout_failure.c index 017523fd6c34..522ef342afbc 100644 --- a/gossipd/test/run-txout_failure.c +++ b/gossipd/test/run-txout_failure.c @@ -30,12 +30,6 @@ bool blinding_next_pubkey(const struct pubkey *pk UNNEEDED, { fprintf(stderr, "blinding_next_pubkey called!\n"); abort(); } /* AUTOGENERATED MOCKS END */ -/* NOOP stub for gossip_store_new */ -struct gossip_store *gossip_store_new(struct daemon *daemon UNNEEDED) -{ - return NULL; -} - int main(int argc, char *argv[]) { struct txout_failures *txf; diff --git a/tests/test_gossip.py b/tests/test_gossip.py index a80a185f9c79..5c61bfa77c35 100644 --- a/tests/test_gossip.py +++ b/tests/test_gossip.py @@ -1168,7 +1168,7 @@ def test_gossip_store_load(node_factory): l1.start() # May preceed the Started msg waited for in 'start'. - wait_for(lambda: l1.daemon.is_in_log(r'gossip_store: Read 1/1/1/0 cannounce/cupdate/nannounce/cdelete from store \(0 deleted\) in 778 bytes')) + wait_for(lambda: l1.daemon.is_in_log('Read 1/1/1/0 cannounce/cupdate/nannounce/delete from store in 800 bytes, now 778 bytes')) assert not l1.daemon.is_in_log('gossip_store.*truncating') @@ -1236,7 +1236,7 @@ def test_gossip_store_load_announce_before_update(node_factory): l1.start() # May preceed the Started msg waited for in 'start'. - wait_for(lambda: l1.daemon.is_in_log(r'gossip_store: Read 1/1/1/0 cannounce/cupdate/nannounce/cdelete from store \(0 deleted\) in 778 bytes')) + wait_for(lambda: l1.daemon.is_in_log('Read 1/1/1/1 cannounce/cupdate/nannounce/delete from store in 950 bytes, now 778 bytes')) assert not l1.daemon.is_in_log('gossip_store.*truncating') @@ -1253,8 +1253,8 @@ def test_gossip_store_load_amount_truncated(node_factory): l1.start() # May preceed the Started msg waited for in 'start'. - wait_for(lambda: l1.daemon.is_in_log(r'gossip_store: dangling channel_announcement. Moving to gossip_store.corrupt and truncating')) - wait_for(lambda: l1.daemon.is_in_log(r'gossip_store: Read 0/0/0/0 cannounce/cupdate/nannounce/cdelete from store \(0 deleted\) in 1 bytes')) + wait_for(lambda: l1.daemon.is_in_log(r'\*\*BROKEN\*\* gossipd: gossip_store: channel_announcement without amount \(offset 1\). Moving to gossip_store.corrupt and truncating')) + wait_for(lambda: l1.daemon.is_in_log(r'gossip_store: Read 0/0/0/0 cannounce/cupdate/nannounce/delete from store in 467 bytes, now 1 bytes \(populated=false\)')) assert os.path.exists(os.path.join(l1.daemon.lightning_dir, TEST_NETWORK, 'gossip_store.corrupt')) @@ -1614,7 +1614,7 @@ def test_gossip_store_load_no_channel_update(node_factory): l1.start() # May preceed the Started msg waited for in 'start'. - wait_for(lambda: l1.daemon.is_in_log(r'gossipd: gossip_store: Read 1/0/1/0 cannounce/cupdate/nannounce/cdelete from store \(0 deleted\) in 628 bytes')) + wait_for(lambda: l1.daemon.is_in_log('Read 1/0/1/0 cannounce/cupdate/nannounce/delete from store in 650 bytes, now 628 bytes')) assert not os.path.exists(os.path.join(l1.daemon.lightning_dir, TEST_NETWORK, 'gossip_store.corrupt')) @@ -1629,9 +1629,7 @@ def test_gossip_store_compact_on_load(node_factory, bitcoind): l2.restart() # These appear before we're fully started, so will already in log: - l2.daemon.is_in_log(r'gossip_store_compact_offline: 2 deleted, 9 copied') - - assert l2.daemon.is_in_log(r'gossip_store: Read 2/4/3/0 cannounce/cupdate/nannounce/cdelete from store \(0 deleted\)') + assert l2.daemon.is_in_log('gossip_store: Read 2/4/3/2 cannounce/cupdate/nannounce/delete from store') def test_gossip_announce_invalid_block(node_factory, bitcoind): From 9cc946a3d8e5cf2465594728cd3b144b2d93761f Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Sat, 3 Feb 2024 11:34:58 +1030 Subject: [PATCH 32/33] pytest: fix YA gossip flake. We can get bad gossip if a node processes a gossip message after we've closed: ``` _________________________________________ ERROR at teardown of test_closing_specified_destination _________________________________________ ... > raise ValueError(str(errors)) E ValueError: E Node errors: E - lightningd-1: had warning messages E - lightningd-4: had bad gossip messages E Global errors: ... lightningd-1 2024-02-03T00:29:02.299Z INFO 0382ce59ebf18be7d84677c2e35f23294b9992ceca95491fcf8a56c6cb2d9de199-connectd: Received WIRE_WARNING: WARNING: channel_announcement: no unspent txout 105x1x0 lightningd-1 2024-02-03T00:29:02.300Z DEBUG 0382ce59ebf18be7d84677c2e35f23294b9992ceca95491fcf8a56c6cb2d9de199-connectd: peer_in WIRE_WARNING lightningd-1 2024-02-03T00:29:02.300Z INFO 0382ce59ebf18be7d84677c2e35f23294b9992ceca95491fcf8a56c6cb2d9de199-connectd: Received WIRE_WARNING: WARNING: channel_announcement: no unspent txout 103x1x0 lightningd-1 2024-02-03T00:29:02.339Z DEBUG 035d2b1192dfba134e10e540875d366ebc8bc353d5aa766b80c090b39c3a5d885d-connectd: peer_in WIRE_WARNING lightningd-1 2024-02-03T00:29:02.339Z INFO 035d2b1192dfba134e10e540875d366ebc8bc353d5aa766b80c090b39c3a5d885d-connectd: Received WIRE_WARNING: WARNING: channel_announcement: no unspent txout 103x1x0 ``` Signed-off-by: Rusty Russell --- tests/test_closing.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/test_closing.py b/tests/test_closing.py index aaabdc624453..90fb0a3a34a5 100644 --- a/tests/test_closing.py +++ b/tests/test_closing.py @@ -297,6 +297,12 @@ def test_closing_specified_destination(node_factory, bitcoind, chainparams): mine_funding_to_announce(bitcoind, [l1, l2, l3, l4]) + # Make sure they all see all the gossip before close, otherwise they might + # get upset with "bad gossip!" + for n in [l1, l2, l3, l4]: + wait_for(lambda: len(n.rpc.listchannels()['channels']) == 6) + wait_for(lambda: ['alias' in node for node in n.rpc.listnodes()['nodes']] == [True] * 4) + addr = chainparams['example_addr'] l1.rpc.close(chan12, None, addr) l1.rpc.call('close', {'id': chan13, 'destination': addr}) From 891c0e969864df7546edb293f4cad8fe04568566 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Sat, 3 Feb 2024 14:01:03 +1030 Subject: [PATCH 33/33] pytest: another bad gossip flake This time in renepay tests. Signed-off-by: Rusty Russell --- tests/test_renepay.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/test_renepay.py b/tests/test_renepay.py index 45caa1ec06dc..4d606bd9a01e 100644 --- a/tests/test_renepay.py +++ b/tests/test_renepay.py @@ -297,6 +297,10 @@ def start_channels(connections): scid = src.get_channel_scid(dst) scids.append(scid) + # Make sure they have all seen block so they don't complain about + # the coming gossip messages + sync_blockheight(bitcoind, nodes) + bitcoind.generate_block(5) # Make sure everyone sees all channels, all other nodes