Skip to content

Commit d78cfa8

Browse files
author
Antoine Riard
committed
Add auto-close if outbound feerate don't complete
According to the lightning security model, commitment transactions must offer a compelling feerate at anytime to maximize odds of block inclusion, and thus claim on-chain time-sensitive HTLCs. This fee efficiency is currently guarantee through the update_fee mechanism, a cooperative update of the channel feerate. However, if the counterparty is malicious or offline, this update might not succeed, therefore exposing our balance at risk. To lighten this risk, we add a new "auto-close" mechanism. If we detect that a update_fee should have been committed but never effectively finalized, we start a timer. At expiration, the channel is force-close, if they're pending *included HTLCs. We choose 60 ticks as "auto-close" timer duration. The mechanism can be disabled through `should_autoclose` (default: true)
1 parent 1a74367 commit d78cfa8

File tree

3 files changed

+118
-3
lines changed

3 files changed

+118
-3
lines changed

lightning/src/ln/channel.rs

Lines changed: 86 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,7 @@ struct HTLCStats {
295295
on_holder_tx_dust_exposure_msat: u64,
296296
holding_cell_msat: u64,
297297
on_holder_tx_holding_cell_htlcs_count: u32, // dust HTLCs *non*-included
298+
on_holder_tx_included_htlcs: u32,
298299
}
299300

300301
/// An enum gathering stats on commitment transaction, either local or remote.
@@ -413,6 +414,12 @@ pub(crate) const CONCURRENT_INBOUND_HTLC_FEE_BUFFER: u32 = 2;
413414
/// transaction (not counting the value of the HTLCs themselves).
414415
pub(crate) const MIN_AFFORDABLE_HTLC_COUNT: usize = 4;
415416

