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

feat: listchannels filter by destination #4614

Merged
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
8 changes: 5 additions & 3 deletions contrib/pyln-client/pyln/client/lightning.py
Original file line number Diff line number Diff line change
Expand Up @@ -844,13 +844,15 @@ def invoice(self, msatoshi, label, description, expiry=None, fallbacks=None, pre
}
return self.call("invoice", payload)

def listchannels(self, short_channel_id=None, source=None):
def listchannels(self, short_channel_id=None, source=None, destination=None):
"""
Show all known channels, accept optional {short_channel_id} or {source}.
Show all known channels or filter by optional
{short_channel_id}, {source} or {destination}.
"""
payload = {
"short_channel_id": short_channel_id,
"source": source
"source": source,
"destination": destination
}
return self.call("listchannels", payload)

Expand Down
15 changes: 10 additions & 5 deletions doc/lightning-listchannels.7

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

12 changes: 8 additions & 4 deletions doc/lightning-listchannels.7.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ lightning-listchannels -- Command to query active lightning channels in the enti
SYNOPSIS
--------

**listchannels** \[*short\_channel\_id*\] \[*source*\]
**listchannels** \[*short\_channel\_id*\] \[*source*\] \[*destination*\]

DESCRIPTION
-----------
Expand All @@ -19,7 +19,11 @@ matching *short\_channel\_id* are returned. Otherwise, it must be null.
If *source* is a node id, then only channels leading from that node id
are returned.

If neither is supplied, data on all lightning channels known to this
If *destination* is a node id, then only channels leading to that node id
are returned.

Only one of *short\_channgel\_id*, *source* or *destination* can be supplied.
If nothing is supplied, data on all lightning channels known to this
node, are returned. These can be local channels or public channels
broadcast on the gossip network.

Expand All @@ -44,8 +48,8 @@ On success, an object containing **channels** is returned. It is an array of ob
- **htlc_maximum_msat** (msat, optional): The largest payment *source* will allow via this channel
[comment]: # (GENERATE-FROM-SCHEMA-END)

If *short\_channel\_id* or *source* is supplied and no matching channels
are found, a "channels" object with an empty list is returned.
If one of *short\_channel\_id*, *source* or *destination* is supplied and no
matching channels are found, a "channels" object with an empty list is returned.

On error the returned object will contain `code` and `message` properties,
with `code` being one of the following:
Expand Down
23 changes: 20 additions & 3 deletions plugins/topology.c
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,7 @@ static void json_add_halfchan(struct json_stream *response,

struct listchannels_opts {
struct node_id *source;
struct node_id *destination;
struct short_channel_id *scid;
};

Expand Down Expand Up @@ -456,6 +457,18 @@ static struct command_result *listpeers_done(struct command *cmd,
c, 1 << dir);
}
}
} else if (opts->destination) {
struct gossmap_node *dst;

dst = gossmap_find_node(gossmap, opts->destination);
if (dst) {
for (size_t i = 0; i < dst->num_chans; i++) {
int dir;
c = gossmap_nth_chan(gossmap, dst, i, &dir);
json_add_halfchan(js, gossmap, connected,
c, 1 << !dir);
}
}
} else {
for (c = gossmap_first_chan(gossmap);
c;
Expand All @@ -480,12 +493,15 @@ static struct command_result *json_listchannels(struct command *cmd,
p_opt("short_channel_id", param_short_channel_id,
&opts->scid),
p_opt("source", param_node_id, &opts->source),
p_opt("destination", param_node_id, &opts->destination),
NULL))
return command_param_failed();

if (opts->scid && opts->source)
if (!!opts->scid + !!opts->source + !!opts->destination > 1)
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"Cannot specify both source and short_channel_id");
"Can only specify one of "
"`short_channel_id`, "
"`source` or `destination`");
req = jsonrpc_request_start(cmd->plugin, cmd, "listpeers",
listpeers_done, forward_error, opts);
return send_outreq(cmd->plugin, req);
Expand Down Expand Up @@ -694,7 +710,8 @@ static const struct plugin_command commands[] = {
"listchannels",
"channels",
"List all known channels in the network",
"Show channel {short_channel_id} or {source} (or all known channels, if not specified)",
"Show channels for {short_channel_id}, {source} or {destination} "
"(or all known channels, if not specified)",
json_listchannels,
},
{
Expand Down
13 changes: 12 additions & 1 deletion tests/test_gossip.py
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,18 @@ def test_gossip_jsonrpc(node_factory):
assert only_one(channels1)['destination'] == l2.info['id']
assert channels1 == channels2

l2.rpc.listchannels()['channels']
# Test listchannels-by-destination
channels1 = l1.rpc.listchannels(destination=l1.info['id'])['channels']
channels2 = l2.rpc.listchannels(destination=l1.info['id'])['channels']
assert only_one(channels1)['destination'] == l1.info['id']
assert only_one(channels1)['source'] == l2.info['id']
assert channels1 == channels2

# Test only one of short_channel_id, source or destination can be supplied
with pytest.raises(RpcError, match=r"Can only specify one of.*"):
l1.rpc.listchannels(source=l1.info['id'], destination=l2.info['id'])
with pytest.raises(RpcError, match=r"Can only specify one of.*"):
l1.rpc.listchannels(short_channel_id="1x1x1", source=l2.info['id'])

# Now proceed to funding-depth and do a full gossip round
l1.bitcoin.generate_block(5)
Expand Down