From c5bd518d2fa59ab2aa229e7d4d3c80db40e19a9a Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Tue, 2 Feb 2021 15:35:05 +1030 Subject: [PATCH] gossmap: change local API. Now we create a separate set of local mods, and apply and unapply it. This is more efficient than the previous approach, since we can do some work up-front. It's also more graceful (and well-defined) when a local modification overlaps an existing one. Signed-off-by: Rusty Russell --- common/gossmap.c | 415 +++++++++++++++++--------------- common/gossmap.h | 30 ++- common/test/run-gossmap_local.c | 47 +++- 3 files changed, 285 insertions(+), 207 deletions(-) diff --git a/common/gossmap.c b/common/gossmap.c index 8947cb50a..adab7664b 100644 --- a/common/gossmap.c +++ b/common/gossmap.c @@ -81,8 +81,8 @@ struct gossmap { /* Linked list of freed ones, if any. */ u32 freed_nodes, freed_chans; - /* local messages (tal array) */ - u8 *local; + /* local messages, if any. */ + const u8 *local; }; /* Accessors for the gossmap */ @@ -395,7 +395,8 @@ void gossmap_remove_node(struct gossmap *map, struct gossmap_node *node) * * [`point`:`node_id_1`] * * [`point`:`node_id_2`] */ -static void add_channel(struct gossmap *map, size_t cannounce_off) +static struct gossmap_chan *add_channel(struct gossmap *map, + size_t cannounce_off) { /* Note that first two bytes are message type */ const size_t feature_len_off = 2 + (64 + 64 + 64 + 64); @@ -403,6 +404,7 @@ static void add_channel(struct gossmap *map, size_t cannounce_off) size_t scid_off; struct node_id node_id[2]; struct gossmap_node *n[2]; + struct gossmap_chan *chan; u32 nidx[2]; feature_len = map_be16(map, cannounce_off + feature_len_off); @@ -424,7 +426,7 @@ static void add_channel(struct gossmap *map, size_t cannounce_off) else nidx[1] = new_node(map); - new_channel(map, cannounce_off, scid_off, nidx[0], nidx[1]); + chan = new_channel(map, cannounce_off, scid_off, nidx[0], nidx[1]); /* Now we have a channel, we can add nodes to htable */ if (!n[0]) @@ -433,6 +435,8 @@ static void add_channel(struct gossmap *map, size_t cannounce_off) if (!n[1]) nodeidx_htable_add(&map->nodes, node2ptrint(map->node_arr + nidx[1])); + + return chan; } /* BOLT #7: @@ -612,13 +616,11 @@ static bool load_gossip_store(struct gossmap *map) if (map->fd < 0) return false; - /* Start with empty local map */ - map->local = tal_arr(map, u8, 0); - fstat(map->fd, &st); map->st_dev = st.st_dev; map->st_ino = st.st_ino; map->map_size = st.st_size; + map->local = NULL; /* If this fails, we fall back to read */ map->mmap = mmap(NULL, map->map_size, PROT_READ, MAP_SHARED, map->fd, 0); if (map->mmap == MAP_FAILED) @@ -661,69 +663,235 @@ static void destroy_map(struct gossmap *map) free(map->node_arr[i].chan_idxs); } -void gossmap_local_cleanup(struct gossmap *map) +/* Local modifications. We only expect a few, so we use a simple + * array. */ +struct localmod { + struct short_channel_id scid; + /* If this is an entirely-local channel, here's its offset. + * Otherwise, 0xFFFFFFFF. */ + u32 local_off; + + /* Are updates in either direction set? */ + bool updates_set[2]; + /* hc[n] defined if updates_set[n]. */ + struct half_chan hc[2]; + /* orig[n] defined if updates_set[n] and local_off == 0xFFFFFFFF */ + struct half_chan orig[2]; +}; + +struct gossmap_localmods { + struct localmod *mods; + /* This is the local array to be used by the gossmap */ + u8 *local; +}; + +struct gossmap_localmods *gossmap_localmods_new(const tal_t *ctx) { - size_t off, msglen; + struct gossmap_localmods *localmods; - /* We need to undo all the local additions and updates. - * FIXME: local updates may have overriden previous ones, but - * we simply mark them disabled (they're usually used to - * update local-only channels anyway). */ - for (off = 0; - off < tal_bytelen(map->local); - off += sizeof(msglen) + msglen) { - struct short_channel_id scid; - struct gossmap_chan *chan; - be64 bescid; - be16 type; + localmods = tal(ctx, struct gossmap_localmods); + localmods->mods = tal_arr(localmods, struct localmod, 0); + localmods->local = tal_arr(localmods, u8, 0); - /* Local cursor */ - u8 *p = map->local + off; - memcpy(&msglen, p, sizeof(msglen)); - p += sizeof(msglen); - memcpy(&type, p, sizeof(type)); - p += sizeof(type); + return localmods; +} - if (type == CPU_TO_BE16(WIRE_CHANNEL_ANNOUNCEMENT)) { - /* Get scid from inside announcement. */ - be16 flen; - p += 64 * 4; - memcpy(&flen, p, sizeof(flen)); - p += sizeof(flen) + be16_to_cpu(flen) + 32; - memcpy(&bescid, p, sizeof(bescid)); - scid.u64 = be64_to_cpu(bescid); +/* Create space at end of local map, return offset it was added at. */ +static size_t insert_local_space(struct gossmap_localmods *localmods, + size_t msglen) +{ + size_t oldlen = tal_bytelen(localmods->local); - chan = gossmap_find_chan(map, &scid); - if (chan) - gossmap_remove_chan(map, chan); - } else { - u8 channel_flags; - assert(type == CPU_TO_BE16(WIRE_CHANNEL_UPDATE)); - p += 64 + 32; - memcpy(&bescid, p, sizeof(bescid)); - p += sizeof(bescid); - scid.u64 = be64_to_cpu(bescid); - p += 4 + 1; - channel_flags = *p; + tal_resize(&localmods->local, oldlen + msglen); + return oldlen; +} - chan = gossmap_find_chan(map, &scid); - /* May have removed it when we processed - * announce above */ - if (chan) - chan->half[channel_flags & 1].enabled = false; - } +static struct localmod *find_localmod(struct gossmap_localmods *localmods, + const struct short_channel_id *scid) +{ + for (size_t i = 0; i < tal_count(localmods->mods); i++) + if (short_channel_id_eq(&localmods->mods[i].scid, scid)) + return &localmods->mods[i]; + return NULL; +} + +bool gossmap_local_addchan(struct gossmap_localmods *localmods, + const struct node_id *n1, + const struct node_id *n2, + const struct short_channel_id *scid, + const u8 *features) +{ + be16 be16; + be64 be64; + size_t off; + struct localmod mod; + + /* Don't create duplicate channels. */ + if (find_localmod(localmods, scid)) + return false; + + mod.scid = *scid; + mod.updates_set[0] = mod.updates_set[1] = false; + + /* We create fake local channel_announcement. */ + off = insert_local_space(localmods, + 2 + 64 * 4 + 2 + tal_bytelen(features) + + 32 + 8 + 33 + 33); + mod.local_off = off; + + /* Set type to be kosher. */ + be16 = CPU_TO_BE16(WIRE_CHANNEL_ANNOUNCEMENT); + memcpy(localmods->local + off, &be16, sizeof(be16)); + off += sizeof(be16); + + /* Skip sigs */ + off += 64 * 4; + + /* Set length and features */ + be16 = cpu_to_be16(tal_bytelen(features)); + memcpy(localmods->local + off, &be16, sizeof(be16)); + off += sizeof(be16); + memcpy(localmods->local + off, features, tal_bytelen(features)); + off += tal_bytelen(features); + + /* Skip chain_hash */ + off += 32; + + /* Set scid */ + be64 = be64_to_cpu(scid->u64); + memcpy(localmods->local + off, &be64, sizeof(be64)); + off += sizeof(be64); + + /* set node_ids */ + memcpy(localmods->local + off, n1->k, sizeof(n1->k)); + off += sizeof(n1->k); + memcpy(localmods->local + off, n2->k, sizeof(n2->k)); + off += sizeof(n2->k); + + assert(off == tal_bytelen(localmods->local)); + + tal_arr_expand(&localmods->mods, mod); + return true; +}; + +/* Insert a local-only channel_update. */ +bool gossmap_local_updatechan(struct gossmap_localmods *localmods, + const struct short_channel_id *scid, + struct amount_msat htlc_min, + struct amount_msat htlc_max, + u32 base_fee, + u32 proportional_fee, + u16 delay, + bool enabled, + int dir) +{ + struct localmod *mod; + + mod = find_localmod(localmods, scid); + if (!mod) { + /* Create new reference to (presumably) existing channel. */ + size_t nmods = tal_count(localmods->mods); + + tal_resize(&localmods->mods, nmods + 1); + mod = &localmods->mods[nmods]; + mod->scid = *scid; + mod->updates_set[0] = mod->updates_set[1] = false; + mod->local_off = 0xFFFFFFFF; } - /* Now zero out map */ - tal_resize(&map->local, 0); + assert(dir == 0 || dir == 1); + mod->updates_set[dir] = true; + mod->hc[dir].enabled = enabled; + /* node_idx needs to be set once we're in the gossmap. */ + mod->hc[dir].htlc_min + = u64_to_fp16(htlc_min.millisatoshis, /* Raw: to fp16 */ + false); + mod->hc[dir].htlc_max + = u64_to_fp16(htlc_max.millisatoshis, /* Raw: to fp16 */ + true); + mod->hc[dir].base_fee = base_fee; + mod->hc[dir].proportional_fee = proportional_fee; + mod->hc[dir].delay = delay; + + /* Check they fit */ + if (mod->hc[dir].base_fee != base_fee + || mod->hc[dir].proportional_fee != proportional_fee + || mod->hc[dir].delay != delay) + return false; + return true; +} + +/* Apply localmods to this map */ +void gossmap_apply_localmods(struct gossmap *map, + struct gossmap_localmods *localmods) +{ + size_t n = tal_count(localmods->mods); + + assert(!map->local); + map->local = localmods->local; + + for (size_t i = 0; i < n; i++) { + struct localmod *mod = &localmods->mods[i]; + struct gossmap_chan *chan; + + /* Find gossmap entry which this applies to. */ + chan = gossmap_find_chan(map, &mod->scid); + /* If it doesn't exist, are we supposed to create a local one? */ + if (!chan) { + if (mod->local_off == 0xFFFFFFFF) + continue; + + /* Create new channel, pointing into local. */ + chan = add_channel(map, map->map_size + mod->local_off); + } + + /* Save old, overwrite (keep nodeidx) */ + for (size_t h = 0; h < 2; h++) { + if (!mod->updates_set[h]) + continue; + mod->orig[h] = chan->half[h]; + chan->half[h] = mod->hc[h]; + chan->half[h].nodeidx = mod->orig[h].nodeidx; + } + } +} + +void gossmap_remove_localmods(struct gossmap *map, + const struct gossmap_localmods *localmods) +{ + size_t n = tal_count(localmods->mods); + + assert(map->local == localmods->local); + + for (size_t i = 0; i < n; i++) { + const struct localmod *mod = &localmods->mods[i]; + struct gossmap_chan *chan = gossmap_find_chan(map, &mod->scid); + + /* If that's a local channel, remove it now. */ + if (chan->cann_off >= map->map_size) { + gossmap_remove_chan(map, chan); + } else { + /* Restore (keep nodeidx). */ + for (size_t h = 0; h < 2; h++) { + u32 nodeidx; + if (!mod->updates_set[h]) + continue; + + nodeidx = chan->half[h].nodeidx; + chan->half[h] = mod->orig[h]; + chan->half[h].nodeidx = nodeidx; + } + } + } + map->local = NULL; } bool gossmap_refresh(struct gossmap *map) { struct stat st; - /* You must clean local updates before this. */ - assert(tal_bytelen(map->local) == 0); + /* You must remoe local updates before this. */ + assert(!map->local); /* If file has changed, move to it. */ if (stat(map->fname, &st) != 0) @@ -762,141 +930,6 @@ struct gossmap *gossmap_load(const tal_t *ctx, const char *filename) return map; } -/* Add something to the local map, return offset it was added at. */ -static size_t insert_local_goss(struct gossmap *map, const u8 *msg TAKES) -{ - size_t oldlen = tal_bytelen(map->local); - size_t msglen = tal_bytelen(msg); - - /* We store length, then the msg. */ - tal_resize(&map->local, oldlen + sizeof(msglen) + msglen); - memcpy(map->local + oldlen, &msglen, sizeof(msglen)); - memcpy(map->local + oldlen + sizeof(msglen), msg, msglen); - if (taken(msg)) - tal_free(msg); - - return map->map_size + oldlen + sizeof(msglen); -} - -void gossmap_local_addchan(struct gossmap *map, - const struct node_id *n1, - const struct node_id *n2, - const struct short_channel_id *scid, - const u8 *features) -{ - be16 be16; - be64 be64; - size_t off; - u8 *fake_ann = tal_arr(NULL, u8, - 2 + 64 * 4 + 2 + tal_bytelen(features) - + 32 + 8 + 33 + 33); - off = 0; - - /* Set type to be kosher. */ - be16 = CPU_TO_BE16(WIRE_CHANNEL_ANNOUNCEMENT); - memcpy(fake_ann + off, &be16, sizeof(be16)); - off += sizeof(be16); - - /* Skip sigs */ - off += 64 * 4; - - /* Set length and features */ - be16 = cpu_to_be16(tal_bytelen(features)); - memcpy(fake_ann + off, &be16, sizeof(be16)); - off += sizeof(be16); - memcpy(fake_ann + off, features, tal_bytelen(features)); - off += tal_bytelen(features); - - /* Skip chain_hash */ - off += 32; - - /* Set scid */ - be64 = be64_to_cpu(scid->u64); - memcpy(fake_ann + off, &be64, sizeof(be64)); - off += sizeof(be64); - - /* set node_ids */ - memcpy(fake_ann + off, n1->k, sizeof(n1->k)); - off += sizeof(n1->k); - memcpy(fake_ann + off, n2->k, sizeof(n2->k)); - off += sizeof(n2->k); - - assert(off == tal_bytelen(fake_ann)); - - add_channel(map, insert_local_goss(map, take(fake_ann))); -} - -/* Insert a local-only channel_update (not in the mmap'ed gossmap, - * cleared on refresh). Must exist! */ -void gossmap_local_updatechan(struct gossmap *map, - const struct short_channel_id *scid, - struct amount_msat htlc_min, - struct amount_msat htlc_max, - u32 base_fee, - u32 proportional_fee, - u16 delay, - bool enabled, - int dir) -{ - be16 be16; - be32 be32; - be64 be64; - size_t off; - u8 *fake_upd = tal_arr(NULL, u8, - 2 + 64 + 32 + 8 + 4 + 1 + 1 + 2 + 8 + 4 + 4 + 8); - - off = 0; - - /* Set type to be kosher. */ - be16 = CPU_TO_BE16(WIRE_CHANNEL_UPDATE); - memcpy(fake_upd + off, &be16, sizeof(be16)); - off += sizeof(be16); - - /* Skip signature and chainhash */ - off += 64 + 32; - - /* Set scid */ - be64 = be64_to_cpu(scid->u64); - memcpy(fake_upd + off, &be64, sizeof(be64)); - off += sizeof(be64); - - /* Skip timestamp. */ - off += 4; - - /* We support htlc_maximum_msat. */ - fake_upd[off] = 1; - off += 1; - - /* Bottom bit is direction, second is disable. */ - fake_upd[off] = dir; - if (!enabled) - fake_upd[off] |= 2; - off += 1; - - be16 = cpu_to_be16(delay); - memcpy(fake_upd + off, &be16, sizeof(be16)); - off += sizeof(be16); - - be64 = cpu_to_be64(htlc_min.millisatoshis); /* Raw: endian */ - memcpy(fake_upd + off, &be64, sizeof(be64)); - off += sizeof(be64); - - be32 = cpu_to_be32(base_fee); - memcpy(fake_upd + off, &be32, sizeof(be32)); - off += sizeof(be32); - - be32 = cpu_to_be32(proportional_fee); - memcpy(fake_upd + off, &be32, sizeof(be32)); - off += sizeof(be32); - - be64 = cpu_to_be64(htlc_max.millisatoshis); /* Raw: endian */ - memcpy(fake_upd + off, &be64, sizeof(be64)); - off += sizeof(be64); - - assert(off == tal_bytelen(fake_upd)); - update_channel(map, insert_local_goss(map, take(fake_upd))); -} - void gossmap_node_get_id(const struct gossmap *map, const struct gossmap_node *node, struct node_id *id) diff --git a/common/gossmap.h b/common/gossmap.h index 494440f5e..401dbf7ec 100644 --- a/common/gossmap.h +++ b/common/gossmap.h @@ -43,20 +43,24 @@ struct gossmap *gossmap_load(const tal_t *ctx, const char *filename); * was updated. Note: this can scramble node and chan indexes! */ bool gossmap_refresh(struct gossmap *map); -/* Insert a local-only channel (not in the mmap'ed gossmap, cleared on - * refresh). */ -void gossmap_local_addchan(struct gossmap *map, +/* Local modifications. */ +struct gossmap_localmods *gossmap_localmods_new(const tal_t *ctx); + +/* Create a local-only channel; if this conflicts with a real channel when added, + * that will be used instead. + * Returns false (and does nothing) if scid was already in localmods. + */ +bool gossmap_local_addchan(struct gossmap_localmods *localmods, const struct node_id *n1, const struct node_id *n2, const struct short_channel_id *scid, const u8 *features) NON_NULL_ARGS(1,2,3,4); -/* Insert a local-only channel_update (not in the mmap'ed gossmap, - * cleared on refresh). Must exist, and usually should be a local - * channel (otherwise channel will be disabled on - * gossmap_local_addchan!) */ -void gossmap_local_updatechan(struct gossmap *map, +/* Create a local-only channel_update: can apply to lcoal-only or + * normal channels. Returns false if amounts don't fit in our + * internal representation (implies channel unusable anyway). */ +bool gossmap_local_updatechan(struct gossmap_localmods *localmods, const struct short_channel_id *scid, struct amount_msat htlc_min, struct amount_msat htlc_max, @@ -67,9 +71,13 @@ void gossmap_local_updatechan(struct gossmap *map, int dir) NO_NULL_ARGS; -/* Remove all local-only changes. Must be done before calling - * gossmap_refresh! */ -void gossmap_local_cleanup(struct gossmap *map); +/* Apply localmods to this map */ +void gossmap_apply_localmods(struct gossmap *map, + struct gossmap_localmods *localmods); + +/* Remove localmods from this map */ +void gossmap_remove_localmods(struct gossmap *map, + const struct gossmap_localmods *localmods); /* Each channel has a unique (low) index. */ u32 gossmap_node_idx(const struct gossmap *map, const struct gossmap_node *node); diff --git a/common/test/run-gossmap_local.c b/common/test/run-gossmap_local.c index dd3222cae..6abcafa1e 100644 --- a/common/test/run-gossmap_local.c +++ b/common/test/run-gossmap_local.c @@ -334,6 +334,7 @@ int main(int argc, char *argv[]) struct node_id l1, l2, l3, l4; struct short_channel_id scid23, scid12, scid_local; struct gossmap_chan *chan; + struct gossmap_localmods *mods; common_setup(argv[0]); @@ -358,11 +359,14 @@ int main(int argc, char *argv[]) assert(gossmap_find_chan(map, &scid12)); /* Now, let's add a new channel l1 -> l4. */ + mods = gossmap_localmods_new(tmpctx); assert(node_id_from_hexstr("0382ce59ebf18be7d84677c2e35f23294b9992ceca95491fcf8a56c6cb2d9de199", 66, &l4)); - assert(short_channel_id_from_str("111x1x1", 7, &scid_local)); - gossmap_local_addchan(map, &l1, &l4, &scid_local, NULL); + assert(gossmap_local_addchan(mods, &l1, &l4, &scid_local, NULL)); + + /* Apply changes, check they work. */ + gossmap_apply_localmods(map, mods); assert(gossmap_find_node(map, &l4)); chan = gossmap_find_chan(map, &scid_local); @@ -370,11 +374,27 @@ int main(int argc, char *argv[]) assert(!gossmap_chan_set(chan, 0)); assert(!gossmap_chan_set(chan, 1)); - /* Now update it. */ - gossmap_local_updatechan(map, &scid_local, + /* Remove, no longer can find. */ + gossmap_remove_localmods(map, mods); + + assert(!gossmap_find_chan(map, &scid_local)); + assert(!gossmap_find_node(map, &l4)); + + /* Now update it both local, and an existing one. */ + gossmap_local_updatechan(mods, &scid_local, AMOUNT_MSAT(1), AMOUNT_MSAT(100000), 2, 3, 4, true, 0); + + /* Adding an existing channel is a noop. */ + assert(gossmap_local_addchan(mods, &l2, &l3, &scid23, NULL)); + + gossmap_local_updatechan(mods, &scid23, + AMOUNT_MSAT(99), + AMOUNT_MSAT(100), + 101, 102, 103, true, 0); + + gossmap_apply_localmods(map, mods); chan = gossmap_find_chan(map, &scid_local); assert(gossmap_chan_set(chan, 0)); assert(!gossmap_chan_set(chan, 1)); @@ -386,8 +406,17 @@ int main(int argc, char *argv[]) assert(chan->half[0].proportional_fee == 3); assert(chan->half[0].delay == 4); + chan = gossmap_find_chan(map, &scid23); + assert(chan->half[0].enabled); + assert(chan->half[0].htlc_min == u64_to_fp16(99, false)); + assert(chan->half[0].htlc_max == u64_to_fp16(100, true)); + assert(chan->half[0].base_fee == 101); + assert(chan->half[0].proportional_fee == 102); + assert(chan->half[0].delay == 103); + /* Cleanup leaves everything previous intact */ - gossmap_local_cleanup(map); + gossmap_remove_localmods(map, mods); + assert(!gossmap_find_node(map, &l4)); assert(!gossmap_find_chan(map, &scid_local)); assert(gossmap_find_node(map, &l1)); @@ -396,6 +425,14 @@ int main(int argc, char *argv[]) assert(gossmap_find_chan(map, &scid23)); assert(gossmap_find_chan(map, &scid12)); + chan = gossmap_find_chan(map, &scid23); + assert(chan->half[0].enabled); + assert(chan->half[0].htlc_min == u64_to_fp16(0, false)); + assert(chan->half[0].htlc_max == u64_to_fp16(990380000, true)); + assert(chan->half[0].base_fee == 20); + assert(chan->half[0].proportional_fee == 1000); + assert(chan->half[0].delay == 6); + /* Now we can refresh. */ assert(write(fd, "", 1) == 1); gossmap_refresh(map);