Browse Source

lightningd: new state AWAITING_UNILATERAL.

When in this state, we send a canned error "Awaiting unilateral close".
We enter this both when we drop to chain, and when we're trying to get
them to drop to chain due to option_data_loss_protect.

As this state (unlike channel errors) is saved to the database, it means
we will *never* talk to a peer again in this state, so they can't
confuse us.

Since we set this state in channel_fail_permanent() (which is the only
place we call drop_to_chain for a unilateral close), we don't need to
save to the db: channel_set_state() does that for us.

This state change has a subtle effect: we return WIRE_UNKNOWN_NEXT_PEER
instead of WIRE_TEMPORARY_CHANNEL_FAILURE as soon as we get a failure
with a peer.  To provoke a temporary failure in test_pay_disconnect we
take the node offline.

Reported-by: Christian Decker @cdecker
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
ppa-0.6.1
Rusty Russell 6 years ago
committed by Christian Decker
parent
commit
36b1cac6e6
  1. 3
      channeld/channel.c
  2. 4
      lightningd/channel.c
  3. 14
      lightningd/channel_control.c
  4. 3
      lightningd/channel_state.h
  5. 13
      lightningd/peer_control.c
  6. 2
      tests/test_connection.py
  7. 21
      tests/test_pay.py

3
channeld/channel.c

@ -1894,8 +1894,7 @@ static void check_future_dataloss_fields(struct peer *peer,
remote_current_per_commitment_point))); remote_current_per_commitment_point)));
/* We have to send them an error to trigger dropping to chain. */ /* We have to send them an error to trigger dropping to chain. */
peer_failed(&peer->cs, &peer->channel_id, peer_failed(&peer->cs, &peer->channel_id, "Awaiting unilateral close");
"Catastrophic failure: please close channel");
} }
/* BOLT #2: /* BOLT #2:

4
lightningd/channel.c

@ -356,6 +356,10 @@ void channel_fail_permanent(struct channel *channel, const char *fmt, ...)
channel_set_owner(channel, NULL, false); channel_set_owner(channel, NULL, false);
/* Drop non-cooperatively (unilateral) to chain. */ /* Drop non-cooperatively (unilateral) to chain. */
drop_to_chain(ld, channel, false); drop_to_chain(ld, channel, false);
if (channel_active(channel))
channel_set_state(channel, channel->state, AWAITING_UNILATERAL);
tal_free(why); tal_free(why);
} }

14
lightningd/channel_control.c

