diff --git a/ccan/README b/ccan/README index 8b86e68fd83a..25c2b543395e 100644 --- a/ccan/README +++ b/ccan/README @@ -1,3 +1,3 @@ CCAN imported from http://ccodearchive.net. -CCAN version: init-2524-g609670cc +CCAN version: init-2540-g8448fd28 diff --git a/ccan/ccan/htable/htable.c b/ccan/ccan/htable/htable.c index cffd0619d338..f631ffebf1f7 100644 --- a/ccan/ccan/htable/htable.c +++ b/ccan/ccan/htable/htable.c @@ -86,14 +86,16 @@ void htable_init(struct htable *ht, ht->table = &ht->common_bits; } +/* Fill to 87.5% */ static inline size_t ht_max(const struct htable *ht) { - return ((size_t)3 << ht->bits) / 4; + return ((size_t)7 << ht->bits) / 8; } -static inline size_t ht_max_with_deleted(const struct htable *ht) +/* Clean deleted if we're full, and more than 12.5% deleted */ +static inline size_t ht_max_deleted(const struct htable *ht) { - return ((size_t)9 << ht->bits) / 10; + return ((size_t)1 << ht->bits) / 8; } bool htable_init_sized(struct htable *ht, @@ -103,7 +105,7 @@ bool htable_init_sized(struct htable *ht, htable_init(ht, rehash, priv); /* Don't go insane with sizing. */ - for (ht->bits = 1; ((size_t)3 << ht->bits) / 4 < expect; ht->bits++) { + for (ht->bits = 1; ht_max(ht) < expect; ht->bits++) { if (ht->bits == 30) break; } @@ -195,12 +197,83 @@ void *htable_prev_(const struct htable *ht, struct htable_iter *i) for (;;) { if (!i->off) return NULL; - i->off --; + i->off--; if (entry_is_valid(ht->table[i->off])) return get_raw_ptr(ht, ht->table[i->off]); } } +/* Another bit currently in mask needs to be exposed, so that a bucket with p in + * it won't appear invalid */ +static COLD void unset_another_common_bit(struct htable *ht, + uintptr_t *maskdiff, + const void *p) +{ + size_t i; + + for (i = sizeof(uintptr_t) * CHAR_BIT - 1; i > 0; i--) { + if (((uintptr_t)p & ((uintptr_t)1 << i)) + && ht->common_mask & ~*maskdiff & ((uintptr_t)1 << i)) + break; + } + /* There must have been one, right? */ + assert(i > 0); + + *maskdiff |= ((uintptr_t)1 << i); +} + +/* We want to change the common mask: this fixes up the table */ +static COLD void fixup_table_common(struct htable *ht, uintptr_t maskdiff) +{ + size_t i; + uintptr_t bitsdiff; + +again: + bitsdiff = ht->common_bits & maskdiff; + + for (i = 0; i < (size_t)1 << ht->bits; i++) { + uintptr_t e; + if (!entry_is_valid(e = ht->table[i])) + continue; + + /* Clear the bits no longer in the mask, set them as + * expected. */ + e &= ~maskdiff; + e |= bitsdiff; + /* If this made it invalid, restart with more exposed */ + if (!entry_is_valid(e)) { + unset_another_common_bit(ht, &maskdiff, get_raw_ptr(ht, e)); + goto again; + } + ht->table[i] = e; + } + + /* Take away those bits from our mask, bits and perfect bit. */ + ht->common_mask &= ~maskdiff; + ht->common_bits &= ~maskdiff; + if (ht_perfect_mask(ht) & maskdiff) + ht->perfect_bitnum = NO_PERFECT_BIT; +} + +/* Limited recursion */ +static void ht_add(struct htable *ht, const void *new, size_t h); + +/* We tried to add this entry, but it looked invalid! We need to + * let another pointer bit through mask */ +static COLD void update_common_fix_invalid(struct htable *ht, const void *p, size_t h) +{ + uintptr_t maskdiff; + + assert(ht->elems != 0); + + maskdiff = 0; + unset_another_common_bit(ht, &maskdiff, p); + fixup_table_common(ht, maskdiff); + + /* Now won't recurse */ + ht_add(ht, p, h); +} + /* This does not expand the hash table, that's up to caller. */ static void ht_add(struct htable *ht, const void *new, size_t h) { @@ -214,6 +287,8 @@ static void ht_add(struct htable *ht, const void *new, size_t h) i = (i + 1) & ((1 << ht->bits)-1); } ht->table[i] = make_hval(ht, new, get_hash_ptr_bits(ht, h)|perfect); + if (!entry_is_valid(ht->table[i])) + update_common_fix_invalid(ht, new, h); } static COLD bool double_table(struct htable *ht) @@ -283,20 +358,10 @@ static COLD void rehash_table(struct htable *ht) /* We stole some bits, now we need to put them back... */ static COLD void update_common(struct htable *ht, const void *p) { - unsigned int i; - uintptr_t maskdiff, bitsdiff; + uintptr_t maskdiff; if (ht->elems == 0) { - /* Always reveal one bit of the pointer in the bucket, - * so it's not zero or HTABLE_DELETED (1), even if - * hash happens to be 0. Assumes (void *)1 is not a - * valid pointer. */ - for (i = sizeof(uintptr_t)*CHAR_BIT - 1; i > 0; i--) { - if ((uintptr_t)p & ((uintptr_t)1 << i)) - break; - } - - ht->common_mask = ~((uintptr_t)1 << i); + ht->common_mask = -1; ht->common_bits = ((uintptr_t)p & ht->common_mask); ht->perfect_bitnum = 0; (void)htable_debug(ht, HTABLE_LOC); @@ -306,33 +371,25 @@ static COLD void update_common(struct htable *ht, const void *p) /* Find bits which are unequal to old common set. */ maskdiff = ht->common_bits ^ ((uintptr_t)p & ht->common_mask); - /* These are the bits which go there in existing entries. */ - bitsdiff = ht->common_bits & maskdiff; - - for (i = 0; i < (size_t)1 << ht->bits; i++) { - if (!entry_is_valid(ht->table[i])) - continue; - /* Clear the bits no longer in the mask, set them as - * expected. */ - ht->table[i] &= ~maskdiff; - ht->table[i] |= bitsdiff; - } - - /* Take away those bits from our mask, bits and perfect bit. */ - ht->common_mask &= ~maskdiff; - ht->common_bits &= ~maskdiff; - if (ht_perfect_mask(ht) & maskdiff) - ht->perfect_bitnum = NO_PERFECT_BIT; + fixup_table_common(ht, maskdiff); (void)htable_debug(ht, HTABLE_LOC); } bool htable_add_(struct htable *ht, size_t hash, const void *p) { - if (ht->elems+1 > ht_max(ht) && !double_table(ht)) - return false; - if (ht->elems+1 + ht->deleted > ht_max_with_deleted(ht)) - rehash_table(ht); + /* Cannot insert NULL, or (void *)1. */ assert(p); + assert(entry_is_valid((uintptr_t)p)); + + /* Getting too full? */ + if (ht->elems+1 + ht->deleted > ht_max(ht)) { + /* If we're more than 1/8 deleted, clean those, + * otherwise double table size. */ + if (ht->deleted > ht_max_deleted(ht)) + rehash_table(ht); + else if (!double_table(ht)) + return false; + } if (((uintptr_t)p & ht->common_mask) != ht->common_bits) update_common(ht, p); @@ -361,8 +418,12 @@ void htable_delval_(struct htable *ht, struct htable_iter *i) assert(entry_is_valid(ht->table[i->off])); ht->elems--; - ht->table[i->off] = HTABLE_DELETED; - ht->deleted++; + /* Cheap test: if the next bucket is empty, don't need delete marker */ + if (ht->table[hash_bucket(ht, i->off+1)] != 0) { + ht->table[i->off] = HTABLE_DELETED; + ht->deleted++; + } else + ht->table[i->off] = 0; } void *htable_pick_(const struct htable *ht, size_t seed, struct htable_iter *i) diff --git a/ccan/ccan/htable/htable.h b/ccan/ccan/htable/htable.h index eac57e37a326..faaf541bd8ce 100644 --- a/ccan/ccan/htable/htable.h +++ b/ccan/ccan/htable/htable.h @@ -132,7 +132,7 @@ bool htable_copy_(struct htable *dst, const struct htable *src); * htable_add - add a pointer into a hash table. * @ht: the htable * @hash: the hash value of the object - * @p: the non-NULL pointer + * @p: the non-NULL pointer (also cannot be (void *)1). * * Also note that this can only fail due to allocation failure. Otherwise, it * returns true. diff --git a/ccan/ccan/htable/test/run-clash.c b/ccan/ccan/htable/test/run-clash.c new file mode 100644 index 000000000000..a4e5d38a2bc0 --- /dev/null +++ b/ccan/ccan/htable/test/run-clash.c @@ -0,0 +1,31 @@ +#include +#include +#include +#include +#include + +/* Clashy hash */ +static size_t hash(const void *elem, void *unused UNNEEDED) +{ + return 0; +} + +int main(void) +{ + struct htable ht; + + plan_tests(254 * 253); + /* We try to get two elements which clash */ + for (size_t i = 2; i < 256; i++) { + for (size_t j = 2; j < 256; j++) { + if (i == j) + continue; + htable_init(&ht, hash, NULL); + htable_add(&ht, hash((void *)i, NULL), (void *)i); + htable_add(&ht, hash((void *)j, NULL), (void *)j); + ok1(htable_check(&ht, "test")); + htable_clear(&ht); + } + } + return exit_status(); +} diff --git a/ccan/ccan/htable/test/run-debug.c b/ccan/ccan/htable/test/run-debug.c index b9f48ee70aa9..399910354da2 100644 --- a/ccan/ccan/htable/test/run-debug.c +++ b/ccan/ccan/htable/test/run-debug.c @@ -107,7 +107,7 @@ static bool check_mask(struct htable *ht, uint64_t val[], unsigned num) int main(void) { - unsigned int i, weight; + unsigned int i; uintptr_t perfect_bit; struct htable ht; uint64_t val[NUM_VALS]; @@ -131,14 +131,7 @@ int main(void) add_vals(&ht, val, 0, 1); ok1(ht.bits == 1); ok1(ht_max(&ht) == 1); - weight = 0; - for (i = 0; i < sizeof(ht.common_mask) * CHAR_BIT; i++) { - if (ht.common_mask & ((uintptr_t)1 << i)) { - weight++; - } - } - /* Only one bit should be clear. */ - ok1(weight == i-1); + ok1(ht.common_mask == -1); /* Mask should be set. */ ok1(check_mask(&ht, val, 1)); diff --git a/ccan/ccan/htable/test/run.c b/ccan/ccan/htable/test/run.c index ada85f95a93d..27007a41ac5f 100644 --- a/ccan/ccan/htable/test/run.c +++ b/ccan/ccan/htable/test/run.c @@ -97,7 +97,7 @@ static bool check_mask(struct htable *ht, uint64_t val[], unsigned num) int main(void) { - unsigned int i, weight; + unsigned int i; uintptr_t perfect_bit; struct htable ht; uint64_t val[NUM_VALS]; @@ -122,14 +122,7 @@ int main(void) add_vals(&ht, val, 0, 1); ok1(ht.bits == 1); ok1(ht_max(&ht) == 1); - weight = 0; - for (i = 0; i < sizeof(ht.common_mask) * CHAR_BIT; i++) { - if (ht.common_mask & ((uintptr_t)1 << i)) { - weight++; - } - } - /* Only one bit should be clear. */ - ok1(weight == i-1); + ok1(ht.common_mask == -1); /* Mask should be set. */ ok1(check_mask(&ht, val, 1)); diff --git a/ccan/ccan/htable/tools/Makefile b/ccan/ccan/htable/tools/Makefile index a2cad59f0c2d..c8a428a7567c 100644 --- a/ccan/ccan/htable/tools/Makefile +++ b/ccan/ccan/htable/tools/Makefile @@ -4,9 +4,10 @@ CFLAGS=-Wall -Werror -O3 -I$(CCANDIR) CCAN_OBJS:=ccan-tal.o ccan-tal-str.o ccan-tal-grab_file.o ccan-take.o ccan-time.o ccan-str.o ccan-noerr.o ccan-list.o -all: speed stringspeed hsearchspeed +all: speed stringspeed hsearchspeed density speed: speed.o hash.o $(CCAN_OBJS) +density: density.o hash.o $(CCAN_OBJS) speed.o: speed.c ../htable.h ../htable.c diff --git a/ccan/ccan/htable/tools/density.c b/ccan/ccan/htable/tools/density.c new file mode 100644 index 000000000000..5f7400b9ece6 --- /dev/null +++ b/ccan/ccan/htable/tools/density.c @@ -0,0 +1,107 @@ +/* Density measurements for hashtables. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* We don't actually hash objects: we put values in as if they were ptrs */ +static uintptr_t key(const ptrint_t *obj) +{ + return ptr2int(obj); +} + +static size_t hash_uintptr(uintptr_t key) +{ + return hashl(&key, 1, 0); +} + +static bool cmp(const ptrint_t *p, uintptr_t k) +{ + return key(p) == k; +} + +HTABLE_DEFINE_TYPE(ptrint_t, key, hash_uintptr, cmp, htable_ptrint); + +/* Nanoseconds per operation */ +static size_t normalize(const struct timeabs *start, + const struct timeabs *stop, + unsigned int num) +{ + return time_to_nsec(time_divide(time_between(*stop, *start), num)); +} + +static size_t average_run(const struct htable_ptrint *ht, size_t count, size_t *longest) +{ + size_t i, total = 0; + + *longest = 0; + for (i = 0; i < count; i++) { + size_t h = hash_uintptr(i + 2); + size_t run = 1; + + while (get_raw_ptr(&ht->raw, ht->raw.table[h % ((size_t)1 << ht->raw.bits)]) != int2ptr(i + 2)) { + h++; + run++; + } + total += run; + if (run > *longest) + *longest = run; + } + return total / count; +} + +int main(int argc, char *argv[]) +{ + unsigned int i; + size_t num; + struct timeabs start, stop; + struct htable_ptrint ht; + + if (argc != 2) + errx(1, "Usage: density "); + + num = atoi(argv[1]); + + printf("Total buckets, buckets used, nanoseconds search time per element, avg run, longest run\n"); + for (i = 1; i <= 99; i++) { + uintptr_t j; + struct htable_ptrint_iter it; + size_t count, avg_run, longest_run; + ptrint_t *p; + + htable_ptrint_init_sized(&ht, num * 3 / 4); + assert((1 << ht.raw.bits) == num); + + /* Can't put 0 or 1 in the hash table: multiply by a prime. */ + for (j = 0; j < num * i / 100; j++) { + htable_ptrint_add(&ht, int2ptr(j + 2)); + /* stop it from doubling! */ + ht.raw.elems = num / 2; + } + /* Must not have changed! */ + assert((1 << ht.raw.bits) == num); + + /* Clean cache */ + count = 0; + for (p = htable_ptrint_first(&ht, &it); p; p = htable_ptrint_next(&ht, &it)) + count++; + assert(count == num * i / 100); + start = time_now(); + for (j = 0; j < count; j++) + assert(htable_ptrint_get(&ht, j + 2)); + stop = time_now(); + avg_run = average_run(&ht, count, &longest_run); + printf("%zu,%zu,%zu,%zu,%zu\n", + num, count, normalize(&start, &stop, count), avg_run, longest_run); + fflush(stdout); + htable_ptrint_clear(&ht); + } + + return 0; +} diff --git a/ccan/ccan/io/io.h b/ccan/ccan/io/io.h index 1197626f1267..eeb5e36ecdff 100644 --- a/ccan/ccan/io/io.h +++ b/ccan/ccan/io/io.h @@ -416,7 +416,6 @@ struct io_plan *io_out_always_(struct io_conn *conn, * // Freed if conn closes normally. * timeout = tal(conn, struct timeout_timer); * timeout->conn = conn; - * timeout->t = conn; * timer_addrel(&timers, &timeout->t, time_from_sec(5)); * return io_sock_shutdown(conn); * } @@ -815,4 +814,13 @@ struct timemono (*io_time_override(struct timemono (*now)(void)))(void); */ int (*io_poll_override(int (*poll)(struct pollfd *fds, nfds_t nfds, int timeout)))(struct pollfd *, nfds_t, int); +/** + * io_have_fd - do we own this file descriptor? + * @fd: the file descriptor. + * @listener: if non-NULL, set to true if it's a listening socket (io_listener). + * + * Returns NULL if we don't own it, otherwise a struct io_conn * or struct io_listener *. + */ +const void *io_have_fd(int fd, bool *listener); + #endif /* CCAN_IO_H */ diff --git a/ccan/ccan/io/poll.c b/ccan/ccan/io/poll.c index 4cc9f4b7dc90..634f83d286a6 100644 --- a/ccan/ccan/io/poll.c +++ b/ccan/ccan/io/poll.c @@ -464,3 +464,15 @@ void *io_loop(struct timers *timers, struct timer **expired) return ret; } + +const void *io_have_fd(int fd, bool *listener) +{ + for (size_t i = 0; i < num_fds; i++) { + if (fds[i]->fd != fd) + continue; + if (listener) + *listener = fds[i]->listener; + return fds[i]; + } + return NULL; +} diff --git a/ccan/ccan/strmap/strmap.c b/ccan/ccan/strmap/strmap.c index 9fa51d0d40db..16a30e036ad4 100644 --- a/ccan/ccan/strmap/strmap.c +++ b/ccan/ccan/strmap/strmap.c @@ -17,9 +17,8 @@ struct node { }; /* Closest member to this in a non-empty map. */ -static struct strmap *closest(struct strmap *n, const char *member) +static struct strmap *closest(struct strmap *n, const char *member, size_t len) { - size_t len = strlen(member); const u8 *bytes = (const u8 *)member; /* Anything with NULL value is a node. */ @@ -35,20 +34,26 @@ static struct strmap *closest(struct strmap *n, const char *member) return n; } -void *strmap_get_(const struct strmap *map, const char *member) +void *strmap_getn_(const struct strmap *map, + const char *member, size_t memberlen) { struct strmap *n; /* Not empty map? */ if (map->u.n) { - n = closest((struct strmap *)map, member); - if (streq(member, n->u.s)) + n = closest((struct strmap *)map, member, memberlen); + if (!strncmp(member, n->u.s, memberlen) && !n->u.s[memberlen]) return n->v; } errno = ENOENT; return NULL; } +void *strmap_get_(const struct strmap *map, const char *member) +{ + return strmap_getn_(map, member, strlen(member)); +} + bool strmap_add_(struct strmap *map, const char *member, const void *value) { size_t len = strlen(member); @@ -68,7 +73,7 @@ bool strmap_add_(struct strmap *map, const char *member, const void *value) } /* Find closest existing member. */ - n = closest(map, member); + n = closest(map, member, len); /* Find where they differ. */ for (byte_num = 0; n->u.s[byte_num] == member[byte_num]; byte_num++) { diff --git a/ccan/ccan/strmap/strmap.h b/ccan/ccan/strmap/strmap.h index 0b32e6befc0c..8724c31dcbb2 100644 --- a/ccan/ccan/strmap/strmap.h +++ b/ccan/ccan/strmap/strmap.h @@ -72,7 +72,7 @@ static inline bool strmap_empty_(const struct strmap *map) /** * strmap_get - get a value from a string map * @map: the typed strmap to search. - * @member: the string to search for. + * @member: the string to search for (nul terminated) * * Returns the value, or NULL if it isn't in the map (and sets errno = ENOENT). * @@ -85,6 +85,23 @@ static inline bool strmap_empty_(const struct strmap *map) tcon_cast((map), canary, strmap_get_(tcon_unwrap(map), (member))) void *strmap_get_(const struct strmap *map, const char *member); +/** + * strmap_getn - get a value from a string map + * @map: the typed strmap to search. + * @member: the string to search for. + * @memberlen: the length of @member. + * + * Returns the value, or NULL if it isn't in the map (and sets errno = ENOENT). + * + * Example: + * val = strmap_getn(&map, "hello", 5); + * if (val) + * printf("hello => %i\n", *val); + */ +#define strmap_getn(map, member, n) \ + tcon_cast((map), canary, strmap_getn_(tcon_unwrap(map), (member), (n))) +void *strmap_getn_(const struct strmap *map, const char *member, size_t n); + /** * strmap_add - place a member in the string map. * @map: the typed strmap to add to. diff --git a/common/gossmap.c b/common/gossmap.c index 918fb913a624..ce605048e85e 100644 --- a/common/gossmap.c +++ b/common/gossmap.c @@ -176,25 +176,25 @@ u32 gossmap_chan_idx(const struct gossmap *map, const struct gossmap_chan *chan) return chan - map->chan_arr; } -/* htable can't handle NULL values, so we add 1 */ +/* htable can't handle NULL or 1 values, so we add 2 */ static struct gossmap_chan *ptrint2chan(const ptrint_t *pidx) { - return map->chan_arr + ptr2int(pidx) - 1; + return map->chan_arr + ptr2int(pidx) - 2; } static ptrint_t *chan2ptrint(const struct gossmap_chan *chan) { - return int2ptr(chan - map->chan_arr + 1); + return int2ptr(chan - map->chan_arr + 2); } static struct gossmap_node *ptrint2node(const ptrint_t *pidx) { - return map->node_arr + ptr2int(pidx) - 1; + return map->node_arr + ptr2int(pidx) - 2; } static ptrint_t *node2ptrint(const struct gossmap_node *node) { - return int2ptr(node - map->node_arr + 1); + return int2ptr(node - map->node_arr + 2); } static struct short_channel_id chanidx_id(const ptrint_t *pidx) diff --git a/common/test/Makefile b/common/test/Makefile index ae2803cf5efa..3091105059d5 100644 --- a/common/test/Makefile +++ b/common/test/Makefile @@ -63,6 +63,14 @@ common/test/run-gossmap_local: \ wire/tlvstream.o \ wire/towire.o +common/test/run-gossmap_canned: \ + common/base32.o \ + common/wireaddr.o \ + wire/fromwire.o \ + wire/peer$(EXP)_wiregen.o \ + wire/tlvstream.o \ + wire/towire.o + common/test/run-bolt12_merkle: \ common/amount.o \ common/bigsize.o \ diff --git a/common/test/run-gossmap_canned.c b/common/test/run-gossmap_canned.c new file mode 100644 index 000000000000..5e2542bef9ff --- /dev/null +++ b/common/test/run-gossmap_canned.c @@ -0,0 +1,186 @@ +/* Test a canned gossmap we had troubl with */ +#include "config.h" +#include "../amount.c" +#include "../fp16.c" +#include "../gossmap.c" +#include "../node_id.c" +#include "../pseudorand.c" +#include +#include +#include +#include +#include + +/* AUTOGENERATED MOCKS START */ +/* Generated stub for fromwire_bigsize */ +bigsize_t fromwire_bigsize(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) +{ fprintf(stderr, "fromwire_bigsize called!\n"); abort(); } +/* Generated stub for fromwire_channel_id */ +bool fromwire_channel_id(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, + struct channel_id *channel_id UNNEEDED) +{ fprintf(stderr, "fromwire_channel_id called!\n"); abort(); } +/* Generated stub for towire_bigsize */ +void towire_bigsize(u8 **pptr UNNEEDED, const bigsize_t val UNNEEDED) +{ fprintf(stderr, "towire_bigsize called!\n"); abort(); } +/* Generated stub for towire_channel_id */ +void towire_channel_id(u8 **pptr UNNEEDED, const struct channel_id *channel_id UNNEEDED) +{ fprintf(stderr, "towire_channel_id called!\n"); abort(); } +/* AUTOGENERATED MOCKS END */ + +/* Canned gossmap, taken from od -tx1 -Anone -v < /tmp/ltests-rtchpzh1/test_peers_1/lightning-2/regtest/gossip_store | sed 's/ / 0x/g'| cut -c2- | sed -e 's/ /, /' -e 's/$/,/' */ +static u8 canned_map[] = { + 0x09,0x00,0x00,0x01,0xbc,0x4a,0xe8,0x33,0xa0,0x00,0x00,0x00,0x00,0x10,0x08,0x00, + 0x00,0x00,0x00,0x00,0x0f,0x42,0x40,0x01,0xb0,0x01,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x22,0x6e, + 0x46,0x11,0x1a,0x0b,0x59,0xca,0xaf,0x12,0x60,0x43,0xeb,0x5b,0xbf,0x28,0xc3,0x4f, + 0x3a,0x5e,0x33,0x2a,0x1f,0xc7,0xb2,0xb7,0x3c,0xf1,0x88,0x91,0x0f,0x00,0x00,0x67, + 0x00,0x00,0x01,0x00,0x01,0x02,0x2d,0x22,0x36,0x20,0xa3,0x59,0xa4,0x7f,0xf7,0xf7, + 0xac,0x44,0x7c,0x85,0xc4,0x6c,0x92,0x3d,0xa5,0x33,0x89,0x22,0x1a,0x00,0x54,0xc1, + 0x1c,0x1e,0x3c,0xa3,0x1d,0x59,0x02,0x66,0xe4,0x59,0x8d,0x1d,0x3c,0x41,0x5f,0x57, + 0x2a,0x84,0x88,0x83,0x0b,0x60,0xf7,0xe7,0x44,0xed,0x92,0x35,0xeb,0x0b,0x1b,0xa9, + 0x32,0x83,0xb3,0x15,0xc0,0x35,0x18,0x03,0x1b,0x84,0xc5,0x56,0x7b,0x12,0x64,0x40, + 0x99,0x5d,0x3e,0xd5,0xaa,0xba,0x05,0x65,0xd7,0x1e,0x18,0x34,0x60,0x48,0x19,0xff, + 0x9c,0x17,0xf5,0xe9,0xd5,0xdd,0x07,0x8f,0x03,0x1b,0x84,0xc5,0x56,0x7b,0x12,0x64, + 0x40,0x99,0x5d,0x3e,0xd5,0xaa,0xba,0x05,0x65,0xd7,0x1e,0x18,0x34,0x60,0x48,0x19, + 0xff,0x9c,0x17,0xf5,0xe9,0xd5,0xdd,0x07,0x8f,0x00,0x00,0x00,0x8e,0xf7,0xf5,0x09, + 0x1e,0x00,0x00,0x00,0x00,0x10,0x06,0x00,0x8a,0x01,0x02,0x53,0x0b,0x21,0x64,0xa7, + 0xd8,0x03,0x4f,0x50,0xb8,0x16,0x9e,0xaa,0x73,0x6b,0xe0,0x70,0x36,0x16,0xc5,0xb1, + 0x04,0xd7,0xfd,0xc0,0x8f,0x68,0x72,0xd7,0x3e,0x38,0x72,0x05,0x7c,0x00,0x81,0x44, + 0x96,0xa2,0x22,0x08,0x15,0xdd,0x88,0x86,0xe7,0xb5,0x97,0x45,0x73,0x46,0x35,0xde, + 0xf0,0x62,0x11,0x22,0x1f,0xea,0x62,0xd0,0x40,0x6f,0x60,0x06,0x22,0x6e,0x46,0x11, + 0x1a,0x0b,0x59,0xca,0xaf,0x12,0x60,0x43,0xeb,0x5b,0xbf,0x28,0xc3,0x4f,0x3a,0x5e, + 0x33,0x2a,0x1f,0xc7,0xb2,0xb7,0x3c,0xf1,0x88,0x91,0x0f,0x00,0x00,0x67,0x00,0x00, + 0x01,0x00,0x01,0x62,0xc4,0xdb,0x93,0x01,0x00,0x00,0x06,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x0a,0x00,0x00,0x00,0x00,0x3b, + 0x02,0x33,0x80,0x00,0x00,0x00,0x8e,0x6f,0xbe,0x57,0x16,0x00,0x00,0x00,0x00,0x10, + 0x06,0x00,0x8a,0x01,0x02,0x0c,0x6c,0x84,0xfe,0xfd,0x31,0xe5,0x56,0x63,0xce,0xea, + 0x62,0x3c,0x82,0x3c,0xf8,0xb8,0xae,0x6d,0x8a,0xc7,0x60,0x44,0xe5,0x0d,0xaf,0xdd, + 0x76,0xae,0x54,0x08,0xec,0x13,0x45,0xa0,0x69,0x60,0x89,0x74,0x88,0xe1,0xe8,0xcb, + 0xcc,0x03,0xe3,0x1b,0x4a,0x77,0x17,0xfe,0xa1,0xe1,0xa3,0x42,0x39,0x2d,0xda,0x7b, + 0x68,0xf3,0xd5,0x97,0x88,0x06,0x22,0x6e,0x46,0x11,0x1a,0x0b,0x59,0xca,0xaf,0x12, + 0x60,0x43,0xeb,0x5b,0xbf,0x28,0xc3,0x4f,0x3a,0x5e,0x33,0x2a,0x1f,0xc7,0xb2,0xb7, + 0x3c,0xf1,0x88,0x91,0x0f,0x00,0x00,0x67,0x00,0x00,0x01,0x00,0x01,0x62,0xc4,0xdb, + 0x93,0x01,0x01,0x00,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0x0a,0x00,0x00,0x00,0x00,0x3b,0x02,0x33,0x80, +}; + +static void check_cannounce(const u8 *cannounce, + const struct short_channel_id *scid, + const struct node_id *n1, + const struct node_id *n2) +{ + secp256k1_ecdsa_signature sig; + u8 *features; + struct bitcoin_blkid chain_hash; + struct short_channel_id actual_scid; + struct node_id actual_n1, actual_n2; + struct pubkey k; + + assert(fromwire_channel_announcement(cannounce, cannounce, + &sig, &sig, &sig, &sig, + &features, &chain_hash, + &actual_scid, + &actual_n1, + &actual_n2, + &k, &k)); + assert(short_channel_id_eq(&actual_scid, scid)); + if (node_id_cmp(n1, n2) < 0) { + assert(node_id_eq(&actual_n1, n1)); + assert(node_id_eq(&actual_n2, n2)); + } else { + assert(node_id_eq(&actual_n1, n2)); + assert(node_id_eq(&actual_n2, n1)); + } +} + +int main(int argc, char *argv[]) +{ + int fd; + char *gossfile; + struct gossmap *map; + struct node_id l1, l2; + struct short_channel_id scid12; + struct amount_sat capacity; + u32 timestamp, fee_base_msat, fee_proportional_millionths; + u8 message_flags, channel_flags; + struct amount_msat htlc_minimum_msat, htlc_maximum_msat; + u8 *cann; + + common_setup(argv[0]); + + fd = tmpdir_mkstemp(tmpctx, "run-gossip_canned.XXXXXX", &gossfile); + assert(write_all(fd, canned_map, sizeof(canned_map))); + + map = gossmap_load(tmpctx, gossfile, NULL); + assert(map); + + /* There is an unannounced channel 1<->2 (103x1x1) */ + assert(node_id_from_hexstr("0266e4598d1d3c415f572a8488830b60f7e744ed9235eb0b1ba93283b315c03518", 66, &l1)); + assert(node_id_from_hexstr("022d223620a359a47ff7f7ac447c85c46c923da53389221a0054c11c1e3ca31d59", 66, &l2)); + assert(gossmap_find_node(map, &l1)); + assert(gossmap_find_node(map, &l2)); + + assert(short_channel_id_from_str("103x1x1", 7, &scid12)); + assert(gossmap_find_chan(map, &scid12)); + assert(gossmap_find_chan(map, &scid12)->private); + assert(gossmap_chan_get_capacity(map, gossmap_find_chan(map, &scid12), + &capacity)); + assert(amount_sat_eq(capacity, AMOUNT_SAT(1000000))); + + gossmap_chan_get_update_details(map, gossmap_find_chan(map, &scid12), + 0, + ×tamp, + &message_flags, + &channel_flags, + &fee_base_msat, + &fee_proportional_millionths, + &htlc_minimum_msat, + &htlc_maximum_msat); + assert(timestamp == 1657068435); + assert(message_flags == 1); + assert(channel_flags == 0); + assert(fee_base_msat == 1); + assert(fee_proportional_millionths == 10); + assert(amount_msat_eq(htlc_minimum_msat, AMOUNT_MSAT(0))); + assert(amount_msat_eq(htlc_maximum_msat, AMOUNT_MSAT(990000000))); + + gossmap_chan_get_update_details(map, gossmap_find_chan(map, &scid12), + 1, + ×tamp, + &message_flags, + &channel_flags, + &fee_base_msat, + &fee_proportional_millionths, + &htlc_minimum_msat, + &htlc_maximum_msat); + assert(timestamp == 1657068435); + assert(message_flags == 1); + assert(channel_flags == 1); + assert(fee_base_msat == 1); + assert(fee_proportional_millionths == 10); + assert(amount_msat_eq(htlc_minimum_msat, AMOUNT_MSAT(0))); + assert(amount_msat_eq(htlc_maximum_msat, AMOUNT_MSAT(990000000))); + + assert(tal_bytelen(gossmap_chan_get_features(tmpctx, map, + gossmap_find_chan(map, &scid12))) == 0); + + cann = gossmap_chan_get_announce(tmpctx, map, + gossmap_find_chan(map, &scid12)); + check_cannounce(cann, &scid12, &l1, &l2); + common_shutdown(); +}