#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* 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; }