diff --git a/lightningd/peer_control.c b/lightningd/peer_control.c index b46db4603..97f50b8b9 100644 --- a/lightningd/peer_control.c +++ b/lightningd/peer_control.c @@ -44,6 +44,7 @@ #include #include #include +#include #include #include #include @@ -1797,7 +1798,10 @@ void peer_connected(struct lightningd *ld, const u8 *msg) plugin_hook_call_peer_connected(ld, cmd_id, hook_payload); } -static void send_reestablish(struct lightningd *ld, struct channel *channel) +static void send_reestablish(struct peer *peer, + const struct channel_id *cid, + const struct shachain *their_shachain, + u64 local_next_index) { u8 *msg; struct secret last_remote_per_commit_secret; @@ -1810,30 +1814,42 @@ static void send_reestablish(struct lightningd *ld, struct channel *channel) * - MUST set `your_last_per_commitment_secret` to the last * `per_commitment_secret` it received */ - num_revocations = revocations_received(&channel->their_shachain.chain); + num_revocations = revocations_received(their_shachain); if (num_revocations == 0) memset(&last_remote_per_commit_secret, 0, sizeof(last_remote_per_commit_secret)); - else if (!shachain_get_secret(&channel->their_shachain.chain, + else if (!shachain_get_secret(their_shachain, num_revocations-1, &last_remote_per_commit_secret)) { - channel_fail_permanent(channel, - REASON_LOCAL, - "Could not get revocation secret %"PRIu64, - num_revocations-1); + log_peer_broken(peer->ld->log, &peer->id, + "%s: cannot get shachain secret %"PRIu64" to send reestablish", + fmt_channel_id(tmpctx, cid), num_revocations-1); return; } - msg = towire_channel_reestablish(tmpctx, &channel->cid, - channel->next_index[LOCAL], + /* BOLT #2: + * The sending node: + * - MUST set `next_commitment_number` to the commitment number of the + * next `commitment_signed` it expects to receive. + * - MUST set `next_revocation_number` to the commitment number of the + * next `revoke_and_ack` message it expects to receive. + * - MUST set `my_current_per_commitment_point` to a valid point. + * - if `next_revocation_number` equals 0: + * - MUST set `your_last_per_commitment_secret` to all zeroes + * - otherwise: + * - MUST set `your_last_per_commitment_secret` to the last `per_commitment_secret` it received + */ + msg = towire_channel_reestablish(tmpctx, cid, + local_next_index, num_revocations, &last_remote_per_commit_secret, - &channel->channel_info.remote_per_commit, + /* Any valid point works, since static_remotekey */ + &peer->ld->our_pubkey, /* No upgrade for you, since we're closed! */ NULL); - subd_send_msg(ld->connectd, - take(towire_connectd_peer_send_msg(NULL, &channel->peer->id, - channel->peer->connectd_counter, + subd_send_msg(peer->ld->connectd, + take(towire_connectd_peer_send_msg(NULL, &peer->id, + peer->connectd_counter, msg))); } @@ -1847,6 +1863,7 @@ void peer_spoke(struct lightningd *ld, const u8 *msg) u16 msgtype; u64 connectd_counter; struct channel *channel; + struct closed_channel *closed_channel; struct channel_id channel_id; struct peer *peer; bool dual_fund; @@ -1885,7 +1902,9 @@ void peer_spoke(struct lightningd *ld, const u8 *msg) "Trouble in paradise?"); goto send_error; } - send_reestablish(ld, channel); + send_reestablish(peer, &channel->cid, + &channel->their_shachain.chain, + channel->next_index[LOCAL]); } /* If we have a canned error for this channel, send it now */ @@ -1978,6 +1997,20 @@ void peer_spoke(struct lightningd *ld, const u8 *msg) /* FIXME: Send informative error? */ close(other_fd); return; + + case WIRE_CHANNEL_REESTABLISH: + /* Maybe a previously closed channel? */ + closed_channel = closed_channel_map_get(peer->ld->closed_channels, &channel_id); + if (closed_channel && closed_channel->their_shachain) { + send_reestablish(peer, &closed_channel->cid, + closed_channel->their_shachain, + closed_channel->next_index[LOCAL]); + log_peer_info(ld->log, &peer->id, "Responded to reestablish for long-closed channel %s", + fmt_channel_id(tmpctx, &channel_id)); + error = towire_errorfmt(tmpctx, &channel_id, + "Channel is closed and forgotten"); + goto send_error; + } } /* Weird message? Log and reply with error. */ diff --git a/lightningd/test/run-invoice-select-inchan.c b/lightningd/test/run-invoice-select-inchan.c index c01317e10..5dfcd4e27 100644 --- a/lightningd/test/run-invoice-select-inchan.c +++ b/lightningd/test/run-invoice-select-inchan.c @@ -362,6 +362,9 @@ u32 get_feerate(const struct fee_states *fee_states UNNEEDED, enum side opener UNNEEDED, enum side side UNNEEDED) { fprintf(stderr, "get_feerate called!\n"); abort(); } +/* Generated stub for hash_cid */ +size_t hash_cid(const struct channel_id *cid UNNEEDED) +{ fprintf(stderr, "hash_cid called!\n"); abort(); } /* Generated stub for hash_htlc_key */ size_t hash_htlc_key(const struct htlc_key *htlc_key UNNEEDED) { fprintf(stderr, "hash_htlc_key called!\n"); abort(); } diff --git a/tests/test_closing.py b/tests/test_closing.py index ac60117e7..dd2eaaeda 100644 --- a/tests/test_closing.py +++ b/tests/test_closing.py @@ -4277,6 +4277,40 @@ def test_onchain_slow_anchor(node_factory, bitcoind): l1.daemon.wait_for_log(r"Low-priority anchorspend aiming for block {} \(feerate 7500\)".format(height + 12)) +def test_reestablish_closed_channels(node_factory, bitcoind): + """Even long-forgotten channels respond to WIRE_REESTABLISH""" + l1, l2 = node_factory.line_graph(2, opts={'may_reconnect': True, + 'dev-no-reconnect': None}) + + # We block l2 from seeing close, so it will try to reestablish. + def no_new_blocks(req): + return {"error": "go away"} + l2.daemon.rpcproxy.mock_rpc('getblockhash', no_new_blocks) + + # Make a payment, make sure it's entirely finished before we close. + l1.rpc.pay(l2.rpc.invoice(200000000, 'test', 'test')['bolt11']) + wait_for(lambda: only_one(l1.rpc.listpeerchannels()['channels'])['htlcs'] == []) + + # l1 closes, unilaterally. + l1.rpc.disconnect(l2.info['id'], force=True) + l1.rpc.close(l2.info['id'], unilateraltimeout=1) + + # 5 blocks before we can spend to-us. + bitcoind.generate_block(5, wait_for_mempool=1) + # 100 more to forget channel + bitcoind.generate_block(100, wait_for_mempool=1) + wait_for(lambda: l1.rpc.listclosedchannels()['closedchannels'] != []) + + # l2 reconnects, gets reestablish before error. + l1.rpc.connect(l2.info['id'], 'localhost', l2.port) + l1.daemon.wait_for_log('Responded to reestablish for long-closed channel') + l2.daemon.wait_for_log('peer_in WIRE_CHANNEL_REESTABLISH') + l2.daemon.wait_for_log('peer_in WIRE_ERROR') + + # Make sure l2 was happy with the reestablish message. + assert not l2.daemon.is_in_log('bad reestablish') + + @unittest.skipIf(TEST_NETWORK != 'regtest', "elementsd doesn't use p2tr anyway") def test_onchain_close_no_p2tr(node_factory, bitcoind): """Closing with a peer which doesn't support OPT_SHUTDOWN_ANYSEGWIT""" diff --git a/tests/test_connection.py b/tests/test_connection.py index d52d12bc3..c5f9a8f0c 100644 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -1393,7 +1393,7 @@ def test_funding_external_wallet_corners(node_factory, bitcoind): except RpcError as err: assert "disconnected during connection" in err.error - l1.daemon.wait_for_log('Unknown channel .* for WIRE_CHANNEL_REESTABLISH') + l1.daemon.wait_for_log('Responded to reestablish for long-closed channel') wait_for(lambda: len(l1.rpc.listpeers()['peers']) == 0) wait_for(lambda: len(l2.rpc.listpeers()['peers']) == 0)