diff --git a/doc/lightning-listpeers.7 b/doc/lightning-listpeers.7 index de7705efd..148ec8bc6 100644 --- a/doc/lightning-listpeers.7 +++ b/doc/lightning-listpeers.7 @@ -129,6 +129,8 @@ a number followed by a string unit\. .IP \[bu] \fItotal_msat\fR: A string describing the total capacity of the channel; a number followed by a string unit\. +.IP \[bu] +\fIfeatures\fR: An array of feature names supported by this channel\. .RE diff --git a/doc/lightning-listpeers.7.md b/doc/lightning-listpeers.7.md index ebdb7f004..e25b5e7a4 100644 --- a/doc/lightning-listpeers.7.md +++ b/doc/lightning-listpeers.7.md @@ -95,6 +95,7 @@ The objects in the *channels* array will have at least these fields: a number followed by a string unit. * *total\_msat*: A string describing the total capacity of the channel; a number followed by a string unit. +* *features*: An array of feature names supported by this channel. These fields may exist if the channel has gotten beyond the `"OPENINGD"` state, or in various circumstances: diff --git a/lightningd/opening_control.c b/lightningd/opening_control.c index 393a5d8ad..d8420fc14 100644 --- a/lightningd/opening_control.c +++ b/lightningd/opening_control.c @@ -154,6 +154,18 @@ void json_add_uncommitted_channel(struct json_stream *response, json_add_amount_msat_compat(response, total, "msatoshi_total", "total_msat"); } + + json_array_start(response, "features"); + if (feature_negotiated(uc->peer->ld->our_features, + uc->peer->their_features, + OPT_STATIC_REMOTEKEY)) + json_add_string(response, NULL, "option_static_remotekey"); + + if (feature_negotiated(uc->peer->ld->our_features, + uc->peer->their_features, + OPT_ANCHOR_OUTPUTS)) + json_add_string(response, NULL, "option_anchor_outputs"); + json_array_end(response); json_object_end(response); } diff --git a/lightningd/peer_control.c b/lightningd/peer_control.c index f4ea1adfa..8231d06e8 100644 --- a/lightningd/peer_control.c +++ b/lightningd/peer_control.c @@ -671,6 +671,13 @@ static void json_add_channel(struct lightningd *ld, response, "private", !(channel->channel_flags & CHANNEL_FLAGS_ANNOUNCE_CHANNEL)); + json_array_start(response, "features"); + if (channel->option_static_remotekey) + json_add_string(response, NULL, "option_static_remotekey"); + if (channel->option_anchor_outputs) + json_add_string(response, NULL, "option_anchor_outputs"); + json_array_end(response); + // FIXME @conscott : Modify this when dual-funded channels // are implemented json_object_start(response, "funding_allocation_msat"); diff --git a/tests/test_connection.py b/tests/test_connection.py index ec93cc3a3..2de9933ca 100644 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -2330,3 +2330,35 @@ def test_wumbo_channels(node_factory, bitcoind): # Exact amount depends on fees, but it will be wumbo! amount = [c['funding_msat'][l1.info['id']] for c in only_one(l1.rpc.listpeers(l2.info['id'])['peers'])['channels'] if c['state'] == 'CHANNELD_NORMAL'][0] assert Millisatoshi(amount) > Millisatoshi(str((1 << 24) - 1) + "sat") + + +def test_channel_features(node_factory, bitcoind): + l1, l2 = node_factory.line_graph(2, fundchannel=False) + + bitcoind.rpc.sendtoaddress(l1.rpc.newaddr()['bech32'], 0.1) + bitcoind.generate_block(1) + wait_for(lambda: l1.rpc.listfunds()['outputs'] != []) + + l1.rpc.fundchannel(l2.info['id'], 'all') + + # We should see features in unconfirmed channels. + chan = only_one(only_one(l1.rpc.listpeers()['peers'])['channels']) + assert 'option_static_remotekey' in chan['features'] + if EXPERIMENTAL_FEATURES: + assert 'option_anchor_outputs' in chan['features'] + + # l2 should agree. + assert only_one(only_one(l2.rpc.listpeers()['peers'])['channels'])['features'] == chan['features'] + + # Confirm it. + bitcoind.generate_block(1) + wait_for(lambda: only_one(only_one(l1.rpc.listpeers()['peers'])['channels'])['state'] == 'CHANNELD_NORMAL') + wait_for(lambda: only_one(only_one(l2.rpc.listpeers()['peers'])['channels'])['state'] == 'CHANNELD_NORMAL') + + chan = only_one(only_one(l1.rpc.listpeers()['peers'])['channels']) + assert 'option_static_remotekey' in chan['features'] + if EXPERIMENTAL_FEATURES: + assert 'option_anchor_outputs' in chan['features'] + + # l2 should agree. + assert only_one(only_one(l2.rpc.listpeers()['peers'])['channels'])['features'] == chan['features']