Skip to content

Commit 0a42051

Browse files
author
Antoine Riard
committed
Add auto-close if outbound feerate update don't complete
1 parent 2ced708 commit 0a42051

File tree

2 files changed

+76
-0
lines changed

2 files changed

+76
-0
lines changed

lightning/src/ln/channel.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,12 @@ pub const FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE: u64 = 2;
370370
#[cfg(not(fuzzing))]
371371
const FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE: u64 = 2;
372372

373+
/// If after this value tick periods, the channel feerate isn't satisfying, we auto-close the
374+
/// channel and goes on-chain to avoid unsafe situations where a commitment transaction with
375+
/// time-sensitive outputs won't confirm due to staling feerate too far away from the upper feerate
376+
/// groups of network mempools.
377+
const AUTOCLOSE_TIMEOUT: u16 = 6;
378+
373379
// TODO: We should refactor this to be an Inbound/OutboundChannel until initial setup handshaking
374380
// has been completed, and then turn into a Channel to get compiler-time enforcement of things like
375381
// calling channel_id() before we're set up or things like get_outbound_funding_signed on an
@@ -392,6 +398,10 @@ pub(super) struct Channel<Signer: Sign> {
392398

393399
latest_monitor_update_id: u64,
394400

401+
// Auto-close timer, if the channel is outbound, and we didn't receive a RAA for counterparty
402+
// commitment transaction after `AUTOCLOSE_TIMEOUT` periods, this channel must be force-closed.
403+
autoclose_timer: u16,
404+
395405
holder_signer: Signer,
396406
shutdown_scriptpubkey: Option<ShutdownScript>,
397407
destination_script: Script,
@@ -682,6 +692,8 @@ impl<Signer: Sign> Channel<Signer> {
682692

683693
latest_monitor_update_id: 0,
684694

695+
autoclose_timer: 0,
696+
685697
holder_signer,
686698
shutdown_scriptpubkey,
687699
destination_script: keys_provider.get_destination_script(),
@@ -945,6 +957,8 @@ impl<Signer: Sign> Channel<Signer> {
945957

946958
latest_monitor_update_id: 0,
947959

960+
autoclose_timer: 0,
961+
948962
holder_signer,
949963
shutdown_scriptpubkey,
950964
destination_script: keys_provider.get_destination_script(),
@@ -2720,6 +2734,18 @@ impl<Signer: Sign> Channel<Signer> {
27202734
}
27212735
}
27222736

2737+
/// Trigger the autoclose timer if it's in the starting position
2738+
fn maybe_trigger_autoclose_timer(&mut self) {
2739+
// Start an auto-close timer, if the channel feerate doesn't increase before its
2740+
// expiration (i.e this outbound feerate update has been committed on both sides),
2741+
// the channel will be marked as unsafe and force-closed.
2742+
// If a timer is already pending, no-op, as a higher-feerate `update_fee` will
2743+
// implicitly override a lower-feerate `update_fee` part of the same update sequence.
2744+
if self.autoclose_timer == 0 {
2745+
self.autoclose_timer = 1;
2746+
}
2747+
}
2748+
27232749
/// Handles receiving a remote's revoke_and_ack. Note that we may return a new
27242750
/// commitment_signed message here in case we had pending outbound HTLCs to add which were
27252751
/// waiting on this revoke_and_ack. The generation of this new commitment_signed may also fail,
@@ -2878,6 +2904,7 @@ impl<Signer: Sign> Channel<Signer> {
28782904
log_trace!(logger, " ...promoting outbound fee update {} to Committed", feerate);
28792905
self.feerate_per_kw = feerate;
28802906
self.pending_update_fee = None;
2907+
self.autoclose_timer = 0;
28812908
},
28822909
FeeUpdateState::RemoteAnnounced => { debug_assert!(!self.is_outbound()); },
28832910
FeeUpdateState::AwaitingRemoteRevokeToAnnounce => {
@@ -2974,6 +3001,8 @@ impl<Signer: Sign> Channel<Signer> {
29743001
return None;
29753002
}
29763003

3004+
self.maybe_trigger_autoclose_timer();
3005+
29773006
debug_assert!(self.pending_update_fee.is_none());
29783007
self.pending_update_fee = Some((feerate_per_kw, FeeUpdateState::Outbound));
29793008

@@ -3163,6 +3192,22 @@ impl<Signer: Sign> Channel<Signer> {
31633192
Ok(())
31643193
}
31653194

3195+
/// If the auto-close timer is reached following the triggering of a auto-close condition
3196+
/// (i.e a non-satisfying feerate to ensure efficient confirmation), we force-close
3197+
/// channel, hopefully narrowing the safety risks for the user funds.
3198+
pub fn check_autoclose(&mut self) -> Result<(), ChannelError> {
3199+
if self.autoclose_timer > 0 && self.autoclose_timer < AUTOCLOSE_TIMEOUT {
3200+
self.autoclose_timer += 1;
3201+
}
3202+
if self.autoclose_timer == AUTOCLOSE_TIMEOUT {
3203+
// If the channel doesn't have pending HTLC outputs to claim on-chain
3204+
if self.pending_inbound_htlcs.len() + self.pending_outbound_htlcs.len() > 0 {
3205+
return Err(ChannelError::Close("Channel has time-sensitive outputs and the auto-close timer has been reached".to_owned()));
3206+
}
3207+
}
3208+
Ok(())
3209+
}
3210+
31663211
fn get_last_revoke_and_ack(&self) -> msgs::RevokeAndACK {
31673212
let next_per_commitment_point = self.holder_signer.get_per_commitment_point(self.cur_holder_commitment_transaction_number, &self.secp_ctx);
31683213
let per_commitment_secret = self.holder_signer.release_commitment_secret(self.cur_holder_commitment_transaction_number + 2);
@@ -5178,6 +5223,7 @@ impl<Signer: Sign> Writeable for Channel<Signer> {
51785223
(5, self.config, required),
51795224
(7, self.shutdown_scriptpubkey, option),
51805225
(9, self.target_closing_feerate_sats_per_kw, option),
5226+
(11, self.autoclose_timer, required),
51815227
});
51825228

51835229
Ok(())
@@ -5411,13 +5457,15 @@ impl<'a, Signer: Sign, K: Deref> ReadableArgs<&'a K> for Channel<Signer>
54115457

54125458
let mut announcement_sigs = None;
54135459
let mut target_closing_feerate_sats_per_kw = None;
5460+
let mut autoclose_timer = 0;
54145461
read_tlv_fields!(reader, {
54155462
(0, announcement_sigs, option),
54165463
(1, minimum_depth, option),
54175464
(3, counterparty_selected_channel_reserve_satoshis, option),
54185465
(5, config, option), // Note that if none is provided we will *not* overwrite the existing one.
54195466
(7, shutdown_scriptpubkey, option),
54205467
(9, target_closing_feerate_sats_per_kw, option),
5468+
(11, autoclose_timer, required),
54215469
});
54225470

54235471
let mut secp_ctx = Secp256k1::new();
@@ -5434,6 +5482,8 @@ impl<'a, Signer: Sign, K: Deref> ReadableArgs<&'a K> for Channel<Signer>
54345482

54355483
latest_monitor_update_id,
54365484

5485+
autoclose_timer,
5486+
54375487
holder_signer,
54385488
shutdown_scriptpubkey,
54395489
destination_script,

lightning/src/ln/channelmanager.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -626,6 +626,7 @@ const CHECK_CLTV_EXPIRY_SANITY: u32 = MIN_CLTV_EXPIRY_DELTA as u32 - LATENCY_GRA
626626
#[allow(dead_code)]
627627
const CHECK_CLTV_EXPIRY_SANITY_2: u32 = MIN_CLTV_EXPIRY_DELTA as u32 - LATENCY_GRACE_PERIOD_BLOCKS - 2*CLTV_CLAIM_BUFFER;
628628

629+
629630
/// Channel parameters which apply to our counterparty. These are split out from [`ChannelDetails`]
630631
/// to better separate parameters.
631632
#[derive(Clone, Debug, PartialEq)]
@@ -2660,6 +2661,23 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
26602661
(retain_channel, NotifyOption::DoPersist, ret_err)
26612662
}
26622663

2664+
fn check_channel_autoclose(&self, short_to_id: &mut HashMap<u64, [u8; 32]>, chan_id: &[u8; 32], chan: &mut Channel<Signer>) -> (bool, NotifyOption, Result<(), MsgHandleErrInternal>) {
2665+
let mut retain_channel = true;
2666+
let mut notify_option = NotifyOption::SkipPersist;
2667+
let res = match chan.check_autoclose() {
2668+
Ok(res) => Ok(res),
2669+
Err(e) => {
2670+
let (drop, res) = convert_chan_err!(self, e, short_to_id, chan, chan_id);
2671+
if drop {
2672+
retain_channel = false;
2673+
notify_option = NotifyOption::DoPersist;
2674+
}
2675+
Err(res)
2676+
}
2677+
};
2678+
(retain_channel, notify_option, res)
2679+
}
2680+
26632681
#[cfg(fuzzing)]
26642682
/// In chanmon_consistency we want to sometimes do the channel fee updates done in
26652683
/// timer_tick_occurred, but we can't generate the disabled channel updates as it considers
@@ -2716,6 +2734,14 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
27162734
let short_to_id = &mut channel_state.short_to_id;
27172735
channel_state.by_id.retain(|chan_id, chan| {
27182736
let counterparty_node_id = chan.get_counterparty_node_id();
2737+
2738+
let (retain_channel, chan_needs_persist, err) = self.check_channel_autoclose(short_to_id, chan_id, chan);
2739+
if chan_needs_persist == NotifyOption::DoPersist { should_persist = NotifyOption::DoPersist; }
2740+
if err.is_err() {
2741+
handle_errors.push((err, counterparty_node_id));
2742+
}
2743+
if !retain_channel { return false; }
2744+
27192745
let (retain_channel, chan_needs_persist, err) = self.update_channel_fee(short_to_id, pending_msg_events, chan_id, chan, new_feerate);
27202746
if chan_needs_persist == NotifyOption::DoPersist { should_persist = NotifyOption::DoPersist; }
27212747
if err.is_err() {

0 commit comments

Comments
 (0)