mirror of
https://github.com/aljazceru/lightning.git
synced 2025-12-19 15:14:23 +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;
|
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 */
|
/* Don't use tal_strdup, since we need tal_len */
|
||||||
u8 *msg = tal_dup_arr(peer, u8, (const u8 *)str, strlen(str) + 1, 0);
|
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,...);
|
PRINTF_FMT(2,3) void peer_fail_transient(struct peer *peer, const char *fmt,...);
|
||||||
/* Peer has failed, give up on it. */
|
/* Peer has failed, give up on it. */
|
||||||
void peer_fail_permanent(struct peer *peer, const u8 *msg TAKES);
|
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. */
|
/* Permanent error, but due to internal problems, not peer. */
|
||||||
void peer_internal_error(struct peer *peer, const char *fmt, ...);
|
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)
|
l1.rpc.sendpay(to_json(route), rhash)
|
||||||
assert l3.rpc.listinvoice('test_forward_pad_fees_and_cltv')[0]['complete'] == True
|
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")
|
@unittest.skipIf(not DEVELOPER, "needs DEVELOPER=1")
|
||||||
def test_disconnect(self):
|
def test_disconnect(self):
|
||||||
# These should all make us fail, and retry.
|
# These should all make us fail, and retry.
|
||||||
|
|||||||
Reference in New Issue
Block a user