417+
/// If after this value tick periods, the channel feerate isn't satisfying, we auto-close the
418+
/// channel and goes on-chain to avoid unsafe situations where a commitment transaction with
419+
/// time-sensitive outputs won't confirm due to staling feerate too far away from the upper feerate
420+
/// groups of network mempools.
421+
const AUTOCLOSE_TIMEOUT: u16 = 60;
422+
416423
// TODO: We should refactor this to be an Inbound/OutboundChannel until initial setup handshaking
417424
// has been completed, and then turn into a Channel to get compiler-time enforcement of things like
418425
// calling channel_id() before we're set up or things like get_outbound_funding_signed on an
@@ -435,6 +442,14 @@ pub(super) struct Channel<Signer: Sign> {
435442

436443
latest_monitor_update_id: u64,
437444

445+
// Auto-close timer, if the channel is outbound, we sent a `update_fee`, and we didn't
446+
// receive a RAA from counterparty committing this state after `AUTOCLOSE_TIMEOUT` periods,
447+
// this channel must be force-closed.
448+
// If the channel is inbound, it has been observed the channel feerate should increase,
449+
// and we didn't receive a RAA from counterparty committing an `update_fee` after
450+
// `AUTOCLOSE_TIMEOUT` periods, this channel must be force-closed.
451+
autoclose_timer: u16,
452+
438453
holder_signer: Signer,
439454
shutdown_scriptpubkey: Option<ShutdownScript>,
440455
destination_script: Script,
@@ -747,6 +762,8 @@ impl<Signer: Sign> Channel<Signer> {
747762

748763
latest_monitor_update_id: 0,
749764

765+
autoclose_timer: 0,
766+
750767
holder_signer,
751768
shutdown_scriptpubkey,
752769
destination_script: keys_provider.get_destination_script(),
@@ -1037,6 +1054,8 @@ impl<Signer: Sign> Channel<Signer> {
10371054

10381055
latest_monitor_update_id: 0,
10391056

1057+
autoclose_timer: 0,
1058+
10401059
holder_signer,
10411060
shutdown_scriptpubkey,
10421061
destination_script: keys_provider.get_destination_script(),
@@ -2042,6 +2061,7 @@ impl<Signer: Sign> Channel<Signer> {
20422061
on_holder_tx_dust_exposure_msat: 0,
20432062
holding_cell_msat: 0,
20442063
on_holder_tx_holding_cell_htlcs_count: 0,
2064+
on_holder_tx_included_htlcs: 0,
20452065
};
20462066

20472067
let counterparty_dust_limit_timeout_sat = (self.get_dust_buffer_feerate(outbound_feerate_update) as u64 * HTLC_TIMEOUT_TX_WEIGHT / 1000) + self.counterparty_dust_limit_satoshis;
@@ -2053,6 +2073,8 @@ impl<Signer: Sign> Channel<Signer> {
20532073
}
20542074
if htlc.amount_msat / 1000 < holder_dust_limit_success_sat {
20552075
stats.on_holder_tx_dust_exposure_msat += htlc.amount_msat;
2076+
} else {
2077+
stats.on_holder_tx_included_htlcs += 1;
20562078
}
20572079
}
20582080
stats
@@ -2067,6 +2089,7 @@ impl<Signer: Sign> Channel<Signer> {
20672089
on_holder_tx_dust_exposure_msat: 0,
20682090
holding_cell_msat: 0,
20692091
on_holder_tx_holding_cell_htlcs_count: 0,
2092+
on_holder_tx_included_htlcs: 0,
20702093
};
20712094

20722095
let counterparty_dust_limit_success_sat = (self.get_dust_buffer_feerate(outbound_feerate_update) as u64 * HTLC_SUCCESS_TX_WEIGHT / 1000) + self.counterparty_dust_limit_satoshis;
@@ -2078,6 +2101,8 @@ impl<Signer: Sign> Channel<Signer> {
20782101
}
20792102
if htlc.amount_msat / 1000 < holder_dust_limit_timeout_sat {
20802103
stats.on_holder_tx_dust_exposure_msat += htlc.amount_msat;
2104+
} else {
2105+
stats.on_holder_tx_included_htlcs += 1;
20812106
}
20822107
}
20832108

@@ -2832,13 +2857,33 @@ impl<Signer: Sign> Channel<Signer> {
28322857
}
28332858
}
28342859

