core-lightning/common/gossmap.c
Rusty Russell daba3e7deb common/gossmap: helper to map the gossip store.
I went overboard on optimization.  I am so sorry:
1. Squeezed channel min/max into 16 bits.
2. Uses mmap and leaves node_ids in the file.
3. Uses offsets instead of pointers where possible.
4. Uses custom free-list to allocate inside arrays.
5. Ignores our autogenerated marshalling code in favor of direct derefs.
6. Carefully aligns everything so we use minimal ram.

The result is that the current gossip_store:
 - load time (-O3 -flto laptop): 40msec
 - load time (-g laptop i.e. DEVELOPER=0): 60msec
 - load time (-O0 laptop i.e. DEVELOPER=1): 110msec
 - Total memory: 2.6MB:
   - 1.5MB for the array of channels
   - 512k for the channel htable to map scid -> channel.
   - 320k for the node htable to map nodeid -> node.
   - 192k for the array of channels inside each node
   - 94k for the array of nodes

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
2020-08-28 10:56:50 +09:30

832 lines
22 KiB
C

#include <assert.h>
#include <ccan/bitops/bitops.h>
#include <ccan/crypto/siphash24/siphash24.h>
#include <ccan/endian/endian.h>
#include <ccan/err/err.h>
#include <ccan/htable/htable_type.h>
#include <ccan/mem/mem.h>
#include <ccan/ptrint/ptrint.h>
#include <ccan/tal/str/str.h>
#include <common/gossip_store.h>
#include <common/gossmap.h>
#include <common/node_id.h>
#include <common/pseudorand.h>
#include <common/type_to_string.h>
#include <common/utils.h>
#include <errno.h>
#include <fcntl.h>
#include <gossipd/gossip_store_wiregen.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <wire/gen_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;
int st_dev, st_ino;
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;
};
/* Accessors for the gossmap */
static void map_copy(const struct gossmap *map, size_t offset,
void *dst, size_t len)
{
assert(offset < map->map_size);
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));
}
/* 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 fp16_t u64_to_fp16(u64 val, bool round_up)
{
u16 mantissa_bits, mantissa, exponent;
if (val == 0)
return 0;
/* How many bits do we need to represent mantissa? */
mantissa_bits = bitops_hs64(val) + 1;
/* We only have 11 bits, so if we need more, we will round. */
if (mantissa_bits > 11) {
exponent = mantissa_bits - 11;
mantissa = (val >> exponent);
/* If we're losing bits here, we're rounding down */
if (round_up && (val & ((1ULL << exponent)-1))) {
mantissa++;
if (mantissa == (1 << 11)) {
mantissa >>= 1;
exponent++;
}
}
/* huge number? Make it max. */
if (exponent >= 32) {
exponent = 31;
mantissa = (1 << 11)-1;
}
} else {
exponent = 0;
mantissa = val;
}
assert((mantissa >> 11) == 0);
return (exponent << 11) | mantissa;
}
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].scid_off = 0;
}
chan_arr[i].cann_off = UINT_MAX;
chan_arr[i].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 scid_off,
u32 n1idx, u32 n2idx)
{
struct gossmap_chan *chan = next_free_chan(map);
chan->cann_off = cannounce_off;
chan->scid_off = scid_off;
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->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 void 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);
size_t feature_len;
size_t scid_off;
struct node_id node_id[2];
struct gossmap_node *n[2];
u32 nidx[2];
feature_len = map_be16(map, cannounce_off + feature_len_off);
scid_off = cannounce_off + feature_len_off + 2 + feature_len + 32;
map_nodeid(map, scid_off + 8, &node_id[0]);
map_nodeid(map, scid_off + 8 + PUBKEY_CMPR_LEN, &node_id[1]);
/* 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);
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])
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]));
}
/* 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 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);
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;
scid.u64 = map_be64(map, scid_off);
chan = gossmap_find_chan(map, &scid);
if (!chan)
errx(1, "update for channel %s not found!",
type_to_string(tmpctx, struct short_channel_id, &scid));
hc.htlc_min = u64_to_fp16(map_be64(map, htlc_minimum_off), true);
/* 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), false);
else
hc.htlc_max = 0xFFFF;
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 */
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)) {
warnx("channel_update %s ignored: fee %u/%u cltv %u too large",
type_to_string(tmpctx, struct short_channel_id, &scid),
map_be32(map, fee_base_off),
map_be32(map, fee_prop_off),
map_be16(map, cltv_expiry_delta_off));
return;
}
chanflags = map_u8(map, channel_flags_off);
hc.enabled = !(chanflags & 2);
/* Preserve this */
hc.nodeidx = chan->half[chanflags & 1].nodeidx;
chan->half[chanflags & 1] = hc;
}
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->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 bool map_catchup(struct gossmap *map)
{
size_t reclen;
bool changed = false;
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_DELETED_BIT|
GOSSIP_STORE_LEN_PUSH_BIT))
+ 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);
else if (type == WIRE_CHANNEL_UPDATE)
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
continue;
changed = true;
}
return changed;
}
static bool load_gossip_store(struct gossmap *map)
{
struct stat st;
map->fd = open(map->fname, O_RDONLY);
if (map->fd < 0)
return false;
fstat(map->fd, &st);
map->st_dev = st.st_dev;
map->st_ino = st.st_ino;
map->map_size = st.st_size;
/* 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, st.st_size / 750 / 2);
nodeidx_htable_init_sized(&map->nodes, st.st_size / 2500 / 2);
map->chan_arr = tal_arr(map, struct gossmap_chan, st.st_size / 750 / 2 + 1);
map->freed_chans = init_chan_arr(map->chan_arr, 0);
map->node_arr = tal_arr(map, struct gossmap_node, st.st_size / 2500 / 2 + 1);
map->freed_nodes = init_node_arr(map->node_arr, 0);
map->map_end = 1;
map_catchup(map);
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);
}
bool gossmap_refresh(struct gossmap *map)
{
struct stat st;
/* 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,
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->scid_off + 8 + PUBKEY_CMPR_LEN*dir, id);
}
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].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.millisatoshis /* Raw: fp16 compare */
< fp16_to_u64(chan->half[direction].htlc_min))
return false;
if (amount.millisatoshis /* Raw: fp16 compare */
> fp16_to_u64(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)
{
u16 len = map_be16(map, c->cann_off);
u8 *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)
{
u16 len = map_be16(map, n->nann_off);
u8 *msg = tal_arr(ctx, u8, len);
map_copy(map, n->nann_off, msg, len);
return msg;
}