Skip to content

Commit 2c2a4e6

Browse files
committed
askrene: add expiration parameter to create-layer
This could be useful if we want to create one-time use layers on the flight and not worry about memory leaks (one time used layers that are not removed constitute a memory leak in a sense). Changelog-Added: askrene: add an optional self-destruction timer (expiration parameter) to new layers. Signed-off-by: Lagrang3 <[email protected]>
1 parent 98679aa commit 2c2a4e6

File tree

3 files changed

+67
-0
lines changed

3 files changed

+67
-0
lines changed

doc/schemas/lightning-askrene-create-layer.json

+7
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,13 @@
2525
"description": [
2626
"True if askrene should save and restore this layer. As a side-effect, create-layer also succeeds if the layer already exists and persistent is true."
2727
]
28+
},
29+
"expiration": {
30+
"type": "u64",
31+
"description": [
32+
"Sets the number of seconds until this layer expires. If this number is specified the layer will be automatically deleted at expiration. A layer cannot be persistent and have an expiration time."
33+
],
34+
"added": "v25.02"
2835
}
2936
}
3037
},

plugins/askrene/askrene.c

+35
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include <common/gossmods_listpeerchannels.h>
1515
#include <common/json_param.h>
1616
#include <common/json_stream.h>
17+
#include <common/memleak.h>
1718
#include <common/route.h>
1819
#include <errno.h>
1920
#include <math.h>
@@ -623,6 +624,7 @@ static struct command_result *do_getroutes(struct command *cmd,
623624
*info->amount, *info->maxfee, *info->finalcltv,
624625
info->layers, localmods, info->local_layer,
625626
&routes, &amounts, info->additional_costs, &probability);
627+
626628
if (err)
627629
return command_fail(cmd, PAY_ROUTE_NOT_FOUND, "%s", err);
628630

@@ -1060,6 +1062,27 @@ static struct command_result *json_askrene_disable_node(struct command *cmd,
10601062
return command_finished(cmd, response);
10611063
}
10621064

1065+
static struct command_result *expire_layer_done(struct command *timer_cmd,
1066+
const char *method,
1067+
const char *buf,
1068+
const jsmntok_t *result,
1069+
void *unused)
1070+
{
1071+
return timer_complete(timer_cmd);
1072+
}
1073+
1074+
static struct command_result *expire_layer(struct command *timer_cmd,
1075+
struct layer *l)
1076+
{
1077+
struct out_req *req;
1078+
req = jsonrpc_request_start(timer_cmd, "askrene-remove-layer",
1079+
expire_layer_done, plugin_broken_cb, NULL);
1080+
json_add_string(req->js, "layer", layer_name(l));
1081+
plugin_log(timer_cmd->plugin, LOG_DBG, "removing expired layer '%s'",
1082+
layer_name(l));
1083+
return send_outreq(req);
1084+
}
1085+
10631086
static struct command_result *json_askrene_create_layer(struct command *cmd,
10641087
const char *buffer,
10651088
const jsmntok_t *params)
@@ -1069,10 +1092,12 @@ static struct command_result *json_askrene_create_layer(struct command *cmd,
10691092
const char *layername;
10701093
struct json_stream *response;
10711094
bool *persistent;
1095+
u64 *expire_seconds;
10721096

10731097
if (!param_check(cmd, buffer, params,
10741098
p_req("layer", param_string, &layername),
10751099
p_opt_def("persistent", param_bool, &persistent, false),
1100+
p_opt("expiration", param_u64, &expire_seconds),
10761101
NULL))
10771102
return command_param_failed();
10781103

@@ -1086,13 +1111,23 @@ static struct command_result *json_askrene_create_layer(struct command *cmd,
10861111
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
10871112
"Layer already exists");
10881113
}
1114+
if (persistent && expire_seconds && *persistent == true) {
1115+
return command_fail(
1116+
cmd, JSONRPC2_INVALID_PARAMS,
1117+
"A persistent layer cannot have an expiration time.");
1118+
}
10891119

10901120
if (command_check_only(cmd))
10911121
return command_check_done(cmd);
10921122

10931123
if (!layer)
10941124
layer = new_layer(askrene, layername, *persistent);
10951125

1126+
if (expire_seconds)
1127+
notleak(global_timer(cmd->plugin,
1128+
time_from_sec(*expire_seconds),
1129+
expire_layer, layer));
1130+
10961131
response = jsonrpc_stream_success(cmd);
10971132
json_add_layers(response, askrene, "layers", layer);
10981133
return command_finished(cmd, response);

tests/test_askrene.py

+25
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,31 @@ def test_layer_persistence(node_factory):
367367
assert l1.rpc.askrene_listlayers() == {'layers': []}
368368

369369

370+
def test_layer_expiration(node_factory):
371+
l1 = node_factory.get_nodes(1, opts={"disable-plugin": "cln-xpay"})[0]
372+
expect = {
373+
"layer": "tmp_layer",
374+
"persistent": False,
375+
"disabled_nodes": [],
376+
"created_channels": [],
377+
"channel_updates": [],
378+
"constraints": [],
379+
"biases": [],
380+
}
381+
382+
assert l1.rpc.askrene_listlayers() == {"layers": []}
383+
# Add a self-destructing layer
384+
l1.rpc.askrene_create_layer(layer="tmp_layer", expiration=5)
385+
assert l1.rpc.askrene_listlayers() == {"layers": [expect]}
386+
time.sleep(6)
387+
assert l1.rpc.askrene_listlayers() == {"layers": []}
388+
389+
with pytest.raises(
390+
RpcError, match="A persistent layer cannot have an expiration time."
391+
):
392+
l1.rpc.askrene_create_layer(layer="pers_layer", persistent=True, expiration=5)
393+
394+
370395
def check_route_as_expected(routes, paths):
371396
"""Make sure all fields in paths are match those in routes"""
372397
def dict_subset_eq(a, b):

0 commit comments

Comments
 (0)