Browse Source

lightningd: fail htlcs we offer if peer unresponsive after deadline.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
ppa-0.6.1
Rusty Russell 7 years ago
parent
commit
1142c44c29
  1. 2
      lightningd/peer_control.c
  2. 2
      lightningd/peer_control.h
  3. 54
      lightningd/peer_htlcs.c
  4. 40
      tests/test_lightningd.py

2
lightningd/peer_control.c

@ -176,7 +176,7 @@ void peer_fail_permanent(struct peer *peer, const u8 *msg TAKES)
return;
}
static void peer_fail_permanent_str(struct peer *peer, const char *str TAKES)
void peer_fail_permanent_str(struct peer *peer, const char *str TAKES)
{
/* Don't use tal_strdup, since we need tal_len */
u8 *msg = tal_dup_arr(peer, u8, (const u8 *)str, strlen(str) + 1, 0);

2
lightningd/peer_control.h

@ -201,6 +201,8 @@ u8 *get_supported_local_features(const tal_t *ctx);
PRINTF_FMT(2,3) void peer_fail_transient(struct peer *peer, const char *fmt,...);
/* Peer has failed, give up on it. */
void peer_fail_permanent(struct peer *peer, const u8 *msg TAKES);
/* Version where we supply the reason string. */
void peer_fail_permanent_str(struct peer *peer, const char *str TAKES);
/* Permanent error, but due to internal problems, not peer. */
void peer_internal_error(struct peer *peer, const char *fmt, ...);

54
lightningd/peer_htlcs.c

@ -1426,8 +1426,58 @@ void peer_htlcs(const tal_t *ctx,
}
}
void notify_new_block(struct lightningd *ld, u32 height)
/* BOLT #2:
*
* For HTLCs we offer: the timeout deadline when we have to fail the channel
* and time it out on-chain. This is `G` blocks after the HTLC
* `cltv_expiry`; 1 block is reasonable.
*/
static u32 htlc_out_deadline(const struct htlc_out *hout)
{
/* FIXME */
return hout->cltv_expiry + 1;
}
void notify_new_block(struct lightningd *ld, u32 height)
{
bool removed;
/* BOLT #2:
*
* A node ... MUST fail the channel if an HTLC which it offered is in
* either node's current commitment transaction past this timeout
* deadline.
*/
/* FIXME: use db to look this up in one go (earliest deadline per-peer) */
do {
struct htlc_out *hout;
struct htlc_out_map_iter outi;
removed = false;
for (hout = htlc_out_map_first(&ld->htlcs_out, &outi);
hout;
hout = htlc_out_map_next(&ld->htlcs_out, &outi)) {
/* Not timed out yet? */
if (height < htlc_out_deadline(hout))
continue;
/* Peer on chain already? */
if (peer_on_chain(hout->key.peer))
continue;
/* Peer already failed, or we hit it? */
if (hout->key.peer->error)
continue;
peer_fail_permanent_str(hout->key.peer,
take(tal_fmt(hout,
"Offered HTLC %"PRIu64
" %s cltv %u hit deadline",
hout->key.id,
htlc_state_name(hout->hstate),
hout->cltv_expiry)));
removed = true;
}
/* Iteration while removing is safe, but can skip entries! */
} while (removed);
}

40
tests/test_lightningd.py

@ -1595,6 +1595,46 @@ class LightningDTests(BaseLightningDTests):
l1.rpc.sendpay(to_json(route), rhash)
assert l3.rpc.listinvoice('test_forward_pad_fees_and_cltv')[0]['complete'] == True
@unittest.skipIf(not DEVELOPER, "needs DEVELOPER=1")
def test_htlc_out_timeout(self):
"""Test that we drop onchain if the peer doesn't time out HTLC"""
# HTLC 1->2, 1 fails after it's irrevocably committed, can't reconnect
disconnects = ['@WIRE_REVOKE_AND_ACK']
l1 = self.node_factory.get_node(disconnect=disconnects,
options=['--no-reconnect'])
l2 = self.node_factory.get_node()
l1.rpc.connect(l2.info['id'], 'localhost:{}'.format(l2.info['port']))
chanid = self.fund_channel(l1, l2, 10**6)
# Wait for route propagation.
bitcoind.rpc.generate(5)
l1.daemon.wait_for_logs(['Received channel_update for channel {}\(0\)'
.format(chanid),
'Received channel_update for channel {}\(1\)'
.format(chanid)])
amt = 200000000
inv = l2.rpc.invoice(amt, 'test_htlc_out_timeout', 'desc')['bolt11']
assert l2.rpc.listinvoice('test_htlc_out_timeout')[0]['complete'] == False
payfuture = self.executor.submit(l1.rpc.pay, inv);
# l1 will drop to chain, not reconnect.
l1.daemon.wait_for_log('dev_disconnect: @WIRE_REVOKE_AND_ACK')
# Takes 6 blocks to timeout (cltv-final + 1), but we also give grace period of 1 block.
bitcoind.rpc.generate(5 + 1)
assert not l1.daemon.is_in_log('hit deadline')
bitcoind.rpc.generate(1)
l1.daemon.wait_for_log('Offered HTLC 0 SENT_ADD_ACK_REVOCATION cltv {} hit deadline'.format(bitcoind.rpc.getblockcount()-1))
l1.daemon.wait_for_log('sendrawtx exit 0')
l1.bitcoin.rpc.generate(1)
l1.daemon.wait_for_log('-> ONCHAIND_OUR_UNILATERAL')
l2.daemon.wait_for_log('-> ONCHAIND_THEIR_UNILATERAL')
@unittest.skipIf(not DEVELOPER, "needs DEVELOPER=1")
def test_disconnect(self):
# These should all make us fail, and retry.

Loading…
Cancel
Save