Skip to content

Commit 0d0c36d

Browse files
committed
[WIP] askrene: add a single path solver
Changelog-Added: askrene: add a routing algorithm for payments that do not support MPP Signed-off-by: Lagrang3 <[email protected]>
1 parent 27fc1c0 commit 0d0c36d

File tree

3 files changed

+213
-49
lines changed

3 files changed

+213
-49
lines changed

plugins/askrene/askrene.c

Lines changed: 26 additions & 6 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");
@@ -581,15 +587,29 @@ static struct command_result *do_getroutes(struct command *cmd,
581587
goto fail;
582588
}
583589

590+
/* auto.no_mpp_support layer overrides any choice of algorithm. */
591+
if (have_layer(info->layers, "auto.no_mpp_support") &&
592+
*info->dev_algo != ALGO_SINGLE_PATH) {
593+
*info->dev_algo = ALGO_SINGLE_PATH;
594+
rq_log(tmpctx, rq, LOG_DBG,
595+
"Layer no_mpp_support is active we switch to a "
596+
"single path algorithm.");
597+
}
598+
584599
/* Compute the routes. At this point we might select between multiple
585600
* algorithms. Right now there is only one algorithm available. */
586601
struct timemono time_start = time_mono();
587-
assert(*info->dev_algo == ALGO_DEFAULT);
588-
err = default_routes(rq, rq, srcnode, dstnode, *info->amount,
589-
/* only one path? = */
590-
have_layer(info->layers, "auto.no_mpp_support"),
591-
*info->maxfee, *info->finalcltv, *info->maxdelay,
592-
&flows, &probability);
602+
if (*info->dev_algo == ALGO_SINGLE_PATH){
603+
err = single_path_routes(
604+
rq, rq, srcnode, dstnode, *info->amount,
605+
*info->maxfee, *info->finalcltv, *info->maxdelay, &flows,
606+
&probability);
607+
} else {
608+
assert(*info->dev_algo == ALGO_DEFAULT);
609+
err = default_routes(rq, rq, srcnode, dstnode, *info->amount,
610+
*info->maxfee, *info->finalcltv,
611+
*info->maxdelay, &flows, &probability);
612+
}
593613
struct timerel time_delta = timemono_between(time_mono(), time_start);
594614

595615
/* log the time of computation */

plugins/askrene/mcf.c

Lines changed: 176 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -949,6 +949,46 @@ get_flow_paths(const tal_t *ctx,
949949
return flows;
950950
}
951951

