From ce39309c0c01dc8d3485804f3db603ea9a92d697 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 31 Jan 2024 14:51:33 +1030 Subject: [PATCH] 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 | 172 ++++++++++++++++++++++++++++++++++++----------- common/gossmap.h | 34 +++++++++- 2 files changed, 163 insertions(+), 43 deletions(-) diff --git a/common/gossmap.c b/common/gossmap.c index 5a897fa38..d8e7e3a6b 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 7185f86e2..d9d2ad399 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);