Skip to content

Commit 6a9dd2b

Browse files
committed
Force-close channels if their feerate gets stale without any update
For quite some time, LDK has force-closed channels if the peer sends us a feerate update which is below our `FeeEstimator`'s concept of a channel lower-bound. This is intended to ensure that channel feerates are always sufficient to get our commitment transaction confirmed on-chain if we do need to force-close. However, we've never checked our channel feerate regularly - if a peer is offline (or just uninterested in updating the channel feerate) and the prevailing feerates on-chain go up, we'll simply ignore it and allow our commitment transaction to sit around with a feerate too low to get confirmed. Here we rectify this oversight by force-closing channels with stale feerates, checking after each block. However, because fee estimators are often buggy and force-closures piss off users, we only do so rather conservatively. Specifically, we only force-close if a channel's feerate is below the minimum `FeeEstimator`-provided minimum across the last day. Further, because fee estimators are often especially buggy on startup (and because peers haven't had a chance to update the channel feerates yet), we don't force-close channels until we have a full day of feerate lower-bound history. This should reduce the incidence of force-closures substantially, but it is expected this will still increase force-closures somewhat substantially depending on the users' `FeeEstimator`. Fixes #993
1 parent 1390f4b commit 6a9dd2b

File tree

2 files changed

+76
-1
lines changed

2 files changed

+76
-1
lines changed

lightning/src/ln/channel.rs

+20
Original file line numberDiff line numberDiff line change
@@ -5300,6 +5300,26 @@ impl<SP: Deref> Channel<SP> where
53005300
}
53015301
}
53025302

