Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

xpay maxdelay parameter #7969

Merged
merged 2 commits into from
Jan 22, 2025
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
10 changes: 10 additions & 0 deletions .msggen.json
Original file line number Diff line number Diff line change
Expand Up @@ -1772,6 +1772,7 @@
"GetRoutes.final_cltv": 7,
"GetRoutes.finalcltv": 6,
"GetRoutes.layers[]": 4,
"GetRoutes.maxdelay": 8,
"GetRoutes.maxfee_msat": 5,
"GetRoutes.source": 1
},
Expand Down Expand Up @@ -3728,6 +3729,7 @@
"Xpay.amount_msat": 2,
"Xpay.invstring": 1,
"Xpay.layers[]": 4,
"Xpay.maxdelay": 7,
"Xpay.maxfee": 3,
"Xpay.partial_msat": 6,
"Xpay.retry_for": 5
Expand Down Expand Up @@ -7317,6 +7319,10 @@
"added": "v24.08",
"deprecated": null
},
"GetRoutes.maxdelay": {
"added": "v25.02",
"deprecated": null
},
"GetRoutes.maxfee_msat": {
"added": "v24.08",
"deprecated": null
Expand Down Expand Up @@ -12969,6 +12975,10 @@
"added": "v24.11",
"deprecated": null
},
"Xpay.maxdelay": {
"added": "v25.02",
"deprecated": null
},
"Xpay.maxfee": {
"added": "v24.11",
"deprecated": null
Expand Down
2 changes: 2 additions & 0 deletions cln-grpc/proto/node.proto

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

4 changes: 4 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.

4 changes: 4 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.

20 changes: 18 additions & 2 deletions contrib/msggen/msggen/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -732,7 +732,7 @@
"description": {
"type": "string",
"description": [
"The bias (-100 to +100)"
"Description/annotation to display in askrene-listlayers(7)"
]
}
}
Expand Down Expand Up @@ -1208,7 +1208,7 @@
"description": {
"type": "string",
"description": [
"The bias (-100 to +100)"
"Description/annotation for the bias"
]
}
}
Expand Down Expand Up @@ -15827,6 +15827,14 @@
"description": [
"Number of blocks for the final node. We need to know this because no HTLC is allowed to have a CLTV delay more than 2016 blocks."
]
},
"maxdelay": {
"type": "u32",
"added": "v25.02",
"description": [
"Maximum number of blocks of delay for the route. Cannot be bigger than 2016."
],
"default": "2016"
}
}
},
Expand Down Expand Up @@ -36357,6 +36365,14 @@
"description": [
"Explicitly state that you are only paying some part of the invoice. Presumably someone else is paying the rest (otherwise the payment will time out at the recipient)."
]
},
"maxdelay": {
"type": "u32",
"added": "v25.02",
"description": [
"A payment may be delayed for up to `maxdelay` blocks by another node; clients should be prepared for this worst case."
],
"default": "2016"
}
}
},
Expand Down
152 changes: 76 additions & 76 deletions contrib/pyln-grpc-proto/pyln/grpc/node_pb2.py

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion doc/schemas/lightning-askrene-create-layer.json
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@
"description": {
"type": "string",
"description": [
"The bias (-100 to +100)"
"Description/annotation to display in askrene-listlayers(7)"
]
}
}
Expand Down
2 changes: 1 addition & 1 deletion doc/schemas/lightning-askrene-listlayers.json
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@
"description": {
"type": "string",
"description": [
"The bias (-100 to +100)"
"Description/annotation for the bias"
]
}
}
Expand Down
8 changes: 8 additions & 0 deletions doc/schemas/lightning-getroutes.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,14 @@
"description": [
"Number of blocks for the final node. We need to know this because no HTLC is allowed to have a CLTV delay more than 2016 blocks."
]
},
"maxdelay": {
"type": "u32",
"added": "v25.02",
"description": [
"Maximum number of blocks of delay for the route. Cannot be bigger than 2016."
],
"default": "2016"
}
}
},
Expand Down
8 changes: 8 additions & 0 deletions doc/schemas/lightning-xpay.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,14 @@
"description": [
"Explicitly state that you are only paying some part of the invoice. Presumably someone else is paying the rest (otherwise the payment will time out at the recipient)."
]
},
"maxdelay": {
"type": "u32",
"added": "v25.02",
"description": [
"A payment may be delayed for up to `maxdelay` blocks by another node; clients should be prepared for this worst case."
],
"default": "2016"
}
}
},
Expand Down
34 changes: 22 additions & 12 deletions plugins/askrene/askrene.c
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,7 @@ static const char *get_routes(const tal_t *ctx,
struct amount_msat amount,
struct amount_msat maxfee,
u32 finalcltv,
u32 maxdelay,
const char **layers,
struct gossmap_localmods *localmods,
const struct layer *local_layer,
Expand Down Expand Up @@ -418,18 +419,11 @@ static const char *get_routes(const tal_t *ctx,
}

/* Too much delay? */
/* BOLT #4:
* ## `max_htlc_cltv` Selection
*
* This ... value is defined as 2016 blocks, based on historical value
* deployed by Lightning implementations.
*/
/* FIXME: Typo in spec for CLTV in descripton! But it breaks our spelling check, so we omit it above */
while (finalcltv + flows_worst_delay(flows) > 2016) {
while (finalcltv + flows_worst_delay(flows) > maxdelay) {
delay_feefactor *= 2;
rq_log(tmpctx, rq, LOG_UNUSUAL,
"The worst flow delay is %"PRIu64" (> %i), retrying with delay_feefactor %f...",
flows_worst_delay(flows), 2016 - finalcltv, delay_feefactor);
flows_worst_delay(flows), maxdelay - finalcltv, delay_feefactor);
flows = minflow(rq, rq, srcnode, dstnode, amount,
mu, delay_feefactor);
if (!flows || delay_feefactor > 10) {
Expand Down Expand Up @@ -487,7 +481,7 @@ static const char *get_routes(const tal_t *ctx,
flows = new_flows;
}

if (finalcltv + flows_worst_delay(flows) > 2016) {
if (finalcltv + flows_worst_delay(flows) > maxdelay) {
ret = rq_log(ctx, rq, LOG_UNUSUAL,
"Could not find route without excessive cost or delays");
goto fail;
Expand Down Expand Up @@ -601,7 +595,7 @@ struct getroutes_info {
struct command *cmd;
struct node_id *source, *dest;
struct amount_msat *amount, *maxfee;
u32 *finalcltv;
u32 *finalcltv, *maxdelay;
const char **layers;
struct additional_cost_htable *additional_costs;
/* Non-NULL if we are told to use "auto.localchans" */
Expand All @@ -621,7 +615,7 @@ static struct command_result *do_getroutes(struct command *cmd,
err = get_routes(cmd, cmd,
info->source, info->dest,
*info->amount, *info->maxfee, *info->finalcltv,
info->layers, localmods, info->local_layer,
*info->maxdelay, info->layers, localmods, info->local_layer,
&routes, &amounts, info->additional_costs, &probability);
if (err)
return command_fail(cmd, PAY_ROUTE_NOT_FOUND, "%s", err);
Expand Down Expand Up @@ -751,6 +745,14 @@ static struct command_result *json_getroutes(struct command *cmd,
const char *buffer,
const jsmntok_t *params)
{
/* BOLT #4:
* ## `max_htlc_cltv` Selection
*
* This ... value is defined as 2016 blocks, based on
* historical value deployed by Lightning implementations.
*/
/* FIXME: Typo in spec for CLTV in descripton! But it breaks our spelling check, so we omit it above */
const u32 maxdelay_allowed = 2016;
struct getroutes_info *info = tal(cmd, struct getroutes_info);

if (!param(cmd, buffer, params,
Expand All @@ -760,9 +762,17 @@ static struct command_result *json_getroutes(struct command *cmd,
p_req("layers", param_layer_names, &info->layers),
p_req("maxfee_msat", param_msat, &info->maxfee),
p_req("final_cltv", param_u32, &info->finalcltv),
p_opt_def("maxdelay", param_u32, &info->maxdelay,
maxdelay_allowed),
NULL))
return command_param_failed();

if (*info->maxdelay > maxdelay_allowed) {
return command_fail(cmd, PAY_USER_ERROR,
"maximum delay allowed is %d",
maxdelay_allowed);
}

info->cmd = cmd;
info->additional_costs = tal(info, struct additional_cost_htable);
additional_cost_htable_init(info->additional_costs);
Expand Down
10 changes: 9 additions & 1 deletion plugins/xpay/xpay.c
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ struct payment {
struct amount_msat full_amount;
/* Maximum fee we're prepare to pay */
struct amount_msat maxfee;
/* Maximum delay on the route we're ok with */
u32 *maxdelay;
/* BOLT11 payment secret (NULL for BOLT12, it uses blinded paths) */
const struct secret *payment_secret;
/* BOLT11 payment metadata (NULL for BOLT12, it uses blinded paths) */
Expand Down Expand Up @@ -1186,6 +1188,7 @@ static struct command_result *getroutes_for(struct command *aux_cmd,
json_array_end(req->js);
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);

return send_payment_req(aux_cmd, payment, req);
}
Expand Down Expand Up @@ -1470,6 +1473,7 @@ static struct command_result *json_xpay_core(struct command *cmd,
p_opt("layers", param_string_array, &payment->layers),
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),
NULL))
return command_param_failed();

Expand Down Expand Up @@ -1884,7 +1888,7 @@ static struct command_result *handle_rpc_command(struct command *cmd,
struct xpay *xpay = xpay_of(cmd->plugin);
const jsmntok_t *rpc_tok, *method_tok, *params_tok, *id_tok,
*bolt11 = NULL, *amount_msat = NULL,
*partial_msat = NULL, *retry_for = NULL;
*partial_msat = NULL, *retry_for = NULL, *maxdelay = NULL;
const char *maxfee = NULL;
struct json_stream *response;

Expand Down Expand Up @@ -1933,6 +1937,8 @@ static struct command_result *handle_rpc_command(struct command *cmd,
maxfeepercent = t + 1;
else if (json_tok_streq(buf, t, "exemptfee"))
exemptfee = t + 1;
else if (json_tok_streq(buf, t, "maxdelay"))
maxdelay = t + 1;
else {
plugin_log(cmd->plugin, LOG_INFORM,
"Not redirecting pay (unknown arg %.*s)",
Expand Down Expand Up @@ -1978,6 +1984,8 @@ static struct command_result *handle_rpc_command(struct command *cmd,
json_add_string(response, "maxfee", maxfee);
if (partial_msat)
json_add_tok(response, "partial_msat", partial_msat, buf);
if (maxdelay)
json_add_tok(response, "maxdelay", maxdelay, buf);
json_object_end(response);
json_object_end(response);
return command_finished(cmd, response);
Expand Down
49 changes: 49 additions & 0 deletions tests/test_askrene.py
Original file line number Diff line number Diff line change
Expand Up @@ -679,6 +679,55 @@ def test_getroutes_auto_sourcefree(node_factory):
final_cltv=99)


def test_getroutes_maxdelay(node_factory):
gsfile, nodemap = generate_gossip_store([GenChannel(0, 1, forward=GenChannel.Half(propfee=10000, delay=80)),
GenChannel(0, 1, forward=GenChannel.Half(propfee=10001, delay=40))])

# Set up l1 with this as the gossip_store
l1 = node_factory.get_node(gossip_store_file=gsfile.name)

# Should prefer the cheaper channel
assert l1.rpc.getroutes(source=nodemap[0],
destination=nodemap[1],
amount_msat=1000,
layers=[],
maxfee_msat=1000,
final_cltv=99) == {'probability_ppm': 999999,
'routes': [{'probability_ppm': 999999,
'final_cltv': 99,
'amount_msat': 1000,
'path': [{'short_channel_id_dir': '0x1x0/1',
'next_node_id': nodemap[1],
'amount_msat': 1010,
'delay': 179}]}]}

# But use the channel with lower delay when needed
assert l1.rpc.getroutes(source=nodemap[0],
destination=nodemap[1],
amount_msat=1000,
layers=[],
maxfee_msat=2000,
final_cltv=99,
maxdelay=170) == {'probability_ppm': 999999,
'routes': [{'probability_ppm': 999999,
'final_cltv': 99,
'amount_msat': 1000,
'path': [{'short_channel_id_dir': '0x1x1/1',
'next_node_id': nodemap[1],
'amount_msat': 1010,
'delay': 139}]}]}

# Excessive maxdelay parameter
with pytest.raises(RpcError, match="maximum delay allowed is 2016"):
l1.rpc.getroutes(source=nodemap[0],
destination=nodemap[1],
amount_msat=100000,
layers=[],
maxfee_msat=100,
final_cltv=99,
maxdelay=2017)


def test_getroutes_auto_localchans(node_factory):
"""Test getroutes call with auto.localchans layer"""
l1 = node_factory.get_node()
Expand Down
Loading