Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .msggen.json
Original file line number Diff line number Diff line change
Expand Up @@ -1863,6 +1863,7 @@
"GetRoutes.layers[]": 4,
"GetRoutes.maxdelay": 8,
"GetRoutes.maxfee_msat": 5,
"GetRoutes.maxparts": 9,
"GetRoutes.source": 1
},
"GetroutesResponse": {
Expand Down Expand Up @@ -7534,6 +7535,10 @@
"added": "v24.08",
"deprecated": null
},
"GetRoutes.maxparts": {
"added": "v25.09",
"deprecated": null
},
"GetRoutes.probability_ppm": {
"added": "v24.08",
"deprecated": null
Expand Down
1 change: 1 addition & 0 deletions cln-grpc/proto/node.proto

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions cln-grpc/src/convert.rs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions cln-rpc/src/model.rs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions contrib/msggen/msggen/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -15221,6 +15221,14 @@
"Maximum number of blocks of delay for the route. Cannot be bigger than 2016."
],
"default": "2016"
},
"maxparts": {
"type": "u32",
"added": "v25.09",
"description": [
"Maximum number of routes in the solution."
],
"default": "100"
}
}
},
Expand Down
180 changes: 90 additions & 90 deletions contrib/pyln-grpc-proto/pyln/grpc/node_pb2.py

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions doc/schemas/getroutes.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,14 @@
"Maximum number of blocks of delay for the route. Cannot be bigger than 2016."
],
"default": "2016"
},
"maxparts": {
"type": "u32",
"added": "v25.09",
"description": [
"Maximum number of routes in the solution."
],
"default": "100"
}
}
},
Expand Down
7 changes: 7 additions & 0 deletions plugins/askrene/askrene.c
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,7 @@ struct getroutes_info {
struct additional_cost_htable *additional_costs;
/* Non-NULL if we are told to use "auto.localchans" */
struct layer *local_layer;
u32 maxparts;
};

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

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

