diff --git a/lightning/src/chain/chainmonitor.rs b/lightning/src/chain/chainmonitor.rs index 17693f8ca7a..b01526e3a0c 100644 --- a/lightning/src/chain/chainmonitor.rs +++ b/lightning/src/chain/chainmonitor.rs @@ -33,7 +33,7 @@ use crate::chain::chaininterface::{BroadcasterInterface, FeeEstimator}; #[cfg(peer_storage)] use crate::chain::channelmonitor::write_chanmon_internal; use crate::chain::channelmonitor::{ - Balance, ChannelMonitor, ChannelMonitorUpdate, MonitorEvent, TransactionOutputs, + self, Balance, ChannelMonitor, ChannelMonitorUpdate, MonitorEvent, TransactionOutputs, WithChannelMonitor, }; use crate::chain::transaction::{OutPoint, TransactionData}; @@ -350,6 +350,12 @@ pub struct ChainMonitor< P::Target: Persist, { monitors: RwLock>>, + + /// Memory-only per-channel configuration for the CLTV buffer used when deciding + /// to force-close channels with claimable inbound HTLCs. This is not persisted + /// and is rebuilt from channel state on restart. + channel_force_close_buffers: RwLock>, + chain_source: Option, broadcaster: T, logger: L, @@ -402,6 +408,7 @@ where let event_notifier = Arc::new(Notifier::new()); Self { monitors: RwLock::new(new_hash_map()), + channel_force_close_buffers: RwLock::new(new_hash_map()), chain_source, broadcaster, logger, @@ -607,6 +614,7 @@ where ) -> Self { Self { monitors: RwLock::new(new_hash_map()), + channel_force_close_buffers: RwLock::new(new_hash_map()), chain_source, broadcaster, logger, @@ -622,6 +630,27 @@ where } } + /// Updates the force-close buffer configuration for a channel. + /// + /// This is a memory-only update and does not trigger persistence. The buffer value + /// determines how many blocks before an inbound HTLC's CLTV expiry the channel will + /// be force-closed to claim it on-chain. + /// + /// Returns an error if the buffer value is below [`CLTV_CLAIM_BUFFER`]. + /// + /// [`CLTV_CLAIM_BUFFER`]: channelmonitor::CLTV_CLAIM_BUFFER + pub fn update_channel_force_close_buffer( + &self, channel_id: ChannelId, force_close_buffer: u32, + ) -> Result<(), ()> { + if force_close_buffer < channelmonitor::CLTV_CLAIM_BUFFER { + return Err(()); + } + + let mut buffers = self.channel_force_close_buffers.write().unwrap(); + buffers.insert(channel_id, force_close_buffer); + Ok(()) + } + /// Gets the balances in the contained [`ChannelMonitor`]s which are claimable on-chain or /// claims which are awaiting confirmation. /// @@ -1128,10 +1157,16 @@ where height ); self.process_chain_data(header, Some(height), &txdata, |monitor, txdata| { + let channel_id = monitor.channel_id(); + let buffers = self.channel_force_close_buffers.read().unwrap(); + let force_close_buffer = + buffers.get(&channel_id).copied().unwrap_or(channelmonitor::CLTV_CLAIM_BUFFER); + monitor.block_connected( header, txdata, height, + force_close_buffer, &self.broadcaster, &self.fee_estimator, &self.logger, @@ -1188,10 +1223,16 @@ where header.block_hash() ); self.process_chain_data(header, None, txdata, |monitor, txdata| { + let channel_id = monitor.channel_id(); + let buffers = self.channel_force_close_buffers.read().unwrap(); + let force_close_buffer = + buffers.get(&channel_id).copied().unwrap_or(channelmonitor::CLTV_CLAIM_BUFFER); + monitor.transactions_confirmed( header, txdata, height, + force_close_buffer, &self.broadcaster, &self.fee_estimator, &self.logger, @@ -1225,9 +1266,15 @@ where // While in practice there shouldn't be any recursive calls when given empty txdata, // it's still possible if a chain::Filter implementation returns a transaction. debug_assert!(txdata.is_empty()); + let channel_id = monitor.channel_id(); + let buffers = self.channel_force_close_buffers.read().unwrap(); + let force_close_buffer = + buffers.get(&channel_id).copied().unwrap_or(channelmonitor::CLTV_CLAIM_BUFFER); + monitor.best_block_updated( header, height, + force_close_buffer, &self.broadcaster, &self.fee_estimator, &self.logger, @@ -1282,6 +1329,7 @@ where hash_map::Entry::Vacant(e) => e, }; log_trace!(logger, "Got new ChannelMonitor"); + let initial_buffer = monitor.get_initial_force_close_buffer(); let update_id = monitor.get_latest_update_id(); let mut pending_monitor_updates = Vec::new(); let persist_res = self.persister.persist_new_channel(monitor.persistence_key(), &monitor); @@ -1306,6 +1354,10 @@ where monitor, pending_monitor_updates: Mutex::new(pending_monitor_updates), }); + + let mut buffers = self.channel_force_close_buffers.write().unwrap(); + buffers.insert(channel_id, initial_buffer); + Ok(persist_res) } diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index 6205fa895a7..2c9aaec28f8 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -278,9 +278,9 @@ pub(crate) const MAX_BLOCKS_FOR_CONF: u32 = 18; /// If an HTLC expires within this many blocks, force-close the channel to broadcast the /// HTLC-Success transaction. /// -/// This is two times [`MAX_BLOCKS_FOR_CONF`] as we need to first get the commitment transaction -/// confirmed, then get an HTLC transaction confirmed. -pub(crate) const CLTV_CLAIM_BUFFER: u32 = MAX_BLOCKS_FOR_CONF * 2; +/// This accounts for the time needed to first get the commitment transaction confirmed, then get +/// an HTLC transaction confirmed. +pub const CLTV_CLAIM_BUFFER: u32 = MAX_BLOCKS_FOR_CONF * 2; /// Number of blocks by which point we expect our counterparty to have seen new blocks on the /// network and done a full update_fail_htlc/commitment_signed dance (+ we've updated all our /// copies of ChannelMonitors, including watchtowers). We could enforce the contract by failing @@ -1234,6 +1234,10 @@ pub(crate) struct ChannelMonitorImpl { on_holder_tx_csv: u16, + /// The configurable number of blocks before an inbound HTLC's CLTV expiry at which we will + /// force-close the channel to claim it on-chain. Defaults to [`CLTV_CLAIM_BUFFER`]. + force_close_claimable_htlc_cltv_buffer: u32, + commitment_secrets: CounterpartyCommitmentSecrets, /// We cannot identify HTLC-Success or HTLC-Timeout transactions by themselves on the chain. /// Nor can we figure out their commitment numbers without the commitment transaction they are @@ -1733,27 +1737,27 @@ pub(crate) fn write_chanmon_internal( }; write_tlv_fields!(writer, { - (1, channel_monitor.funding_spend_confirmed, option), - (3, channel_monitor.htlcs_resolved_on_chain, required_vec), - (5, pending_monitor_events, required_vec), - (7, channel_monitor.funding_spend_seen, required), - (9, channel_monitor.counterparty_node_id, required), - (11, channel_monitor.confirmed_commitment_tx_counterparty_output, option), - (13, channel_monitor.spendable_txids_confirmed, required_vec), - (15, channel_monitor.counterparty_fulfilled_htlcs, required), - (17, channel_monitor.initial_counterparty_commitment_info, option), - (19, channel_monitor.channel_id, required), - (21, channel_monitor.balances_empty_height, option), - (23, channel_monitor.holder_pays_commitment_tx_fee, option), - (25, channel_monitor.payment_preimages, required), - (27, channel_monitor.first_negotiated_funding_txo, required), - (29, channel_monitor.initial_counterparty_commitment_tx, option), - (31, channel_monitor.funding.channel_parameters, required), - (32, channel_monitor.pending_funding, optional_vec), - (33, channel_monitor.htlcs_resolved_to_user, required), - (34, channel_monitor.alternative_funding_confirmed, option), - (35, channel_monitor.is_manual_broadcast, required), - (37, channel_monitor.funding_seen_onchain, required), + (1, channel_monitor.funding_spend_confirmed, option), + (3, channel_monitor.htlcs_resolved_on_chain, required_vec), + (5, pending_monitor_events, required_vec), + (7, channel_monitor.funding_spend_seen, required), + (9, channel_monitor.counterparty_node_id, required), + (11, channel_monitor.confirmed_commitment_tx_counterparty_output, option), + (13, channel_monitor.spendable_txids_confirmed, required_vec), + (15, channel_monitor.counterparty_fulfilled_htlcs, required), + (17, channel_monitor.initial_counterparty_commitment_info, option), + (19, channel_monitor.channel_id, required), + (21, channel_monitor.balances_empty_height, option), + (23, channel_monitor.holder_pays_commitment_tx_fee, option), + (25, channel_monitor.payment_preimages, required), + (27, channel_monitor.first_negotiated_funding_txo, required), + (29, channel_monitor.initial_counterparty_commitment_tx, option), + (31, channel_monitor.funding.channel_parameters, required), + (32, channel_monitor.pending_funding, optional_vec), + (33, channel_monitor.htlcs_resolved_to_user, required), + (34, channel_monitor.alternative_funding_confirmed, option), + (35, channel_monitor.is_manual_broadcast, required), + (37, channel_monitor.funding_seen_onchain, required), }); Ok(()) @@ -1927,6 +1931,8 @@ impl ChannelMonitor { on_holder_tx_csv: counterparty_channel_parameters.selected_contest_delay, + force_close_claimable_htlc_cltv_buffer: CLTV_CLAIM_BUFFER, + commitment_secrets: CounterpartyCommitmentSecrets::new(), counterparty_commitment_txn_on_chain: new_hash_map(), counterparty_hash_commitment_number: new_hash_map(), @@ -2113,6 +2119,15 @@ impl ChannelMonitor { self.inner.lock().unwrap().channel_type_features().clone() } + /// Gets the initial force-close buffer configuration. + /// + /// This is used during initialization to populate ChainMonitor's memory-only config. + /// For monitors deserialized from old versions, this returns the persisted value. + /// For new monitors, this returns the value passed during construction. + pub(crate) fn get_initial_force_close_buffer(&self) -> u32 { + self.inner.lock().unwrap().force_close_claimable_htlc_cltv_buffer + } + /// Gets a list of txids, with their output scripts (in the order they appear in the /// transaction), which we must learn about spends of via block_connected(). #[rustfmt::skip] @@ -2364,6 +2379,7 @@ impl ChannelMonitor { header: &Header, txdata: &TransactionData, height: u32, + force_close_buffer: u32, broadcaster: B, fee_estimator: F, logger: &L, @@ -2371,7 +2387,7 @@ impl ChannelMonitor { let mut inner = self.inner.lock().unwrap(); let logger = WithChannelMonitor::from_impl(logger, &*inner, None); inner.block_connected( - header, txdata, height, broadcaster, fee_estimator, &logger) + header, txdata, height, force_close_buffer, broadcaster, fee_estimator, &logger) } /// Determines if the disconnected block contained any transactions of interest and updates @@ -2397,6 +2413,7 @@ impl ChannelMonitor { header: &Header, txdata: &TransactionData, height: u32, + force_close_buffer: u32, broadcaster: B, fee_estimator: F, logger: &L, @@ -2405,7 +2422,7 @@ impl ChannelMonitor { let mut inner = self.inner.lock().unwrap(); let logger = WithChannelMonitor::from_impl(logger, &*inner, None); inner.transactions_confirmed( - header, txdata, height, broadcaster, &bounded_fee_estimator, &logger) + header, txdata, height, force_close_buffer, broadcaster, &bounded_fee_estimator, &logger) } /// Processes a transaction that was reorganized out of the chain. @@ -2442,6 +2459,7 @@ impl ChannelMonitor { &self, header: &Header, height: u32, + force_close_buffer: u32, broadcaster: B, fee_estimator: F, logger: &L, @@ -2450,7 +2468,7 @@ impl ChannelMonitor { let mut inner = self.inner.lock().unwrap(); let logger = WithChannelMonitor::from_impl(logger, &*inner, None); inner.best_block_updated( - header, height, broadcaster, &bounded_fee_estimator, &logger + header, height, force_close_buffer, broadcaster, &bounded_fee_estimator, &logger ) } @@ -5213,14 +5231,14 @@ impl ChannelMonitorImpl { #[rustfmt::skip] fn block_connected( - &mut self, header: &Header, txdata: &TransactionData, height: u32, broadcaster: B, + &mut self, header: &Header, txdata: &TransactionData, height: u32, force_close_buffer: u32, broadcaster: B, fee_estimator: F, logger: &WithContext, ) -> Vec { let block_hash = header.block_hash(); self.best_block = BestBlock::new(block_hash, height); let bounded_fee_estimator = LowerBoundedFeeEstimator::new(fee_estimator); - self.transactions_confirmed(header, txdata, height, broadcaster, &bounded_fee_estimator, logger) + self.transactions_confirmed(header, txdata, height, force_close_buffer, broadcaster, &bounded_fee_estimator, logger) } #[rustfmt::skip] @@ -5228,6 +5246,7 @@ impl ChannelMonitorImpl { &mut self, header: &Header, height: u32, + force_close_buffer: u32, broadcaster: B, fee_estimator: &LowerBoundedFeeEstimator, logger: &WithContext, @@ -5237,7 +5256,7 @@ impl ChannelMonitorImpl { if height > self.best_block.height { self.best_block = BestBlock::new(block_hash, height); log_trace!(logger, "Connecting new block {} at height {}", block_hash, height); - self.block_confirmed(height, block_hash, vec![], vec![], vec![], &broadcaster, &fee_estimator, logger) + self.block_confirmed(height, block_hash, vec![], vec![], vec![], force_close_buffer, &broadcaster, &fee_estimator, logger) } else if block_hash != self.best_block.block_hash { self.best_block = BestBlock::new(block_hash, height); log_trace!(logger, "Best block re-orged, replaced with new block {} at height {}", block_hash, height); @@ -5256,6 +5275,7 @@ impl ChannelMonitorImpl { header: &Header, txdata: &TransactionData, height: u32, + force_close_buffer: u32, broadcaster: B, fee_estimator: &LowerBoundedFeeEstimator, logger: &WithContext, @@ -5519,7 +5539,7 @@ impl ChannelMonitorImpl { watch_outputs.append(&mut outputs); } - self.block_confirmed(height, block_hash, txn_matched, watch_outputs, claimable_outpoints, &broadcaster, &fee_estimator, logger) + self.block_confirmed(height, block_hash, txn_matched, watch_outputs, claimable_outpoints, force_close_buffer, &broadcaster, &fee_estimator, logger) } /// Update state for new block(s)/transaction(s) confirmed. Note that the caller must update @@ -5538,6 +5558,7 @@ impl ChannelMonitorImpl { txn_matched: Vec<&Transaction>, mut watch_outputs: Vec, mut claimable_outpoints: Vec, + force_close_buffer: u32, broadcaster: &B, fee_estimator: &LowerBoundedFeeEstimator, logger: &WithContext, @@ -5547,7 +5568,7 @@ impl ChannelMonitorImpl { // Only generate claims if we haven't already done so (e.g., in transactions_confirmed). if claimable_outpoints.is_empty() { - let should_broadcast = self.should_broadcast_holder_commitment_txn(logger); + let should_broadcast = self.should_broadcast_holder_commitment_txn(force_close_buffer, logger); if let Some(payment_hash) = should_broadcast { let reason = ClosureReason::HTLCsTimedOut { payment_hash: Some(payment_hash) }; let (mut new_outpoints, mut new_outputs) = @@ -5913,7 +5934,7 @@ impl ChannelMonitorImpl { #[rustfmt::skip] fn should_broadcast_holder_commitment_txn( - &self, logger: &WithContext + &self, force_close_buffer: u32, logger: &WithContext ) -> Option { // There's no need to broadcast our commitment transaction if we've seen one confirmed (even // with 1 confirmation) as it'll be rejected as duplicate/conflicting. @@ -5952,7 +5973,7 @@ impl ChannelMonitorImpl { // on-chain for an expired HTLC. let htlc_outbound = $holder_tx == htlc.offered; if ( htlc_outbound && htlc.cltv_expiry + LATENCY_GRACE_PERIOD_BLOCKS <= height) || - (!htlc_outbound && htlc.cltv_expiry <= height + CLTV_CLAIM_BUFFER && self.payment_preimages.contains_key(&htlc.payment_hash)) { + (!htlc_outbound && htlc.cltv_expiry <= height + force_close_buffer && self.payment_preimages.contains_key(&htlc.payment_hash)) { log_info!(logger, "Force-closing channel due to {} HTLC timeout - HTLC with payment hash {} expires at {}", if htlc_outbound { "outbound" } else { "inbound"}, htlc.payment_hash, htlc.cltv_expiry); return Some(htlc.payment_hash); } @@ -6262,7 +6283,15 @@ impl, T, F, L) { fn filtered_block_connected(&self, header: &Header, txdata: &TransactionData, height: u32) { - self.0.block_connected(header, txdata, height, &self.1, &self.2, &self.3); + self.0.block_connected( + header, + txdata, + height, + CLTV_CLAIM_BUFFER, + &self.1, + &self.2, + &self.3, + ); } fn blocks_disconnected(&self, fork_point: BestBlock) { @@ -6276,7 +6305,15 @@ where M: Deref>, { fn transactions_confirmed(&self, header: &Header, txdata: &TransactionData, height: u32) { - self.0.transactions_confirmed(header, txdata, height, &self.1, &self.2, &self.3); + self.0.transactions_confirmed( + header, + txdata, + height, + CLTV_CLAIM_BUFFER, + &self.1, + &self.2, + &self.3, + ); } fn transaction_unconfirmed(&self, txid: &Txid) { @@ -6284,7 +6321,7 @@ where } fn best_block_updated(&self, header: &Header, height: u32) { - self.0.best_block_updated(header, height, &self.1, &self.2, &self.3); + self.0.best_block_updated(header, height, CLTV_CLAIM_BUFFER, &self.1, &self.2, &self.3); } fn get_relevant_txids(&self) -> Vec<(Txid, u32, Option)> { @@ -6520,6 +6557,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP let mut alternative_funding_confirmed = None; let mut is_manual_broadcast = RequiredWrapper(None); let mut funding_seen_onchain = RequiredWrapper(None); + let mut force_close_claimable_htlc_cltv_buffer = CLTV_CLAIM_BUFFER; read_tlv_fields!(reader, { (1, funding_spend_confirmed, option), (3, htlcs_resolved_on_chain, optional_vec), @@ -6542,6 +6580,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP (34, alternative_funding_confirmed, option), (35, is_manual_broadcast, (default_value, false)), (37, funding_seen_onchain, (default_value, true)), + (39, force_close_claimable_htlc_cltv_buffer, (default_value, CLTV_CLAIM_BUFFER)), }); // Note that `payment_preimages_with_info` was added (and is always written) in LDK 0.1, so // we can use it to determine if this monitor was last written by LDK 0.1 or later. @@ -6681,6 +6720,8 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP on_holder_tx_csv, + force_close_claimable_htlc_cltv_buffer, + commitment_secrets, counterparty_commitment_txn_on_chain, counterparty_hash_commitment_number, diff --git a/lightning/src/chain/mod.rs b/lightning/src/chain/mod.rs index bc47f1b1db6..79071f9dada 100644 --- a/lightning/src/chain/mod.rs +++ b/lightning/src/chain/mod.rs @@ -345,6 +345,20 @@ pub trait Watch { fn release_pending_monitor_events( &self, ) -> Vec<(OutPoint, ChannelId, Vec, PublicKey)>; + + /// Updates the force-close buffer configuration for a channel. + /// + /// This is a memory-only update that controls how many blocks before an inbound HTLC's + /// CLTV expiry the channel will be force-closed. This method is optional and has a default + /// no-op implementation for backward compatibility. + /// + /// Returns an error if the implementation cannot apply the update or if the buffer value + /// is invalid. + fn update_channel_force_close_buffer( + &self, _channel_id: ChannelId, _force_close_buffer: u32, + ) -> Result<(), ()> { + Ok(()) + } } impl + ?Sized, W: Deref> @@ -367,6 +381,12 @@ impl + ?Sized, W: Der ) -> Vec<(OutPoint, ChannelId, Vec, PublicKey)> { self.deref().release_pending_monitor_events() } + + fn update_channel_force_close_buffer( + &self, channel_id: ChannelId, force_close_buffer: u32, + ) -> Result<(), ()> { + self.deref().update_channel_force_close_buffer(channel_id, force_close_buffer) + } } /// The `Filter` trait defines behavior for indicating chain activity of interest pertaining to diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index eb227a50855..2f604127e38 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -3381,7 +3381,7 @@ trait InitialRemoteCommitmentReceiver { &funding.channel_transaction_parameters, funding.is_outbound(), obscure_factor, holder_commitment_tx, best_block, context.counterparty_node_id, context.channel_id(), context.is_manual_broadcast, - ); + ); channel_monitor.provide_initial_counterparty_commitment_tx( counterparty_initial_commitment_tx.clone(), ); diff --git a/lightning/src/ln/channel_open_tests.rs b/lightning/src/ln/channel_open_tests.rs index b7965c4fb66..0469900ab70 100644 --- a/lightning/src/ln/channel_open_tests.rs +++ b/lightning/src/ln/channel_open_tests.rs @@ -1119,6 +1119,7 @@ pub fn test_manually_accept_inbound_channel_request() { cltv_expiry_delta: None, max_dust_htlc_exposure_msat: None, force_close_avoidance_max_fee_satoshis: None, + force_close_claimable_htlc_cltv_buffer: None, accept_underpaying_htlcs: None, }), }; diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 8a84de69cfc..6b4b127beeb 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -6619,6 +6619,13 @@ impl< err: format!("The chosen CLTV expiry delta is below the minimum of {}", MIN_CLTV_EXPIRY_DELTA), }); } + if config_update.force_close_claimable_htlc_cltv_buffer + .map(|buf| buf < CLTV_CLAIM_BUFFER).unwrap_or(false) + { + return Err(APIError::APIMisuseError { + err: format!("The chosen force-close CLTV buffer is below the minimum of {}", CLTV_CLAIM_BUFFER), + }); + } let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self); let per_peer_state = self.per_peer_state.read().unwrap(); @@ -6641,6 +6648,13 @@ impl< if !channel.context_mut().update_config(&config) { continue; } + if let Some(buffer) = config_update.force_close_claimable_htlc_cltv_buffer { + if let Err(_) = self.chain_monitor.update_channel_force_close_buffer(*channel_id, buffer) { + return Err(APIError::APIMisuseError { + err: "Failed to update chain monitor force-close buffer".to_string(), + }); + } + } if let Some(channel) = channel.as_funded() { if let Ok((msg, node_id_1, node_id_2)) = self.get_channel_update_for_broadcast(channel) { let mut pending_broadcast_messages = self.pending_broadcast_messages.lock().unwrap(); diff --git a/lightning/src/util/config.rs b/lightning/src/util/config.rs index feb326cfad6..c1ef51725bc 100644 --- a/lightning/src/util/config.rs +++ b/lightning/src/util/config.rs @@ -10,6 +10,7 @@ //! Various user-configurable channel limits and settings which ChannelManager //! applies for you. +use crate::chain::channelmonitor::CLTV_CLAIM_BUFFER; use crate::ln::channel::MAX_FUNDING_SATOSHIS_NO_WUMBO; use crate::ln::channelmanager::{BREAKDOWN_TIMEOUT, MAX_LOCAL_BREAKDOWN_TIMEOUT}; @@ -593,6 +594,26 @@ pub struct ChannelConfig { /// [`NonAnchorChannelFee`]: crate::chain::chaininterface::ConfirmationTarget::NonAnchorChannelFee /// [`ChannelCloseMinimum`]: crate::chain::chaininterface::ConfirmationTarget::ChannelCloseMinimum pub force_close_avoidance_max_fee_satoshis: u64, + /// The number of blocks before an inbound HTLC's CLTV expiry at which we will force-close the + /// channel in order to claim it on-chain with an HTLC-Success transaction. This applies when + /// we have the preimage for the HTLC (i.e., it is a claimable HTLC that we forwarded). + /// + /// Increasing this value gives you more tolerance for your own downtime at the expense of + /// being less tolerant of counterparty unresponsiveness (more force-closes). When set higher, + /// you force-close earlier, leaving more blocks to get the commitment transaction and + /// HTLC-Success transaction confirmed on-chain. + /// + /// Note that when changing this value, you should also ensure that + /// [`cltv_expiry_delta`] is large enough to accommodate the new buffer. + /// + /// Default value: [`CLTV_CLAIM_BUFFER`] (36 blocks) + /// + /// Minimum value: [`CLTV_CLAIM_BUFFER`] (Any values less than this will be treated as + /// [`CLTV_CLAIM_BUFFER`] instead.) + /// + /// [`cltv_expiry_delta`]: ChannelConfig::cltv_expiry_delta + /// [`CLTV_CLAIM_BUFFER`]: crate::chain::channelmonitor::CLTV_CLAIM_BUFFER + pub force_close_claimable_htlc_cltv_buffer: u32, /// If set, allows this channel's counterparty to skim an additional fee off this node's inbound /// HTLCs. Useful for liquidity providers to offload on-chain channel costs to end users. /// @@ -650,6 +671,11 @@ impl ChannelConfig { { self.force_close_avoidance_max_fee_satoshis = force_close_avoidance_max_fee_satoshis; } + if let Some(force_close_claimable_htlc_cltv_buffer) = + update.force_close_claimable_htlc_cltv_buffer + { + self.force_close_claimable_htlc_cltv_buffer = force_close_claimable_htlc_cltv_buffer; + } if let Some(accept_underpaying_htlcs) = update.accept_underpaying_htlcs { self.accept_underpaying_htlcs = accept_underpaying_htlcs; } @@ -665,6 +691,7 @@ impl Default for ChannelConfig { cltv_expiry_delta: 6 * 12, // 6 blocks/hour * 12 hours max_dust_htlc_exposure: MaxDustHTLCExposure::FeeRateMultiplier(10000), force_close_avoidance_max_fee_satoshis: 1000, + force_close_claimable_htlc_cltv_buffer: CLTV_CLAIM_BUFFER, accept_underpaying_htlcs: false, } } @@ -687,6 +714,7 @@ impl crate::util::ser::Writeable for ChannelConfig { // LegacyChannelConfig. To make sure that serialization is not compatible with this one, we use // the next required type of 10, which if seen by the old serialization will always fail. (10, self.force_close_avoidance_max_fee_satoshis, required), + (11, self.force_close_claimable_htlc_cltv_buffer, (default_value, CLTV_CLAIM_BUFFER)), }); Ok(()) } @@ -701,6 +729,7 @@ impl crate::util::ser::Readable for ChannelConfig { let mut max_dust_htlc_exposure_msat = None; let mut max_dust_htlc_exposure_enum = None; let mut force_close_avoidance_max_fee_satoshis = 1000; + let mut force_close_claimable_htlc_cltv_buffer = CLTV_CLAIM_BUFFER; read_tlv_fields!(reader, { (0, forwarding_fee_proportional_millionths, required), (1, accept_underpaying_htlcs, (default_value, false)), @@ -710,6 +739,7 @@ impl crate::util::ser::Readable for ChannelConfig { // Has always been written, but became optionally read in 0.0.116 (6, max_dust_htlc_exposure_msat, option), (10, force_close_avoidance_max_fee_satoshis, required), + (11, force_close_claimable_htlc_cltv_buffer, (default_value, CLTV_CLAIM_BUFFER)), }); let max_dust_htlc_fixed_limit = max_dust_htlc_exposure_msat.unwrap_or(5_000_000); let max_dust_htlc_exposure_msat = max_dust_htlc_exposure_enum @@ -721,6 +751,7 @@ impl crate::util::ser::Readable for ChannelConfig { cltv_expiry_delta, max_dust_htlc_exposure: max_dust_htlc_exposure_msat, force_close_avoidance_max_fee_satoshis, + force_close_claimable_htlc_cltv_buffer, }) } } @@ -747,6 +778,10 @@ pub struct ChannelConfigUpdate { /// funds. See [`ChannelConfig::force_close_avoidance_max_fee_satoshis`]. pub force_close_avoidance_max_fee_satoshis: Option, + /// The number of blocks before an inbound HTLC's CLTV expiry at which we will force-close to claim it + /// on-chain. See [`ChannelConfig::force_close_claimable_htlc_cltv_buffer`]. + pub force_close_claimable_htlc_cltv_buffer: Option, + /// If set, allows this channel's counterparty to skim an additional fee off this node's inbound HTLCs. See /// [`ChannelConfig::accept_underpaying_htlcs`]. pub accept_underpaying_htlcs: Option, @@ -764,6 +799,9 @@ impl From for ChannelConfigUpdate { force_close_avoidance_max_fee_satoshis: Some( config.force_close_avoidance_max_fee_satoshis, ), + force_close_claimable_htlc_cltv_buffer: Some( + config.force_close_claimable_htlc_cltv_buffer, + ), accept_underpaying_htlcs: Some(config.accept_underpaying_htlcs), } } @@ -846,6 +884,7 @@ impl crate::util::ser::Readable for LegacyChannelConfig { max_dust_htlc_exposure: max_dust_htlc_exposure_msat, cltv_expiry_delta, force_close_avoidance_max_fee_satoshis, + force_close_claimable_htlc_cltv_buffer: CLTV_CLAIM_BUFFER, forwarding_fee_base_msat, accept_underpaying_htlcs: false, },