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>
This commit is contained in:
Rusty Russell 2018-08-23 10:38:48 +09:30 committed by Christian Decker
parent a5ecc95c42
commit 36b1cac6e6
7 changed files with 38 additions and 22 deletions

View File

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

View File

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

View File

@ -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)
{
struct pubkey per_commitment_point;
struct channel_id cid;
if (!fromwire_channel_fail_fallen_behind(msg, &per_commitment_point)) {
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
= tal_dup(channel, struct pubkey, &per_commitment_point);
/* TODO(cdecker) Selectively save updated fields to DB */
wallet_channel_save(channel->peer->ld->wallet, channel);
/* 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");
/* Peer sees this, so send a generic msg about unilateral close. */
channel_fail_permanent(channel, "Awaiting unilateral close");
}
static void peer_start_closingd_after_shutdown(struct channel *channel,

View File

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

View File

@ -503,6 +503,19 @@ void peer_connected(struct lightningd *ld, const u8 *msg,
/* Channel is supposed to be active! */
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_NORMAL:
case CHANNELD_SHUTTING_DOWN:

View File

@ -1205,7 +1205,7 @@ def test_dataloss_protection(node_factory, bitcoind):
l2.start()
# 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.daemon.wait_for_log('sendrawtx exit 0')

View File

@ -85,7 +85,8 @@ def test_pay0(node_factory):
@unittest.skipIf(not DEVELOPER, "needs DEVELOPER=1")
def test_pay_disconnect(node_factory, bitcoind):
"""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')
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
route = l1.rpc.getroute(l2.info['id'], 123000, 1)["route"]
# 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')
l2.stop()
l1.daemon.wait_for_log('Disabling channel .*, active 1 -> 0')
# Can't pay while its offline.
with pytest.raises(RpcError):
l1.rpc.sendpay(route, rhash)
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):
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')
# After it sees block, someone should close channel.