core-lightning/common/gossmap.c
Rusty Russell 2b7915359c gossmap: handle case where private channel turns into public.
Usually we won't see this, since private is deleted.  But we could
have already read the private channel before that.  Handle it properly.

(Tested by removing the gossip_store deletion code and making sure
this worked).

We have to fix up the test, which announces a channel twice!

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
2022-06-27 17:21:35 +09:30

1281 lines
34 KiB
C

#include "config.h"
#include <assert.h>
#include <ccan/crypto/siphash24/siphash24.h>
#include <ccan/err/err.h>
#include <ccan/htable/htable_type.h>
#include <ccan/ptrint/ptrint.h>
#include <ccan/tal/str/str.h>
#include <common/features.h>
#include <common/gossip_store.h>
#include <common/gossmap.h>
#include <common/pseudorand.h>
#include <common/type_to_string.h>
#include <errno.h>
#include <fcntl.h>
#include <gossipd/gossip_store_wiregen.h>
#include <sys/mman.h>
#include <unistd.h>
#include <wire/peer_wire.h>
/* We need this global to decode indexes for hash functions */
static struct gossmap *map;
/* This makes an htable of indices into our array. */
static struct short_channel_id chanidx_id(const ptrint_t *pidx);
static bool chanidx_eq_id(const ptrint_t *pidx,
struct short_channel_id scid)
{
struct short_channel_id pidxid = chanidx_id(pidx);
return short_channel_id_eq(&pidxid, &scid);
}
static size_t scid_hash(const struct short_channel_id scid)
{
return siphash24(siphash_seed(), &scid, sizeof(scid));
}
HTABLE_DEFINE_TYPE(ptrint_t, chanidx_id, scid_hash, chanidx_eq_id,
chanidx_htable);
static struct node_id nodeidx_id(const ptrint_t *pidx);
static bool nodeidx_eq_id(const ptrint_t *pidx, const struct node_id id)
{
struct node_id pidxid = nodeidx_id(pidx);
return node_id_eq(&pidxid, &id);
}
static size_t nodeid_hash(const struct node_id id)
{
return siphash24(siphash_seed(), &id, PUBKEY_CMPR_LEN);
}
HTABLE_DEFINE_TYPE(ptrint_t, nodeidx_id, nodeid_hash, nodeidx_eq_id,
nodeidx_htable);
struct gossmap {
/* The file descriptor and filename to monitor */
int fd;
const char *fname;
/* The memory map of the file: u8 for arithmetic portability */
u8 *mmap;
/* map_end is where we read to so far, map_size is total size */
size_t map_end, map_size;
/* Map of node id -> node */
struct nodeidx_htable nodes;
/* Map of short_channel_id id -> channel */
struct chanidx_htable channels;
/* Array of nodes, so we can use simple index. */
struct gossmap_node *node_arr;
/* Array of chans, so we can use simple index */
struct gossmap_chan *chan_arr;
/* Linked list of freed ones, if any. */
u32 freed_nodes, freed_chans;
/* local messages, if any. */
const u8 *local;
};
/* Accessors for the gossmap */
static void map_copy(const struct gossmap *map, size_t offset,
void *dst, size_t len)
{
if (offset >= map->map_size) {
size_t localoff = offset - map->map_size;
assert(localoff + len <= tal_bytelen(map->local));
memcpy(dst, map->local + localoff, len);
} else {
assert(offset + len <= map->map_size);
if (map->mmap)
memcpy(dst, map->mmap + offset, len);
else {
/* Yeah, we'll crash on I/O errors. */
if (pread(map->fd, dst, len, offset) != len)
abort();
}
}
}
static u8 map_u8(const struct gossmap *map, size_t offset)
{
u8 u8;
map_copy(map, offset, &u8, sizeof(u8));
return u8;
}
static u16 map_be16(const struct gossmap *map, size_t offset)
{
be16 be16;
map_copy(map, offset, &be16, sizeof(be16));
return be16_to_cpu(be16);
}
static u32 map_be32(const struct gossmap *map, size_t offset)
{
be32 be32;
map_copy(map, offset, &be32, sizeof(be32));
return be32_to_cpu(be32);
}
static u64 map_be64(const struct gossmap *map, size_t offset)
{
be64 be64;
map_copy(map, offset, &be64, sizeof(be64));
return be64_to_cpu(be64);
}
static void map_nodeid(const struct gossmap *map, size_t offset,
struct node_id *id)
{
map_copy(map, offset, id, sizeof(*id));
}
/* Returns optional or compulsory feature if set, otherwise -1 */
static int map_feature_test(const struct gossmap *map,
int compulsory_bit,
size_t offset, size_t len)
{
size_t bytenum = compulsory_bit / 8;
u8 bits;
assert(COMPULSORY_FEATURE(compulsory_bit) == compulsory_bit);
if (bytenum >= len)
return -1;
/* Note reversed! */
bits = map_u8(map, offset + len - 1 - bytenum);
if (bits & (1 << (compulsory_bit % 8)))
return compulsory_bit;
if (bits & (1 << (OPTIONAL_FEATURE(compulsory_bit) % 8)))
return OPTIONAL_FEATURE(compulsory_bit);
return -1;
}
/* These values can change across calls to gossmap_check. */
u32 gossmap_max_node_idx(const struct gossmap *map)
{
return tal_count(map->node_arr);
}
u32 gossmap_max_chan_idx(const struct gossmap *map)
{
return tal_count(map->chan_arr);
}
/* Each channel has a unique (low) index. */
u32 gossmap_node_idx(const struct gossmap *map, const struct gossmap_node *node)
{
assert(node - map->node_arr < tal_count(map->node_arr));
return node - map->node_arr;
}
u32 gossmap_chan_idx(const struct gossmap *map, const struct gossmap_chan *chan)
{
assert(chan - map->chan_arr < tal_count(map->chan_arr));
return chan - map->chan_arr;
}
/* htable can't handle NULL values, so we add 1 */
static struct gossmap_chan *ptrint2chan(const ptrint_t *pidx)
{
return map->chan_arr + ptr2int(pidx) - 1;
}
static ptrint_t *chan2ptrint(const struct gossmap_chan *chan)
{
return int2ptr(chan - map->chan_arr + 1);
}
static struct gossmap_node *ptrint2node(const ptrint_t *pidx)
{
return map->node_arr + ptr2int(pidx) - 1;
}
static ptrint_t *node2ptrint(const struct gossmap_node *node)
{
return int2ptr(node - map->node_arr + 1);
}
static struct short_channel_id chanidx_id(const ptrint_t *pidx)
{
return gossmap_chan_scid(map, ptrint2chan(pidx));
}
static struct node_id nodeidx_id(const ptrint_t *pidx)
{
struct node_id id;
gossmap_node_get_id(map, ptrint2node(pidx), &id);
return id;
}
struct gossmap_node *gossmap_find_node(const struct gossmap *map,
const struct node_id *id)
{
ptrint_t *pi = nodeidx_htable_get(&map->nodes, *id);
if (pi)
return ptrint2node(pi);
return NULL;
}
struct gossmap_chan *gossmap_find_chan(const struct gossmap *map,
const struct short_channel_id *scid)
{
ptrint_t *pi = chanidx_htable_get(&map->channels, *scid);
if (pi)
return ptrint2chan(pi);
return NULL;
}
static u32 init_node_arr(struct gossmap_node *node_arr, size_t start)
{
size_t i;
for (i = start; i < tal_count(node_arr) - 1; i++) {
node_arr[i].nann_off = i + 1;
node_arr[i].chan_idxs = NULL;
}
node_arr[i].nann_off = UINT_MAX;
node_arr[i].chan_idxs = NULL;
return start;
}
/* Freelist links through node_off of unused entries. */
static struct gossmap_node *next_free_node(struct gossmap *map)
{
size_t f;
if (map->freed_nodes == UINT_MAX) {
/* Double in size, add second half to free list */
size_t n = tal_count(map->node_arr);
tal_resize(&map->node_arr, n * 2);
map->freed_nodes = init_node_arr(map->node_arr, n);
}
f = map->freed_nodes;
map->freed_nodes = map->node_arr[f].nann_off;
return &map->node_arr[f];
}
static u32 new_node(struct gossmap *map)
{
struct gossmap_node *node = next_free_node(map);
assert(node->chan_idxs == NULL);
node->nann_off = 0;
node->num_chans = 0;
return gossmap_node_idx(map, node);
}
static void remove_node(struct gossmap *map, struct gossmap_node *node)
{
u32 nodeidx = gossmap_node_idx(map, node);
if (!nodeidx_htable_del(&map->nodes, node2ptrint(node)))
abort();
node->nann_off = map->freed_nodes;
free(node->chan_idxs);
node->chan_idxs = NULL;
node->num_chans = 0;
map->freed_nodes = nodeidx;
}
static void node_add_channel(struct gossmap_node *node, u32 chanidx)
{
node->num_chans++;
node->chan_idxs = realloc(node->chan_idxs,
node->num_chans * sizeof(*node->chan_idxs));
node->chan_idxs[node->num_chans-1] = chanidx;
}
static u32 init_chan_arr(struct gossmap_chan *chan_arr, size_t start)
{
size_t i;
for (i = start; i < tal_count(chan_arr) - 1; i++) {
chan_arr[i].cann_off = i + 1;
chan_arr[i].plus_scid_off = 0;
}
chan_arr[i].cann_off = UINT_MAX;
chan_arr[i].plus_scid_off = 0;
return start;
}
/* Freelist links through scid of unused entries. */
static struct gossmap_chan *next_free_chan(struct gossmap *map)
{
size_t f;
if (map->freed_chans == UINT_MAX) {
/* Double in size, add second half to free list */
size_t n = tal_count(map->chan_arr);
tal_resize(&map->chan_arr, n * 2);
map->freed_chans = init_chan_arr(map->chan_arr, n);
}
f = map->freed_chans;
map->freed_chans = map->chan_arr[f].cann_off;
return &map->chan_arr[f];
}
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));
chan->half[0].nodeidx = n1idx;
chan->half[1].nodeidx = n2idx;
node_add_channel(map->node_arr + n1idx, gossmap_chan_idx(map, chan));
node_add_channel(map->node_arr + n2idx, gossmap_chan_idx(map, chan));
chanidx_htable_add(&map->channels, chan2ptrint(chan));
return chan;
}
static void remove_chan_from_node(struct gossmap *map,
struct gossmap_node *node,
u32 chanidx)
{
size_t i;
if (node->num_chans == 1) {
remove_node(map, node);
return;
}
for (i = 0; node->chan_idxs[i] != chanidx; i++)
assert(i < node->num_chans);
memmove(node->chan_idxs + i,
node->chan_idxs + i + 1,
sizeof(node->chan_idxs[0]) * (node->num_chans - i - 1));
node->num_chans--;
}
void gossmap_remove_chan(struct gossmap *map, struct gossmap_chan *chan)
{
u32 chanidx = gossmap_chan_idx(map, chan);
if (!chanidx_htable_del(&map->channels, chan2ptrint(chan)))
abort();
remove_chan_from_node(map, gossmap_nth_node(map, chan, 0), chanidx);
remove_chan_from_node(map, gossmap_nth_node(map, chan, 1), chanidx);
chan->cann_off = map->freed_chans;
chan->plus_scid_off = 0;
map->freed_chans = chanidx;
}
void gossmap_remove_node(struct gossmap *map, struct gossmap_node *node)
{
while (node->num_chans != 0)
gossmap_remove_chan(map, gossmap_nth_chan(map, node, 0, NULL));
}
/* 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`]
*/
static struct gossmap_chan *add_channel(struct gossmap *map,
size_t cannounce_off,
bool private)
{
/* Note that first two bytes are message type */
const size_t feature_len_off = 2 + (64 + 64 + 64 + 64);
size_t feature_len;
size_t plus_scid_off;
struct short_channel_id scid;
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);
plus_scid_off = feature_len_off + 2 + feature_len + 32;
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 */
scid.u64 = map_be64(map, cannounce_off + plus_scid_off);
chan = gossmap_find_chan(map, &scid);
if (chan) {
assert(chan->private);
assert(!private);
gossmap_remove_chan(map, chan);
}
/* We carefully map pointers to indexes, since new_node can move them! */
n[0] = gossmap_find_node(map, &node_id[0]);
if (n[0])
nidx[0] = gossmap_node_idx(map, n[0]);
else
nidx[0] = new_node(map);
n[1] = gossmap_find_node(map, &node_id[1]);
if (n[1])
nidx[1] = gossmap_node_idx(map, n[1]);
else
nidx[1] = new_node(map);
chan = new_channel(map, cannounce_off, plus_scid_off, private,
nidx[0], nidx[1]);
/* Now we have a channel, we can add nodes to htable */
if (!n[0])
nodeidx_htable_add(&map->nodes,
node2ptrint(map->node_arr + nidx[0]));
if (!n[1])
nodeidx_htable_add(&map->nodes,
node2ptrint(map->node_arr + nidx[1]));
return chan;
}
/* 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`] (option_channel_htlc_max)
*/
static bool 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);
const size_t message_flags_off = scid_off + 8 + 4;
const size_t channel_flags_off = message_flags_off + 1;
const size_t cltv_expiry_delta_off = channel_flags_off + 1;
const size_t htlc_minimum_off = cltv_expiry_delta_off + 2;
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 gossmap_chan *chan;
struct half_chan hc;
u8 chanflags;
bool dumb_values;
scid.u64 = map_be64(map, scid_off);
chan = gossmap_find_chan(map, &scid);
/* This can happen if channel gets deleted! */
if (!chan)
return false;
/* We round this *down*, since too-low min is more conservative */
hc.htlc_min = u64_to_fp16(map_be64(map, htlc_minimum_off), false);
/* I checked my node: 60189 of 62358 channel_update have
* htlc_maximum_msat, so we don't bother setting the rest to the
* channel size (which we don't even read from the gossip_store, let
* alone give up precious bytes to remember) */
if (map_u8(map, message_flags_off) & 1)
hc.htlc_max
= u64_to_fp16(map_be64(map, htlc_maximum_off), true);
else
hc.htlc_max = 0xFFFF;
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);
/* 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;
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)
{
struct short_channel_id scid;
struct gossmap_chan *chan;
/* They can delete things we don't know about, since they also
* get their length marked with the deleted bit */
/* Note that first two bytes are message type */
scid.u64 = map_be64(map, del_off + 2);
chan = gossmap_find_chan(map, &scid);
if (!chan)
return;
gossmap_remove_chan(map, chan);
}
struct short_channel_id gossmap_chan_scid(const struct gossmap *map,
const struct gossmap_chan *c)
{
struct short_channel_id scid;
scid.u64 = map_be64(map, c->cann_off + c->plus_scid_off);
return scid;
}
/* 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`]
*/
static void node_announcement(struct gossmap *map, size_t nann_off)
{
const size_t feature_len_off = 2 + 64;
size_t feature_len;
struct gossmap_node *n;
struct node_id id;
feature_len = map_be16(map, nann_off + feature_len_off);
map_nodeid(map, nann_off + feature_len_off + 2 + feature_len + 4, &id);
n = gossmap_find_node(map, &id);
n->nann_off = nann_off;
}
static void reopen_store(struct gossmap *map, size_t ended_off)
{
int fd = open(map->fname, O_RDONLY);
if (fd < 0)
err(1, "Failed to reopen %s", map->fname);
/* This tells us the equivalent offset in new map */
map->map_end = map_be64(map, ended_off + 2);
close(map->fd);
map->fd = fd;
gossmap_refresh(map, NULL);
}
static bool map_catchup(struct gossmap *map, size_t *num_rejected)
{
size_t reclen;
bool changed = false;
size_t num_bad = 0;
for (; map->map_end + sizeof(struct gossip_hdr) < map->map_size;
map->map_end += reclen) {
struct gossip_hdr ghdr;
size_t off;
u16 type;
map_copy(map, map->map_end, &ghdr, sizeof(ghdr));
reclen = (be32_to_cpu(ghdr.len) & GOSSIP_STORE_LEN_MASK)
+ sizeof(ghdr);
if (be32_to_cpu(ghdr.len) & GOSSIP_STORE_LEN_DELETED_BIT)
continue;
/* Partial write, this can happen. */
if (map->map_end + reclen > map->map_size)
break;
off = map->map_end + sizeof(ghdr);
type = map_be16(map, off);
if (type == WIRE_CHANNEL_ANNOUNCEMENT)
add_channel(map, off, false);
else if (type == WIRE_GOSSIP_STORE_PRIVATE_CHANNEL)
add_channel(map, off + 2 + 8 + 2, true);
else if (type == WIRE_CHANNEL_UPDATE)
num_bad += !update_channel(map, off);
else if (type == WIRE_GOSSIP_STORE_PRIVATE_UPDATE)
num_bad += !update_channel(map, off + 2 + 2);
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
continue;
changed = true;
}
if (num_rejected)
*num_rejected = num_bad;
return changed;
}
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 */
map->mmap = mmap(NULL, map->map_size, PROT_READ, MAP_SHARED, map->fd, 0);
if (map->mmap == MAP_FAILED)
map->mmap = NULL;
if (map_u8(map, 0) != GOSSIP_STORE_VERSION) {
close(map->fd);
if (map->mmap)
munmap(map->mmap, map->map_size);
errno = EINVAL;
return false;
}
/* Since channel_announcement is ~430 bytes, and channel_update is 136,
* node_announcement is 144, and current topology has 35000 channels
* and 10000 nodes, let's assume each channel gets about 750 bytes.
*
* We halve this, since often some records are deleted. */
chanidx_htable_init_sized(&map->channels, map->map_size / 750 / 2);
nodeidx_htable_init_sized(&map->nodes, map->map_size / 2500 / 2);
map->chan_arr = tal_arr(map, struct gossmap_chan, map->map_size / 750 / 2 + 1);
map->freed_chans = init_chan_arr(map->chan_arr, 0);
map->node_arr = tal_arr(map, struct gossmap_node, map->map_size / 2500 / 2 + 1);
map->freed_nodes = init_node_arr(map->node_arr, 0);
map->map_end = 1;
map_catchup(map, num_rejected);
return true;
}
static void destroy_map(struct gossmap *map)
{
if (map->mmap)
munmap(map->mmap, map->map_size);
chanidx_htable_clear(&map->channels);
nodeidx_htable_clear(&map->nodes);
for (size_t i = 0; i < tal_count(map->node_arr); i++)
free(map->node_arr[i].chan_idxs);
}
/* 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];
/* Original update offsets */
u32 orig_cupdate_off[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)
{
struct gossmap_localmods *localmods;
localmods = tal(ctx, struct gossmap_localmods);
localmods->mods = tal_arr(localmods, struct localmod, 0);
localmods->local = tal_arr(localmods, u8, 0);
return localmods;
}
/* 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);
tal_resize(&localmods->local, oldlen + msglen);
return oldlen;
}
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;
}
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,
true);
}
/* 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];
mod->orig_cupdate_off[h] = chan->cupdate_off[h];
chan->half[h] = mod->hc[h];
chan->half[h].nodeidx = mod->orig[h].nodeidx;
chan->cupdate_off[h] = 0xFFFFFFFF;
}
}
}
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;
chan->cupdate_off[h] = mod->orig_cupdate_off[h];
}
}
}
map->local = NULL;
}
bool gossmap_refresh(struct gossmap *map, size_t *num_rejected)
{
off_t len;
/* You must remove local updates before this. */
assert(!map->local);
/* If file has gotten larger, try rereading */
len = lseek(map->fd, 0, SEEK_END);
if (len == map->map_size)
return false;
if (map->mmap)
munmap(map->mmap, map->map_size);
map->map_size = len;
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);
}
struct gossmap *gossmap_load(const tal_t *ctx, const char *filename,
size_t *num_channel_updates_rejected)
{
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);
return map;
}
void gossmap_node_get_id(const struct gossmap *map,
const struct gossmap_node *node,
struct node_id *id)
{
/* We extract nodeid from first channel. */
int dir;
struct gossmap_chan *c = gossmap_nth_chan(map, node, 0, &dir);
map_nodeid(map, c->cann_off + c->plus_scid_off
+ 8 + PUBKEY_CMPR_LEN*dir, id);
}
bool gossmap_chan_get_capacity(const struct gossmap *map,
const struct gossmap_chan *c,
struct amount_sat *amount)
{
struct gossip_hdr ghdr;
size_t off;
u16 type;
/* 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));
off += sizeof(ghdr) + (be32_to_cpu(ghdr.len) & GOSSIP_STORE_LEN_MASK);
/* Partial write, this can happen. */
if (off + sizeof(ghdr) + 2 > map->map_size)
return false;
/* Get type of next field. */
type = map_be16(map, off + sizeof(ghdr));
if (type != WIRE_GOSSIP_STORE_CHANNEL_AMOUNT)
return false;
*amount = amount_sat(map_be64(map, off + sizeof(ghdr) + sizeof(be16)));
return true;
}
struct gossmap_chan *gossmap_nth_chan(const struct gossmap *map,
const struct gossmap_node *node,
u32 n,
int *which_half)
{
struct gossmap_chan *chan;
assert(n < node->num_chans);
assert(node->chan_idxs[n] < tal_count(map->chan_arr));
chan = map->chan_arr + node->chan_idxs[n];
if (which_half) {
if (chan->half[0].nodeidx == gossmap_node_idx(map, node))
*which_half = 0;
else {
assert(chan->half[1].nodeidx == gossmap_node_idx(map, node));
*which_half = 1;
}
}
return chan;
}
struct gossmap_node *gossmap_nth_node(const struct gossmap *map,
const struct gossmap_chan *chan,
int n)
{
assert(n == 0 || n == 1);
return map->node_arr + chan->half[n].nodeidx;
}
size_t gossmap_num_nodes(const struct gossmap *map)
{
return nodeidx_htable_count(&map->nodes);
}
static struct gossmap_node *node_iter(const struct gossmap *map, size_t start)
{
for (size_t i = start; i < tal_count(map->node_arr); i++) {
if (map->node_arr[i].chan_idxs != NULL)
return &map->node_arr[i];
}
return NULL;
}
struct gossmap_node *gossmap_first_node(const struct gossmap *map)
{
return node_iter(map, 0);
}
struct gossmap_node *gossmap_next_node(const struct gossmap *map,
const struct gossmap_node *prev)
{
return node_iter(map, prev - map->node_arr + 1);
}
size_t gossmap_num_chans(const struct gossmap *map)
{
return chanidx_htable_count(&map->channels);
}
static struct gossmap_chan *chan_iter(const struct gossmap *map, size_t start)
{
for (size_t i = start; i < tal_count(map->chan_arr); i++) {
if (map->chan_arr[i].plus_scid_off != 0)
return &map->chan_arr[i];
}
return NULL;
}
struct gossmap_chan *gossmap_first_chan(const struct gossmap *map)
{
return chan_iter(map, 0);
}
struct gossmap_chan *gossmap_next_chan(const struct gossmap *map,
struct gossmap_chan *prev)
{
return chan_iter(map, prev - map->chan_arr + 1);
}
bool gossmap_chan_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;
if (amount_msat_greater_fp16(amount, chan->half[direction].htlc_max))
return false;
return true;
}
/* Get the announcement msg which created this chan */
u8 *gossmap_chan_get_announce(const tal_t *ctx,
const struct gossmap *map,
const struct gossmap_chan *c)
{
u32 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_be32(map, c->cann_off - pre_off) & GOSSIP_STORE_LEN_MASK);
msg = tal_arr(ctx, u8, len);
map_copy(map, c->cann_off, msg, len);
return msg;
}
/* Get the announcement msg (if any) for this node. */
u8 *gossmap_node_get_announce(const tal_t *ctx,
const struct gossmap *map,
const struct gossmap_node *n)
{
u32 len;
u8 *msg;
if (n->nann_off == 0)
return NULL;
len = (map_be32(map, n->nann_off - sizeof(struct gossip_hdr))
& GOSSIP_STORE_LEN_MASK);
msg = tal_arr(ctx, u8, len);
map_copy(map, n->nann_off, msg, len);
return msg;
}
/* 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`]
*/
int gossmap_chan_get_feature(const struct gossmap *map,
const struct gossmap_chan *c,
int fbit)
{
/* Note that first two bytes are message type */
const size_t feature_len_off = 2 + (64 + 64 + 64 + 64);
size_t feature_len;
feature_len = map_be16(map, c->cann_off + feature_len_off);
return map_feature_test(map, COMPULSORY_FEATURE(fbit),
c->cann_off + feature_len_off + 2, feature_len);
}
u8 *gossmap_chan_get_features(const tal_t *ctx,
const struct gossmap *map,
const struct gossmap_chan *c)
{
u8 *ret;
/* Note that first two bytes are message type */
const size_t feature_len_off = 2 + (64 + 64 + 64 + 64);
size_t feature_len;
feature_len = map_be16(map, c->cann_off + feature_len_off);
ret = tal_arr(ctx, u8, feature_len);
map_copy(map, c->cann_off + feature_len_off + 2, ret, feature_len);
return ret;
}
/* 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`] (option_channel_htlc_max)
*/
void gossmap_chan_get_update_details(const struct gossmap *map,
const struct gossmap_chan *chan,
int dir,
u32 *timestamp,
u8 *message_flags,
u8 *channel_flags,
u32 *fee_base_msat,
u32 *fee_proportional_millionths,
struct amount_msat *htlc_minimum_msat,
/* iff message_flags & 1 */
struct amount_msat *htlc_maximum_msat)
{
/* Note that first two bytes are message type */
const size_t scid_off = chan->cupdate_off[dir] + 2 + (64 + 32);
const size_t timestamp_off = scid_off + 8;
const size_t message_flags_off = timestamp_off + 4;
const size_t channel_flags_off = message_flags_off + 1;
const size_t cltv_expiry_delta_off = channel_flags_off + 1;
const size_t htlc_minimum_off = cltv_expiry_delta_off + 2;
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;
u8 mflags;
assert(gossmap_chan_set(chan, dir));
if (timestamp)
*timestamp = map_be32(map, timestamp_off);
/* We need this (below), even if they don't want it */
mflags = map_u8(map, message_flags_off);
if (message_flags)
*message_flags = mflags;
if (channel_flags)
*channel_flags = map_u8(map, channel_flags_off);
if (fee_base_msat)
*fee_base_msat = map_be32(map, fee_base_off);
if (fee_proportional_millionths)
*fee_proportional_millionths = map_be32(map, fee_prop_off);
if (htlc_minimum_msat)
*htlc_minimum_msat
= amount_msat(map_be64(map, htlc_minimum_off));
if (htlc_maximum_msat && (mflags & 1))
*htlc_maximum_msat
= amount_msat(map_be64(map, htlc_maximum_off));
}
/* 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`]
*/
int gossmap_node_get_feature(const struct gossmap *map,
const struct gossmap_node *n,
int fbit)
{
const size_t feature_len_off = 2 + 64;
size_t feature_len;
if (n->nann_off == 0)
return -1;
feature_len = map_be16(map, n->nann_off + feature_len_off);
return map_feature_test(map, COMPULSORY_FEATURE(fbit),
n->nann_off + feature_len_off + 2, feature_len);
}
/* There are two 33-byte pubkeys possible: choose the one which appears
* in the graph (otherwise payment will fail anyway). */
void gossmap_guess_node_id(const struct gossmap *map,
const struct point32 *point32,
struct node_id *id)
{
id->k[0] = SECP256K1_TAG_PUBKEY_EVEN;
secp256k1_xonly_pubkey_serialize(secp256k1_ctx,
id->k + 1,
&point32->pubkey);
/* If we don't find this, let's assume it's odd. */
if (!gossmap_find_node(map, id))
id->k[0] = SECP256K1_TAG_PUBKEY_ODD;
}