askrene: askrene-create-layer and askrene-remove-layer.

It's generally better to be explicit with these things: currently typos
would be ignored.  But it's also much easier to clean up entire layers
as we use them for temporary (per-payment) effects.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
Rusty Russell 2024-10-04 08:53:53 +09:30
parent 29cc227a53
commit b88f4cb854
11 changed files with 599 additions and 74 deletions

View file

@ -265,7 +265,7 @@
"description": [
"WARNING: experimental, so API may change.",
"",
"The **askrene-create-channel** RPC command tells askrene to populate one direction of a channel in the given layer. If the channel already exists, it will be overridden. If the layer does not exist, it will be created."
"The **askrene-create-channel** RPC command tells askrene to populate one direction of a channel in the given layer. If the channel already exists, it will be overridden."
],
"request": {
"required": [
@ -361,6 +361,201 @@
"Main web site: <https://github.com/ElementsProject/lightning>"
]
},
"lightning-askrene-create-layer.json": {
"$schema": "../rpc-schema-draft.json",
"type": "object",
"additionalProperties": false,
"rpc": "askrene-create-layer",
"title": "Command to create a new layer (EXPERIMENTAL)",
"description": [
"WARNING: experimental, so API may change.",
"",
"The **askrene-create-layer** RPC command tells askrene to create a new, empty layer. This layer can then be populated with `askrene-create-channel` and `askrene-inform-channel`, and be used in `getroutes`."
],
"request": {
"required": [
"layer"
],
"properties": {
"layer": {
"type": "string",
"description": [
"The name of the layer to create."
]
}
}
},
"response": {
"required": [
"layers"
],
"properties": {
"layers": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"required": [
"layer",
"disabled_nodes",
"disabled_channels",
"created_channels",
"constraints"
],
"properties": {
"layer": {
"type": "string",
"description": [
"The name of the layer."
]
},
"disabled_nodes": {
"type": "array",
"items": {
"type": "pubkey",
"description": [
"The id of the disabled node."
]
}
},
"disabled_channels": {
"type": "array",
"items": {
"type": "short_channel_id_dir",
"description": [
"The channel and direction which is disabled."
]
}
},
"created_channels": {
"type": "array",
"items": {
"type": "object",
"required": [
"source",
"destination",
"short_channel_id",
"capacity_msat",
"htlc_minimum_msat",
"htlc_maximum_msat",
"fee_base_msat",
"fee_proportional_millionths",
"delay"
],
"properties": {
"source": {
"type": "pubkey",
"description": [
"The source node id for the channel."
]
},
"destination": {
"type": "pubkey",
"description": [
"The destination node id for the channel."
]
},
"short_channel_id": {
"type": "short_channel_id",
"description": [
"The short channel id for the channel."
]
},
"capacity_msat": {
"type": "msat",
"description": [
"The capacity (onchain size) of the channel."
]
},
"htlc_minimum_msat": {
"type": "msat",
"description": [
"The minimum value allowed in this direction."
]
},
"htlc_maximum_msat": {
"type": "msat",
"description": [
"The maximum value allowed in this direction."
]
},
"fee_base_msat": {
"type": "msat",
"description": [
"The base fee to apply to use the channel in this direction."
]
},
"fee_proportional_millionths": {
"type": "u32",
"description": [
"The proportional fee (in parts per million) to apply to use the channel in this direction."
]
},
"delay": {
"type": "u16",
"description": [
"The CLTV delay required for this direction."
]
}
}
}
},
"constraints": {
"type": "array",
"items": {
"type": "object",
"required": [
"short_channel_id",
"direction"
],
"properties": {
"short_channel_id": {
"type": "short_channel_id",
"description": [
"The short channel id."
]
},
"direction": {
"type": "u32",
"description": [
"The direction."
]
},
"maximum_msat": {
"type": "msat",
"description": [
"The maximum value which this channel could pass. This or *minimum_msat* will be present, but not both."
]
},
"minimum_msat": {
"type": "msat",
"description": [
"The minimum value which this channel could pass. This or *minimum_msat* will be present, but not both."
]
}
}
}
}
}
}
}
}
},
"see_also": [
"lightning-askrene-remove-layer(7)",
"lightning-getroutes(7)",
"lightning-askrene-create-channel(7)",
"lightning-askrene-inform-channel(7)",
"lightning-askrene-listlayers(7)",
"lightning-askrene-age(7)"
],
"author": [
"Rusty Russell <<rusty@rustcorp.com.au>> is mainly responsible."
],
"resources": [
"Main web site: <https://github.com/ElementsProject/lightning>"
]
},
"lightning-askrene-disable-channel.json": {
"$schema": "../rpc-schema-draft.json",
"type": "object",
@ -469,7 +664,7 @@
"description": [
"WARNING: experimental, so API may change.",
"",
"The **askrene-inform-channel** RPC command tells askrene about the minimum or maximum current capacity of a given channel. It can be applied whether the curren channel exists or not. If the layer does not exist, it will be created."
"The **askrene-inform-channel** RPC command tells askrene about the minimum or maximum current capacity of a given channel. It can be applied whether the current channel exists or not."
],
"request": {
"required": [
@ -742,6 +937,45 @@
"Main web site: <https://github.com/ElementsProject/lightning>"
]
},
"lightning-askrene-remove-layer.json": {
"$schema": "../rpc-schema-draft.json",
"type": "object",
"additionalProperties": false,
"rpc": "askrene-remove-layer",
"title": "Command to destroy a layer (EXPERIMENTAL)",
"description": [
"WARNING: experimental, so API may change.",
"",
"The **askrene-remove-layer** RPC command tells askrene to forget a layer."
],
"request": {
"required": [
"layer"
],
"properties": {
"layer": {
"type": "string",
"description": [
"The name of the layer to remove."
]
}
}
},
"response": {
"required": [],
"properties": {}
},
"see_also": [
"lightning-askrene-create-layer(7)",
"lightning-askrene-listlayers(7)"
],
"author": [
"Rusty Russell <<rusty@rustcorp.com.au>> is mainly responsible."
],
"resources": [
"Main web site: <https://github.com/ElementsProject/lightning>"
]
},
"lightning-askrene-reserve.json": {
"$schema": "../rpc-schema-draft.json",
"type": "object",

View file

@ -6,6 +6,8 @@ doc-wrongdir:
GENERATE_MARKDOWN := doc/lightning-addgossip.7 \
doc/lightning-addpsbtoutput.7 \
doc/lightning-askrene-create-layer.7 \
doc/lightning-askrene-remove-layer.7 \
doc/lightning-askrene-create-channel.7 \
doc/lightning-askrene-disable-node.7 \
doc/lightning-askrene-inform-channel.7 \

View file

@ -15,9 +15,11 @@ Core Lightning Documentation
lightning-addgossip <lightning-addgossip.7.md>
lightning-addpsbtoutput <lightning-addpsbtoutput.7.md>
lightning-askrene-create-channel <lightning-askrene-create-channel.7.md>
lightning-askrene-create-layer <lightning-askrene-create-layer.7.md>
lightning-askrene-disable-node <lightning-askrene-disable-node.7.md>
lightning-askrene-inform-channel <lightning-askrene-inform-channel.7.md>
lightning-askrene-listlayers <lightning-askrene-listlayers.7.md>
lightning-askrene-remove-layer <lightning-askrene-remove-layer.7.md>
lightning-askrene-reserve <lightning-askrene-reserve.7.md>
lightning-askrene-unreserve <lightning-askrene-unreserve.7.md>
lightning-autoclean-once <lightning-autoclean-once.7.md>

View file

@ -7,7 +7,7 @@
"description": [
"WARNING: experimental, so API may change.",
"",
"The **askrene-create-channel** RPC command tells askrene to populate one direction of a channel in the given layer. If the channel already exists, it will be overridden. If the layer does not exist, it will be created."
"The **askrene-create-channel** RPC command tells askrene to populate one direction of a channel in the given layer. If the channel already exists, it will be overridden."
],
"request": {
"required": [

View file

@ -0,0 +1,195 @@
{
"$schema": "../rpc-schema-draft.json",
"type": "object",
"additionalProperties": false,
"rpc": "askrene-create-layer",
"title": "Command to create a new layer (EXPERIMENTAL)",
"description": [
"WARNING: experimental, so API may change.",
"",
"The **askrene-create-layer** RPC command tells askrene to create a new, empty layer. This layer can then be populated with `askrene-create-channel` and `askrene-inform-channel`, and be used in `getroutes`."
],
"request": {
"required": [
"layer"
],
"properties": {
"layer": {
"type": "string",
"description": [
"The name of the layer to create."
]
}
}
},
"response": {
"required": [
"layers"
],
"properties": {
"layers": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"required": [
"layer",
"disabled_nodes",
"disabled_channels",
"created_channels",
"constraints"
],
"properties": {
"layer": {
"type": "string",
"description": [
"The name of the layer."
]
},
"disabled_nodes": {
"type": "array",
"items": {
"type": "pubkey",
"description": [
"The id of the disabled node."
]
}
},
"disabled_channels": {
"type": "array",
"items": {
"type": "short_channel_id_dir",
"description": [
"The channel and direction which is disabled."
]
}
},
"created_channels": {
"type": "array",
"items": {
"type": "object",
"required": [
"source",
"destination",
"short_channel_id",
"capacity_msat",
"htlc_minimum_msat",
"htlc_maximum_msat",
"fee_base_msat",
"fee_proportional_millionths",
"delay"
],
"properties": {
"source": {
"type": "pubkey",
"description": [
"The source node id for the channel."
]
},
"destination": {
"type": "pubkey",
"description": [
"The destination node id for the channel."
]
},
"short_channel_id": {
"type": "short_channel_id",
"description": [
"The short channel id for the channel."
]
},
"capacity_msat": {
"type": "msat",
"description": [
"The capacity (onchain size) of the channel."
]
},
"htlc_minimum_msat": {
"type": "msat",
"description": [
"The minimum value allowed in this direction."
]
},
"htlc_maximum_msat": {
"type": "msat",
"description": [
"The maximum value allowed in this direction."
]
},
"fee_base_msat": {
"type": "msat",
"description": [
"The base fee to apply to use the channel in this direction."
]
},
"fee_proportional_millionths": {
"type": "u32",
"description": [
"The proportional fee (in parts per million) to apply to use the channel in this direction."
]
},
"delay": {
"type": "u16",
"description": [
"The CLTV delay required for this direction."
]
}
}
}
},
"constraints": {
"type": "array",
"items": {
"type": "object",
"required": [
"short_channel_id",
"direction"
],
"properties": {
"short_channel_id": {
"type": "short_channel_id",
"description": [
"The short channel id."
]
},
"direction": {
"type": "u32",
"description": [
"The direction."
]
},
"maximum_msat": {
"type": "msat",
"description": [
"The maximum value which this channel could pass. This or *minimum_msat* will be present, but not both."
]
},
"minimum_msat": {
"type": "msat",
"description": [
"The minimum value which this channel could pass. This or *minimum_msat* will be present, but not both."
]
}
}
}
}
}
}
}
}
},
"see_also": [
"lightning-askrene-remove-layer(7)",
"lightning-getroutes(7)",
"lightning-askrene-create-channel(7)",
"lightning-askrene-inform-channel(7)",
"lightning-askrene-listlayers(7)",
"lightning-askrene-age(7)"
],
"author": [
"Rusty Russell <<rusty@rustcorp.com.au>> is mainly responsible."
],
"resources": [
"Main web site: <https://github.com/ElementsProject/lightning>"
]
}

View file

@ -7,7 +7,7 @@
"description": [
"WARNING: experimental, so API may change.",
"",
"The **askrene-inform-channel** RPC command tells askrene about the minimum or maximum current capacity of a given channel. It can be applied whether the curren channel exists or not. If the layer does not exist, it will be created."
"The **askrene-inform-channel** RPC command tells askrene about the minimum or maximum current capacity of a given channel. It can be applied whether the current channel exists or not."
],
"request": {
"required": [

View file

@ -0,0 +1,39 @@
{
"$schema": "../rpc-schema-draft.json",
"type": "object",
"additionalProperties": false,
"rpc": "askrene-remove-layer",
"title": "Command to destroy a layer (EXPERIMENTAL)",
"description": [
"WARNING: experimental, so API may change.",
"",
"The **askrene-remove-layer** RPC command tells askrene to forget a layer."
],
"request": {
"required": [
"layer"
],
"properties": {
"layer": {
"type": "string",
"description": [
"The name of the layer to remove."
]
}
}
},
"response": {
"required": [],
"properties": {}
},
"see_also": [
"lightning-askrene-create-layer(7)",
"lightning-askrene-listlayers(7)"
],
"author": [
"Rusty Russell <<rusty@rustcorp.com.au>> is mainly responsible."
],
"resources": [
"Main web site: <https://github.com/ElementsProject/lightning>"
]
}

View file

@ -62,24 +62,35 @@ static bool have_layer(const char **layers, const char *name)
return false;
}
/* JSON helpers */
static struct command_result *param_string_array(struct command *cmd,
const char *name,
const char *buffer,
const jsmntok_t *tok,
const char ***arr)
/* Valid, known layers */
static struct command_result *param_layer_names(struct command *cmd,
const char *name,
const char *buffer,
const jsmntok_t *tok,
const char ***arr)
{
size_t i;
const jsmntok_t *t;
if (tok->type != JSMN_ARRAY)
return command_fail_badparam(cmd, name, buffer, tok, "should be an array");
return command_fail_badparam(cmd, name, buffer, tok,
"should be an array");
*arr = tal_arr(cmd, const char *, tok->size);
json_for_each_arr(i, t, tok) {
if (t->type != JSMN_STRING)
return command_fail_badparam(cmd, name, buffer, t, "should be a string");
return command_fail_badparam(cmd, name, buffer, t,
"should be a string");
(*arr)[i] = json_strdup(*arr, buffer, t);
/* Must be a known layer name */
if (streq((*arr)[i], "auto.localchans")
|| streq((*arr)[i], "auto.sourcefree"))
continue;
if (!find_layer(get_askrene(cmd->plugin), (*arr)[i])) {
return command_fail_badparam(cmd, name, buffer, t,
"unknown layer");
}
}
return NULL;
}
@ -285,11 +296,14 @@ static const char *get_routes(const tal_t *ctx,
for (size_t i = 0; i < tal_count(layers); i++) {
const struct layer *l = find_layer(askrene, layers[i]);
if (!l) {
if (local_layer && streq(layers[i], "auto.localchans")) {
if (streq(layers[i], "auto.localchans")) {
plugin_log(plugin, LOG_DBG, "Adding auto.localchans");
l = local_layer;
} else
} else {
/* Handled below, after other layers */
assert(streq(layers[i], "auto.sourcefree"));
continue;
}
}
tal_arr_expand(&rq->layers, l);
@ -652,7 +666,7 @@ static struct command_result *json_getroutes(struct command *cmd,
p_req("source", param_node_id, &info->source),
p_req("destination", param_node_id, &info->dest),
p_req("amount_msat", param_msat, &info->amount),
p_req("layers", param_string_array, &info->layers),
p_req("layers", param_layer_names, &info->layers),
p_req("maxfee_msat", param_msat, &info->maxfee),
p_req("final_cltv", param_u32, &info->finalcltv),
NULL))
@ -737,25 +751,10 @@ static struct command_result *json_askrene_unreserve(struct command *cmd,
return command_finished(cmd, response);
}
static struct command_result *param_layername(struct command *cmd,
const char *name,
const char *buffer,
const jsmntok_t *tok,
const char **str)
{
*str = tal_strndup(cmd, buffer + tok->start,
tok->end - tok->start);
if (strstarts(*str, "auto."))
return command_fail_badparam(cmd, name, buffer, tok,
"New layers cannot start with auto.");
return NULL;
}
static struct command_result *json_askrene_create_channel(struct command *cmd,
const char *buffer,
const jsmntok_t *params)
{
const char *layername;
struct layer *layer;
const struct local_channel *lc;
struct node_id *src, *dst;
@ -765,10 +764,9 @@ static struct command_result *json_askrene_create_channel(struct command *cmd,
struct amount_msat *htlc_min, *htlc_max, *base_fee;
u32 *proportional_fee;
u16 *delay;
struct askrene *askrene = get_askrene(cmd->plugin);
if (!param_check(cmd, buffer, params,
p_req("layer", param_layername, &layername),
p_req("layer", param_known_layer, &layer),
p_req("source", param_node_id, &src),
p_req("destination", param_node_id, &dst),
p_req("short_channel_id", param_short_channel_id, &scid),
@ -782,22 +780,15 @@ static struct command_result *json_askrene_create_channel(struct command *cmd,
return command_param_failed();
/* If it exists, it must match */
layer = find_layer(askrene, layername);
if (layer) {
lc = layer_find_local_channel(layer, *scid);
if (lc && !layer_check_local_channel(lc, src, dst, *capacity)) {
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"channel already exists with different values!");
}
} else
lc = NULL;
lc = layer_find_local_channel(layer, *scid);
if (lc && !layer_check_local_channel(lc, src, dst, *capacity)) {
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"channel already exists with different values!");
}
if (command_check_only(cmd))
return command_check_done(cmd);
if (!layer)
layer = new_layer(askrene, layername);
layer_update_local_channel(layer, src, dst, *scid, *capacity,
*base_fee, *proportional_fee, *delay,
*htlc_min, *htlc_max);
@ -811,15 +802,13 @@ static struct command_result *json_askrene_inform_channel(struct command *cmd,
const jsmntok_t *params)
{
struct layer *layer;
const char *layername;
struct short_channel_id_dir *scidd;
struct json_stream *response;
struct amount_msat *max, *min;
const struct constraint *c;
struct askrene *askrene = get_askrene(cmd->plugin);
if (!param_check(cmd, buffer, params,
p_req("layer", param_layername, &layername),
p_req("layer", param_known_layer, &layer),
p_req("short_channel_id_dir", param_short_channel_id_dir, &scidd),
p_opt("minimum_msat", param_msat, &min),
p_opt("maximum_msat", param_msat, &max),
@ -834,10 +823,6 @@ static struct command_result *json_askrene_inform_channel(struct command *cmd,
if (command_check_only(cmd))
return command_check_done(cmd);
layer = find_layer(askrene, layername);
if (!layer)
layer = new_layer(askrene, layername);
if (min) {
c = layer_update_constraint(layer, scidd, CONSTRAINT_MIN,
time_now().ts.tv_sec, *min);
@ -855,21 +840,15 @@ static struct command_result *json_askrene_disable_channel(struct command *cmd,
const jsmntok_t *params)
{
struct short_channel_id_dir *scidd;
const char *layername;
struct layer *layer;
struct json_stream *response;
struct askrene *askrene = get_askrene(cmd->plugin);
if (!param(cmd, buffer, params,
p_req("layer", param_layername, &layername),
p_req("layer", param_known_layer, &layer),
p_req("short_channel_id_dir", param_short_channel_id_dir, &scidd),
NULL))
return command_param_failed();
layer = find_layer(askrene, layername);
if (!layer)
layer = new_layer(askrene, layername);
layer_add_disabled_channel(layer, scidd);
response = jsonrpc_stream_success(cmd);
@ -881,21 +860,15 @@ static struct command_result *json_askrene_disable_node(struct command *cmd,
const jsmntok_t *params)
{
struct node_id *node;
const char *layername;
struct layer *layer;
struct json_stream *response;
struct askrene *askrene = get_askrene(cmd->plugin);
if (!param(cmd, buffer, params,
p_req("layer", param_layername, &layername),
p_req("layer", param_known_layer, &layer),
p_req("node", param_node_id, &node),
NULL))
return command_param_failed();
layer = find_layer(askrene, layername);
if (!layer)
layer = new_layer(askrene, layername);
/* We save this in the layer, because they want us to disable all the channels
* to the node at *use* time (a new channel might be gossiped!). */
layer_add_disabled_node(layer, node);
@ -904,21 +877,71 @@ static struct command_result *json_askrene_disable_node(struct command *cmd,
return command_finished(cmd, response);
}
static struct command_result *json_askrene_create_layer(struct command *cmd,
const char *buffer,
const jsmntok_t *params)
{
struct askrene *askrene = get_askrene(cmd->plugin);
struct layer *layer;
const char *layername;
struct json_stream *response;
if (!param_check(cmd, buffer, params,
p_req("layer", param_string, &layername),
NULL))
return command_param_failed();
if (find_layer(askrene, layername))
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"Layer already exists");
if (strstarts(layername, "auto."))
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"Cannot create auto layer");
if (command_check_only(cmd))
return command_check_done(cmd);
layer = new_layer(askrene, layername);
response = jsonrpc_stream_success(cmd);
json_add_layers(response, askrene, "layers", layer);
return command_finished(cmd, response);
}
static struct command_result *json_askrene_remove_layer(struct command *cmd,
const char *buffer,
const jsmntok_t *params)
{
struct layer *layer;
struct json_stream *response;
if (!param(cmd, buffer, params,
p_req("layer", param_known_layer, &layer),
NULL))
return command_param_failed();
tal_free(layer);
response = jsonrpc_stream_success(cmd);
return command_finished(cmd, response);
}
static struct command_result *json_askrene_listlayers(struct command *cmd,
const char *buffer,
const jsmntok_t *params)
{
struct askrene *askrene = get_askrene(cmd->plugin);
const char *layername;
struct layer *layer;
struct json_stream *response;
if (!param(cmd, buffer, params,
p_opt("layer", param_string, &layername),
p_opt("layer", param_known_layer, &layer),
NULL))
return command_param_failed();
response = jsonrpc_stream_success(cmd);
json_add_layers(response, askrene, "layers", layername);
json_add_layers(response, askrene, "layers", layer);
return command_finished(cmd, response);
}
@ -970,6 +993,14 @@ static const struct plugin_command commands[] = {
"askrene-inform-channel",
json_askrene_inform_channel,
},
{
"askrene-create-layer",
json_askrene_create_layer,
},
{
"askrene-remove-layer",
json_askrene_remove_layer,
},
{
"askrene-listlayers",
json_askrene_listlayers,

View file

@ -103,10 +103,16 @@ struct layer *new_temp_layer(const tal_t *ctx, const char *name)
return l;
}
static void destroy_layer(struct layer *l, struct askrene *askrene)
{
list_del_from(&askrene->layers, &l->list);
}
struct layer *new_layer(struct askrene *askrene, const char *name)
{
struct layer *l = new_temp_layer(askrene, name);
list_add(&askrene->layers, &l->list);
tal_add_destructor2(l, destroy_layer, askrene);
return l;
}
@ -477,13 +483,13 @@ static void json_add_layer(struct json_stream *js,
void json_add_layers(struct json_stream *js,
struct askrene *askrene,
const char *fieldname,
const char *layername)
const struct layer *layer)
{
struct layer *l;
json_array_start(js, fieldname);
list_for_each(&askrene->layers, l, list) {
if (layername && !streq(l->name, layername))
if (layer && l != layer)
continue;
json_add_layer(js, NULL, l);
}

View file

@ -106,11 +106,11 @@ void layer_add_disabled_node(struct layer *layer, const struct node_id *node);
void layer_add_disabled_channel(struct layer *layer,
const struct short_channel_id_dir *scidd);
/* Print out a json object per layer, or all if layer is NULL */
/* Print out a json object for this layer, or all if layer is NULL */
void json_add_layers(struct json_stream *js,
struct askrene *askrene,
const char *fieldname,
const char *layername);
const struct layer *layer);
/* Print a single constraint */
void json_add_constraint(struct json_stream *js,

View file

@ -20,22 +20,27 @@ def test_layers(node_factory):
l1, l2, l3 = node_factory.line_graph(3, wait_for_announce=True)
assert l2.rpc.askrene_listlayers() == {'layers': []}
assert l2.rpc.askrene_listlayers('test_layers') == {'layers': []}
with pytest.raises(RpcError, match="Unknown layer"):
l2.rpc.askrene_listlayers('test_layers')
expect = {'layer': 'test_layers',
'disabled_nodes': [],
'disabled_channels': [],
'created_channels': [],
'constraints': []}
l2.rpc.askrene_create_layer('test_layers')
l2.rpc.askrene_disable_node('test_layers', l1.info['id'])
expect['disabled_nodes'].append(l1.info['id'])
assert l2.rpc.askrene_listlayers('test_layers') == {'layers': [expect]}
assert l2.rpc.askrene_listlayers() == {'layers': [expect]}
assert l2.rpc.askrene_listlayers('test_layers2') == {'layers': []}
with pytest.raises(RpcError, match="Unknown layer"):
l2.rpc.askrene_listlayers('test_layers2')
l2.rpc.askrene_disable_channel('test_layers', "1x2x3/0")
expect['disabled_channels'].append("1x2x3/0")
assert l2.rpc.askrene_listlayers('test_layers') == {'layers': [expect]}
with pytest.raises(RpcError, match="Layer already exists"):
l2.rpc.askrene_create_layer('test_layers')
# Tell it l3 connects to l1!
l2.rpc.askrene_create_channel('test_layers',
@ -114,6 +119,12 @@ def test_layers(node_factory):
listlayers = l2.rpc.askrene_listlayers('test_layers')
assert listlayers == {'layers': [expect]}
with pytest.raises(RpcError, match="Unknown layer"):
l2.rpc.askrene_remove_layer('test_layers_unknown')
assert l2.rpc.askrene_remove_layer('test_layers') == {}
assert l2.rpc.askrene_listlayers() == {'layers': []}
def check_route_as_expected(routes, paths):
"""Make sure all fields in paths are match those in routes"""
@ -173,6 +184,7 @@ def test_getroutes(node_factory):
l1 = node_factory.get_node(gossip_store_file=gsfile.name)
# Disabling channels makes getroutes fail
l1.rpc.askrene_create_layer('chans_disabled')
l1.rpc.askrene_disable_channel("chans_disabled", '0x1x0/1')
with pytest.raises(RpcError, match="Could not find route"):
l1.rpc.getroutes(source=nodemap[0],
@ -435,6 +447,7 @@ def test_fees_dont_exceed_constraints(node_factory):
l1 = node_factory.get_node(gossip_store_file=gsfile.name)
chan = only_one([c for c in l1.rpc.listchannels(source=nodemap[0])['channels'] if c['destination'] == nodemap[1]])
l1.rpc.askrene_create_layer('test_layers')
l1.rpc.askrene_inform_channel(layer='test_layers',
short_channel_id_dir=f"{chan['short_channel_id']}/{chan['direction']}",
maximum_msat=max_msat)
@ -460,6 +473,7 @@ def test_sourcefree_on_mods(node_factory, bitcoind):
l1 = node_factory.get_node(gossip_store_file=gsfile.name)
# Add a local channel from 0->l1 (we just needed a nodeid).
l1.rpc.askrene_create_layer('test_layers')
l1.rpc.askrene_create_channel('test_layers',
nodemap[0],
l1.info['id'],
@ -580,6 +594,7 @@ def test_limits_fake_gossmap(node_factory, bitcoind):
for scidd in spendable:
assert scidd in [f"{c['short_channel_id']}/{c['direction']}" for c in l1.rpc.listchannels(source=nodemap[0])['channels']]
l1.rpc.askrene_create_layer('localchans')
for scidd, amount in spendable.items():
l1.rpc.askrene_inform_channel(layer='localchans',
short_channel_id_dir=scidd,
@ -640,6 +655,7 @@ def test_max_htlc(node_factory, bitcoind):
[{'short_channel_id_dir': '0x1x1/1', 'amount_msat': 19_000_019, 'delay': 10 + 6}]])
# If we can't use channel 2, we fail.
l1.rpc.askrene_create_layer('removechan2')
l1.rpc.askrene_inform_channel(layer='removechan2',
short_channel_id_dir='0x1x1/1',
maximum_msat=0)