952+
/* Given a single path build a flow set. */
953+
static struct flow **
954+
get_flow_singlepath(const tal_t *ctx, const tal_t *working_ctx,
955+
const struct graph *graph, const struct gossmap *gossmap,
956+
const struct node source, const struct node destination,
957+
const u64 pay_amount, const struct arc *prev)
958+
{
959+
struct flow **flows = tal_arr(ctx, struct flow *, 0);
960+
961+
size_t length = 0;
962+
963+
for (u32 cur_idx = destination.idx; cur_idx != source.idx;) {
964+
assert(cur_idx != INVALID_INDEX);
965+
length++;
966+
struct arc arc = prev[cur_idx];
967+
struct node next = arc_tail(graph, arc);
968+
cur_idx = next.idx;
969+
}
970+
struct flow *f = tal(ctx, struct flow);
971+
f->path = tal_arr(f, const struct gossmap_chan *, length);
972+
f->dirs = tal_arr(f, int, length);
973+
974+
for (u32 cur_idx = destination.idx; cur_idx != source.idx;) {
975+
int chandir;
976+
u32 chanidx;
977+
struct arc arc = prev[cur_idx];
978+
arc_to_parts(arc, &chanidx, &chandir, NULL, NULL);
979+
980+
length--;
981+
f->path[length] = gossmap_chan_byidx(gossmap, chanidx);
982+
f->dirs[length] = chandir;
983+
984+
struct node next = arc_tail(graph, arc);
985+
cur_idx = next.idx;
986+
}
987+
988+
tal_arr_expand(&flows, f);
989+
return flows;
990+
}
991+
952992
// TODO(eduardo): choose some default values for the minflow parameters
953993
/* eduardo: I think it should be clear that this module deals with linear
954994
* flows, ie. base fees are not considered. Hence a flow along a path is
@@ -967,8 +1007,7 @@ struct flow **minflow(const tal_t *ctx,
9671007
const struct gossmap_node *target,
9681008
struct amount_msat amount,
9691009
u32 mu,
970-
double delay_feefactor,
971-
bool single_part)
1010+
double delay_feefactor)
9721011
{
9731012
struct flow **flow_paths;
9741013
/* We allocate everything off this, and free it at the end,
@@ -1051,31 +1090,105 @@ struct flow **minflow(const tal_t *ctx,
10511090
goto fail;
10521091
}
10531092
tal_free(working_ctx);
1093+
return flow_paths;
10541094

1055-
/* This is dumb, but if you don't support MPP you don't deserve any
1056-
* better. Pile it into the largest part if not already. */
1057-
if (single_part) {
1058-
struct flow *best = flow_paths[0];
1059-
for (size_t i = 1; i < tal_count(flow_paths); i++) {
1060-
if (amount_msat_greater(flow_paths[i]->delivers, best->delivers))
1061-
best = flow_paths[i];
1062-
}
1063-
for (size_t i = 0; i < tal_count(flow_paths); i++) {
1064-
if (flow_paths[i] == best)
1065-
continue;
1066-
if (!amount_msat_accumulate(&best->delivers,
1067-
flow_paths[i]->delivers)) {
1068-
rq_log(tmpctx, rq, LOG_BROKEN,
1069-
"%s: failed to extract accumulate flow paths %s+%s",
1070-
__func__,
1071-
fmt_amount_msat(tmpctx, best->delivers),
1072-
fmt_amount_msat(tmpctx, flow_paths[i]->delivers));
1073-
goto fail;
1074-
}
1075-
}
1076-
flow_paths[0] = best;
1077-
tal_resize(&flow_paths, 1);
1095+
fail:
1096+
tal_free(working_ctx);
1097+
return NULL;
1098+
}
1099+
1100+
static struct flow **single_path_flow(const tal_t *ctx,
1101+
const struct route_query *rq,
1102+
const struct gossmap_node *source,
1103+
const struct gossmap_node *target,
1104+
struct amount_msat amount, u32 mu,
1105+
double delay_feefactor)
1106+
{
1107+
struct flow **flow_paths;
1108+
/* We allocate everything off this, and free it at the end,
1109+
* as we can be called multiple times without cleaning tmpctx! */
1110+
tal_t *working_ctx = tal(NULL, char);
1111+
struct pay_parameters *params = tal(working_ctx, struct pay_parameters);
1112+
1113+
params->rq = rq;
1114+
params->source = source;
1115+
params->target = target;
1116+
params->amount = amount;
1117+
/* for the single-path solver the accuracy does not detriment
1118+
* performance */
1119+
params->accuracy = AMOUNT_MSAT(1000);
1120+
params->delay_feefactor = delay_feefactor;
1121+
params->base_fee_penalty = base_fee_penalty_estimate(amount);
1122+
1123+
// build the uncertainty network with linearization and residual arcs
1124+
struct linear_network *linear_network =
1125+
init_linear_network(working_ctx, params);
1126+
1127+
const size_t max_num_chans = gossmap_max_chan_idx(rq->gossmap);
1128+
const size_t max_num_arcs = max_num_chans * ARCS_PER_CHANNEL;
1129+
const size_t max_num_nodes = gossmap_max_node_idx(rq->gossmap);
1130+
1131+
struct graph *graph = graph_new(working_ctx, max_num_nodes,
1132+
max_num_arcs, ARC_DUAL_BITOFF);
1133+
double *arc_prob_cost = tal_arr(working_ctx, double, max_num_arcs);
1134+
for (size_t i = 0; i < max_num_arcs; ++i)
1135+
arc_prob_cost[i] = DBL_MAX;
1136+
s64 *arc_fee_cost = tal_arr(working_ctx, s64, max_num_arcs);
1137+
for (size_t i = 0; i < max_num_arcs; ++i)
1138+
arc_fee_cost[i] = INT64_MAX;
1139+
s64 *capacity = tal_arrz(working_ctx, s64, max_num_arcs);
1140+
1141+
// FIXME: compute costs for arcs
1142+
1143+
const struct node dst = {.idx = gossmap_node_idx(rq->gossmap, target)};
1144+
const struct node src = {.idx = gossmap_node_idx(rq->gossmap, source)};
1145+
1146+
/* Since we have constraint accuracy, ask to find a payment solution
1147+
* that can pay a bit more than the actual value rather than undershoot
1148+
* it. That's why we use the ceil function here. */
1149+
const u64 pay_amount =
1150+
amount_msat_ratio_ceil(params->amount, params->accuracy);
1151+
1152+
// FIXME: combine cost functions
1153+
combine_cost_function(working_ctx, linear_network, residual_network,
1154+
rq->biases, mu);
1155+
1156+
s64 *potential = tal_arrz(working_ctx, s64, max_num_nodes);
1157+
s64 *distance = tal_arrz(working_ctx, s64, max_num_nodes);
1158+
struct arc *prev = tal_arrz(working_ctx, struct arc, max_num_nodes);
1159+
1160+
/* We solve a linear cost flow problem. */
1161+
if (!dijkstra_path(working_ctx, linear_network->graph, src, dst,
1162+
/* prune = */ true, residual_network->cap,
1163+
pay_amount, residual_network->cost, potential, prev,
1164+
distance)) {
1165+
rq_log(tmpctx, rq, LOG_BROKEN,
1166+
"%s: could not find a feasible single path", __func__);
1167+
goto fail;
10781168
}
1169+
1170+
/* We dissect the flow into payment routes.
1171+
* Actual amounts considering fees are computed for every
1172+
* channel in the routes. */
1173+
flow_paths =
1174+
get_flow_singlepath(ctx, working_ctx, linear_network->graph,
1175+
rq->gossmap, src, dst, pay_amount, prev);
1176+
if (!flow_paths) {
1177+
rq_log(tmpctx, rq, LOG_BROKEN,
1178+
"%s: failed to extract flow paths from the single-path "
1179+
"solution",
1180+
__func__);
1181+
goto fail;
1182+
}
1183+
if (tal_count(flow_paths) != 1) {
1184+
rq_log(
1185+
tmpctx, rq, LOG_BROKEN,
1186+
"%s: single-path solution returned a multi route solution",
1187+
__func__);
1188+
goto fail;
1189+
}
1190+
tal_free(working_ctx);
1191+
10791192
return flow_paths;
10801193

10811194
fail:
@@ -1099,22 +1212,22 @@ static struct amount_msat linear_flows_cost(struct flow **flows,
10991212
return total;
11001213
}
11011214

1102-
1103-
const char *default_routes(const tal_t *ctx, struct route_query *rq,
1104-
const struct gossmap_node *srcnode,
1105-
const struct gossmap_node *dstnode,
1106-
struct amount_msat amount, bool single_path,
1107-
struct amount_msat maxfee, u32 finalcltv,
1108-
u32 maxdelay, struct flow ***flows,
1109-
double *probability)
1215+
static const char *linear_routes(
1216+
const tal_t *ctx, struct route_query *rq,
1217+
const struct gossmap_node *srcnode, const struct gossmap_node *dstnode,
1218+
struct amount_msat amount, struct amount_msat maxfee, u32 finalcltv,
1219+
u32 maxdelay, struct flow ***flows, double *probability,
1220+
struct flow **(*solver_cb)(const tal_t *, const struct route_query *,
1221+
const struct gossmap_node *,
1222+
const struct gossmap_node *, struct amount_msat,
1223+
u32, double))
11101224
{
11111225
const char *ret;
11121226
double delay_feefactor = 1.0 / 1000000;
11131227

11141228
/* First up, don't care about fees (well, just enough to tiebreak!) */
11151229
u32 mu = 1;
1116-
*flows = minflow(ctx, rq, srcnode, dstnode, amount, mu, delay_feefactor,
1117-
single_path);
1230+
*flows = solver_cb(ctx, rq, srcnode, dstnode, amount, mu, delay_feefactor);
11181231
if (!*flows) {
11191232
ret = explain_failure(ctx, rq, srcnode, dstnode, amount);
11201233
goto fail;
@@ -1128,8 +1241,8 @@ const char *default_routes(const tal_t *ctx, struct route_query *rq,
11281241
" (> %i), retrying with delay_feefactor %f...",
11291242
flows_worst_delay(*flows), maxdelay - finalcltv,
11301243
delay_feefactor);
1131-
*flows = minflow(ctx, rq, srcnode, dstnode, amount, mu,
1132-
delay_feefactor, single_path);
1244+
*flows = solver_cb(ctx, rq, srcnode, dstnode, amount, mu,
1245+
delay_feefactor);
11331246
if (!*flows || delay_feefactor > 10) {
11341247
ret = rq_log(
11351248
ctx, rq, LOG_UNUSUAL,
@@ -1152,9 +1265,8 @@ const char *default_routes(const tal_t *ctx, struct route_query *rq,
11521265
"retrying with mu of %u%%...",
11531266
fmt_amount_msat(tmpctx, flowset_fee(rq->plugin, *flows)),
11541267
fmt_amount_msat(tmpctx, maxfee), mu);
1155-
new_flows =
1156-
minflow(ctx, rq, srcnode, dstnode, amount,
1157-
mu > 100 ? 100 : mu, delay_feefactor, single_path);
1268+
new_flows = solver_cb(ctx, rq, srcnode, dstnode, amount,
1269+
mu > 100 ? 100 : mu, delay_feefactor);
11581270
if (!*flows || mu >= 100) {
11591271
ret = rq_log(
11601272
ctx, rq, LOG_UNUSUAL,
@@ -1235,3 +1347,27 @@ const char *default_routes(const tal_t *ctx, struct route_query *rq,
12351347
assert(ret != NULL);
12361348
return ret;
12371349
}
1350+
1351+
const char *default_routes(const tal_t *ctx, struct route_query *rq,
1352+
const struct gossmap_node *srcnode,
1353+
const struct gossmap_node *dstnode,
1354+
struct amount_msat amount, struct amount_msat maxfee,
1355+
u32 finalcltv, u32 maxdelay, struct flow ***flows,
1356+
double *probability)
1357+
{
1358+
return linear_routes(ctx, rq, srcnode, dstnode, amount, maxfee,
1359+
finalcltv, maxdelay, flows, probability, minflow);
1360+
}
1361+
1362+
const char *single_path_routes(const tal_t *ctx, struct route_query *rq,
1363+
const struct gossmap_node *srcnode,
1364+
const struct gossmap_node *dstnode,
1365+
struct amount_msat amount,
1366+
struct amount_msat maxfee, u32 finalcltv,
1367+
u32 maxdelay, struct flow ***flows,
1368+
double *probability)
1369+
{
1370+
return linear_routes(ctx, rq, srcnode, dstnode, amount, maxfee,
1371+
finalcltv, maxdelay, flows, probability,
1372+
single_path_flow);
1373+
}

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
/* To sanity check: this is the approximation mcf uses for the cost
3837
* of each channel. */
@@ -45,9 +44,18 @@ struct amount_msat linear_flow_cost(const struct flow *flow,
4544
const char *default_routes(const tal_t *ctx, struct route_query *rq,
4645
const struct gossmap_node *srcnode,
4746
const struct gossmap_node *dstnode,
48-
struct amount_msat amount, bool single_path,
47+
struct amount_msat amount,
4948
struct amount_msat maxfee, u32 finalcltv,
5049
u32 maxdelay, struct flow ***flows,
5150
double *probability);
5251

52+
/* A wrapper to the single-path constrained solver. */
53+
const char *single_path_routes(const tal_t *ctx, struct route_query *rq,
54+
const struct gossmap_node *srcnode,
55+
const struct gossmap_node *dstnode,
56+
struct amount_msat amount,
57+
struct amount_msat maxfee, u32 finalcltv,
58+
u32 maxdelay, struct flow ***flows,
59+
double *probability);
60+
5361
#endif /* LIGHTNING_PLUGINS_ASKRENE_MCF_H */

0 commit comments

Comments
 (0)