mirror of
https://github.com/ElementsProject/lightning.git
synced 2025-02-20 13:54:36 +01:00
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:
parent
29cc227a53
commit
b88f4cb854
11 changed files with 599 additions and 74 deletions
|
@ -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",
|
||||
|
|
|
@ -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 \
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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": [
|
||||
|
|
195
doc/schemas/lightning-askrene-create-layer.json
Normal file
195
doc/schemas/lightning-askrene-create-layer.json
Normal 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>"
|
||||
]
|
||||
}
|
|
@ -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": [
|
||||
|
|
39
doc/schemas/lightning-askrene-remove-layer.json
Normal file
39
doc/schemas/lightning-askrene-remove-layer.json
Normal 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>"
|
||||
]
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Add table
Reference in a new issue