diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index 22006897a0f..7d63e162da8 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -282,6 +282,7 @@ impl TestChainMonitor { Arc::clone(&persister), Arc::clone(&keys), keys.get_peer_storage_key(), + channelmonitor::CLTV_CLAIM_BUFFER, )), logger, keys, diff --git a/fuzz/src/full_stack.rs b/fuzz/src/full_stack.rs index 5dfa51079d8..ddbd05aed35 100644 --- a/fuzz/src/full_stack.rs +++ b/fuzz/src/full_stack.rs @@ -38,6 +38,7 @@ use lightning::chain::chaininterface::{ BroadcasterInterface, ConfirmationTarget, FeeEstimator, TransactionType, }; use lightning::chain::chainmonitor; +use lightning::chain::channelmonitor; use lightning::chain::transaction::OutPoint; use lightning::chain::{BestBlock, ChannelMonitorUpdateStatus, Confirm, Listen}; use lightning::events::Event; @@ -603,6 +604,7 @@ pub fn do_test(mut data: &[u8], logger: &Arc Arc::new(TestPersister { update_ret: Mutex::new(ChannelMonitorUpdateStatus::Completed) }), Arc::clone(&keys_manager), keys_manager.get_peer_storage_key(), + channelmonitor::CLTV_CLAIM_BUFFER, )); let network = Network::Bitcoin; diff --git a/fuzz/src/lsps_message.rs b/fuzz/src/lsps_message.rs index 8371d1c5fc7..f712f9c7ffe 100644 --- a/fuzz/src/lsps_message.rs +++ b/fuzz/src/lsps_message.rs @@ -6,7 +6,7 @@ use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey}; use bitcoin::Network; use lightning::chain::Filter; -use lightning::chain::{chainmonitor, BestBlock}; +use lightning::chain::{chainmonitor, channelmonitor, BestBlock}; use lightning::ln::channelmanager::{ChainParameters, ChannelManager}; use lightning::ln::peer_handler::CustomMessageHandler; use lightning::ln::wire::CustomMessageReader; @@ -59,6 +59,7 @@ pub fn do_test(data: &[u8]) { Arc::clone(&kv_store), Arc::clone(&keys_manager), keys_manager.get_peer_storage_key(), + channelmonitor::CLTV_CLAIM_BUFFER, )); let best_block = BestBlock::from_network(network); let params = ChainParameters { network, best_block }; diff --git a/lightning-background-processor/src/lib.rs b/lightning-background-processor/src/lib.rs index da415c70a32..2b7e37e1335 100644 --- a/lightning-background-processor/src/lib.rs +++ b/lightning-background-processor/src/lib.rs @@ -1896,7 +1896,7 @@ mod tests { use bitcoin::transaction::{Transaction, TxOut}; use bitcoin::{Amount, ScriptBuf, Txid}; use core::sync::atomic::{AtomicBool, Ordering}; - use lightning::chain::channelmonitor::ANTI_REORG_DELAY; + use lightning::chain::channelmonitor::{self as channelmonitor, ANTI_REORG_DELAY}; use lightning::chain::transaction::OutPoint; use lightning::chain::{chainmonitor, BestBlock, Confirm, Filter}; use lightning::events::{Event, PathFailure, ReplayEvent}; @@ -2444,6 +2444,7 @@ mod tests { Arc::clone(&kv_store), Arc::clone(&keys_manager), keys_manager.get_peer_storage_key(), + channelmonitor::CLTV_CLAIM_BUFFER, )); let best_block = BestBlock::from_network(network); let params = ChainParameters { network, best_block }; diff --git a/lightning/src/chain/chainmonitor.rs b/lightning/src/chain/chainmonitor.rs index 7db1b697c2b..3de32b3ec1c 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,17 @@ pub struct ChainMonitor< P::Target: Persist, { monitors: RwLock>>, + + /// The number of blocks before an inbound HTLC's CLTV expiry at which we will force-close + /// the channel to claim it on-chain. This is a global, memory-only setting configured at + /// construction time. + /// + /// Must be between [`CLTV_CLAIM_BUFFER`] and [`HTLC_FAIL_BACK_BUFFER`] inclusive. + /// + /// [`CLTV_CLAIM_BUFFER`]: channelmonitor::CLTV_CLAIM_BUFFER + /// [`HTLC_FAIL_BACK_BUFFER`]: channelmonitor::HTLC_FAIL_BACK_BUFFER + force_close_claimable_htlc_cltv_buffer: u32, + chain_source: Option, broadcaster: T, logger: L, @@ -398,10 +409,19 @@ where chain_source: Option, broadcaster: T, logger: L, feeest: F, persister: MonitorUpdatingPersisterAsync, _entropy_source: ES, _our_peerstorage_encryption_key: PeerStorageKey, + force_close_claimable_htlc_cltv_buffer: u32, ) -> Self { + assert!( + force_close_claimable_htlc_cltv_buffer >= channelmonitor::CLTV_CLAIM_BUFFER + && force_close_claimable_htlc_cltv_buffer <= channelmonitor::HTLC_FAIL_BACK_BUFFER, + "force_close_claimable_htlc_cltv_buffer must be between {} and {} inclusive", + channelmonitor::CLTV_CLAIM_BUFFER, + channelmonitor::HTLC_FAIL_BACK_BUFFER, + ); let event_notifier = Arc::new(Notifier::new()); Self { monitors: RwLock::new(new_hash_map()), + force_close_claimable_htlc_cltv_buffer, chain_source, broadcaster, logger, @@ -604,9 +624,18 @@ where pub fn new( chain_source: Option, broadcaster: T, logger: L, feeest: F, persister: P, _entropy_source: ES, _our_peerstorage_encryption_key: PeerStorageKey, + force_close_claimable_htlc_cltv_buffer: u32, ) -> Self { + assert!( + force_close_claimable_htlc_cltv_buffer >= channelmonitor::CLTV_CLAIM_BUFFER + && force_close_claimable_htlc_cltv_buffer <= channelmonitor::HTLC_FAIL_BACK_BUFFER, + "force_close_claimable_htlc_cltv_buffer must be between {} and {} inclusive", + channelmonitor::CLTV_CLAIM_BUFFER, + channelmonitor::HTLC_FAIL_BACK_BUFFER, + ); Self { monitors: RwLock::new(new_hash_map()), + force_close_claimable_htlc_cltv_buffer, chain_source, broadcaster, logger, @@ -1132,6 +1161,7 @@ where header, txdata, height, + self.force_close_claimable_htlc_cltv_buffer, &self.broadcaster, &self.fee_estimator, &self.logger, @@ -1192,6 +1222,7 @@ where header, txdata, height, + self.force_close_claimable_htlc_cltv_buffer, &self.broadcaster, &self.fee_estimator, &self.logger, @@ -1228,6 +1259,7 @@ where monitor.best_block_updated( header, height, + self.force_close_claimable_htlc_cltv_buffer, &self.broadcaster, &self.fee_estimator, &self.logger, @@ -1306,6 +1338,7 @@ where monitor, pending_monitor_updates: Mutex::new(pending_monitor_updates), }); + Ok(persist_res) } diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index a8d055a9c5b..7be4e095239 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 +/// 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; +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 @@ -2365,6 +2365,7 @@ impl ChannelMonitor { header: &Header, txdata: &TransactionData, height: u32, + force_close_buffer: u32, broadcaster: B, fee_estimator: F, logger: &L, @@ -2372,7 +2373,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 @@ -2398,6 +2399,7 @@ impl ChannelMonitor { header: &Header, txdata: &TransactionData, height: u32, + force_close_buffer: u32, broadcaster: B, fee_estimator: F, logger: &L, @@ -2406,7 +2408,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. @@ -2443,6 +2445,7 @@ impl ChannelMonitor { &self, header: &Header, height: u32, + force_close_buffer: u32, broadcaster: B, fee_estimator: F, logger: &L, @@ -2451,7 +2454,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 ) } @@ -5214,14 +5217,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] @@ -5229,6 +5232,7 @@ impl ChannelMonitorImpl { &mut self, header: &Header, height: u32, + force_close_buffer: u32, broadcaster: B, fee_estimator: &LowerBoundedFeeEstimator, logger: &WithContext, @@ -5238,7 +5242,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); @@ -5257,6 +5261,7 @@ impl ChannelMonitorImpl { header: &Header, txdata: &TransactionData, height: u32, + force_close_buffer: u32, broadcaster: B, fee_estimator: &LowerBoundedFeeEstimator, logger: &WithContext, @@ -5520,7 +5525,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 @@ -5539,6 +5544,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, @@ -5548,7 +5554,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) = @@ -5914,7 +5920,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. @@ -5953,7 +5959,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); } @@ -6263,7 +6269,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) { @@ -6277,7 +6291,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) { @@ -6285,7 +6307,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)> { diff --git a/lightning/src/ln/chanmon_update_fail_tests.rs b/lightning/src/ln/chanmon_update_fail_tests.rs index cd32d219b93..b661d4a68c0 100644 --- a/lightning/src/ln/chanmon_update_fail_tests.rs +++ b/lightning/src/ln/chanmon_update_fail_tests.rs @@ -14,7 +14,9 @@ use crate::chain::chaininterface::LowerBoundedFeeEstimator; use crate::chain::chainmonitor::ChainMonitor; -use crate::chain::channelmonitor::{ChannelMonitor, MonitorEvent, ANTI_REORG_DELAY}; +use crate::chain::channelmonitor::{ + ChannelMonitor, MonitorEvent, ANTI_REORG_DELAY, CLTV_CLAIM_BUFFER, +}; use crate::chain::transaction::OutPoint; use crate::chain::{ChannelMonitorUpdateStatus, Listen, Watch}; use crate::events::{ClosureReason, Event, HTLCHandlingFailureType, PaymentPurpose}; @@ -4927,6 +4929,7 @@ fn native_async_persist() { native_async_persister, Arc::clone(&keys_manager), keys_manager.get_peer_storage_key(), + CLTV_CLAIM_BUFFER, ); // Write the initial ChannelMonitor async, testing primarily that the `MonitorEvent::Completed` diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index ada27af749f..3ecc9992a52 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -6885,7 +6885,6 @@ impl< err: format!("The chosen CLTV expiry delta is below the minimum of {}", MIN_CLTV_EXPIRY_DELTA), }); } - let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self); let per_peer_state = self.per_peer_state.read().unwrap(); let peer_state_mutex = per_peer_state.get(counterparty_node_id) @@ -21163,6 +21162,7 @@ mod tests { #[cfg(ldk_bench)] pub mod bench { use crate::chain::chainmonitor::{ChainMonitor, Persist}; + use crate::chain::channelmonitor::CLTV_CLAIM_BUFFER; use crate::chain::Listen; use crate::events::Event; use crate::ln::channelmanager::{ @@ -21250,7 +21250,7 @@ pub mod bench { let seed_a = [1u8; 32]; let keys_manager_a = KeysManager::new(&seed_a, 42, 42, true); - let chain_monitor_a = ChainMonitor::new(None, &tx_broadcaster, &logger_a, &fee_estimator, &persister_a, &keys_manager_a, keys_manager_a.get_peer_storage_key()); + let chain_monitor_a = ChainMonitor::new(None, &tx_broadcaster, &logger_a, &fee_estimator, &persister_a, &keys_manager_a, keys_manager_a.get_peer_storage_key(), CLTV_CLAIM_BUFFER); let node_a = ChannelManager::new(&fee_estimator, &chain_monitor_a, &tx_broadcaster, &router, &message_router, &logger_a, &keys_manager_a, &keys_manager_a, &keys_manager_a, config.clone(), ChainParameters { network, best_block: BestBlock::from_network(network), @@ -21260,7 +21260,7 @@ pub mod bench { let logger_b = test_utils::TestLogger::with_id("node a".to_owned()); let seed_b = [2u8; 32]; let keys_manager_b = KeysManager::new(&seed_b, 42, 42, true); - let chain_monitor_b = ChainMonitor::new(None, &tx_broadcaster, &logger_a, &fee_estimator, &persister_b, &keys_manager_b, keys_manager_b.get_peer_storage_key()); + let chain_monitor_b = ChainMonitor::new(None, &tx_broadcaster, &logger_a, &fee_estimator, &persister_b, &keys_manager_b, keys_manager_b.get_peer_storage_key(), CLTV_CLAIM_BUFFER); let node_b = ChannelManager::new(&fee_estimator, &chain_monitor_b, &tx_broadcaster, &router, &message_router, &logger_b, &keys_manager_b, &keys_manager_b, &keys_manager_b, config.clone(), ChainParameters { network, best_block: BestBlock::from_network(network), diff --git a/lightning/src/util/test_utils.rs b/lightning/src/util/test_utils.rs index 22be4367c7a..0e33af6ffef 100644 --- a/lightning/src/util/test_utils.rs +++ b/lightning/src/util/test_utils.rs @@ -536,6 +536,7 @@ impl<'a> TestChainMonitor<'a> { persister, keys_manager, keys_manager.get_peer_storage_key(), + crate::chain::channelmonitor::CLTV_CLAIM_BUFFER, ), keys_manager, expect_channel_force_closed: Mutex::new(None),