Skip to content

Commit 9d260d4

Browse files
committed
askrene: add maxparts
Changelog-Added: askrene: add a new parameter maxparts to getroutes that limits the number of routes in the solution. Signed-off-by: Lagrang3 <[email protected]>
1 parent cc01601 commit 9d260d4

File tree

10 files changed

+183
-94
lines changed

10 files changed

+183
-94
lines changed

.msggen.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1863,6 +1863,7 @@
18631863
"GetRoutes.layers[]": 4,
18641864
"GetRoutes.maxdelay": 8,
18651865
"GetRoutes.maxfee_msat": 5,
1866+
"GetRoutes.maxparts": 9,
18661867
"GetRoutes.source": 1
18671868
},
18681869
"GetroutesResponse": {
@@ -7534,6 +7535,10 @@
75347535
"added": "v24.08",
75357536
"deprecated": null
75367537
},
7538+
"GetRoutes.maxparts": {
7539+
"added": "v25.09",
7540+
"deprecated": null
7541+
},
75377542
"GetRoutes.probability_ppm": {
75387543
"added": "v24.08",
75397544
"deprecated": null

cln-grpc/proto/node.proto

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cln-grpc/src/convert.rs

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cln-rpc/src/model.rs

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

contrib/msggen/msggen/schema.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15221,6 +15221,14 @@
1522115221
"Maximum number of blocks of delay for the route. Cannot be bigger than 2016."
1522215222
],
1522315223
"default": "2016"
15224+
},
15225+
"maxparts": {
15226+
"type": "u32",
15227+
"added": "v25.09",
15228+
"description": [
15229+
"Maximum number of routes in the solution."
15230+
],
15231+
"default": "100"
1522415232
}
1522515233
}
1522615234
},

contrib/pyln-grpc-proto/pyln/grpc/node_pb2.py

Lines changed: 90 additions & 90 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

doc/schemas/getroutes.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,14 @@
7373
"Maximum number of blocks of delay for the route. Cannot be bigger than 2016."
7474
],
7575
"default": "2016"
76+
},
77+
"maxparts": {
78+
"type": "u32",
79+
"added": "v25.09",
80+
"description": [
81+
"Maximum number of routes in the solution."
82+
],
83+
"default": "100"
7684
}
7785
}
7886
},

plugins/askrene/askrene.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,7 @@ struct getroutes_info {
366366
struct additional_cost_htable *additional_costs;
367367
/* Non-NULL if we are told to use "auto.localchans" */
368368
struct layer *local_layer;
369+
u32 maxparts;
369370
};
370371

