/* Test based on bug report from Ken Sedgewick, where routing would crash. * I took his gossip_store (this version is compacted) and brute forced routes from * his node with different amounts until I reproduced it */ #include "config.h" #include #include #include #include #include #include #include #include #include #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 fromwire_tlv */ bool fromwire_tlv(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, const struct tlv_record_type *types UNNEEDED, size_t num_types UNNEEDED, void *record UNNEEDED, struct tlv_field **fields UNNEEDED, const u64 *extra_types UNNEEDED, size_t *err_off UNNEEDED, u64 *err_type UNNEEDED) { fprintf(stderr, "fromwire_tlv called!\n"); abort(); } /* Generated stub for sciddir_or_pubkey_from_node_id */ bool sciddir_or_pubkey_from_node_id(struct sciddir_or_pubkey *sciddpk UNNEEDED, const struct node_id *node_id UNNEEDED) { fprintf(stderr, "sciddir_or_pubkey_from_node_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(); } /* Generated stub for towire_tlv */ void towire_tlv(u8 **pptr UNNEEDED, const struct tlv_record_type *types UNNEEDED, size_t num_types UNNEEDED, const void *record UNNEEDED) { fprintf(stderr, "towire_tlv called!\n"); abort(); } /* AUTOGENERATED MOCKS END */ /* Node id 03942f5fe67645fdce4584e7f159c1f0a396b05fbc15f0fb7d6e83c553037b1c73 */ static struct gossmap *gossmap; static double capacity_bias(const struct gossmap *map, const struct gossmap_chan *c, int dir, struct amount_msat amount) { struct amount_msat msat; u64 amtmsat = amount.millisatoshis; /* Raw: lengthy math */ double capmsat; msat = gossmap_chan_get_capacity(map, c); capmsat = (double)msat.millisatoshis; /* Raw: log */ return -log((capmsat + 1 - amtmsat) / (capmsat + 1)); } /* Prioritize costs over distance, but bias to larger channels. */ static u64 route_score(struct amount_msat fee, struct amount_msat risk, struct amount_msat total, int dir, const struct gossmap_chan *c) { double score; struct amount_msat msat; /* These two are comparable, so simply sum them. */ if (!amount_msat_add(&msat, fee, risk)) msat = AMOUNT_MSAT(-1ULL); /* Slight tiebreaker bias: 1 msat per distance */ if (!amount_msat_accumulate(&msat, AMOUNT_MSAT(1))) msat = AMOUNT_MSAT(-1ULL); /* Percent penalty at different channel capacities: * 1%: 1% * 10%: 11% * 25%: 29% * 50%: 69% * 75%: 138% * 90%: 230% * 95%: 300% * 99%: 461% */ score = (capacity_bias(gossmap, c, dir, total) + 1) * msat.millisatoshis; /* Raw: Weird math */ if (score > 0xFFFFFFFF) return 0xFFFFFFFF; /* Cast unnecessary, but be explicit! */ return (u64)score; } int main(int argc, char *argv[]) { const double riskfactor = 10; const u32 final_delay = 159; struct node_id my_id; const struct gossmap_node **nodes, *me; u64 amt; common_setup(argv[0]); /* This used to crash! */ if (!argv[1]) amt = 8388607; else amt = atol(argv[1]); gossmap = gossmap_load(tmpctx, "tests/data/routing_gossip_store", NULL); nodes = tal_arr(tmpctx, const struct gossmap_node *, 0); for (struct gossmap_node *n = gossmap_first_node(gossmap); n; n = gossmap_next_node(gossmap, n)) { tal_arr_expand(&nodes, n); } assert(node_id_from_hexstr("03942f5fe67645fdce4584e7f159c1f0a396b05fbc15f0fb7d6e83c553037b1c73", strlen("03942f5fe67645fdce4584e7f159c1f0a396b05fbc15f0fb7d6e83c553037b1c73"), &my_id)); me = gossmap_find_node(gossmap, &my_id); assert(me); /* We overlay our own channels as zero fee & delay, since we don't pay fees */ struct gossmap_localmods *localmods = gossmap_localmods_new(gossmap); for (size_t i = 0; i < me->num_chans; i++) { struct short_channel_id_dir scidd; const struct amount_msat base_fee = AMOUNT_MSAT(0); const u32 proportional_fee = 0; struct gossmap_chan *c = gossmap_nth_chan(gossmap, me, i, &scidd.dir); if (!c->half[scidd.dir].enabled) continue; scidd.scid = gossmap_chan_scid(gossmap, c); assert(gossmap_local_updatechan(localmods, &scidd, NULL, NULL, NULL, &base_fee, &proportional_fee, NULL)); } gossmap_apply_localmods(gossmap, localmods); printf("Destination node, success, probability, hops, fees, cltv, scid...\n"); for (size_t i = 0; i < tal_count(nodes); i++) { const struct dijkstra *dij; struct route_hop *r; struct node_id them; if (nodes[i] == me) continue; dij = dijkstra(tmpctx, gossmap, nodes[i], amount_msat(amt), riskfactor, route_can_carry, route_score, NULL); r = route_from_dijkstra(tmpctx, gossmap, dij, me, amount_msat(amt), final_delay); gossmap_node_get_id(gossmap, nodes[i], &them); printf("%s,", fmt_node_id(tmpctx, &them)); if (!r) { printf("0,0.0,"); } else { double probability = 1; for (size_t j = 0; j < tal_count(r); j++) { struct amount_msat msat; u64 cap_msat; struct gossmap_chan *c = gossmap_find_chan(gossmap, &r[j].scid); assert(c); msat = gossmap_chan_get_capacity(gossmap, c); cap_msat = msat.millisatoshis; /* Assume linear distribution, implying probability depends on * amount we would leave in channel */ assert(cap_msat >= r[0].amount.millisatoshis); probability *= (double)(cap_msat - r[0].amount.millisatoshis) / cap_msat; } printf("1,%f,%zu,%"PRIu64",%u", probability, tal_count(r), r[0].amount.millisatoshis - amt, r[0].delay - final_delay); for (size_t j = 0; j < tal_count(r); j++) printf(",%s/%u", fmt_short_channel_id(tmpctx, r[j].scid), r[j].direction); } printf("\n"); } common_shutdown(); return 0; }