mirror of
https://github.com/aljazceru/lightning.git
synced 2025-12-19 23:24:27 +01:00
lightningd: fail htlcs we offer if peer unresponsive after deadline.
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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, ...);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user