371372
static void apply_layers(struct askrene *askrene, struct route_query *rq,
@@ -551,6 +552,7 @@ static struct command_result *do_getroutes(struct command *cmd,
551552
rq->capacities = tal_dup_talarr(rq, fp16_t, askrene->capacities);
552553
/* FIXME: we still need to do something useful with these */
553554
rq->additional_costs = info->additional_costs;
555+
rq->maxparts = info->maxparts;
554556

555557
/* apply selected layers to the localmods */
556558
apply_layers(askrene, rq, &info->source, info->amount, localmods,
@@ -764,12 +766,14 @@ static struct command_result *json_getroutes(struct command *cmd,
764766
*/
765767
/* FIXME: Typo in spec for CLTV in descripton! But it breaks our spelling check, so we omit it above */
766768
const u32 maxdelay_allowed = 2016;
769+
const u32 default_maxparts = 100;
767770
struct getroutes_info *info = tal(cmd, struct getroutes_info);
768771
/* param functions require pointers */
769772
struct node_id *source, *dest;
770773
struct amount_msat *amount, *maxfee;
771774
u32 *finalcltv, *maxdelay;
772775
enum algorithm *dev_algo;
776+
u32 *maxparts;
773777

774778
if (!param_check(cmd, buffer, params,
775779
p_req("source", param_node_id, &source),
@@ -780,6 +784,8 @@ static struct command_result *json_getroutes(struct command *cmd,
780784
p_req("final_cltv", param_u32, &finalcltv),
781785
p_opt_def("maxdelay", param_u32, &maxdelay,
782786
maxdelay_allowed),
787+
p_opt_def("maxparts", param_u32, &maxparts,
788+
default_maxparts),
783789
p_opt_dev("dev_algorithm", param_algorithm,
784790
&dev_algo, ALGO_DEFAULT),
785791
NULL))
@@ -811,6 +817,7 @@ static struct command_result *json_getroutes(struct command *cmd,
811817
info->dev_algo = *dev_algo;
812818
info->additional_costs = tal(info, struct additional_cost_htable);
813819
additional_cost_htable_init(info->additional_costs);
820+
info->maxparts = *maxparts;
814821

815822
if (have_layer(info->layers, "auto.localchans")) {
816823
struct out_req *req;

plugins/askrene/askrene.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ struct route_query {
6464

6565
/* channels we disable during computation to meet constraints */
6666
bitmap *disabled_chans;
67+
68+
/* limit the number of paths in the solution */
69+
u32 maxparts;
6770
};
6871

6972
/* Given a gossmap channel, get the current known min/max */

plugins/askrene/mcf.c

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1375,7 +1375,26 @@ linear_routes(const tal_t *ctx, struct route_query *rq,
13751375
struct reserve_hop *reservations = new_reservations(working_ctx, rq);
13761376

13771377
while (!amount_msat_is_zero(amount_to_deliver)) {
1378-
new_flows = tal_free(new_flows);
1378+
size_t num_parts, parts_slots, excess_parts;
1379+
1380+
/* FIXME: This algorithm to limit the number of parts is dumb
1381+
* for two reasons:
1382+
* 1. it does not take into account that several loop
1383+
* iterations here may produce two flows along the same
1384+
* path that after "squash_flows" become a single flow.
1385+
* 2. limiting the number of "slots" to 1 makes us fail to
1386+
* see some solutions that use more than one of those
1387+
* existing paths.
1388+
*
1389+
* A better approach could be to run MCF, remove the excess
1390+
* paths and then recompute a MCF on a network where each arc is
1391+
* one of the previously remaining paths, ie. redistributing the
1392+
* payment amount among the selected paths in a cost-efficient
1393+
* way. */
1394+
new_flows = tal_free(new_flows);
1395+
num_parts = tal_count(*flows);
1396+
assert(num_parts < rq->maxparts);
1397+
parts_slots = rq->maxparts - num_parts;
13791398

13801399
/* If the amount_to_deliver is very small we better use a single
13811400
* path computation because:
@@ -1385,9 +1404,12 @@ linear_routes(const tal_t *ctx, struct route_query *rq,
13851404
* refine_with_fees_and_limits we might have a set of flows that
13861405
* do not deliver the entire payment amount by just a small
13871406
* amount. */
1388-
if(amount_msat_less_eq(amount_to_deliver, SINGLE_PATH_THRESHOLD)){
1389-
new_flows = single_path_flow(working_ctx, rq, srcnode, dstnode,
1390-
amount_to_deliver, mu, delay_feefactor);
1407+
if (amount_msat_less_eq(amount_to_deliver,
1408+
SINGLE_PATH_THRESHOLD) ||
1409+
parts_slots == 1) {
1410+
new_flows = single_path_flow(working_ctx, rq, srcnode,
1411+
dstnode, amount_to_deliver,
1412+
mu, delay_feefactor);
13911413
} else {
13921414
new_flows =
13931415
solver(working_ctx, rq, srcnode, dstnode,
@@ -1425,6 +1447,27 @@ linear_routes(const tal_t *ctx, struct route_query *rq,
14251447
assert(!amount_msat_is_zero(new_flows[i]->delivers));
14261448
}
14271449

1450+
if (tal_count(new_flows) > parts_slots) {
1451+
/* Remove the excees of parts and leave one slot for the
1452+
* next round of computations. */
1453+
excess_parts = 1 + tal_count(new_flows) - parts_slots;
1454+
} else if (tal_count(new_flows) == parts_slots &&
1455+
amount_msat_less(all_deliver, amount_to_deliver)) {
1456+
/* Leave exactly 1 slot for the next round of
1457+
* computations. */
1458+
excess_parts = 1;
1459+
} else
1460+
excess_parts = 0;
1461+
if (excess_parts > 0 &&
1462+
!remove_flows(&new_flows, excess_parts)) {
1463+
error_message = rq_log(ctx, rq, LOG_BROKEN,
1464+
"%s: failed to remove %zu"
1465+
" flows from a set of %zu",
1466+
__func__, excess_parts,
1467+
tal_count(new_flows));
1468+
goto fail;
1469+
}
1470+
14281471
/* Is this set of flows too expensive?
14291472
* We can check if the new flows are within the fee budget,
14301473
* however in some cases we have discarded some flows at this
@@ -1570,6 +1613,16 @@ linear_routes(const tal_t *ctx, struct route_query *rq,
15701613
*flows = tal_free(*flows);
15711614
goto fail;
15721615
}
1616+
if (tal_count(*flows) > rq->maxparts) {
1617+
error_message = rq_log(
1618+
rq, rq, LOG_BROKEN,
1619+
"%s: the number of flows (%zu) exceeds the limit set "
1620+
"on payment parts (%" PRIu32
1621+
"), please submit a bug report",
1622+
__func__, tal_count(*flows), rq->maxparts);
1623+
*flows = tal_free(*flows);
1624+
goto fail;
1625+
}
15731626

15741627
return NULL;
15751628
fail:

0 commit comments

Comments
 (0)