Browse Source

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 <rusty@rustcorp.com.au>
master
Rusty Russell 4 years ago
parent
commit
c5bd518d2f
  1. 377
      common/gossmap.c
  2. 30
      common/gossmap.h
  3. 47
      common/test/run-gossmap_local.c

377
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,124 +663,59 @@ static void destroy_map(struct gossmap *map)
free(map->node_arr[i].chan_idxs);
}
void gossmap_local_cleanup(struct gossmap *map)
{
size_t off, msglen;
/* 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;
/* Local cursor */
u8 *p = map->local + off;
memcpy(&msglen, p, sizeof(msglen));
p += sizeof(msglen);
memcpy(&type, p, sizeof(type));
p += sizeof(type);
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);
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;
chan = gossmap_find_chan(map, &scid);
/* May have removed it when we processed
* announce above */
if (chan)
chan->half[channel_flags & 1].enabled = false;
}
}
/* 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];
};
/* Now zero out map */
tal_resize(&map->local, 0);
}
struct gossmap_localmods {
struct localmod *mods;
/* This is the local array to be used by the gossmap */
u8 *local;
};
bool gossmap_refresh(struct gossmap *map)
struct gossmap_localmods *gossmap_localmods_new(const tal_t *ctx)
{
struct stat st;
/* You must clean local updates before this. */
assert(tal_bytelen(map->local) == 0);
/* If file has changed, move to it. */
if (stat(map->fname, &st) != 0)
err(1, "statting %s", map->fname);
if (map->st_ino != st.st_ino || map->st_dev != st.st_dev) {
destroy_map(map);
tal_free(map->chan_arr);
tal_free(map->node_arr);
if (!load_gossip_store(map))
err(1, "reloading %s", map->fname);
return true;
}
struct gossmap_localmods *localmods;
/* If file has gotten larger, try rereading */
if (st.st_size == map->map_size)
return false;
localmods = tal(ctx, struct gossmap_localmods);
localmods->mods = tal_arr(localmods, struct localmod, 0);
localmods->local = tal_arr(localmods, u8, 0);
if (map->mmap)
munmap(map->mmap, map->map_size);
map->map_size = st.st_size;
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);
return localmods;
}
struct gossmap *gossmap_load(const tal_t *ctx, const char *filename)
/* 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)
{
map = tal(ctx, struct gossmap);
map->fname = tal_strdup(map, filename);
if (load_gossip_store(map))
tal_add_destructor(map, destroy_map);
else
map = tal_free(map);
return map;
size_t oldlen = tal_bytelen(localmods->local);
tal_resize(&localmods->local, oldlen + msglen);
return oldlen;
}
/* 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)
static struct localmod *find_localmod(struct gossmap_localmods *localmods,
const struct short_channel_id *scid)
{
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);
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;
}
void gossmap_local_addchan(struct gossmap *map,
bool gossmap_local_addchan(struct gossmap_localmods *localmods,
const struct node_id *n1,
const struct node_id *n2,
const struct short_channel_id *scid,
@ -787,14 +724,24 @@ void gossmap_local_addchan(struct gossmap *map,
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;
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(fake_ann + off, &be16, sizeof(be16));
memcpy(localmods->local + off, &be16, sizeof(be16));
off += sizeof(be16);
/* Skip sigs */
@ -802,9 +749,9 @@ void gossmap_local_addchan(struct gossmap *map,
/* Set length and features */
be16 = cpu_to_be16(tal_bytelen(features));
memcpy(fake_ann + off, &be16, sizeof(be16));
memcpy(localmods->local + off, &be16, sizeof(be16));
off += sizeof(be16);
memcpy(fake_ann + off, features, tal_bytelen(features));
memcpy(localmods->local + off, features, tal_bytelen(features));
off += tal_bytelen(features);
/* Skip chain_hash */
@ -812,23 +759,23 @@ void gossmap_local_addchan(struct gossmap *map,
/* Set scid */
be64 = be64_to_cpu(scid->u64);
memcpy(fake_ann + off, &be64, sizeof(be64));
memcpy(localmods->local + off, &be64, sizeof(be64));
off += sizeof(be64);
/* set node_ids */
memcpy(fake_ann + off, n1->k, sizeof(n1->k));
memcpy(localmods->local + off, n1->k, sizeof(n1->k));
off += sizeof(n1->k);
memcpy(fake_ann + off, n2->k, sizeof(n2->k));
memcpy(localmods->local + off, n2->k, sizeof(n2->k));
off += sizeof(n2->k);
assert(off == tal_bytelen(fake_ann));
assert(off == tal_bytelen(localmods->local));
add_channel(map, insert_local_goss(map, take(fake_ann)));
}
tal_arr_expand(&localmods->mods, mod);
return true;
};
/* Insert a local-only channel_update (not in the mmap'ed gossmap,
* cleared on refresh). Must exist! */
void gossmap_local_updatechan(struct gossmap *map,
/* 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,
@ -838,63 +785,149 @@ void gossmap_local_updatechan(struct gossmap *map,
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);
struct localmod *mod;
off = 0;
mod = find_localmod(localmods, scid);
if (!mod) {
/* Create new reference to (presumably) existing channel. */
size_t nmods = tal_count(localmods->mods);
/* Set type to be kosher. */
be16 = CPU_TO_BE16(WIRE_CHANNEL_UPDATE);
memcpy(fake_upd + off, &be16, sizeof(be16));
off += sizeof(be16);
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;
}
/* Skip signature and chainhash */
off += 64 + 32;
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;
/* Set scid */
be64 = be64_to_cpu(scid->u64);
memcpy(fake_upd + off, &be64, sizeof(be64));
off += sizeof(be64);
/* 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;
}
/* Skip timestamp. */
off += 4;
/* Apply localmods to this map */
void gossmap_apply_localmods(struct gossmap *map,
struct gossmap_localmods *localmods)
{
size_t n = tal_count(localmods->mods);
/* We support htlc_maximum_msat. */
fake_upd[off] = 1;
off += 1;
assert(!map->local);
map->local = localmods->local;
/* Bottom bit is direction, second is disable. */
fake_upd[off] = dir;
if (!enabled)
fake_upd[off] |= 2;
off += 1;
for (size_t i = 0; i < n; i++) {
struct localmod *mod = &localmods->mods[i];
struct gossmap_chan *chan;
be16 = cpu_to_be16(delay);
memcpy(fake_upd + off, &be16, sizeof(be16));
off += sizeof(be16);
/* 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;
be64 = cpu_to_be64(htlc_min.millisatoshis); /* Raw: endian */
memcpy(fake_upd + off, &be64, sizeof(be64));
off += sizeof(be64);
/* Create new channel, pointing into local. */
chan = add_channel(map, map->map_size + mod->local_off);
}
be32 = cpu_to_be32(base_fee);
memcpy(fake_upd + off, &be32, sizeof(be32));
off += sizeof(be32);
/* 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;
}
}
}
be32 = cpu_to_be32(proportional_fee);
memcpy(fake_upd + off, &be32, sizeof(be32));
off += sizeof(be32);
void gossmap_remove_localmods(struct gossmap *map,
const struct gossmap_localmods *localmods)
{
size_t n = tal_count(localmods->mods);
be64 = cpu_to_be64(htlc_max.millisatoshis); /* Raw: endian */
memcpy(fake_upd + off, &be64, sizeof(be64));
off += sizeof(be64);
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;
}
assert(off == tal_bytelen(fake_upd));
update_channel(map, insert_local_goss(map, take(fake_upd)));
bool gossmap_refresh(struct gossmap *map)
{
struct stat st;
/* You must remoe local updates before this. */
assert(!map->local);
/* If file has changed, move to it. */
if (stat(map->fname, &st) != 0)
err(1, "statting %s", map->fname);
if (map->st_ino != st.st_ino || map->st_dev != st.st_dev) {
destroy_map(map);
tal_free(map->chan_arr);
tal_free(map->node_arr);
if (!load_gossip_store(map))
err(1, "reloading %s", map->fname);
return true;
}
/* If file has gotten larger, try rereading */
if (st.st_size == map->map_size)
return false;
if (map->mmap)
munmap(map->mmap, map->map_size);
map->map_size = st.st_size;
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);
}
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))
tal_add_destructor(map, destroy_map);
else
map = tal_free(map);
return map;
}
void gossmap_node_get_id(const struct gossmap *map,

30
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);

47
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);

Loading…
Cancel
Save