From a39c97c9605ecda90fc416089e4c4d06dddfdf65 Mon Sep 17 00:00:00 2001 From: lisa neigut Date: Fri, 7 Dec 2018 15:38:41 -0800 Subject: [PATCH] channeld: support private channel creation, fixes #2125 Adds a new 'announce' field for `fundchannel`, which if false won't broadcast a `channel_announcement`. --- channeld/channeld.c | 16 ++++++++++++++++ contrib/pylightning/lightning/lightning.py | 9 ++++++--- lightningd/opening_control.c | 7 +++++++ tests/test_connection.py | 15 +++++++++++++++ tests/utils.py | 5 +++-- 5 files changed, 47 insertions(+), 5 deletions(-) diff --git a/channeld/channeld.c b/channeld/channeld.c index d242d2d57..8a7f1567c 100644 --- a/channeld/channeld.c +++ b/channeld/channeld.c @@ -417,6 +417,22 @@ static void channel_announcement_negotiate(struct peer *peer) peer->channel_local_active = true; make_channel_local_active(peer); } + + /* BOLT #7: + * + * A node: + * - if the `open_channel` message has the `announce_channel` bit set + * AND a `shutdown` message has not been sent: + * - MUST send the `announcement_signatures` message. + * - MUST NOT send `announcement_signatures` messages until + * `funding_locked` has been sent AND the funding transaction has + * at least six confirmations. + * - otherwise: + * - MUST NOT send the `announcement_signatures` message. + */ + if (!(peer->channel_flags & CHANNEL_FLAGS_ANNOUNCE_CHANNEL)) + return; + /* BOLT #7: * * - MUST NOT send `announcement_signatures` messages until diff --git a/contrib/pylightning/lightning/lightning.py b/contrib/pylightning/lightning/lightning.py index aaefc5165..8def7a861 100644 --- a/contrib/pylightning/lightning/lightning.py +++ b/contrib/pylightning/lightning/lightning.py @@ -361,14 +361,17 @@ class LightningRpc(UnixDomainSocketRpc): } return self.call("listpeers", payload) - def fundchannel(self, node_id, satoshi, feerate=None): + def fundchannel(self, node_id, satoshi, feerate=None, announce=True): """ - Fund channel with {id} using {satoshi} satoshis" + Fund channel with {id} using {satoshi} satoshis + with feerate of {feerate} (uses default feerate if unset). + If {announce} is False, don't send channel announcements. """ payload = { "id": node_id, "satoshi": satoshi, - "feerate": feerate + "feerate": feerate, + "announce": announce } return self.call("fundchannel", payload) diff --git a/lightningd/opening_control.c b/lightningd/opening_control.c index 84c22b912..a11215a22 100644 --- a/lightningd/opening_control.c +++ b/lightningd/opening_control.c @@ -770,6 +770,7 @@ static void json_fund_channel(struct command *cmd, struct peer *peer; struct channel *channel; u32 *feerate_per_kw; + bool *announce_channel; u8 *msg; u64 max_funding_satoshi = get_chainparams(cmd->ld)->max_funding_satoshi; @@ -780,6 +781,7 @@ static void json_fund_channel(struct command *cmd, p_req("id", json_tok_pubkey, &id), p_req("satoshi", json_tok_tok, &sattok), p_opt("feerate", json_tok_feerate, &feerate_per_kw), + p_opt_def("announce", json_tok_bool, &announce_channel, true), NULL)) return; @@ -826,6 +828,11 @@ static void json_fund_channel(struct command *cmd, /* FIXME: Support push_msat? */ fc->push_msat = 0; fc->channel_flags = OUR_CHANNEL_FLAGS; + if (!*announce_channel) { + fc->channel_flags &= ~CHANNEL_FLAGS_ANNOUNCE_CHANNEL; + log_info(peer->ld->log, "Will open private channel with node %s", + type_to_string(fc, struct pubkey, id)); + } if (!wtx_select_utxos(&fc->wtx, *feerate_per_kw, BITCOIN_SCRIPTPUBKEY_P2WSH_LEN)) diff --git a/tests/test_connection.py b/tests/test_connection.py index 9241b225c..10441931b 100644 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -791,6 +791,21 @@ def test_channel_persistence(node_factory, bitcoind, executor): l1.daemon.wait_for_log(' to ONCHAIN') +def test_private_channel(node_factory): + l1, l2 = node_factory.line_graph(2, announce_channels=False, wait_for_announce=False) + l3, l4 = node_factory.line_graph(2, announce_channels=True, wait_for_announce=True) + + assert l1.daemon.is_in_log('Will open private channel with node {}'.format(l2.info['id'])) + assert not l2.daemon.is_in_log('Will open private channel with node {}'.format(l1.info['id'])) + assert not l3.daemon.is_in_log('Will open private channel with node {}'.format(l4.info['id'])) + + l3.daemon.wait_for_log('Received node_announcement for node {}'.format(l4.info['id'])) + l4.daemon.wait_for_log('Received node_announcement for node {}'.format(l3.info['id'])) + + assert not l1.daemon.is_in_log('Received node_announcement for node {}'.format(l2.info['id'])) + assert not l2.daemon.is_in_log('Received node_announcement for node {}'.format(l1.info['id'])) + + @unittest.skipIf(not DEVELOPER, "needs DEVELOPER=1 for --dev-broadcast-interval") def test_channel_reenable(node_factory): l1, l2 = node_factory.line_graph(2, opts={'may_reconnect': True}, fundchannel=True, wait_for_announce=True) diff --git a/tests/utils.py b/tests/utils.py index 160621b5b..057e48070 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -788,9 +788,10 @@ class NodeFactory(object): raise return node - def line_graph(self, num_nodes, fundchannel=True, fundamount=10**6, wait_for_announce=False, opts=None): + def line_graph(self, num_nodes, fundchannel=True, fundamount=10**6, wait_for_announce=False, opts=None, announce_channels=True): """ Create nodes, connect them and optionally fund channels. """ + assert not (wait_for_announce and not announce_channels), "You've asked to wait for an announcement that's not coming. (wait_for_announce=True,announce_channels=False)" nodes = self.get_nodes(num_nodes, opts=opts) bitcoin = nodes[0].bitcoin connections = [(nodes[i], nodes[i + 1]) for i in range(0, num_nodes - 1)] @@ -813,7 +814,7 @@ class NodeFactory(object): bitcoin.generate_block(1) for src, dst in connections: wait_for(lambda: len(src.rpc.listfunds()['outputs']) > 0) - tx = src.rpc.fundchannel(dst.info['id'], fundamount) + tx = src.rpc.fundchannel(dst.info['id'], fundamount, announce=announce_channels) wait_for(lambda: tx['txid'] in bitcoin.rpc.getrawmempool()) # Confirm all channels and wait for them to become usable