if (!param_check(cmd, buffer, params,
p_req("source", param_node_id, &source),
Expand All @@ -780,6 +784,8 @@ static struct command_result *json_getroutes(struct command *cmd,
p_req("final_cltv", param_u32, &finalcltv),
p_opt_def("maxdelay", param_u32, &maxdelay,
maxdelay_allowed),
p_opt_def("maxparts", param_u32, &maxparts,
default_maxparts),
p_opt_dev("dev_algorithm", param_algorithm,
&dev_algo, ALGO_DEFAULT),
NULL))
Expand Down Expand Up @@ -811,6 +817,7 @@ static struct command_result *json_getroutes(struct command *cmd,
info->dev_algo = *dev_algo;
info->additional_costs = tal(info, struct additional_cost_htable);
additional_cost_htable_init(info->additional_costs);
info->maxparts = *maxparts;

if (have_layer(info->layers, "auto.localchans")) {
struct out_req *req;
Expand Down
3 changes: 3 additions & 0 deletions plugins/askrene/askrene.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ struct route_query {

/* channels we disable during computation to meet constraints */
bitmap *disabled_chans;

/* limit the number of paths in the solution */
u32 maxparts;
};

/* Given a gossmap channel, get the current known min/max */
Expand Down
61 changes: 57 additions & 4 deletions plugins/askrene/mcf.c
Original file line number Diff line number Diff line change
Expand Up @@ -1375,7 +1375,26 @@ linear_routes(const tal_t *ctx, struct route_query *rq,
struct reserve_hop *reservations = new_reservations(working_ctx, rq);

while (!amount_msat_is_zero(amount_to_deliver)) {
new_flows = tal_free(new_flows);
size_t num_parts, parts_slots, excess_parts;

/* FIXME: This algorithm to limit the number of parts is dumb
* for two reasons:
* 1. it does not take into account that several loop
* iterations here may produce two flows along the same
* path that after "squash_flows" become a single flow.
* 2. limiting the number of "slots" to 1 makes us fail to
* see some solutions that use more than one of those
* existing paths.
*
* A better approach could be to run MCF, remove the excess
* paths and then recompute a MCF on a network where each arc is
* one of the previously remaining paths, ie. redistributing the
* payment amount among the selected paths in a cost-efficient
* way. */
new_flows = tal_free(new_flows);
num_parts = tal_count(*flows);
assert(num_parts < rq->maxparts);
parts_slots = rq->maxparts - num_parts;

/* If the amount_to_deliver is very small we better use a single
* path computation because:
Expand All @@ -1385,9 +1404,12 @@ linear_routes(const tal_t *ctx, struct route_query *rq,
* refine_with_fees_and_limits we might have a set of flows that
* do not deliver the entire payment amount by just a small
* amount. */
if(amount_msat_less_eq(amount_to_deliver, SINGLE_PATH_THRESHOLD)){
new_flows = single_path_flow(working_ctx, rq, srcnode, dstnode,
amount_to_deliver, mu, delay_feefactor);
if (amount_msat_less_eq(amount_to_deliver,
SINGLE_PATH_THRESHOLD) ||
parts_slots == 1) {
new_flows = single_path_flow(working_ctx, rq, srcnode,
dstnode, amount_to_deliver,
mu, delay_feefactor);
} else {
new_flows =
solver(working_ctx, rq, srcnode, dstnode,
Expand Down Expand Up @@ -1425,6 +1447,27 @@ linear_routes(const tal_t *ctx, struct route_query *rq,
assert(!amount_msat_is_zero(new_flows[i]->delivers));
}

if (tal_count(new_flows) > parts_slots) {
/* Remove the excees of parts and leave one slot for the
* next round of computations. */
excess_parts = 1 + tal_count(new_flows) - parts_slots;
} else if (tal_count(new_flows) == parts_slots &&
amount_msat_less(all_deliver, amount_to_deliver)) {
/* Leave exactly 1 slot for the next round of
* computations. */
excess_parts = 1;
} else
excess_parts = 0;
if (excess_parts > 0 &&
!remove_flows(&new_flows, excess_parts)) {
error_message = rq_log(ctx, rq, LOG_BROKEN,
"%s: failed to remove %zu"
" flows from a set of %zu",
__func__, excess_parts,
tal_count(new_flows));
goto fail;
}

/* Is this set of flows too expensive?
* We can check if the new flows are within the fee budget,
* however in some cases we have discarded some flows at this
Expand Down Expand Up @@ -1570,6 +1613,16 @@ linear_routes(const tal_t *ctx, struct route_query *rq,
*flows = tal_free(*flows);
goto fail;
}
if (tal_count(*flows) > rq->maxparts) {
error_message = rq_log(
rq, rq, LOG_BROKEN,
"%s: the number of flows (%zu) exceeds the limit set "
"on payment parts (%" PRIu32
"), please submit a bug report",
__func__, tal_count(*flows), rq->maxparts);
*flows = tal_free(*flows);
goto fail;
}

return NULL;
fail:
Expand Down
27 changes: 27 additions & 0 deletions plugins/askrene/refine.c
Original file line number Diff line number Diff line change
Expand Up @@ -632,3 +632,30 @@ double flows_probability(const tal_t *ctx, struct route_query *rq,
tal_free(working_ctx);
return probability;
}

/* Compare flows by deliver amount */
static int reverse_cmp_flows(struct flow *const *fa, struct flow *const *fb,
void *unused UNUSED)
{
if (amount_msat_eq((*fa)->delivers, (*fb)->delivers))
return 0;
if (amount_msat_greater((*fa)->delivers, (*fb)->delivers))
return -1;
return 1;
}

bool remove_flows(struct flow ***flows, u32 n)
{
if (n == 0)
goto fail;
if (n > tal_count(*flows))
goto fail;
asort(*flows, tal_count(*flows), reverse_cmp_flows, NULL);
for (size_t count = tal_count(*flows); n > 0; n--, count--) {
assert(count > 0);
tal_arr_remove(flows, count - 1);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You didn't free the flow when you removed it? I think you'll want this helper later, so:

static void remove_and_free_flow(struct flow ***flows, size_t i)
{
tal_free((*flows)[i]);
tal_arr_remove(flows, i);
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But in this case it doesn't leak, so we can leave this neatning until later.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Removal was handled in the first version but I forgot after the changes.
Will do.

}
return true;
fail:
return false;
}
4 changes: 4 additions & 0 deletions plugins/askrene/refine.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,8 @@ void squash_flows(const tal_t *ctx, struct route_query *rq,

double flows_probability(const tal_t *ctx, struct route_query *rq,
struct flow ***flows);

/* Helper function: removes n flows from the set. It will remove those flows
* with the lowest amount values. */
bool remove_flows(struct flow ***flows, u32 n);
#endif /* LIGHTNING_PLUGINS_ASKRENE_REFINE_H */
19 changes: 19 additions & 0 deletions plugins/xpay/xpay.c
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ struct payment {
struct amount_msat maxfee;
/* Maximum delay on the route we're ok with */
u32 *maxdelay;
/* Maximum number of payment routes that can be pending. */
u32 *maxparts;
/* Do we have to do it all in a single part? */
bool disable_mpp;
/* BOLT11 payment secret (NULL for BOLT12, it uses blinded paths) */
Expand Down Expand Up @@ -334,6 +336,15 @@ static u32 initial_cltv_delta(const struct attempt *attempt)
return attempt->hops[0].cltv_value_in;
}

/* Find the total number of pending attempts */
static size_t count_current_attempts(const struct payment *payment)
{
const struct attempt *i;
size_t result = 0;
list_for_each(&payment->current_attempts, i, list) { result++; }
return result;
}

/* We total up all attempts which succeeded in the past (if we're not
* in slow mode, that's only the one which just succeeded), and then we
* assume any others currently-in-flight will also succeed. */
Expand Down Expand Up @@ -1296,6 +1307,7 @@ static struct command_result *getroutes_for(struct command *aux_cmd,
struct out_req *req;
const struct pubkey *dst;
struct amount_msat maxfee;
size_t count_pending;

/* I would normally assert here, but we have reports of this happening... */
if (amount_msat_is_zero(deliver)) {
Expand Down Expand Up @@ -1356,6 +1368,9 @@ static struct command_result *getroutes_for(struct command *aux_cmd,
json_add_amount_msat(req->js, "maxfee_msat", maxfee);
json_add_u32(req->js, "final_cltv", payment->final_cltv);
json_add_u32(req->js, "maxdelay", *payment->maxdelay);
count_pending = count_current_attempts(payment);
assert(*payment->maxparts > count_pending);
json_add_u32(req->js, "maxparts", *payment->maxparts - count_pending);

return send_payment_req(aux_cmd, payment, req);
}
Expand Down Expand Up @@ -1646,8 +1661,12 @@ static struct command_result *json_xpay_core(struct command *cmd,
p_opt_def("retry_for", param_number, &retryfor, 60),
p_opt("partial_msat", param_msat, &partial),
p_opt_def("maxdelay", param_u32, &payment->maxdelay, 2016),
p_opt_dev("dev_maxparts", param_u32, &payment->maxparts, 100),
NULL))
return command_param_failed();
if (*payment->maxparts == 0)
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"maxparts cannot be zero");

list_head_init(&payment->current_attempts);
list_head_init(&payment->past_attempts);
Expand Down
Loading