5303+
pub fn check_for_stale_feerate<L: Logger>(&mut self, logger: &L, min_feerate: u32) -> Result<(), ClosureReason> {
5304+
if self.context.is_outbound() {
5305+
// While its possible our fee is too low for an outbound channel because we've been
5306+
// unable to increase the fee, we don't try to force-close directly here.
5307+
return Ok(());
5308+
}
5309+
if self.context.feerate_per_kw < min_feerate {
5310+
log_info!(logger,
5311+
"Closing channel as feerate of {} is below required {} (the minimum required rate over the past day)",
5312+
self.context.feerate_per_kw, min_feerate
5313+
);
5314+
Err(ClosureReason::PeerFeerateTooLow {
5315+
peer_feerate_sat_per_kw: self.context.feerate_per_kw,
5316+
required_feerate_sat_per_kw: min_feerate,
5317+
})
5318+
} else {
5319+
Ok(())
5320+
}
5321+
}
5322+
53035323
pub fn update_fee<F: Deref, L: Deref>(&mut self, fee_estimator: &LowerBoundedFeeEstimator<F>, msg: &msgs::UpdateFee, logger: &L) -> Result<(), ChannelError>
53045324
where F::Target: FeeEstimator, L::Target: Logger
53055325
{

lightning/src/ln/channelmanager.rs

+56-1
Original file line numberDiff line numberDiff line change
@@ -959,6 +959,11 @@ pub(super) struct InboundChannelRequest {
959959
/// accepted. An unaccepted channel that exceeds this limit will be abandoned.
960960
const UNACCEPTED_INBOUND_CHANNEL_AGE_LIMIT_TICKS: i32 = 2;
961961

962+
/// The number of blocks of historical feerate estimates we keep around and consider when deciding
963+
/// to force-close a channel for having too-low fees. Also the number of blocks we have to see
964+
/// after startup before we consider force-closing channels for having too-low fees.
965+
const FEERATE_TRACKING_BLOCKS: usize = 144;
966+
962967
/// Stores a PaymentSecret and any other data we may need to validate an inbound payment is
963968
/// actually ours and not some duplicate HTLC sent to us by a node along the route.
964969
///
@@ -2093,6 +2098,21 @@ where
20932098
/// Tracks the message events that are to be broadcasted when we are connected to some peer.
20942099
pending_broadcast_messages: Mutex<Vec<MessageSendEvent>>,
20952100

2101+
/// We only want to force-close our channels on peers based on stale feerates when we're
2102+
/// confident the feerate on the channel is *really* stale, not just became stale recently.
2103+
/// Thus, we store the fee estimates we had as of the last [`FEERATE_TRACKING_BLOCKS`] blocks
2104+
/// (after startup completed) here, and only force-close when channels have a lower feerate
2105+
/// than we predicted any time in the last [`FEERATE_TRACKING_BLOCKS`] blocks.
2106+
///
2107+
/// We only keep this in memory as we assume any feerates we receive immediately after startup
2108+
/// may be bunk (as they often are if Bitcoin Core crashes) and want to delay taking any
2109+
/// actions for a day anyway.
2110+
///
2111+
/// The first element in the pair is the
2112+
/// [`ConfirmationTarget::MinAllowedAnchorChannelRemoteFee`] estimate, the second the
2113+
/// [`ConfirmationTarget::MinAllowedNonAnchorChannelRemoteFee`] estimate.
2114+
last_days_feerates: Mutex<VecDeque<(u32, u32)>>,
2115+
20962116
entropy_source: ES,
20972117
node_signer: NS,
20982118
signer_provider: SP,
@@ -3189,6 +3209,8 @@ where
31893209
pending_offers_messages: Mutex::new(Vec::new()),
31903210
pending_broadcast_messages: Mutex::new(Vec::new()),
31913211

3212+
last_days_feerates: Mutex::new(VecDeque::new()),
3213+
31923214
entropy_source,
31933215
node_signer,
31943216
signer_provider,
@@ -9395,7 +9417,38 @@ where
93959417
self, || -> NotifyOption { NotifyOption::DoPersist });
93969418
*self.best_block.write().unwrap() = BestBlock::new(block_hash, height);
93979419

9398-
self.do_chain_event(Some(height), |channel| channel.best_block_updated(height, header.time, self.chain_hash, &self.node_signer, &self.default_configuration, &&WithChannelContext::from(&self.logger, &channel.context)));
9420+
let mut min_anchor_feerate = None;
9421+
let mut min_non_anchor_feerate = None;
9422+
if self.background_events_processed_since_startup.load(Ordering::Relaxed) {
9423+
// If we're past the startup phase, update our feerate cache
9424+
let mut last_days_feerates = self.last_days_feerates.lock().unwrap();
9425+
if last_days_feerates.len() >= FEERATE_TRACKING_BLOCKS {
9426+
last_days_feerates.pop_front();
9427+
}
9428+
let anchor_feerate = self.fee_estimator
9429+
.bounded_sat_per_1000_weight(ConfirmationTarget::MinAllowedAnchorChannelRemoteFee);
9430+
let non_anchor_feerate = self.fee_estimator
9431+
.bounded_sat_per_1000_weight(ConfirmationTarget::MinAllowedNonAnchorChannelRemoteFee);
9432+
last_days_feerates.push_back((anchor_feerate, non_anchor_feerate));
9433+
if last_days_feerates.len() >= FEERATE_TRACKING_BLOCKS {
9434+
min_anchor_feerate = last_days_feerates.iter().map(|(f, _)| f).min().copied();
9435+
min_non_anchor_feerate = last_days_feerates.iter().map(|(_, f)| f).min().copied();
9436+
}
9437+
}
9438+
9439+
self.do_chain_event(Some(height), |channel| {
9440+
let logger = WithChannelContext::from(&self.logger, &channel.context);
9441+
if channel.context.get_channel_type().supports_anchors_zero_fee_htlc_tx() {
9442+
if let Some(feerate) = min_anchor_feerate {
9443+
channel.check_for_stale_feerate(&logger, feerate)?;
9444+
}
9445+
} else {
9446+
if let Some(feerate) = min_non_anchor_feerate {
9447+
channel.check_for_stale_feerate(&logger, feerate)?;
9448+
}
9449+
}
9450+
channel.best_block_updated(height, header.time, self.chain_hash, &self.node_signer, &self.default_configuration, &&logger)
9451+
});
93999452

94009453
macro_rules! max_time {
94019454
($timestamp: expr) => {
@@ -12350,6 +12403,8 @@ where
1235012403
node_signer: args.node_signer,
1235112404
signer_provider: args.signer_provider,
1235212405

12406+
last_days_feerates: Mutex::new(VecDeque::new()),
12407+
1235312408
logger: args.logger,
1235412409
default_configuration: args.default_config,
1235512410
};

0 commit comments

Comments
 (0)