diff --git a/doc/lightning-listchannels.7.md b/doc/lightning-listchannels.7.md index 1c0388d0d24b..a9aadf3c8d55 100644 --- a/doc/lightning-listchannels.7.md +++ b/doc/lightning-listchannels.7.md @@ -44,7 +44,7 @@ On success, an object containing **channels** is returned. It is an array of ob - **amount\_msat** (msat): the total capacity of this channel (always a whole number of satoshis) - **message\_flags** (u8): as defined by BOLT #7 - **channel\_flags** (u8): as defined by BOLT #7 -- **active** (boolean): true unless source has disabled it, or it's a local channel and the peer is disconnected or it's still opening or closing +- **active** (boolean): true unless source has disabled it (or (deprecated in *v24.02*) it's a local channel and the peer is disconnected or it's still opening or closing) - **last\_update** (u32): UNIX timestamp on the last channel\_update from *source* - **base\_fee\_millisatoshi** (u32): Base fee changed by *source* to use this channel - **fee\_per\_millionth** (u32): Proportional fee changed by *source* to use this channel, in parts-per-million @@ -83,4 +83,4 @@ Lightning RFC site - BOLT \#7: -[comment]: # ( SHA256STAMP:5e729a362943aa9481cc12e410c1f507020983251871fbac497dbb8679ca36ca) +[comment]: # ( SHA256STAMP:c32fcbcb0d0ba926513978a96e6b68ee9e3d7f732b5a9cc167aa77cdd33d717f) diff --git a/plugins/topology.c b/plugins/topology.c index b2771707f8e2..38b458026512 100644 --- a/plugins/topology.c +++ b/plugins/topology.c @@ -345,6 +345,29 @@ static struct node_map *local_connected(const tal_t *ctx, return connected; } +/* Only add a local entry if it's unknown publicly */ +static void gossmod_add_unknown_localchan(struct gossmap_localmods *mods, + const struct node_id *self, + const struct node_id *peer, + const struct short_channel_id_dir *scidd, + struct amount_msat min, + struct amount_msat max, + struct amount_msat fee_base, + u32 fee_proportional, + u32 cltv_delta, + bool enabled, + const char *buf UNUSED, + const jsmntok_t *chantok UNUSED, + struct gossmap *gossmap) +{ + if (gossmap_find_chan(gossmap, &scidd->scid)) + return; + + gossmod_add_localchan(mods, self, peer, scidd, min, max, + fee_base, fee_proportional, cltv_delta, enabled, + buf, chantok, gossmap); +} + /* FIXME: We don't need this listpeerchannels at all if not deprecated! */ static struct command_result *listpeerchannels_done(struct command *cmd, const char *buf, @@ -355,12 +378,23 @@ static struct command_result *listpeerchannels_done(struct command *cmd, struct gossmap_chan *c; struct json_stream *js; struct gossmap *gossmap = get_gossmap(); + struct gossmap_localmods *mods; if (deprecated_apis) connected = local_connected(opts, buf, result); else connected = NULL; + /* In deprecated mode, re-add private channels */ + if (deprecated_apis) { + mods = gossmods_from_listpeerchannels(tmpctx, &local_id, + buf, result, + gossmod_add_unknown_localchan, + gossmap); + gossmap_apply_localmods(gossmap, mods); + } else + mods = NULL; + js = jsonrpc_stream_success(cmd); json_array_start(js, "channels"); if (opts->scid) { @@ -401,6 +435,9 @@ static struct command_result *listpeerchannels_done(struct command *cmd, json_array_end(js); + if (mods) + gossmap_remove_localmods(gossmap, mods); + return command_finished(cmd, js); } diff --git a/tests/test_gossip.py b/tests/test_gossip.py index 0451c62c7287..984542b2908c 100644 --- a/tests/test_gossip.py +++ b/tests/test_gossip.py @@ -6,7 +6,8 @@ from utils import ( wait_for, TIMEOUT, only_one, sync_blockheight, expected_node_features, - mine_funding_to_announce, default_ln_port, CHANNEL_SIZE + mine_funding_to_announce, default_ln_port, CHANNEL_SIZE, + first_scid, ) import json @@ -2334,3 +2335,36 @@ def test_read_spam_nannounce(node_factory, bitcoind): l1.daemon.wait_for_log('Received node_announcement') l1.restart() assert not l1.daemon.is_in_log('BROKEN') + + +def test_listchannels_deprecated_local(node_factory, bitcoind): + """Test listchannels shows local/private channels only in deprecated mode""" + l1, l2, l3 = node_factory.get_nodes(3, + opts=[{}, {'allow-deprecated-apis': True}, {}]) + # This will be in block 103 + node_factory.join_nodes([l1, l2], wait_for_announce=False) + l1l2 = first_scid(l1, l2) + # This will be in block 104 + node_factory.join_nodes([l2, l3], wait_for_announce=False) + l2l3 = first_scid(l2, l3) + + # Non-deprecated nodes say no. + assert l1.rpc.listchannels() == {'channels': []} + assert l3.rpc.listchannels() == {'channels': []} + # Deprecated API lists both sides of local channels: + + vals = [(c['active'], c['public'], c['short_channel_id']) for c in l2.rpc.listchannels()['channels']] + # Either order + assert vals == [(True, False, l1l2)] * 2 + [(True, False, l2l3)] * 2 or vals == [(True, False, l2l3)] * 2 + [(True, False, l1l2)] * 2 + + # Mine l1-l2 channel so it's public. + bitcoind.generate_block(4) + sync_blockheight(bitcoind, [l1, l2, l3]) + + wait_for(lambda: len(l1.rpc.listchannels()['channels']) == 2) + wait_for(lambda: len(l3.rpc.listchannels()['channels']) == 2) + + # l2 shows public one correctly, and private one correctly + # Either order + vals = [(c['active'], c['public'], c['short_channel_id']) for c in l2.rpc.listchannels()['channels']] + assert vals == [(True, True, l1l2)] * 2 + [(True, False, l2l3)] * 2 or vals == [(True, False, l2l3)] * 2 + [(True, True, l1l2)] * 2