2024-03-07 13:26:33 +10:30
|
|
|
/* 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 <assert.h>
|
|
|
|
#include <common/channel_type.h>
|
|
|
|
#include <common/dijkstra.h>
|
|
|
|
#include <common/gossmap.h>
|
|
|
|
#include <common/gossip_store.h>
|
|
|
|
#include <common/route.h>
|
|
|
|
#include <common/setup.h>
|
|
|
|
#include <common/utils.h>
|
|
|
|
#include <math.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <inttypes.h>
|
|
|
|
#include <wire/peer_wiregen.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
|
|
|
|
/* 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 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;
|
|
|
|
|
2024-03-07 13:26:36 +10:30
|
|
|
static double capacity_bias(const struct gossmap *map,
|
|
|
|
const struct gossmap_chan *c,
|
|
|
|
int dir,
|
|
|
|
struct amount_msat amount)
|
2024-03-07 13:26:33 +10:30
|
|
|
{
|
|
|
|
struct amount_sat capacity;
|
|
|
|
u64 amtmsat = amount.millisatoshis; /* Raw: lengthy math */
|
|
|
|
double capmsat;
|
|
|
|
|
|
|
|
/* Can fail in theory if gossmap changed underneath. */
|
|
|
|
if (!gossmap_chan_get_capacity(map, c, &capacity))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
capmsat = (double)capacity.satoshis * 1000; /* Raw: lengthy math */
|
|
|
|
return -log((capmsat + 1 - amtmsat) / (capmsat + 1));
|
|
|
|
}
|
|
|
|
|
2024-03-07 13:26:36 +10:30
|
|
|
/* Prioritize costs over distance, but bias to larger channels. */
|
|
|
|
static u64 route_score(struct amount_msat fee,
|
2024-03-07 13:26:33 +10:30
|
|
|
struct amount_msat risk,
|
2024-03-07 13:26:36 +10:30
|
|
|
struct amount_msat total,
|
2024-03-07 13:26:33 +10:30
|
|
|
int dir,
|
|
|
|
const struct gossmap_chan *c)
|
|
|
|
{
|
2024-03-07 13:26:36 +10:30
|
|
|
double score;
|
|
|
|
struct amount_msat msat;
|
2024-03-07 13:26:33 +10:30
|
|
|
|
2024-03-07 13:26:36 +10:30
|
|
|
/* These two are comparable, so simply sum them. */
|
|
|
|
if (!amount_msat_add(&msat, fee, risk))
|
|
|
|
msat = AMOUNT_MSAT(-1ULL);
|
2024-03-07 13:26:33 +10:30
|
|
|
|
2024-03-07 13:26:36 +10:30
|
|
|
/* Slight tiebreaker bias: 1 msat per distance */
|
|
|
|
if (!amount_msat_add(&msat, 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;
|
2024-03-07 13:26:33 +10:30
|
|
|
}
|
|
|
|
|
|
|
|
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]);
|
|
|
|
|
2024-03-07 13:26:36 +10:30
|
|
|
/* This used to crash! */
|
2024-03-07 13:26:33 +10:30
|
|
|
if (!argv[1])
|
2024-03-07 13:26:36 +10:30
|
|
|
amt = 8388607;
|
2024-03-07 13:26:33 +10:30
|
|
|
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);
|
|
|
|
|
pay: ignore fees on our own channels when determining routing.
I noticed that run-route-infloop chose some worse-looking paths after
routing was fixed, eg the second node:
Before:
Destination node, success, probability, hops, fees, cltv, scid...
02b3aa1e4ed31be83cca4bd367b2c01e39502cb25e282a9b4520ad376a1ba0a01a,1,0.991856,2,1004,40,2572260x39x0/1,2131897x45x0/0
After:
Destination node, success, probability, hops, fees, cltv, scid...
02b3aa1e4ed31be83cca4bd367b2c01e39502cb25e282a9b4520ad376a1ba0a01a,1,0.954540,3,1046,46,2570715x21x0/1,2346882x26x14/1,2131897x45x0/0
This is because although the final costs don't reflect it, routing was taking
into account local channels, and 2572260x39x0/1 has a base fee of 2970.
There's an easy fix: when we the pay plugin creates localmods for our
gossip graph, add all local channels with delay and fees equal to 0.
We do the same thing in our unit test. This improves things across
the board:
Linear success probability (when found): min-max(mean +/- stddev)
Before: 0.487040-0.999543(0.952548+/-0.075)
After: 0.486985-0.999750(0.975978+/-0.053)
Hops:
Before: 1-5(2.98374+/-0.77)
After: 1-5(2.09593+/-0.63)
Fees:
Before: 0-50848(922.457+/-2.7e+03)
After: 0-50041(861.621+/-2.7e+03)
Delay (blocks):
Before: 0-196(65.8081+/-60)
After: 0-190(60.3285+/-60)
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
Changelog-Changed: Plugins: `pay` route algorithm doesn't bias against our own "expensive" channels any more.
2024-03-07 13:26:58 +10:30
|
|
|
/* 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++) {
|
|
|
|
int dir;
|
|
|
|
struct short_channel_id scid;
|
|
|
|
struct gossmap_chan *c = gossmap_nth_chan(gossmap, me, i, &dir);
|
|
|
|
|
|
|
|
if (!c->half[dir].enabled)
|
|
|
|
continue;
|
|
|
|
scid = gossmap_chan_scid(gossmap, c);
|
2024-03-20 12:29:51 +10:30
|
|
|
assert(gossmap_local_updatechan(localmods, scid,
|
pay: ignore fees on our own channels when determining routing.
I noticed that run-route-infloop chose some worse-looking paths after
routing was fixed, eg the second node:
Before:
Destination node, success, probability, hops, fees, cltv, scid...
02b3aa1e4ed31be83cca4bd367b2c01e39502cb25e282a9b4520ad376a1ba0a01a,1,0.991856,2,1004,40,2572260x39x0/1,2131897x45x0/0
After:
Destination node, success, probability, hops, fees, cltv, scid...
02b3aa1e4ed31be83cca4bd367b2c01e39502cb25e282a9b4520ad376a1ba0a01a,1,0.954540,3,1046,46,2570715x21x0/1,2346882x26x14/1,2131897x45x0/0
This is because although the final costs don't reflect it, routing was taking
into account local channels, and 2572260x39x0/1 has a base fee of 2970.
There's an easy fix: when we the pay plugin creates localmods for our
gossip graph, add all local channels with delay and fees equal to 0.
We do the same thing in our unit test. This improves things across
the board:
Linear success probability (when found): min-max(mean +/- stddev)
Before: 0.487040-0.999543(0.952548+/-0.075)
After: 0.486985-0.999750(0.975978+/-0.053)
Hops:
Before: 1-5(2.98374+/-0.77)
After: 1-5(2.09593+/-0.63)
Fees:
Before: 0-50848(922.457+/-2.7e+03)
After: 0-50041(861.621+/-2.7e+03)
Delay (blocks):
Before: 0-196(65.8081+/-60)
After: 0-190(60.3285+/-60)
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
Changelog-Changed: Plugins: `pay` route algorithm doesn't bias against our own "expensive" channels any more.
2024-03-07 13:26:58 +10:30
|
|
|
amount_msat(fp16_to_u64(c->half[dir].htlc_min)),
|
|
|
|
amount_msat(fp16_to_u64(c->half[dir].htlc_max)),
|
|
|
|
0, 0, 0, true, dir));
|
|
|
|
}
|
|
|
|
gossmap_apply_localmods(gossmap, localmods);
|
|
|
|
|
2024-03-07 13:26:33 +10:30
|
|
|
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);
|
|
|
|
|
2024-03-20 11:10:16 +10:30
|
|
|
printf("%s,", fmt_node_id(tmpctx, &them));
|
2024-03-07 13:26:33 +10:30
|
|
|
if (!r) {
|
|
|
|
printf("0,0.0,");
|
|
|
|
} else {
|
|
|
|
double probability = 1;
|
|
|
|
for (size_t j = 0; j < tal_count(r); j++) {
|
|
|
|
struct amount_sat capacity_sat;
|
|
|
|
u64 cap_msat;
|
|
|
|
struct gossmap_chan *c = gossmap_find_chan(gossmap, &r[j].scid);
|
|
|
|
assert(c);
|
|
|
|
assert(gossmap_chan_get_capacity(gossmap, c, &capacity_sat));
|
|
|
|
|
|
|
|
cap_msat = capacity_sat.satoshis * 1000;
|
|
|
|
/* 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++)
|
2024-03-20 11:10:16 +10:30
|
|
|
printf(",%s/%u", fmt_short_channel_id(tmpctx, r[j].scid), r[j].direction);
|
2024-03-07 13:26:33 +10:30
|
|
|
}
|
|
|
|
printf("\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
common_shutdown();
|
|
|
|
return 0;
|
|
|
|
}
|