Skip to content

Commit ca8a10d

Browse files
committed
lightningd: respond with channel_reestablish if contacted about long-closed channels.
This may be useful for their recovery, though they should see the spend onchain. Signed-off-by: Rusty Russell <[email protected]> Changelog-Added: Protocol: We now reply to `channel_reestablish` even on long-closed channels.
1 parent 1ac3a46 commit ca8a10d

File tree

4 files changed

+85
-15
lines changed

4 files changed

+85
-15
lines changed

lightningd/peer_control.c

+47-14
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
#include <lightningd/channel.h>
4545
#include <lightningd/channel_control.h>
4646
#include <lightningd/channel_gossip.h>
47+
#include <lightningd/closed_channel.h>
4748
#include <lightningd/closing_control.h>
4849
#include <lightningd/connect_control.h>
4950
#include <lightningd/dual_open_control.h>
@@ -1797,7 +1798,10 @@ void peer_connected(struct lightningd *ld, const u8 *msg)
17971798
plugin_hook_call_peer_connected(ld, cmd_id, hook_payload);
17981799
}
17991800

1800-
static void send_reestablish(struct lightningd *ld, struct channel *channel)
1801+
static void send_reestablish(struct peer *peer,
1802+
const struct channel_id *cid,
1803+
const struct shachain *their_shachain,
1804+
u64 local_next_index)
18011805
{
18021806
u8 *msg;
18031807
struct secret last_remote_per_commit_secret;
@@ -1810,30 +1814,42 @@ static void send_reestablish(struct lightningd *ld, struct channel *channel)
18101814
* - MUST set `your_last_per_commitment_secret` to the last
18111815
* `per_commitment_secret` it received
18121816
*/
1813-
num_revocations = revocations_received(&channel->their_shachain.chain);
1817+
num_revocations = revocations_received(their_shachain);
18141818
if (num_revocations == 0)
18151819
memset(&last_remote_per_commit_secret, 0,
18161820
sizeof(last_remote_per_commit_secret));
1817-
else if (!shachain_get_secret(&channel->their_shachain.chain,
1821+
else if (!shachain_get_secret(their_shachain,
18181822
num_revocations-1,
18191823
&last_remote_per_commit_secret)) {
1820-
channel_fail_permanent(channel,
1821-
REASON_LOCAL,
1822-
"Could not get revocation secret %"PRIu64,
1823-
num_revocations-1);
1824+
log_peer_broken(peer->ld->log, &peer->id,
1825+
"%s: cannot get shachain secret %"PRIu64" to send reestablish",
1826+
fmt_channel_id(tmpctx, cid), num_revocations-1);
18241827
return;
18251828
}
18261829

1827-
msg = towire_channel_reestablish(tmpctx, &channel->cid,
1828-
channel->next_index[LOCAL],
1830+
/* BOLT #2:
1831+
* The sending node:
1832+
* - MUST set `next_commitment_number` to the commitment number of the
1833+
* next `commitment_signed` it expects to receive.
1834+
* - MUST set `next_revocation_number` to the commitment number of the
1835+
* next `revoke_and_ack` message it expects to receive.
1836+
* - MUST set `my_current_per_commitment_point` to a valid point.
1837+
* - if `next_revocation_number` equals 0:
1838+
* - MUST set `your_last_per_commitment_secret` to all zeroes
1839+
* - otherwise:
1840+
* - MUST set `your_last_per_commitment_secret` to the last `per_commitment_secret` it received
1841+
*/
1842+
msg = towire_channel_reestablish(tmpctx, cid,
1843+
local_next_index,
18291844
num_revocations,
18301845
&last_remote_per_commit_secret,
1831-
&channel->channel_info.remote_per_commit,
1846+
/* Any valid point works, since static_remotekey */
1847+
&peer->ld->our_pubkey,
18321848
/* No upgrade for you, since we're closed! */
18331849
NULL);
1834-
subd_send_msg(ld->connectd,
1835-
take(towire_connectd_peer_send_msg(NULL, &channel->peer->id,
1836-
channel->peer->connectd_counter,
1850+
subd_send_msg(peer->ld->connectd,
1851+
take(towire_connectd_peer_send_msg(NULL, &peer->id,
1852+
peer->connectd_counter,
18371853
msg)));
18381854
}
18391855

@@ -1847,6 +1863,7 @@ void peer_spoke(struct lightningd *ld, const u8 *msg)
18471863
u16 msgtype;
18481864
u64 connectd_counter;
18491865
struct channel *channel;
1866+
struct closed_channel *closed_channel;
18501867
struct channel_id channel_id;
18511868
struct peer *peer;
18521869
bool dual_fund;
@@ -1885,7 +1902,9 @@ void peer_spoke(struct lightningd *ld, const u8 *msg)
18851902
"Trouble in paradise?");
18861903
goto send_error;
18871904
}
1888-
send_reestablish(ld, channel);
1905+
send_reestablish(peer, &channel->cid,
1906+
&channel->their_shachain.chain,
1907+
channel->next_index[LOCAL]);
18891908
}
18901909

18911910
/* 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)
19781997
/* FIXME: Send informative error? */
19791998
close(other_fd);
19801999
return;
2000+
2001+
case WIRE_CHANNEL_REESTABLISH:
2002+
/* Maybe a previously closed channel? */
2003+
closed_channel = closed_channel_map_get(peer->ld->closed_channels, &channel_id);
2004+
if (closed_channel && closed_channel->their_shachain) {
2005+
send_reestablish(peer, &closed_channel->cid,
2006+
closed_channel->their_shachain,
2007+
closed_channel->next_index[LOCAL]);
2008+
log_peer_info(ld->log, &peer->id, "Responded to reestablish for long-closed channel %s",
2009+
fmt_channel_id(tmpctx, &channel_id));
2010+
error = towire_errorfmt(tmpctx, &channel_id,
2011+
"Channel is closed and forgotten");
2012+
goto send_error;
2013+
}
19812014
}
19822015

19832016
/* Weird message? Log and reply with error. */

lightningd/test/run-invoice-select-inchan.c

+3
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,9 @@ u32 get_feerate(const struct fee_states *fee_states UNNEEDED,
362362
enum side opener UNNEEDED,
363363
enum side side UNNEEDED)
364364
{ fprintf(stderr, "get_feerate called!\n"); abort(); }
365+
/* Generated stub for hash_cid */
366+
size_t hash_cid(const struct channel_id *cid UNNEEDED)
367+
{ fprintf(stderr, "hash_cid called!\n"); abort(); }
365368
/* Generated stub for hash_htlc_key */
366369
size_t hash_htlc_key(const struct htlc_key *htlc_key UNNEEDED)
367370
{ fprintf(stderr, "hash_htlc_key called!\n"); abort(); }

tests/test_closing.py

+34
Original file line numberDiff line numberDiff line change
@@ -4277,6 +4277,40 @@ def censoring_sendrawtx(r):
42774277
l1.daemon.wait_for_log(r"Low-priority anchorspend aiming for block {} \(feerate 7500\)".format(height + 12))
42784278

42794279

4280+
def test_reestablish_closed_channels(node_factory, bitcoind):
4281+
"""Even long-forgotten channels respond to WIRE_REESTABLISH"""
4282+
l1, l2 = node_factory.line_graph(2, opts={'may_reconnect': True,
4283+
'dev-no-reconnect': None})
4284+
4285+
# We block l2 from seeing close, so it will try to reestablish.
4286+
def no_new_blocks(req):
4287+
return {"error": "go away"}
4288+
l2.daemon.rpcproxy.mock_rpc('getblockhash', no_new_blocks)
4289+
4290+
# Make a payment, make sure it's entirely finished before we close.
4291+
l1.rpc.pay(l2.rpc.invoice(200000000, 'test', 'test')['bolt11'])
4292+
wait_for(lambda: only_one(l1.rpc.listpeerchannels()['channels'])['htlcs'] == [])
4293+
4294+
# l1 closes, unilaterally.
4295+
l1.rpc.disconnect(l2.info['id'], force=True)
4296+
l1.rpc.close(l2.info['id'], unilateraltimeout=1)
4297+
4298+
# 5 blocks before we can spend to-us.
4299+
bitcoind.generate_block(5, wait_for_mempool=1)
4300+
# 100 more to forget channel
4301+
bitcoind.generate_block(100, wait_for_mempool=1)
4302+
wait_for(lambda: l1.rpc.listclosedchannels()['closedchannels'] != [])
4303+
4304+
# l2 reconnects, gets reestablish before error.
4305+
l1.rpc.connect(l2.info['id'], 'localhost', l2.port)
4306+
l1.daemon.wait_for_log('Responded to reestablish for long-closed channel')
4307+
l2.daemon.wait_for_log('peer_in WIRE_CHANNEL_REESTABLISH')
4308+
l2.daemon.wait_for_log('peer_in WIRE_ERROR')
4309+
4310+
# Make sure l2 was happy with the reestablish message.
4311+
assert not l2.daemon.is_in_log('bad reestablish')
4312+
4313+
42804314
@unittest.skipIf(TEST_NETWORK != 'regtest', "elementsd doesn't use p2tr anyway")
42814315
def test_onchain_close_no_p2tr(node_factory, bitcoind):
42824316
"""Closing with a peer which doesn't support OPT_SHUTDOWN_ANYSEGWIT"""

tests/test_connection.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1393,7 +1393,7 @@ def test_funding_external_wallet_corners(node_factory, bitcoind):
13931393
except RpcError as err:
13941394
assert "disconnected during connection" in err.error
13951395

1396-
l1.daemon.wait_for_log('Unknown channel .* for WIRE_CHANNEL_REESTABLISH')
1396+
l1.daemon.wait_for_log('Responded to reestablish for long-closed channel')
13971397
wait_for(lambda: len(l1.rpc.listpeers()['peers']) == 0)
13981398
wait_for(lambda: len(l2.rpc.listpeers()['peers']) == 0)
13991399

0 commit comments

Comments
 (0)