Skip to content

Commit 25c8844

Browse files
Lagrang3rustyrussell
authored andcommitted
askrene: add algorithm for single path routing
Changelog-Added: askrene: an optimal single-path solver has been added, it can be called using the developer option --dev_algorithm=single-path or by adding the layer "auto.no_mpp_support" Signed-off-by: Lagrang3 <lagrang3@protonmail.com>
1 parent 47b0f67 commit 25c8844

File tree

4 files changed

+158
-69
lines changed

4 files changed

+158
-69
lines changed

plugins/askrene/askrene.c

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,11 @@ const char *fmt_flow_full(const tal_t *ctx,
332332
}
333333

334334
enum algorithm {
335+
/* Min. Cost Flow by successive shortests paths. */
335336
ALGO_DEFAULT,
337+
/* Algorithm that finds the optimal routing solution constrained to a
338+
* single path. */
339+
ALGO_SINGLE_PATH,
336340
};
337341

338342
static struct command_result *
@@ -343,6 +347,8 @@ param_algorithm(struct command *cmd, const char *name, const char *buffer,
343347
*algo = tal(cmd, enum algorithm);
344348
if (streq(algo_str, "default"))
345349
**algo = ALGO_DEFAULT;
350+
else if (streq(algo_str, "single-path"))
351+
**algo = ALGO_SINGLE_PATH;
346352
else
347353
return command_fail_badparam(cmd, name, buffer, tok,
348354
"unknown algorithm");
@@ -517,7 +523,7 @@ void get_constraints(const struct route_query *rq,
517523

518524
static struct command_result *do_getroutes(struct command *cmd,
519525
struct gossmap_localmods *localmods,
520-
const struct getroutes_info *info)
526+
struct getroutes_info *info)
521527
{
522528
struct askrene *askrene = get_askrene(cmd->plugin);
523529
struct route_query *rq = tal(cmd, struct route_query);
@@ -586,15 +592,28 @@ static struct command_result *do_getroutes(struct command *cmd,
586592
goto fail;
587593
}
588594

595+
/* auto.no_mpp_support layer overrides any choice of algorithm. */
596+
if (have_layer(info->layers, "auto.no_mpp_support") &&
597+
info->dev_algo != ALGO_SINGLE_PATH) {
598+
info->dev_algo = ALGO_SINGLE_PATH;
599+
rq_log(tmpctx, rq, LOG_DBG,
600+
"Layer no_mpp_support is active we switch to a "
601+
"single path algorithm.");
602+
}
603+
589604
/* Compute the routes. At this point we might select between multiple
590605
* algorithms. Right now there is only one algorithm available. */
591606
struct timemono time_start = time_mono();
592-
assert(info->dev_algo == ALGO_DEFAULT);
593-
err = default_routes(rq, rq, srcnode, dstnode, info->amount,
594-
/* only one path? = */
595-
have_layer(info->layers, "auto.no_mpp_support"),
596-
info->maxfee, info->finalcltv, info->maxdelay,
597-
&flows, &probability);
607+
if (info->dev_algo == ALGO_SINGLE_PATH) {
608+
err = single_path_routes(rq, rq, srcnode, dstnode, info->amount,
609+
info->maxfee, info->finalcltv,
610+
info->maxdelay, &flows, &probability);
611+
} else {
612+
assert(info->dev_algo == ALGO_DEFAULT);
613+
err = default_routes(rq, rq, srcnode, dstnode, info->amount,
614+
info->maxfee, info->finalcltv,
615+
info->maxdelay, &flows, &probability);
616+
}
598617
struct timerel time_delta = timemono_between(time_mono(), time_start);
599618

600619
/* log the time of computation */

plugins/askrene/mcf.c

Lines changed: 40 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -947,8 +947,7 @@ struct flow **minflow(const tal_t *ctx,
947947
const struct gossmap_node *target,
948948
struct amount_msat amount,
949949
u32 mu,
950-
double delay_feefactor,
951-
bool single_part)
950+
double delay_feefactor)
952951
{
953952
struct flow **flow_paths;
954953
/* We allocate everything off this, and free it at the end,
@@ -1039,31 +1038,6 @@ struct flow **minflow(const tal_t *ctx,
10391038
goto fail;
10401039
}
10411040
tal_free(working_ctx);
1042-
1043-
/* This is dumb, but if you don't support MPP you don't deserve any
1044-
* better. Pile it into the largest part if not already. */
1045-
if (single_part) {
1046-
struct flow *best = flow_paths[0];
1047-
for (size_t i = 1; i < tal_count(flow_paths); i++) {
1048-
if (amount_msat_greater(flow_paths[i]->delivers, best->delivers))
1049-
best = flow_paths[i];
1050-
}
1051-
for (size_t i = 0; i < tal_count(flow_paths); i++) {
1052-
if (flow_paths[i] == best)
1053-
continue;
1054-
if (!amount_msat_accumulate(&best->delivers,
1055-
flow_paths[i]->delivers)) {
1056-
rq_log(tmpctx, rq, LOG_BROKEN,
1057-
"%s: failed to extract accumulate flow paths %s+%s",
1058-
__func__,
1059-
fmt_amount_msat(tmpctx, best->delivers),
1060-
fmt_amount_msat(tmpctx, flow_paths[i]->delivers));
1061-
goto fail;
1062-
}
1063-
}
1064-
flow_paths[0] = best;
1065-
tal_resize(&flow_paths, 1);
1066-
}
10671041
return flow_paths;
10681042

10691043
fail:
@@ -1268,13 +1242,16 @@ struct flow **single_path_flow(const tal_t *ctx, const struct route_query *rq,
12681242
return NULL;
12691243
}
12701244

1271-
const char *default_routes(const tal_t *ctx, struct route_query *rq,
1272-
const struct gossmap_node *srcnode,
1273-
const struct gossmap_node *dstnode,
1274-
struct amount_msat amount, bool single_path,
1275-
struct amount_msat maxfee, u32 finalcltv,
1276-
u32 maxdelay, struct flow ***flows,
1277-
double *probability)
1245+
static const char *
1246+
linear_routes(const tal_t *ctx, struct route_query *rq,
1247+
const struct gossmap_node *srcnode,
1248+
const struct gossmap_node *dstnode, struct amount_msat amount,
1249+
struct amount_msat maxfee, u32 finalcltv, u32 maxdelay,
1250+
struct flow ***flows, double *probability,
1251+
struct flow **(*solver)(const tal_t *, const struct route_query *,
1252+
const struct gossmap_node *,
1253+
const struct gossmap_node *,
1254+
struct amount_msat, u32, double))
12781255
{
12791256
*flows = NULL;
12801257
const char *ret;
@@ -1283,8 +1260,7 @@ const char *default_routes(const tal_t *ctx, struct route_query *rq,
12831260
/* First up, don't care about fees (well, just enough to tiebreak!) */
12841261
u32 mu = 1;
12851262
tal_free(*flows);
1286-
*flows = minflow(ctx, rq, srcnode, dstnode, amount, mu, delay_feefactor,
1287-
single_path);
1263+
*flows = solver(ctx, rq, srcnode, dstnode, amount, mu, delay_feefactor);
12881264
if (!*flows) {
12891265
ret = explain_failure(ctx, rq, srcnode, dstnode, amount);
12901266
goto fail;
@@ -1299,8 +1275,8 @@ const char *default_routes(const tal_t *ctx, struct route_query *rq,
12991275
flows_worst_delay(*flows), maxdelay - finalcltv,
13001276
delay_feefactor);
13011277
tal_free(*flows);
1302-
*flows = minflow(ctx, rq, srcnode, dstnode, amount, mu,
1303-
delay_feefactor, single_path);
1278+
*flows = solver(ctx, rq, srcnode, dstnode, amount, mu,
1279+
delay_feefactor);
13041280
if (!*flows || delay_feefactor > 10) {
13051281
ret = rq_log(
13061282
ctx, rq, LOG_UNUSUAL,
@@ -1323,9 +1299,8 @@ const char *default_routes(const tal_t *ctx, struct route_query *rq,
13231299
"retrying with mu of %u%%...",
13241300
fmt_amount_msat(tmpctx, flowset_fee(rq->plugin, *flows)),
13251301
fmt_amount_msat(tmpctx, maxfee), mu);
1326-
new_flows =
1327-
minflow(ctx, rq, srcnode, dstnode, amount,
1328-
mu > 100 ? 100 : mu, delay_feefactor, single_path);
1302+
new_flows = solver(ctx, rq, srcnode, dstnode, amount,
1303+
mu > 100 ? 100 : mu, delay_feefactor);
13291304
if (!*flows || mu >= 100) {
13301305
ret = rq_log(
13311306
ctx, rq, LOG_UNUSUAL,
@@ -1406,3 +1381,27 @@ const char *default_routes(const tal_t *ctx, struct route_query *rq,
14061381
assert(ret != NULL);
14071382
return ret;
14081383
}
1384+
1385+
const char *default_routes(const tal_t *ctx, struct route_query *rq,
1386+
const struct gossmap_node *srcnode,
1387+
const struct gossmap_node *dstnode,
1388+
struct amount_msat amount, struct amount_msat maxfee,
1389+
u32 finalcltv, u32 maxdelay, struct flow ***flows,
1390+
double *probability)
1391+
{
1392+
return linear_routes(ctx, rq, srcnode, dstnode, amount, maxfee,
1393+
finalcltv, maxdelay, flows, probability, minflow);
1394+
}
1395+
1396+
const char *single_path_routes(const tal_t *ctx, struct route_query *rq,
1397+
const struct gossmap_node *srcnode,
1398+
const struct gossmap_node *dstnode,
1399+
struct amount_msat amount,
1400+
struct amount_msat maxfee, u32 finalcltv,
1401+
u32 maxdelay, struct flow ***flows,
1402+
double *probability)
1403+
{
1404+
return linear_routes(ctx, rq, srcnode, dstnode, amount, maxfee,
1405+
finalcltv, maxdelay, flows, probability,
1406+
single_path_flow);
1407+
}

plugins/askrene/mcf.h

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,7 @@ struct flow **minflow(const tal_t *ctx,
3131
const struct gossmap_node *target,
3232
struct amount_msat amount,
3333
u32 mu,
34-
double delay_feefactor,
35-
bool single_part);
34+
double delay_feefactor);
3635

3736
/**
3837
* API for min cost single path.
@@ -67,9 +66,18 @@ struct amount_msat linear_flow_cost(const struct flow *flow,
6766
const char *default_routes(const tal_t *ctx, struct route_query *rq,
6867
const struct gossmap_node *srcnode,
6968
const struct gossmap_node *dstnode,
70-
struct amount_msat amount, bool single_path,
69+
struct amount_msat amount,
7170
struct amount_msat maxfee, u32 finalcltv,
7271
u32 maxdelay, struct flow ***flows,
7372
double *probability);
7473

74+
/* A wrapper to the single-path constrained solver. */
75+
const char *single_path_routes(const tal_t *ctx, struct route_query *rq,
76+
const struct gossmap_node *srcnode,
77+
const struct gossmap_node *dstnode,
78+
struct amount_msat amount,
79+
struct amount_msat maxfee, u32 finalcltv,
80+
u32 maxdelay, struct flow ***flows,
81+
double *probability);
82+
7583
#endif /* LIGHTNING_PLUGINS_ASKRENE_MCF_H */

tests/test_askrene.py

Lines changed: 81 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -568,25 +568,88 @@ def test_getroutes(node_factory):
568568
'amount_msat': 5500005,
569569
'delay': 99 + 6}]])
570570

571-
# We realize that this is impossible in a single path:
572-
with pytest.raises(RpcError, match="The shortest path is 0x2x1, but 0x2x1/1 marked disabled by layer auto.no_mpp_support."):
573-
l1.rpc.getroutes(source=nodemap[0],
574-
destination=nodemap[2],
575-
amount_msat=10000000,
576-
layers=['auto.no_mpp_support'],
577-
maxfee_msat=1000,
578-
final_cltv=99)
579571

580-
# But this will work.
581-
check_getroute_paths(l1,
582-
nodemap[0],
583-
nodemap[2],
584-
9000000,
585-
[[{'short_channel_id_dir': '0x2x3/1',
586-
'next_node_id': nodemap[2],
587-
'amount_msat': 9000009,
588-
'delay': 99 + 6}]],
589-
layers=['auto.no_mpp_support'])
572+
def test_getroutes_single_path(node_factory):
573+
"""Test getroutes generating single path payments"""
574+
gsfile, nodemap = generate_gossip_store(
575+
[
576+
GenChannel(0, 1),
577+
GenChannel(1, 2, capacity_sats=9000),
578+
GenChannel(1, 2, capacity_sats=10000),
579+
]
580+
)
581+
# Set up l1 with this as the gossip_store
582+
l1 = node_factory.get_node(gossip_store_file=gsfile.name)
583+
584+
# To be able to route this amount two parts are needed, therefore a single
585+
# pay search will fail.
586+
# FIXME: the explanation for the failure is wrong
587+
with pytest.raises(RpcError):
588+
l1.rpc.getroutes(
589+
source=nodemap[1],
590+
destination=nodemap[2],
591+
amount_msat=10000001,
592+
layers=["auto.no_mpp_support"],
593+
maxfee_msat=1000,
594+
final_cltv=99,
595+
)
596+
597+
# For this amount, only one solution is possible
598+
check_getroute_paths(
599+
l1,
600+
nodemap[1],
601+
nodemap[2],
602+
10000000,
603+
[
604+
[
605+
{
606+
"short_channel_id_dir": "1x2x2/1",
607+
"next_node_id": nodemap[2],
608+
"amount_msat": 10000010,
609+
"delay": 99 + 6,
610+
}
611+
]
612+
],
613+
layers=["auto.no_mpp_support"],
614+
)
615+
616+
# To be able to route this amount two parts are needed, therefore a single
617+
# pay search will fail.
618+
# FIXME: the explanation for the failure is wrong
619+
with pytest.raises(RpcError):
620+
l1.rpc.getroutes(
621+
source=nodemap[0],
622+
destination=nodemap[2],
623+
amount_msat=10000001,
624+
layers=["auto.no_mpp_support"],
625+
maxfee_msat=1000,
626+
final_cltv=99,
627+
)
628+
629+
# For this amount, only one solution is possible
630+
check_getroute_paths(
631+
l1,
632+
nodemap[0],
633+
nodemap[2],
634+
10000000,
635+
[
636+
[
637+
{
638+
"short_channel_id_dir": "0x1x0/1",
639+
"next_node_id": nodemap[1],
640+
"amount_msat": 10000020,
641+
"delay": 99 + 6 + 6,
642+
},
643+
{
644+
"short_channel_id_dir": "1x2x2/1",
645+
"next_node_id": nodemap[2],
646+
"amount_msat": 10000010,
647+
"delay": 99 + 6,
648+
},
649+
]
650+
],
651+
layers=["auto.no_mpp_support"],
652+
)
590653

591654

592655
def test_getroutes_fee_fallback(node_factory):

0 commit comments

Comments
 (0)