@ -105,7 +105,6 @@ static void peer_got_shutdown(struct channel *channel, const u8 *msg)
static void channel_fail_fallen_behind(struct channel *channel, const u8 *msg) static void channel_fail_fallen_behind(struct channel *channel, const u8 *msg)
{ {
struct pubkey per_commitment_point; struct pubkey per_commitment_point;
struct channel_id cid;
if (!fromwire_channel_fail_fallen_behind(msg, &per_commitment_point)) { if (!fromwire_channel_fail_fallen_behind(msg, &per_commitment_point)) {
channel_internal_error(channel, channel_internal_error(channel,
@ -117,17 +116,8 @@ static void channel_fail_fallen_behind(struct channel *channel, const u8 *msg)
channel->future_per_commitment_point channel->future_per_commitment_point
= tal_dup(channel, struct pubkey, &per_commitment_point); = tal_dup(channel, struct pubkey, &per_commitment_point);
/* TODO(cdecker) Selectively save updated fields to DB */ /* Peer sees this, so send a generic msg about unilateral close. */
wallet_channel_save(channel->peer->ld->wallet, channel); channel_fail_permanent(channel, "Awaiting unilateral close");
/* We don't fail yet, since we want daemon to send them an error
* to trigger rebroadcasting. But make sure we set error now in
* case something else goes wrong! */
derive_channel_id(&cid,
&channel->funding_txid,
channel->funding_outnum);
channel->error = towire_errorfmt(channel, &cid,
"Catastrophic failure: please close channel");
} }
static void peer_start_closingd_after_shutdown(struct channel *channel, static void peer_start_closingd_after_shutdown(struct channel *channel,

3
lightningd/channel_state.h

@ -19,6 +19,9 @@ enum channel_state {
/* Waiting for onchain event. */ /* Waiting for onchain event. */
CLOSINGD_COMPLETE, CLOSINGD_COMPLETE,
/* Waiting for unilateral close to hit blockchain. */
AWAITING_UNILATERAL,
/* We've seen the funding spent, we're waiting for onchaind. */ /* We've seen the funding spent, we're waiting for onchaind. */
FUNDING_SPEND_SEEN, FUNDING_SPEND_SEEN,

13
lightningd/peer_control.c

@ -503,6 +503,19 @@ void peer_connected(struct lightningd *ld, const u8 *msg,
/* Channel is supposed to be active! */ /* Channel is supposed to be active! */
abort(); abort();
/* We consider this "active" but we only send an error */
case AWAITING_UNILATERAL: {
struct channel_id cid;
derive_channel_id(&cid,
&channel->funding_txid,
channel->funding_outnum);
/* channel->error is not saved in db, so this can
* happen if we restart. */
error = towire_errorfmt(tmpctx, &cid,
"Awaiting unilateral close");
goto send_error;
}
case CHANNELD_AWAITING_LOCKIN: case CHANNELD_AWAITING_LOCKIN:
case CHANNELD_NORMAL: case CHANNELD_NORMAL:
case CHANNELD_SHUTTING_DOWN: case CHANNELD_SHUTTING_DOWN:

2
tests/test_connection.py

@ -1205,7 +1205,7 @@ def test_dataloss_protection(node_factory, bitcoind):
l2.start() l2.start()
# l2 should freak out! # l2 should freak out!
l2.daemon.wait_for_log("Catastrophic failure: please close channel") l2.daemon.wait_for_log("Peer permanent failure in CHANNELD_NORMAL: Awaiting unilateral close")
# l1 should drop to chain. # l1 should drop to chain.
l1.daemon.wait_for_log('sendrawtx exit 0') l1.daemon.wait_for_log('sendrawtx exit 0')

21
tests/test_pay.py

@ -85,7 +85,8 @@ def test_pay0(node_factory):
@unittest.skipIf(not DEVELOPER, "needs DEVELOPER=1") @unittest.skipIf(not DEVELOPER, "needs DEVELOPER=1")
def test_pay_disconnect(node_factory, bitcoind): def test_pay_disconnect(node_factory, bitcoind):
"""If the remote node has disconnected, we fail payment, but can try again when it reconnects""" """If the remote node has disconnected, we fail payment, but can try again when it reconnects"""
l1, l2 = node_factory.line_graph(2, opts={'dev-max-fee-multiplier': 5}) l1, l2 = node_factory.line_graph(2, opts={'dev-max-fee-multiplier': 5,
'may_reconnect': True})
inv = l2.rpc.invoice(123000, 'test_pay_disconnect', 'description') inv = l2.rpc.invoice(123000, 'test_pay_disconnect', 'description')
rhash = inv['payment_hash'] rhash = inv['payment_hash']
@ -93,21 +94,27 @@ def test_pay_disconnect(node_factory, bitcoind):
# Can't use `pay` since that'd notice that we can't route, due to disabling channel_update # Can't use `pay` since that'd notice that we can't route, due to disabling channel_update
route = l1.rpc.getroute(l2.info['id'], 123000, 1)["route"] route = l1.rpc.getroute(l2.info['id'], 123000, 1)["route"]
# Make l2 upset by asking for crazy fee. l2.stop()
l1.rpc.dev_setfees('150000') l1.daemon.wait_for_log('Disabling channel .*, active 1 -> 0')
# Wait for l1 notice
l1.daemon.wait_for_log(r'Peer permanent failure in CHANNELD_NORMAL: lightning_channeld: received ERROR channel .*: update_fee 150000 outside range 1875-75000')
# Can't pay while its offline. # Can't pay while its offline.
with pytest.raises(RpcError): with pytest.raises(RpcError):
l1.rpc.sendpay(route, rhash) l1.rpc.sendpay(route, rhash)
l1.daemon.wait_for_log('failed: WIRE_TEMPORARY_CHANNEL_FAILURE \\(First peer not ready\\)') l1.daemon.wait_for_log('failed: WIRE_TEMPORARY_CHANNEL_FAILURE \\(First peer not ready\\)')
# Should fail due to temporary channel fail l2.start()
l1.daemon.wait_for_log('peer_out WIRE_CHANNEL_REESTABLISH')
# Make l2 upset by asking for crazy fee.
l1.rpc.dev_setfees('150000')
# Wait for l1 notice
l1.daemon.wait_for_log(r'Peer permanent failure in CHANNELD_NORMAL: lightning_channeld: received ERROR channel .*: update_fee 150000 outside range 1875-75000')
# Should fail due to permenant channel fail
with pytest.raises(RpcError): with pytest.raises(RpcError):
l1.rpc.sendpay(route, rhash) l1.rpc.sendpay(route, rhash)
l1.daemon.wait_for_log('failed: WIRE_TEMPORARY_CHANNEL_FAILURE \\(First peer not ready\\)') l1.daemon.wait_for_log('failed: WIRE_UNKNOWN_NEXT_PEER \\(First peer not ready\\)')
assert not l1.daemon.is_in_log('Payment is still in progress') assert not l1.daemon.is_in_log('Payment is still in progress')
# After it sees block, someone should close channel. # After it sees block, someone should close channel.

Loading…
Cancel
Save