2860+
/// Trigger the autoclose timer if it's in the starting position
2861+
pub fn maybe_trigger_autoclose_timer(&mut self, current_feerate: u32, new_feerate: u32) -> bool {
2862+
2863+
// Verify the new feerate is at least superior by 30% of current feerate before to
2864+
// start a autoclose timer and current feerate has fallen behind background feerate.
2865+
if new_feerate > current_feerate * 1300 / 1000 {
2866+
return false;
2867+
}
2868+
2869+
// Start an auto-close timer, if the channel feerate doesn't increase before its
2870+
// expiration (i.e this outbound feerate update has been committed on both sides),
2871+
// the channel will be marked as unsafe and force-closed.
2872+
// If a timer is already pending, no-op, as a higher-feerate `update_fee` will
2873+
// implicitly override a lower-feerate `update_fee` part of the same update sequence.
2874+
if self.autoclose_timer == 0 {
2875+
self.autoclose_timer = 1;
2876+
return true;
2877+
}
2878+
return false;
2879+
}
2880+
28352881
/// Handles receiving a remote's revoke_and_ack. Note that we may return a new
28362882
/// commitment_signed message here in case we had pending outbound HTLCs to add which were
28372883
/// waiting on this revoke_and_ack. The generation of this new commitment_signed may also fail,
28382884
/// generating an appropriate error *after* the channel state has been updated based on the
28392885
/// revoke_and_ack message.
2840-
pub fn revoke_and_ack<L: Deref>(&mut self, msg: &msgs::RevokeAndACK, logger: &L) -> Result<RAAUpdates, ChannelError>
2841-
where L::Target: Logger,
2886+
pub fn revoke_and_ack<L: Deref>(&mut self, msg: &msgs::RevokeAndACK, logger: &L) -> Result<RAAUpdates, ChannelError> where L::Target: Logger
28422887
{
28432888
if (self.channel_state & (ChannelState::ChannelFunded as u32)) != (ChannelState::ChannelFunded as u32) {
28442889
return Err(ChannelError::Close("Got revoke/ACK message when channel was not in an operational state".to_owned()));
@@ -2999,6 +3044,7 @@ impl<Signer: Sign> Channel<Signer> {
29993044
log_trace!(logger, " ...promoting outbound fee update {} to Committed", feerate);
30003045
self.feerate_per_kw = feerate;
30013046
self.pending_update_fee = None;
3047+
self.autoclose_timer = 0;
30023048
},
30033049
FeeUpdateState::RemoteAnnounced => { debug_assert!(!self.is_outbound()); },
30043050
FeeUpdateState::AwaitingRemoteRevokeToAnnounce => {
@@ -3007,6 +3053,7 @@ impl<Signer: Sign> Channel<Signer> {
30073053
require_commitment = true;
30083054
self.feerate_per_kw = feerate;
30093055
self.pending_update_fee = None;
3056+
self.autoclose_timer = 0;
30103057
},
30113058
}
30123059
}
@@ -3145,6 +3192,8 @@ impl<Signer: Sign> Channel<Signer> {
31453192
return None;
31463193
}
31473194

3195+
self.maybe_trigger_autoclose_timer(self.get_feerate(), feerate_per_kw);
3196+
31483197
debug_assert!(self.pending_update_fee.is_none());
31493198
self.pending_update_fee = Some((feerate_per_kw, FeeUpdateState::Outbound));
31503199

@@ -3343,6 +3392,33 @@ impl<Signer: Sign> Channel<Signer> {
33433392
Ok(())
33443393
}
33453394

3395+
/// If the auto-close timer is reached following the triggering of a auto-close condition
3396+
/// (i.e a non-satisfying feerate to ensure efficient confirmation), we force-close
3397+
/// channel, hopefully narrowing the safety risks for the user funds.
3398+
pub fn check_autoclose(&mut self, new_feerate: u32) -> Result<(), ChannelError> {
3399+
if self.autoclose_timer > 0 && self.autoclose_timer < AUTOCLOSE_TIMEOUT {
3400+
// Again, check at each autoclose tick that mempool feerate hasn't fall back
3401+
// more in-sync to the current channel feerate. Otherwise, reset autoclose
3402+
// timer from scratch.
3403+
if new_feerate < self.get_feerate() * 1300 / 1000 {
3404+
self.autoclose_timer += 1;
3405+
} else {
3406+
self.autoclose_timer = 0;
3407+
}
3408+
}
3409+
if self.autoclose_timer == AUTOCLOSE_TIMEOUT {
3410+
let inbound_stats = self.get_inbound_pending_htlc_stats(None);
3411+
let outbound_stats = self.get_outbound_pending_htlc_stats(None);
3412+
// If the channel has pending *included* HTLCs to claim on-chain and `should_autoclose`=y, close the channel
3413+
if inbound_stats.on_holder_tx_included_htlcs + outbound_stats.on_holder_tx_included_htlcs > 0 && self.config.should_autoclose {
3414+
return Err(ChannelError::Close("Channel has time-sensitive outputs and the auto-close timer has been reached".to_owned()));
3415+
}
3416+
// Otherwise, do not reset the autoclose timer as a substantial HTLC can be committed
3417+
// at anytime on the still low-feerate channel.
3418+
}
3419+
Ok(())
3420+
}
3421+
33463422
fn get_last_revoke_and_ack(&self) -> msgs::RevokeAndACK {
33473423
let next_per_commitment_point = self.holder_signer.get_per_commitment_point(self.cur_holder_commitment_transaction_number, &self.secp_ctx);
33483424
let per_commitment_secret = self.holder_signer.release_commitment_secret(self.cur_holder_commitment_transaction_number + 2);
@@ -3419,7 +3495,8 @@ impl<Signer: Sign> Channel<Signer> {
34193495

34203496
/// May panic if some calls other than message-handling calls (which will all Err immediately)
34213497
/// have been called between remove_uncommitted_htlcs_and_mark_paused and this call.
3422-
pub fn channel_reestablish<L: Deref>(&mut self, msg: &msgs::ChannelReestablish, logger: &L) -> Result<(Option<msgs::FundingLocked>, Option<msgs::RevokeAndACK>, Option<msgs::CommitmentUpdate>, Option<ChannelMonitorUpdate>, RAACommitmentOrder, Vec<(HTLCSource, PaymentHash)>, Option<msgs::Shutdown>), ChannelError> where L::Target: Logger {
3498+
pub fn channel_reestablish<L: Deref>(&mut self, msg: &msgs::ChannelReestablish, logger: &L) -> Result<(Option<msgs::FundingLocked>, Option<msgs::RevokeAndACK>, Option<msgs::CommitmentUpdate>, Option<ChannelMonitorUpdate>, RAACommitmentOrder, Vec<(HTLCSource, PaymentHash)>, Option<msgs::Shutdown>), ChannelError> where L::Target: Logger,
3499+
{
34233500
if self.channel_state & (ChannelState::PeerDisconnected as u32) == 0 {
34243501
// While BOLT 2 doesn't indicate explicitly we should error this channel here, it
34253502
// almost certainly indicates we are going to end up out-of-sync in some way, so we
@@ -4865,6 +4942,7 @@ impl<Signer: Sign> Channel<Signer> {
48654942
log_trace!(logger, " ...promoting inbound AwaitingRemoteRevokeToAnnounce fee update {} to Committed", feerate);
48664943
self.feerate_per_kw = feerate;
48674944
self.pending_update_fee = None;
4945+
self.autoclose_timer = 0;
48684946
}
48694947
}
48704948
self.resend_order = RAACommitmentOrder::RevokeAndACKFirst;
@@ -5384,6 +5462,7 @@ impl<Signer: Sign> Writeable for Channel<Signer> {
53845462
(9, self.target_closing_feerate_sats_per_kw, option),
53855463
(11, self.monitor_pending_finalized_fulfills, vec_type),
53865464
(13, self.channel_creation_height, required),
5465+
(15, self.autoclose_timer, required),
53875466
});
53885467

53895468
Ok(())
@@ -5623,6 +5702,7 @@ impl<'a, Signer: Sign, K: Deref> ReadableArgs<(&'a K, u32)> for Channel<Signer>
56235702
// only, so we default to that if none was written.
56245703
let mut channel_type = Some(ChannelTypeFeatures::only_static_remote_key());
56255704
let mut channel_creation_height = Some(serialized_height);
5705+
let mut autoclose_timer = 0;
56265706
read_tlv_fields!(reader, {
56275707
(0, announcement_sigs, option),
56285708
(1, minimum_depth, option),
@@ -5633,6 +5713,7 @@ impl<'a, Signer: Sign, K: Deref> ReadableArgs<(&'a K, u32)> for Channel<Signer>
56335713
(9, target_closing_feerate_sats_per_kw, option),
56345714
(11, monitor_pending_finalized_fulfills, vec_type),
56355715
(13, channel_creation_height, option),
5716+
(15, autoclose_timer, required),
56365717
});
56375718

56385719
let chan_features = channel_type.as_ref().unwrap();
@@ -5661,6 +5742,8 @@ impl<'a, Signer: Sign, K: Deref> ReadableArgs<(&'a K, u32)> for Channel<Signer>
56615742

56625743
latest_monitor_update_id,
56635744

5745+
autoclose_timer,
5746+
56645747
holder_signer,
56655748
shutdown_scriptpubkey,
56665749
destination_script,

lightning/src/ln/channelmanager.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3021,6 +3021,24 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
30213021
(retain_channel, NotifyOption::DoPersist, ret_err)
30223022
}
30233023

