diff --git a/contrib/msggen/msggen/schema.json b/contrib/msggen/msggen/schema.json index a9b6d1572ac4..35ebf2e3472d 100644 --- a/contrib/msggen/msggen/schema.json +++ b/contrib/msggen/msggen/schema.json @@ -703,6 +703,88 @@ "Main web site: " ] }, + "lightning-askrene-query-reserve.json": { + "$schema": "../rpc-schema-draft.json", + "type": "object", + "additionalProperties": false, + "rpc": "askrene-query-reserve", + "title": "Command to query the reserved liquidity in a channel (EXPERIMENTAL)", + "description": [ + "WARNING: experimental, so API may change.", + "", + "The **askrene-query-reserve** RPC command provides information about the number of in-flight HTLCs in a channel and the sum of their respective amounts." + ], + "request": { + "required": [ + "short_channel_id", + "direction" + ], + "properties": { + "short_channel_id": { + "type": "short_channel_id", + "description": [ + "The short channel id of the channel." + ] + }, + "direction": { + "type": "u32", + "description": [ + "The direction of the channel." + ] + } + } + }, + "response": { + "required": [ + "short_channel_id", + "direction", + "num_htlcs", + "amount_msat" + ], + "properties": { + "short_channel_id": { + "type": "short_channel_id", + "description": [ + "The *short_channel_id* specified." + ] + }, + "direction": { + "type": "u32", + "description": [ + "The *direction* specified." + ] + }, + "num_htlcs": { + "type": "u32", + "description": [ + "The number of HTLCs in-flight in the channel." + ] + }, + "amount_msat": { + "type": "msat", + "description": [ + "The total amount reserved in the channel." + ] + } + } + }, + "see_also": [ + "lightning-getroutes(7)", + "lightning-askrene-reserve(7)", + "lightning-askrene-unreserve(7)", + "lightning-askrene-inform-channel(7)", + "lightning-askrene-disable-node(7)", + "lightning-askrene-create-channel(7)", + "lightning-askrene-listlayers(7)", + "lightning-askrene-age(7)" + ], + "author": [ + "Lagrang3 <> is mainly responsible." + ], + "resources": [ + "Main web site: " + ] + }, "lightning-askrene-reserve.json": { "$schema": "../rpc-schema-draft.json", "type": "object", @@ -762,6 +844,7 @@ "see_also": [ "lightning-getroutes(7)", "lightning-askrene-unreserve(7)", + "lightning-askrene-query-reserve(7)", "lightning-askrene-disable-node(7)", "lightning-askrene-create-channel(7)", "lightning-askrene-inform-channel(7)", @@ -834,6 +917,7 @@ "see_also": [ "lightning-getroutes(7)", "lightning-askrene-reserve(7)", + "lightning-askrene-query-reserve(7)", "lightning-askrene-disable-node(7)", "lightning-askrene-create-channel(7)", "lightning-askrene-inform-channel(7)", diff --git a/doc/Makefile b/doc/Makefile index fd59c4182646..cdd4287529e7 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -12,6 +12,7 @@ GENERATE_MARKDOWN := doc/lightning-addgossip.7 \ doc/lightning-askrene-listlayers.7 \ doc/lightning-askrene-reserve.7 \ doc/lightning-askrene-unreserve.7 \ + doc/lightning-askrene-query-reserve.7 \ doc/lightning-autoclean-once.7 \ doc/lightning-autoclean-status.7 \ doc/lightning-batching.7 \ diff --git a/doc/index.rst b/doc/index.rst index 5515229db141..642ce19573fb 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -18,6 +18,7 @@ Core Lightning Documentation lightning-askrene-disable-node lightning-askrene-inform-channel lightning-askrene-listlayers + lightning-askrene-query-reserve lightning-askrene-reserve lightning-askrene-unreserve lightning-autoclean-once diff --git a/doc/schemas/lightning-askrene-query-reserve.json b/doc/schemas/lightning-askrene-query-reserve.json new file mode 100644 index 000000000000..b3a08ff4e9f8 --- /dev/null +++ b/doc/schemas/lightning-askrene-query-reserve.json @@ -0,0 +1,82 @@ +{ + "$schema": "../rpc-schema-draft.json", + "type": "object", + "additionalProperties": false, + "rpc": "askrene-query-reserve", + "title": "Command to query the reserved liquidity in a channel (EXPERIMENTAL)", + "description": [ + "WARNING: experimental, so API may change.", + "", + "The **askrene-query-reserve** RPC command provides information about the number of in-flight HTLCs in a channel and the sum of their respective amounts." + ], + "request": { + "required": [ + "short_channel_id", + "direction" + ], + "properties": { + "short_channel_id": { + "type": "short_channel_id", + "description": [ + "The short channel id of the channel." + ] + }, + "direction": { + "type": "u32", + "description": [ + "The direction of the channel." + ] + } + } + }, + "response": { + "required": [ + "short_channel_id", + "direction", + "num_htlcs", + "amount_msat" + ], + "properties": { + "short_channel_id": { + "type": "short_channel_id", + "description": [ + "The *short_channel_id* specified." + ] + }, + "direction": { + "type": "u32", + "description": [ + "The *direction* specified." + ] + }, + "num_htlcs": { + "type": "u32", + "description": [ + "The number of HTLCs in-flight in the channel." + ] + }, + "amount_msat": { + "type": "msat", + "description": [ + "The total amount reserved in the channel." + ] + } + } + }, + "see_also": [ + "lightning-getroutes(7)", + "lightning-askrene-reserve(7)", + "lightning-askrene-unreserve(7)", + "lightning-askrene-inform-channel(7)", + "lightning-askrene-disable-node(7)", + "lightning-askrene-create-channel(7)", + "lightning-askrene-listlayers(7)", + "lightning-askrene-age(7)" + ], + "author": [ + "Lagrang3 <> is mainly responsible." + ], + "resources": [ + "Main web site: " + ] +} diff --git a/doc/schemas/lightning-askrene-reserve.json b/doc/schemas/lightning-askrene-reserve.json index 7b91e3800998..fd5b9ea29ca4 100644 --- a/doc/schemas/lightning-askrene-reserve.json +++ b/doc/schemas/lightning-askrene-reserve.json @@ -57,6 +57,7 @@ "see_also": [ "lightning-getroutes(7)", "lightning-askrene-unreserve(7)", + "lightning-askrene-query-reserve(7)", "lightning-askrene-disable-node(7)", "lightning-askrene-create-channel(7)", "lightning-askrene-inform-channel(7)", diff --git a/doc/schemas/lightning-askrene-unreserve.json b/doc/schemas/lightning-askrene-unreserve.json index 377595a5caa5..b36d26b5ea13 100644 --- a/doc/schemas/lightning-askrene-unreserve.json +++ b/doc/schemas/lightning-askrene-unreserve.json @@ -57,6 +57,7 @@ "see_also": [ "lightning-getroutes(7)", "lightning-askrene-reserve(7)", + "lightning-askrene-query-reserve(7)", "lightning-askrene-disable-node(7)", "lightning-askrene-create-channel(7)", "lightning-askrene-inform-channel(7)", diff --git a/plugins/askrene/askrene.c b/plugins/askrene/askrene.c index 751b687416ef..c0b5410b67b0 100644 --- a/plugins/askrene/askrene.c +++ b/plugins/askrene/askrene.c @@ -761,6 +761,39 @@ static struct command_result *json_askrene_unreserve(struct command *cmd, return command_finished(cmd, response); } +static struct command_result * +json_askrene_query_reserve(struct command *cmd, const char *buffer, + const jsmntok_t *params) +{ + struct askrene *askrene = get_askrene(cmd->plugin); + struct short_channel_id *scid; + int *direction; + struct short_channel_id_dir scidd; + + if (!param(cmd, buffer, params, + p_req("short_channel_id", param_short_channel_id, &scid), + p_req("direction", param_zero_or_one, &direction), NULL)) + return command_param_failed(); + + scidd.scid = *scid; + scidd.dir = *direction; + + struct json_stream *response = jsonrpc_stream_success(cmd); + json_add_short_channel_id(response, "short_channel_id", scidd.scid); + json_add_num(response, "direction", scidd.dir); + + const struct reserve *r = find_reserve(askrene->reserved, &scidd); + if (!r) { + json_add_u32(response, "num_htlcs", 0); + json_add_amount_msat(response, "amount_msat", AMOUNT_MSAT(0)); + } else { + + json_add_u32(response, "num_htlcs", r->num_htlcs); + json_add_amount_msat(response, "amount_msat", r->amount); + } + return command_finished(cmd, response); +} + static struct command_result *param_layername(struct command *cmd, const char *name, const char *buffer, @@ -963,6 +996,10 @@ static const struct plugin_command commands[] = { "askrene-unreserve", json_askrene_unreserve, }, + { + "askrene-query-reserve", + json_askrene_query_reserve, + }, { "askrene-disable-node", json_askrene_disable_node, diff --git a/tests/test_askrene.py b/tests/test_askrene.py index a87ae3c6ec53..6f345b98fd1c 100644 --- a/tests/test_askrene.py +++ b/tests/test_askrene.py @@ -677,3 +677,49 @@ def test_min_htlc_after_excess(node_factory, bitcoind): layers=[], maxfee_msat=20_000_000, final_cltv=10) + + +def test_reserve(node_factory): + """Test reserves""" + # Set up l1 with this as the gossip_store + l1 = node_factory.get_node() + expected = { + "short_channel_id": "1x1x1", + "direction": 0, + "num_htlcs": 0, + "amount_msat": 0, + } + + assert l1.rpc.askrene_query_reserve("1x1x1", 0) == expected + + l1.rpc.askrene_reserve( + [{"short_channel_id": "1x1x1", "direction": 0, "amount_msat": 105}] + ) + expected["num_htlcs"] += 1 + expected["amount_msat"] += 105 + + assert l1.rpc.askrene_query_reserve("1x1x1", 0) == expected + + l1.rpc.askrene_reserve( + [{"short_channel_id": "1x1x1", "direction": 0, "amount_msat": 900}] + ) + expected["num_htlcs"] += 1 + expected["amount_msat"] += 900 + + assert l1.rpc.askrene_query_reserve("1x1x1", 0) == expected + + l1.rpc.askrene_unreserve( + [{"short_channel_id": "1x1x1", "direction": 0, "amount_msat": 105}] + ) + expected["num_htlcs"] -= 1 + expected["amount_msat"] -= 105 + + assert l1.rpc.askrene_query_reserve("1x1x1", 0) == expected + + l1.rpc.askrene_unreserve( + [{"short_channel_id": "1x1x1", "direction": 0, "amount_msat": 900}] + ) + expected["num_htlcs"] -= 1 + expected["amount_msat"] -= 900 + + assert l1.rpc.askrene_query_reserve("1x1x1", 0) == expected