Drop the need for fork headers when calling `Listen`'s disconnect
What changed, and why it matters
This commit changes how the Lightning Dev Kit (LDK) library is told about blockchain reorganizations. Previously, callers had to provide the full headers of every block being disconnected, one at a time. Now they only provide the new 'fork point' (the last block that is still on both the old and new chain). This makes it easier to recover from deep reorgs or switch to a new node that doesn't have old fork headers. The change removes some internal consistency checks that relied on seeing each disconnected block in order, which slightly weakens defensive assumptions but is described by the authors as acceptable because LDK now provides its own block-sync helper.
Treat as a design-level change rather than an acute vulnerability. Review downstream callers that implement `Listen` directly to ensure they correctly compute the fork point and do not rely on the removed per-block disconnection ordering. Audit the modified `ChannelMonitor` and `Sweeper` reorg handling for edge cases around exactly-height-matched confirmations. Consider regression tests for deep reorgs and for switching block sources without fork headers.
Security signals we found
API contract relaxation: listeners lose per-block header verification during reorgs
Assertion downgrade: in-order disconnection guarantees replaced by height-decrease check
Behavioral change in ChannelMonitor: threshold-conf event retention condition altered from `< height` to `<= new_height`
Behavioral change in ChannelMonitor: alternative funding confirmation now cleared when conf_height > new_height instead of equality
Sweeper unconfirmation logic changed from hash-match to height comparison
No explicit security framing by vendor; appears as routine interface redesign
Evidence from the diff
The patch refactors the chain::Listen trait: block_disconnected(&Header, u32) is replaced by blocks_disconnected(BestBlock). Implementations in ChannelManager, ChainMonitor, ChannelMonitor, Sweeper, and lightning-liquidity are updated. The new interface receives only the post-reorg best block (fork point), not each disconnected header. Several strict assertions requiring in-order, per-block disconnection are relaxed to a single check that the new height is lower than the prior best height. lightning-block-sync’s ChainNotifier now batches disconnect notifications and calls listeners once with the fork point. A subtle logic change in ChannelMonitorImpl::blocks_disconnected retains onchain_events_awaiting_threshold_conf entries with height <= new_height (previously < height) and clears alternative_funding_confirmed when its confirmation height exceeds the new height. The Sweeper now marks outputs unconfirmed based on confirmation height rather than matching the disconnected block hash, and confirmation_hash() is removed.
Changed components
lightning/src/chain/mod.rs (Listen trait)lightning/src/chain/chainmonitor.rslightning/src/chain/channelmonitor.rslightning/src/ln/channelmanager.rslightning/src/util/sweep.rslightning-liquidity/src/manager.rslightning-block-sync/src/lib.rsInspect captured patch +98 / −120
diff --git a/fuzz/src/full_stack.rs b/fuzz/src/full_stack.rs
index 2e4e6bd..eddf2ce 100644
--- a/fuzz/src/full_stack.rs
+++ b/fuzz/src/full_stack.rs
@@ -343,8 +343,9 @@ impl<'a> MoneyLossDetector<'a> {
self.header_hashes[self.height - 1].0,
self.header_hashes[self.height].1,
);
- self.manager.block_disconnected(&header, self.height as u32);
- self.monitor.block_disconnected(&header, self.height as u32);
+ let best_block = BestBlock::new(header.prev_blockhash, self.height as u32 - 1);
+ self.manager.blocks_disconnected(best_block);
+ self.monitor.blocks_disconnected(best_block);
self.height -= 1;
let removal_height = self.height;
self.txids_confirmed.retain(|_, height| removal_height != *height);
diff --git a/lightning-block-sync/src/init.rs b/lightning-block-sync/src/init.rs
index f71a724..a870f8c 100644
--- a/lightning-block-sync/src/init.rs
+++ b/lightning-block-sync/src/init.rs
@@ -9,6 +9,7 @@ use bitcoin::hash_types::BlockHash;
use bitcoin::network::Network;
use lightning::chain;
+use lightning::chain::BestBlock;
use std::ops::Deref;
@@ -230,8 +231,8 @@ impl<'a, L: chain::Listen + ?Sized> chain::Listen for DynamicChainListener<'a, L
unreachable!()
}
- fn block_disconnected(&self, header: &Header, height: u32) {
- self.0.block_disconnected(header, height)
+ fn blocks_disconnected(&self, fork_point: BestBlock) {
+ self.0.blocks_disconnected(fork_point)
}
}
@@ -257,7 +258,7 @@ impl<'a, L: chain::Listen + ?Sized> chain::Listen for ChainListenerSet<'a, L> {
}
}
- fn block_disconnected(&self, _header: &Header, _height: u32) {
+ fn blocks_disconnected(&self, _fork_point: BestBlock) {
unreachable!()
}
}
@@ -300,19 +301,16 @@ mod tests {
let fork_chain_3 = main_chain.fork_at_height(3);
let listener_1 = MockChainListener::new()
- .expect_block_disconnected(*fork_chain_1.at_height(4))
- .expect_block_disconnected(*fork_chain_1.at_height(3))
- .expect_block_disconnected(*fork_chain_1.at_height(2))
+ .expect_blocks_disconnected(*fork_chain_1.at_height(1))
.expect_block_connected(*main_chain.at_height(2))
.expect_block_connected(*main_chain.at_height(3))
.expect_block_connected(*main_chain.at_height(4));
let listener_2 = MockChainListener::new()
- .expect_block_disconnected(*fork_chain_2.at_height(4))
- .expect_block_disconnected(*fork_chain_2.at_height(3))
+ .expect_blocks_disconnected(*fork_chain_2.at_height(2))
.expect_block_connected(*main_chain.at_height(3))
.expect_block_connected(*main_chain.at_height(4));
let listener_3 = MockChainListener::new()
- .expect_block_disconnected(*fork_chain_3.at_height(4))
+ .expect_blocks_disconnected(*fork_chain_3.at_height(3))
.expect_block_connected(*main_chain.at_height(4));
let listeners = vec![
@@ -337,23 +335,17 @@ mod tests {
let fork_chain_3 = fork_chain_2.fork_at_height(3);
let listener_1 = MockChainListener::new()
- .expect_block_disconnected(*fork_chain_1.at_height(4))
- .expect_block_disconnected(*fork_chain_1.at_height(3))
- .expect_block_disconnected(*fork_chain_1.at_height(2))
+ .expect_blocks_disconnected(*fork_chain_1.at_height(1))
.expect_block_connected(*main_chain.at_height(2))
.expect_block_connected(*main_chain.at_height(3))
.expect_block_connected(*main_chain.at_height(4));
let listener_2 = MockChainListener::new()
- .expect_block_disconnected(*fork_chain_2.at_height(4))
- .expect_block_disconnected(*fork_chain_2.at_height(3))
- .expect_block_disconnected(*fork_chain_2.at_height(2))
+ .expect_blocks_disconnected(*fork_chain_2.at_height(1))
.expect_block_connected(*main_chain.at_height(2))
.expect_block_connected(*main_chain.at_height(3))
.expect_block_connected(*main_chain.at_height(4));
let listener_3 = MockChainListener::new()
- .expect_block_disconnected(*fork_chain_3.at_height(4))
- .expect_block_disconnected(*fork_chain_3.at_height(3))
- .expect_block_disconnected(*fork_chain_3.at_height(2))
+ .expect_blocks_disconnected(*fork_chain_3.at_height(1))
.expect_block_connected(*main_chain.at_height(2))
.expect_block_connected(*main_chain.at_height(3))
.expect_block_connected(*main_chain.at_height(4));
@@ -380,7 +372,7 @@ mod tests {
let old_tip = fork_chain.tip();
let listener = MockChainListener::new()
- .expect_block_disconnected(*old_tip)
+ .expect_blocks_disconnected(*fork_chain.at_height(1))
.expect_block_connected(*new_tip);
let listeners = vec![(old_tip.block_hash, &listener as &dyn chain::Listen)];
diff --git a/lightning-block-sync/src/lib.rs b/lightning-block-sync/src/lib.rs
index 281a05a..c168453 100644
--- a/lightning-block-sync/src/lib.rs
+++ b/lightning-block-sync/src/lib.rs
@@ -49,7 +49,7 @@ use bitcoin::hash_types::BlockHash;
use bitcoin::pow::Work;
use lightning::chain;
-use lightning::chain::Listen;
+use lightning::chain::{BestBlock, Listen};
use std::future::Future;
use std::ops::Deref;
@@ -398,12 +398,15 @@ where
}
/// Notifies the chain listeners of disconnected blocks.
- fn disconnect_blocks(&mut self, mut disconnected_blocks: Vec<ValidatedBlockHeader>) {
- for header in disconnected_blocks.drain(..) {
+ fn disconnect_blocks(&mut self, disconnected_blocks: Vec<ValidatedBlockHeader>) {
+ for header in disconnected_blocks.iter() {
if let Some(cached_header) = self.header_cache.block_disconnected(&header.block_hash) {
- assert_eq!(cached_header, header);
+ assert_eq!(cached_header, *header);
}
- self.chain_listener.block_disconnected(&header.header, header.height);
+ }
+ if let Some(block) = disconnected_blocks.last() {
+ let fork_point = BestBlock::new(block.header.prev_blockhash, block.height - 1);
+ self.chain_listener.blocks_disconnected(fork_point);
}
}
@@ -615,7 +618,7 @@ mod chain_notifier_tests {
let new_tip = fork_chain.tip();
let old_tip = main_chain.tip();
let chain_listener = &MockChainListener::new()
- .expect_block_disconnected(*old_tip)
+ .expect_blocks_disconnected(*fork_chain.at_height(1))
.expect_block_connected(*new_tip);
let mut notifier =
ChainNotifier { header_cache: &mut main_chain.header_cache(0..=2), chain_listener };
@@ -635,8 +638,7 @@ mod chain_notifier_tests {
let new_tip = fork_chain.tip();
let old_tip = main_chain.tip();
let chain_listener = &MockChainListener::new()
- .expect_block_disconnected(*old_tip)
- .expect_block_disconnected(*main_chain.at_height(2))
+ .expect_blocks_disconnected(*main_chain.at_height(1))
.expect_block_connected(*new_tip);
let mut notifier =
ChainNotifier { header_cache: &mut main_chain.header_cache(0..=3), chain_listener };
@@ -656,7 +658,7 @@ mod chain_notifier_tests {
let new_tip = fork_chain.tip();
let old_tip = main_chain.tip();
let chain_listener = &MockChainListener::new()
- .expect_block_disconnected(*old_tip)
+ .expect_blocks_disconnected(*fork_chain.at_height(1))
.expect_block_connected(*fork_chain.at_height(2))
.expect_block_connected(*new_tip);
let mut notifier =
diff --git a/lightning-block-sync/src/test_utils.rs b/lightning-block-sync/src/test_utils.rs
index 098f1a8..d307c45 100644
--- a/lightning-block-sync/src/test_utils.rs
+++ b/lightning-block-sync/src/test_utils.rs
@@ -13,6 +13,7 @@ use bitcoin::transaction;
use bitcoin::Transaction;
use lightning::chain;
+use lightning::chain::BestBlock;
use std::cell::RefCell;
use std::collections::VecDeque;
@@ -203,7 +204,7 @@ impl chain::Listen for NullChainListener {
&self, _header: &Header, _txdata: &chain::transaction::TransactionData, _height: u32,
) {
}
- fn block_disconnected(&self, _header: &Header, _height: u32) {}
+ fn blocks_disconnected(&self, _fork_point: BestBlock) {}
}
pub struct MockChainListener {
@@ -231,8 +232,8 @@ impl MockChainListener {
self
}
- pub fn expect_block_disconnected(self, block: BlockHeaderData) -> Self {
- self.expected_blocks_disconnected.borrow_mut().push_back(block);
+ pub fn expect_blocks_disconnected(self, fork_point: BlockHeaderData) -> Self {
+ self.expected_blocks_disconnected.borrow_mut().push_back(fork_point);
self
}
}
@@ -264,14 +265,17 @@ impl chain::Listen for MockChainListener {
}
}
- fn block_disconnected(&self, header: &Header, height: u32) {
+ fn blocks_disconnected(&self, fork_point: BestBlock) {
match self.expected_blocks_disconnected.borrow_mut().pop_front() {
None => {
- panic!("Unexpected block disconnected: {:?}", header.block_hash());
+ panic!(
+ "Unexpected block(s) disconnected {} at height {}",
+ fork_point.block_hash, fork_point.height,
+ );
},
- Some(expected_block) => {
- assert_eq!(header.block_hash(), expected_block.header.block_hash());
- assert_eq!(height, expected_block.height);
+ Some(expected) => {
+ assert_eq!(fork_point.block_hash, expected.header.block_hash());
+ assert_eq!(fork_point.height, expected.height);
},
}
}
diff --git a/lightning-liquidity/src/manager.rs b/lightning-liquidity/src/manager.rs
index 4cf9778..2495583 100644
--- a/lightning-liquidity/src/manager.rs
+++ b/lightning-liquidity/src/manager.rs
@@ -767,14 +767,11 @@ where
self.best_block_updated(header, height);
}
- fn block_disconnected(&self, header: &bitcoin::block::Header, height: u32) {
- let new_height = height - 1;
+ fn blocks_disconnected(&self, fork_point: BestBlock) {
if let Some(best_block) = self.best_block.write().unwrap().as_mut() {
- assert_eq!(best_block.block_hash, header.block_hash(),
- "Blocks must be disconnected in chain-order - the disconnected header must be the last connected header");
- assert_eq!(best_block.height, height,
- "Blocks must be disconnected in chain-order - the disconnected block must have the correct height");
- *best_block = BestBlock::new(header.prev_blockhash, new_height)
+ assert!(best_block.height > fork_point.height,
+ "Blocks disconnected must indicate disconnection from the current best height, i.e. the new chain tip must be lower than the previous best height");
+ *best_block = fork_point;
}
// TODO: Call block_disconnected on all sub-modules that require it, e.g., LSPS1MessageHandler.
diff --git a/lightning/src/chain/chainmonitor.rs b/lightning/src/chain/chainmonitor.rs
index 386ef0a..8016351 100644
--- a/lightning/src/chain/chainmonitor.rs
+++ b/lightning/src/chain/chainmonitor.rs
@@ -35,7 +35,7 @@ use crate::chain::channelmonitor::{
WithChannelMonitor,
};
use crate::chain::transaction::{OutPoint, TransactionData};
-use crate::chain::{ChannelMonitorUpdateStatus, Filter, WatchedOutput};
+use crate::chain::{BestBlock, ChannelMonitorUpdateStatus, Filter, WatchedOutput};
use crate::events::{self, Event, EventHandler, ReplayEvent};
use crate::ln::channel_state::ChannelDetails;
#[cfg(peer_storage)]
@@ -1008,18 +1008,17 @@ where
self.event_notifier.notify();
}
- fn block_disconnected(&self, header: &Header, height: u32) {
+ fn blocks_disconnected(&self, fork_point: BestBlock) {
let monitor_states = self.monitors.read().unwrap();
log_debug!(
self.logger,
- "Latest block {} at height {} removed via block_disconnected",
- header.block_hash(),
- height
+ "Block(s) removed to height {} via blocks_disconnected. New best block is {}",
+ fork_point.height,
+ fork_point.block_hash,
);
for monitor_state in monitor_states.values() {
- monitor_state.monitor.block_disconnected(
- header,
- height,
+ monitor_state.monitor.blocks_disconnected(
+ fork_point,
&*self.broadcaster,
&*self.fee_estimator,
&self.logger,
diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs
index ee36c40..af58d96 100644
--- a/lightning/src/chain/channelmonitor.rs
+++ b/lightning/src/chain/channelmonitor.rs
@@ -2297,14 +2297,8 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
/// Determines if the disconnected block contained any transactions of interest and updates
/// appropriately.
- #[rustfmt::skip]
- pub fn block_disconnected<B: Deref, F: Deref, L: Deref>(
- &self,
- header: &Header,
- height: u32,
- broadcaster: B,
- fee_estimator: F,
- logger: &L,
+ pub fn blocks_disconnected<B: Deref, F: Deref, L: Deref>(
+ &self, fork_point: BestBlock, broadcaster: B, fee_estimator: F, logger: &L,
) where
B::Target: BroadcasterInterface,
F::Target: FeeEstimator,
@@ -2312,8 +2306,7 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
{
let mut inner = self.inner.lock().unwrap();
let logger = WithChannelMonitor::from_impl(logger, &*inner, None);
- inner.block_disconnected(
- header, height, broadcaster, fee_estimator, &logger)
+ inner.blocks_disconnected(fork_point, broadcaster, fee_estimator, &logger)
}
/// Processes transactions confirmed in a block with the given header and height, returning new
@@ -2347,10 +2340,10 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
/// Processes a transaction that was reorganized out of the chain.
///
- /// Used instead of [`block_disconnected`] by clients that are notified of transactions rather
+ /// Used instead of [`blocks_disconnected`] by clients that are notified of transactions rather
/// than blocks. See [`chain::Confirm`] for calling expectations.
///
- /// [`block_disconnected`]: Self::block_disconnected
+ /// [`blocks_disconnected`]: Self::blocks_disconnected
#[rustfmt::skip]
pub fn transaction_unconfirmed<B: Deref, F: Deref, L: Deref>(
&self,
@@ -5441,12 +5434,12 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
!unmatured_htlcs.contains(&source),
"An unmature HTLC transaction conflicts with a maturing one; failed to \
call either transaction_unconfirmed for the conflicting transaction \
- or block_disconnected for a block containing it.");
+ or blocks_disconnected for a block before it.");
debug_assert!(
!matured_htlcs.contains(&source),
"A matured HTLC transaction conflicts with a maturing one; failed to \
call either transaction_unconfirmed for the conflicting transaction \
- or block_disconnected for a block containing it.");
+ or blocks_disconnected for a block before it.");
matured_htlcs.push(source.clone());
}
@@ -5594,23 +5587,26 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
}
#[rustfmt::skip]
- fn block_disconnected<B: Deref, F: Deref, L: Deref>(
- &mut self, header: &Header, height: u32, broadcaster: B, fee_estimator: F, logger: &WithChannelMonitor<L>
+ fn blocks_disconnected<B: Deref, F: Deref, L: Deref>(
+ &mut self, fork_point: BestBlock, broadcaster: B, fee_estimator: F, logger: &WithChannelMonitor<L>
) where B::Target: BroadcasterInterface,
F::Target: FeeEstimator,
L::Target: Logger,
{
- log_trace!(logger, "Block {} at height {} disconnected", header.block_hash(), height);
+ let new_height = fork_point.height;
+ log_trace!(logger, "Block(s) disconnected to height {}", new_height);
+ assert!(self.best_block.height > fork_point.height,
+ "Blocks disconnected must indicate disconnection from the current best height, i.e. the new chain tip must be lower than the previous best height");
//We may discard:
//- htlc update there as failure-trigger tx (revoked commitment tx, non-revoked commitment tx, HTLC-timeout tx) has been disconnected
//- maturing spendable output has transaction paying us has been disconnected
- self.onchain_events_awaiting_threshold_conf.retain(|ref entry| entry.height < height);
+ self.onchain_events_awaiting_threshold_conf.retain(|ref entry| entry.height <= new_height);
// TODO: Replace with `take_if` once our MSRV is >= 1.80.
let mut should_broadcast_commitment = false;
if let Some((_, conf_height)) = self.alternative_funding_confirmed.as_ref() {
- if *conf_height == height {
+ if *conf_height > new_height {
self.alternative_funding_confirmed.take();
if self.holder_tx_signed || self.funding_spend_seen {
// Cancel any previous claims that are no longer valid as they stemmed from a
@@ -5627,7 +5623,7 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
let bounded_fee_estimator = LowerBoundedFeeEstimator::new(fee_estimator);
let conf_target = self.closure_conf_target();
self.onchain_tx_handler.block_disconnected(
- height, &broadcaster, conf_target, &self.destination_script, &bounded_fee_estimator, logger
+ new_height + 1, &broadcaster, conf_target, &self.destination_script, &bounded_fee_estimator, logger
);
// Only attempt to broadcast the new commitment after the `block_disconnected` call above so that
@@ -5636,7 +5632,7 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
self.queue_latest_holder_commitment_txn_for_broadcast(&broadcaster, &bounded_fee_estimator, logger);
}
- self.best_block = BestBlock::new(header.prev_blockhash, height - 1);
+ self.best_block = fork_point;
}
#[rustfmt::skip]
@@ -6110,8 +6106,8 @@ where
self.0.block_connected(header, txdata, height, &*self.1, &*self.2, &self.3);
}
- fn block_disconnected(&self, header: &Header, height: u32) {
- self.0.block_disconnected(header, height, &*self.1, &*self.2, &self.3);
+ fn blocks_disconnected(&self, fork_point: BestBlock) {
+ self.0.blocks_disconnected(fork_point, &*self.1, &*self.2, &self.3);
}
}
diff --git a/lightning/src/chain/mod.rs b/lightning/src/chain/mod.rs
index c16ee25..35a01d7 100644
--- a/lightning/src/chain/mod.rs
+++ b/lightning/src/chain/mod.rs
@@ -84,8 +84,13 @@ pub trait Listen {
self.filtered_block_connected(&block.header, &txdata, height);
}
- /// Notifies the listener that a block was removed at the given height.
- fn block_disconnected(&self, header: &Header, height: u32);
+ /// Notifies the listener that one or more blocks were removed in anticipation of a reorg.
+ ///
+ /// The provided [`BestBlock`] is the new best block after disconnecting blocks in the reorg
+ /// but before connecting new ones (i.e. the "fork point" block). For backwards compatibility,
+ /// you may instead walk the chain backwards, calling `blocks_disconnected` for each block
+ /// that is disconnected in a reorg.
+ fn blocks_disconnected(&self, fork_point_block: BestBlock);
}
/// The `Confirm` trait is used to notify LDK when relevant transactions have been confirmed on
@@ -272,7 +277,7 @@ pub trait Watch<ChannelSigner: EcdsaChannelSigner> {
///
/// Implementations are responsible for watching the chain for the funding transaction along
/// with any spends of outputs returned by [`get_outputs_to_watch`]. In practice, this means
- /// calling [`block_connected`] and [`block_disconnected`] on the monitor.
+ /// calling [`block_connected`] and [`blocks_disconnected`] on the monitor.
///
/// A return of `Err(())` indicates that the channel should immediately be force-closed without
/// broadcasting the funding transaction.
@@ -282,7 +287,7 @@ pub trait Watch<ChannelSigner: EcdsaChannelSigner> {
///
/// [`get_outputs_to_watch`]: channelmonitor::ChannelMonitor::get_outputs_to_watch
/// [`block_connected`]: channelmonitor::ChannelMonitor::block_connected
- /// [`block_disconnected`]: channelmonitor::ChannelMonitor::block_disconnected
+ /// [`blocks_disconnected`]: channelmonitor::ChannelMonitor::blocks_disconnected
fn watch_channel(
&self, channel_id: ChannelId, monitor: ChannelMonitor<ChannelSigner>,
) -> Result<ChannelMonitorUpdateStatus, ()>;
@@ -393,8 +398,8 @@ impl<T: Listen> Listen for dyn core::ops::Deref<Target = T> {
(**self).filtered_block_connected(header, txdata, height);
}
- fn block_disconnected(&self, header: &Header, height: u32) {
- (**self).block_disconnected(header, height);
+ fn blocks_disconnected(&self, fork_point: BestBlock) {
+ (**self).blocks_disconnected(fork_point);
}
}
@@ -408,9 +413,9 @@ where
self.1.filtered_block_connected(header, txdata, height);
}
- fn block_disconnected(&self, header: &Header, height: u32) {
- self.0.block_disconnected(header, height);
- self.1.block_disconnected(header, height);
+ fn blocks_disconnected(&self, fork_point: BestBlock) {
+ self.0.blocks_disconnected(fork_point);
+ self.1.blocks_disconnected(fork_point);
}
}
diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs
index cfef054..78bd2fa 100644
--- a/lightning/src/ln/channelmanager.rs
+++ b/lightning/src/ln/channelmanager.rs
@@ -3716,12 +3716,12 @@ where
/// Non-proportional fees are fixed according to our risk using the provided fee estimator.
///
/// Users need to notify the new `ChannelManager` when a new block is connected or
- /// disconnected using its [`block_connected`] and [`block_disconnected`] methods, starting
+ /// disconnected using its [`block_connected`] and [`blocks_disconnected`] methods, starting
/// from after [`params.best_block.block_hash`]. See [`chain::Listen`] and [`chain::Confirm`] for
/// more details.
///
/// [`block_connected`]: chain::Listen::block_connected
- /// [`block_disconnected`]: chain::Listen::block_disconnected
+ /// [`blocks_disconnected`]: chain::Listen::blocks_disconnected
/// [`params.best_block.block_hash`]: chain::BestBlock::block_hash
#[rustfmt::skip]
pub fn new(
@@ -13286,26 +13286,23 @@ where
self.best_block_updated(header, height);
}
- fn block_disconnected(&self, header: &Header, height: u32) {
+ fn blocks_disconnected(&self, fork_point: BestBlock) {
let _persistence_guard =
PersistenceNotifierGuard::optionally_notify_skipping_background_events(
self,
|| -> NotifyOption { NotifyOption::DoPersist },
);
- let new_height = height - 1;
{
let mut best_block = self.best_block.write().unwrap();
- assert_eq!(best_block.block_hash, header.block_hash(),
- "Blocks must be disconnected in chain-order - the disconnected header must be the last connected header");
- assert_eq!(best_block.height, height,
- "Blocks must be disconnected in chain-order - the disconnected block must have the correct height");
- *best_block = BestBlock::new(header.prev_blockhash, new_height)
+ assert!(best_block.height > fork_point.height,
+ "Blocks disconnected must indicate disconnection from the current best height, i.e. the new chain tip must be lower than the previous best height");
+ *best_block = fork_point;
}
- self.do_chain_event(Some(new_height), |channel| {
+ self.do_chain_event(Some(fork_point.height), |channel| {
channel.best_block_updated(
- new_height,
- header.time,
+ fork_point.height,
+ 0,
self.chain_hash,
&self.node_signer,
&self.config.read().unwrap(),
diff --git a/lightning/src/ln/functional_test_utils.rs b/lightning/src/ln/functional_test_utils.rs
index 53d5173..68d73c7 100644
--- a/lightning/src/ln/functional_test_utils.rs
+++ b/lightning/src/ln/functional_test_utils.rs
@@ -428,8 +428,9 @@ pub fn disconnect_blocks<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, count: u32)
match *node.connect_style.borrow() {
ConnectStyle::FullBlockViaListen => {
- node.chain_monitor.chain_monitor.block_disconnected(&orig.0.header, orig.1);
- Listen::block_disconnected(node.node, &orig.0.header, orig.1);
+ let best_block = BestBlock::new(orig.0.header.prev_blockhash, orig.1 - 1);
+ node.chain_monitor.chain_monitor.blocks_disconnected(best_block);
+ Listen::blocks_disconnected(node.node, best_block);
},
ConnectStyle::BestBlockFirstSkippingBlocks
| ConnectStyle::TransactionsFirstSkippingBlocks
diff --git a/lightning/src/util/sweep.rs b/lightning/src/util/sweep.rs
index b72dddb..aba2258 100644
--- a/lightning/src/util/sweep.rs
+++ b/lightning/src/util/sweep.rs
@@ -281,16 +281,6 @@ impl OutputSpendStatus {
}
}
- fn confirmation_hash(&self) -> Option<BlockHash> {
- match self {
- Self::PendingInitialBroadcast { .. } => None,
- Self::PendingFirstConfirmation { .. } => None,
- Self::PendingThresholdConfirmations { confirmation_hash, .. } => {
- Some(*confirmation_hash)
- },
- }
- }
-
fn latest_spending_tx(&self) -> Option<&Transaction> {
match self {
Self::PendingInitialBroadcast { .. } => None,
@@ -759,21 +749,15 @@ where
self.best_block_updated_internal(&mut state_lock, header, height);
}
- fn block_disconnected(&self, header: &Header, height: u32) {
+ fn blocks_disconnected(&self, fork_point: BestBlock) {
let mut state_lock = self.sweeper_state.lock().unwrap();
- let new_height = height - 1;
- let block_hash = header.block_hash();
-
- assert_eq!(state_lock.best_block.block_hash, block_hash,
- "Blocks must be disconnected in chain-order - the disconnected header must be the last connected header");
- assert_eq!(state_lock.best_block.height, height,
- "Blocks must be disconnected in chain-order - the disconnected block must have the correct height");
- state_lock.best_block = BestBlock::new(header.prev_blockhash, new_height);
+ assert!(state_lock.best_block.height > fork_point.height,
+ "Blocks disconnected must indicate disconnection from the current best height, i.e. the new chain tip must be lower than the previous best height");
+ state_lock.best_block = fork_point;
for output_info in state_lock.outputs.iter_mut() {
- if output_info.status.confirmation_hash() == Some(block_hash) {
- debug_assert_eq!(output_info.status.confirmation_height(), Some(height));
+ if output_info.status.confirmation_height() > Some(fork_point.height) {
output_info.status.unconfirmed();
}
}
Why this scored 33/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.