3024+
/// Close the channel if the feerate is non-satisfying to ensure efficient confirmation
3025+
fn check_channel_autoclose(&self, short_to_id: &mut HashMap<u64, [u8; 32]>, chan_id: &[u8; 32], chan: &mut Channel<Signer>, new_feerate: u32) -> (bool, NotifyOption, Result<(), MsgHandleErrInternal>) {
3026+
let mut retain_channel = true;
3027+
let mut notify_option = NotifyOption::SkipPersist;
3028+
let res = match chan.check_autoclose(new_feerate) {
3029+
Ok(res) => Ok(res),
3030+
Err(e) => {
3031+
let (drop, res) = convert_chan_err!(self, e, short_to_id, chan, chan_id);
3032+
if drop {
3033+
retain_channel = false;
3034+
notify_option = NotifyOption::DoPersist;
3035+
}
3036+
Err(res)
3037+
}
3038+
};
3039+
(retain_channel, notify_option, res)
3040+
}
3041+
30243042
#[cfg(fuzzing)]
30253043
/// In chanmon_consistency we want to sometimes do the channel fee updates done in
30263044
/// timer_tick_occurred, but we can't generate the disabled channel updates as it considers
@@ -3077,6 +3095,14 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
30773095
let short_to_id = &mut channel_state.short_to_id;
30783096
channel_state.by_id.retain(|chan_id, chan| {
30793097
let counterparty_node_id = chan.get_counterparty_node_id();
3098+
3099+
let (retain_channel, chan_needs_persist, err) = self.check_channel_autoclose(short_to_id, chan_id, chan, new_feerate);
3100+
if chan_needs_persist == NotifyOption::DoPersist { should_persist = NotifyOption::DoPersist; }
3101+
if err.is_err() {
3102+
handle_errors.push((err, counterparty_node_id));
3103+
}
3104+
if !retain_channel { return false; }
3105+
30803106
let (retain_channel, chan_needs_persist, err) = self.update_channel_fee(short_to_id, pending_msg_events, chan_id, chan, new_feerate);
30813107
if chan_needs_persist == NotifyOption::DoPersist { should_persist = NotifyOption::DoPersist; }
30823108
if err.is_err() {

lightning/src/util/config.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,10 @@ pub struct ChannelConfig {
246246
/// [`Normal`]: crate::chain::chaininterface::ConfirmationTarget::Normal
247247
/// [`Background`]: crate::chain::chaininterface::ConfirmationTarget::Background
248248
pub force_close_avoidance_max_fee_satoshis: u64,
249+
/// If this is set to false, we do not auto-close channel with pending time-sensitive outputs,
250+
/// of which the feerate has been detected as stalling.
251+
/// Default value: true.
252+
pub should_autoclose: bool,
249253
}
250254

251255
impl Default for ChannelConfig {
@@ -259,6 +263,7 @@ impl Default for ChannelConfig {
259263
commit_upfront_shutdown_pubkey: true,
260264
max_dust_htlc_exposure_msat: 5_000_000,
261265
force_close_avoidance_max_fee_satoshis: 1000,
266+
should_autoclose: true,
262267
}
263268
}
264269
}
@@ -271,6 +276,7 @@ impl_writeable_tlv_based!(ChannelConfig, {
271276
(4, announced_channel, required),
272277
(6, commit_upfront_shutdown_pubkey, required),
273278
(8, forwarding_fee_base_msat, required),
279+
(10, should_autoclose, (default_value, true)),
274280
});
275281

276282
/// Top-level config which holds ChannelHandshakeLimits and ChannelConfig.

0 commit comments

Comments
 (0)