Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 91 additions & 9 deletions lightning-background-processor/src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -103,7 +103,7 @@ use alloc::vec::Vec;
/// * Monitoring whether the [`ChannelManager`] needs to be re-persisted to disk, and if so,
/// writing it to disk/backups by invoking the callback given to it at startup.
/// [`ChannelManager`] persistence should be done in the background.
/// * Calling [`ChannelManager::timer_tick_occurred`], [`ChainMonitor::rebroadcast_pending_claims`]
/// * Calling [`ChannelManager::timer_tick_occurred`], [`lightning::chain::chainmonitor::ChainMonitor::rebroadcast_pending_claims`]
/// and [`PeerManager::timer_tick_occurred`] at the appropriate intervals.
/// * Calling [`NetworkGraph::remove_stale_channels_and_tracking`] (if a [`GossipSync`] with a
/// [`NetworkGraph`] is provided to [`BackgroundProcessor::start`]).
Expand DownExpand Up@@ -824,7 +824,7 @@ use futures_util::{dummy_waker, Joiner, OptionalSelector, Selector, SelectorOutp
/// # fn send_data(&mut self, _data: &[u8], _continue_read: bool) -> usize { 0 }
/// # fn disconnect_socket(&mut self) {}
/// # }
/// # type ChainMonitor<B, F, FE> = lightning::chain::chainmonitor::ChainMonitor<lightning::sign::InMemorySigner, Arc<F>, Arc<B>, Arc<FE>, Arc<Logger>, Arc<StoreSync>, Arc<lightning::sign::KeysManager>>;
/// # type ChainMonitor<B, F, FE> = lightning::chain::deferred::DeferredChainMonitor<lightning::sign::InMemorySigner, Arc<F>, Arc<B>, Arc<FE>, Arc<Logger>, Arc<StoreSync>, Arc<lightning::sign::KeysManager>>;
/// # type NetworkGraph = lightning::routing::gossip::NetworkGraph<Arc<Logger>>;
/// # type P2PGossipSync<UL> = lightning::routing::gossip::P2PGossipSync<Arc<NetworkGraph>, Arc<UL>, Arc<Logger>>;
/// # type ChannelManager<B, F, FE> = lightning::ln::channelmanager::SimpleArcChannelManager<ChainMonitor<B, F, FE>, B, FE, Logger>;
Expand DownExpand Up@@ -963,7 +963,9 @@ pub async fn process_events_async<
sleeper: Sleeper, mobile_interruptable_platform: bool, fetch_time: FetchTime,
) -> Result<(), lightning::io::Error>
where
M::Target: AChainMonitor<Signer = <CM::Target as AChannelManager>::Signer, Logger = L>,
M::Target: AChainMonitor<Signer = <CM::Target as AChannelManager>::Signer, Logger = L>
+ lightning::chain::Watch<<CM::Target as AChannelManager>::Signer>
+ lightning::events::EventsProvider,
CM::Target: AChannelManager,
OM::Target: AOnionMessenger,
PM::Target: APeerManager,
Expand DownExpand Up@@ -1120,6 +1122,11 @@ where

let mut futures = Joiner::new();

// Capture the number of pending monitor writes before persisting the channel manager.
// We'll only flush this many writes after the manager is persisted, to avoid flushing
// monitor updates that arrived after the manager state was captured.
let pending_monitor_writes = chain_monitor.pending_operation_count();

if channel_manager.get_cm().get_and_clear_needs_persistence() {
log_trace!(logger, "Persisting ChannelManager...");

Expand DownExpand Up@@ -1317,6 +1324,15 @@ where
res?;
}

// Flush the monitor writes that were pending before we persisted the channel manager.
// Any writes that arrived after are left in the queue for the next iteration. There's
// no need to "chase the tail" by processing new updates that arrive during flushing -
// they'll be handled in the next round.
if pending_monitor_writes > 0 {
log_trace!(logger, "Flushing {} monitor writes", pending_monitor_writes);
chain_monitor.flush(pending_monitor_writes, &logger);
}

match check_and_reset_sleeper(&mut last_onion_message_handler_call, || {
sleeper(ONION_MESSAGE_HANDLER_TIMER)
}) {
Expand DownExpand Up@@ -1381,6 +1397,14 @@ where
channel_manager.get_cm().encode(),
)
.await?;

// Flush all pending monitor writes after final channel manager persistence.
let pending_monitor_writes = chain_monitor.pending_operation_count();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO we should move this above the CM write. A common cause of FCs is actually buggy shutdown logic where the node is still running while the BP is being shut down, causing monitor writes after the CM is written in the exit write above.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in #4351

if pending_monitor_writes > 0 {
log_trace!(logger, "Flushing {} monitor writes on shutdown", pending_monitor_writes);
chain_monitor.flush(pending_monitor_writes, &logger);
}

if let Some(ref scorer) = scorer {
kv_store
.write(
Expand DownExpand Up@@ -1461,7 +1485,9 @@ pub async fn process_events_async_with_kv_store_sync<
sleeper: Sleeper, mobile_interruptable_platform: bool, fetch_time: FetchTime,
) -> Result<(), lightning::io::Error>
where
M::Target: AChainMonitor<Signer = <CM::Target as AChannelManager>::Signer, Logger = L>,
M::Target: AChainMonitor<Signer = <CM::Target as AChannelManager>::Signer, Logger = L>
+ lightning::chain::Watch<<CM::Target as AChannelManager>::Signer>
+ lightning::events::EventsProvider,
CM::Target: AChannelManager,
OM::Target: AOnionMessenger,
PM::Target: APeerManager,
Expand DownExpand Up@@ -1570,8 +1596,11 @@ impl BackgroundProcessor {
liquidity_manager: Option<LM>, sweeper: Option<OS>, logger: L, scorer: Option<S>,
) -> Self
where
L::Target: 'static + Logger,
M::Target: AChainMonitor<Signer = <CM::Target as AChannelManager>::Signer, Logger = L>,
L::Target: 'static + Logger + Sized,
M::Target: 'static
+ AChainMonitor<Signer = <CM::Target as AChannelManager>::Signer, Logger = L>
+ lightning::chain::Watch<<CM::Target as AChannelManager>::Signer>
+ lightning::events::EventsProvider,
CM::Target: AChannelManager,
OM::Target: AOnionMessenger,
PM::Target: APeerManager,
Expand DownExpand Up@@ -1684,6 +1713,10 @@ impl BackgroundProcessor {
channel_manager.get_cm().timer_tick_occurred();
last_freshness_call = Instant::now();
}

// Capture the number of pending monitor writes before persisting the channel manager.
let pending_monitor_writes = chain_monitor.pending_operation_count();

if channel_manager.get_cm().get_and_clear_needs_persistence() {
log_trace!(logger, "Persisting ChannelManager...");
(kv_store.write(
Expand All@@ -1695,6 +1728,14 @@ impl BackgroundProcessor {
log_trace!(logger, "Done persisting ChannelManager.");
}

// Flush the monitor writes that were pending before we persisted the channel manager.
// There's no need to "chase the tail" by processing new updates that arrive during
// flushing - they'll be handled in the next round.
if pending_monitor_writes > 0 {
log_trace!(logger, "Flushing {} monitor writes", pending_monitor_writes);
chain_monitor.flush(pending_monitor_writes, &logger);
}

if let Some(liquidity_manager) = liquidity_manager.as_ref() {
log_trace!(logger, "Persisting LiquidityManager...");
let _ = liquidity_manager.get_lm().persist().map_err(|e| {
Expand DownExpand Up@@ -1815,6 +1856,18 @@ impl BackgroundProcessor {
CHANNEL_MANAGER_PERSISTENCE_KEY,
channel_manager.get_cm().encode(),
)?;

// Flush all pending monitor writes after final channel manager persistence.
let pending_monitor_writes = chain_monitor.pending_operation_count();
if pending_monitor_writes > 0 {
log_trace!(
logger,
"Flushing {} monitor writes on shutdown",
pending_monitor_writes
);
chain_monitor.flush(pending_monitor_writes, &logger);
}

if let Some(ref scorer) = scorer {
kv_store.write(
SCORER_PERSISTENCE_PRIMARY_NAMESPACE,
Expand DownExpand Up@@ -1896,9 +1949,10 @@ mod tests {
use bitcoin::transaction::{Transaction, TxOut};
use bitcoin::{Amount, ScriptBuf, Txid};
use core::sync::atomic::{AtomicBool, Ordering};
use lightning::chain::chainmonitor::AChainMonitor;
use lightning::chain::channelmonitor::ANTI_REORG_DELAY;
use lightning::chain::transaction::OutPoint;
use lightning::chain::{chainmonitor, BestBlock, Confirm, Filter};
use lightning::chain::{deferred, BestBlock, Confirm, Filter};
use lightning::events::{Event, PathFailure, ReplayEvent};
use lightning::ln::channelmanager;
use lightning::ln::channelmanager::{
Expand DownExpand Up@@ -2008,7 +2062,7 @@ mod tests {
Arc<test_utils::TestLogger>,
>;

type ChainMonitor = chainmonitor::ChainMonitor<
type ChainMonitor = deferred::DeferredChainMonitor<
InMemorySigner,
Arc<test_utils::TestChainSource>,
Arc<test_utils::TestBroadcaster>,
Expand DownExpand Up@@ -2436,7 +2490,7 @@ mod tests {
let now = Duration::from_secs(genesis_block.header.time as u64);
let keys_manager =
Arc::new(KeysManager::new(&seed, now.as_secs(), now.subsec_nanos(), true));
let chain_monitor = Arc::new(chainmonitor::ChainMonitor::new(
let chain_monitor = Arc::new(deferred::DeferredChainMonitor::new(
Some(Arc::clone(&chain_source)),
Arc::clone(&tx_broadcaster),
Arc::clone(&logger),
Expand DownExpand Up@@ -2580,19 +2634,31 @@ mod tests {
tx.clone(),
)
.unwrap();
// Flush deferred monitor operations so messages aren't held back
$node_a
.chain_monitor
.flush($node_a.chain_monitor.pending_operation_count(), &$node_a.logger);
let msg_a = get_event_msg!(
$node_a,
MessageSendEvent::SendFundingCreated,
$node_b.node.get_our_node_id()
);
$node_b.node.handle_funding_created($node_a.node.get_our_node_id(), &msg_a);
// Flush node_b's monitor so it releases the FundingSigned message
$node_b
.chain_monitor
.flush($node_b.chain_monitor.pending_operation_count(), &$node_b.logger);
get_event!($node_b, Event::ChannelPending);
let msg_b = get_event_msg!(
$node_b,
MessageSendEvent::SendFundingSigned,
$node_a.node.get_our_node_id()
);
$node_a.node.handle_funding_signed($node_b.node.get_our_node_id(), &msg_b);
// Flush node_a's monitor for the final update
$node_a
.chain_monitor
.flush($node_a.chain_monitor.pending_operation_count(), &$node_a.logger);
get_event!($node_a, Event::ChannelPending);
tx
}};
Expand DownExpand Up@@ -3039,11 +3105,23 @@ mod tests {
.node
.funding_transaction_generated(temporary_channel_id, node_1_id, funding_tx.clone())
.unwrap();
// Flush node_0's deferred monitor operations so the FundingCreated message is released
nodes[0]
.chain_monitor
.flush(nodes[0].chain_monitor.pending_operation_count(), &nodes[0].logger);
let msg_0 = get_event_msg!(nodes[0], MessageSendEvent::SendFundingCreated, node_1_id);
nodes[1].node.handle_funding_created(node_0_id, &msg_0);
// Flush node_1's deferred monitor operations so events and FundingSigned are released
nodes[1]
.chain_monitor
.flush(nodes[1].chain_monitor.pending_operation_count(), &nodes[1].logger);
get_event!(nodes[1], Event::ChannelPending);
let msg_1 = get_event_msg!(nodes[1], MessageSendEvent::SendFundingSigned, node_0_id);
nodes[0].node.handle_funding_signed(node_1_id, &msg_1);
// Flush node_0's monitor for the funding_signed update
nodes[0]
.chain_monitor
.flush(nodes[0].chain_monitor.pending_operation_count(), &nodes[0].logger);
channel_pending_recv
.recv_timeout(EVENT_DEADLINE)
.expect("ChannelPending not handled within deadline");
Expand DownExpand Up@@ -3104,6 +3182,10 @@ mod tests {
error_message.to_string(),
)
.unwrap();
// Flush the monitor update triggered by force close so the commitment tx is broadcasted
nodes[0]
.chain_monitor
.flush(nodes[0].chain_monitor.pending_operation_count(), &nodes[0].logger);
let commitment_tx = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().pop().unwrap();
confirm_transaction_depth(&mut nodes[0], &commitment_tx, BREAKDOWN_TIMEOUT as u32);

Expand Down
31 changes: 30 additions & 1 deletion lightning/src/chain/chainmonitor.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1488,10 +1488,12 @@ where
}
}

/// A trivial trait which describes any [`ChainMonitor`].
/// A trivial trait which describes any [`ChainMonitor`] or [`DeferredChainMonitor`].
///
/// This is not exported to bindings users as general cover traits aren't useful in other
/// languages.
///
/// [`DeferredChainMonitor`]: crate::chain::deferred::DeferredChainMonitor
pub trait AChainMonitor {
/// A type implementing [`EcdsaChannelSigner`].
type Signer: EcdsaChannelSigner + Sized;
Expand DownExpand Up@@ -1521,6 +1523,24 @@ pub trait AChainMonitor {
Self::Persister,
Self::EntropySource,
>;

/// Returns the number of pending monitor operations queued for later execution.
///
/// For monitors that process operations immediately (like [`ChainMonitor`]), this
/// always returns 0.
fn pending_operation_count(&self) -> usize;

/// Flushes pending monitor operations.
///
/// # Arguments
///
/// * `count` - The maximum number of operations to flush. If `count` is greater than
/// the number of pending operations, all pending operations are flushed.
/// * `logger` - Logger for error messages during flush operations.
///
/// For monitors that process operations immediately (like [`ChainMonitor`]), this
/// is a no-op.
fn flush(&self, count: usize, logger: &Self::Logger);
}

impl<
Expand All@@ -1546,6 +1566,15 @@ where
fn get_cm(&self) -> &ChainMonitor<ChannelSigner, C, T, F, L, P, ES> {
self
}

fn pending_operation_count(&self) -> usize {
// ChainMonitor processes operations immediately, so there are never any pending.
0
}

fn flush(&self, _count: usize, _logger: &L) {
// No-op: ChainMonitor processes operations immediately.
}
}

#[cfg(test)]
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Defer ChainMonitor updates and persistence to flush (wrapper approach) by joostjager · Pull Request #4345 · lightningdevkit/rust-lightning · GitHub
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 91 additions & 9 deletions lightning-background-processor/src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -103,7 +103,7 @@ use alloc::vec::Vec;
/// * Monitoring whether the [`ChannelManager`] needs to be re-persisted to disk, and if so,
/// writing it to disk/backups by invoking the callback given to it at startup.
/// [`ChannelManager`] persistence should be done in the background.
/// * Calling [`ChannelManager::timer_tick_occurred`], [`ChainMonitor::rebroadcast_pending_claims`]
/// * Calling [`ChannelManager::timer_tick_occurred`], [`lightning::chain::chainmonitor::ChainMonitor::rebroadcast_pending_claims`]
/// and [`PeerManager::timer_tick_occurred`] at the appropriate intervals.
/// * Calling [`NetworkGraph::remove_stale_channels_and_tracking`] (if a [`GossipSync`] with a
/// [`NetworkGraph`] is provided to [`BackgroundProcessor::start`]).
Expand DownExpand Up@@ -824,7 +824,7 @@ use futures_util::{dummy_waker, Joiner, OptionalSelector, Selector, SelectorOutp
/// # fn send_data(&mut self, _data: &[u8], _continue_read: bool) -> usize { 0 }
/// # fn disconnect_socket(&mut self) {}
/// # }
/// # type ChainMonitor<B, F, FE> = lightning::chain::chainmonitor::ChainMonitor<lightning::sign::InMemorySigner, Arc<F>, Arc<B>, Arc<FE>, Arc<Logger>, Arc<StoreSync>, Arc<lightning::sign::KeysManager>>;
/// # type ChainMonitor<B, F, FE> = lightning::chain::deferred::DeferredChainMonitor<lightning::sign::InMemorySigner, Arc<F>, Arc<B>, Arc<FE>, Arc<Logger>, Arc<StoreSync>, Arc<lightning::sign::KeysManager>>;
/// # type NetworkGraph = lightning::routing::gossip::NetworkGraph<Arc<Logger>>;
/// # type P2PGossipSync<UL> = lightning::routing::gossip::P2PGossipSync<Arc<NetworkGraph>, Arc<UL>, Arc<Logger>>;
/// # type ChannelManager<B, F, FE> = lightning::ln::channelmanager::SimpleArcChannelManager<ChainMonitor<B, F, FE>, B, FE, Logger>;
Expand DownExpand Up@@ -963,7 +963,9 @@ pub async fn process_events_async<
sleeper: Sleeper, mobile_interruptable_platform: bool, fetch_time: FetchTime,
) -> Result<(), lightning::io::Error>
where
M::Target: AChainMonitor<Signer = <CM::Target as AChannelManager>::Signer, Logger = L>,
M::Target: AChainMonitor<Signer = <CM::Target as AChannelManager>::Signer, Logger = L>
+ lightning::chain::Watch<<CM::Target as AChannelManager>::Signer>
+ lightning::events::EventsProvider,
CM::Target: AChannelManager,
OM::Target: AOnionMessenger,
PM::Target: APeerManager,
Expand DownExpand Up@@ -1120,6 +1122,11 @@ where

let mut futures = Joiner::new();

// Capture the number of pending monitor writes before persisting the channel manager.
// We'll only flush this many writes after the manager is persisted, to avoid flushing
// monitor updates that arrived after the manager state was captured.
let pending_monitor_writes = chain_monitor.pending_operation_count();

if channel_manager.get_cm().get_and_clear_needs_persistence() {
log_trace!(logger, "Persisting ChannelManager...");

Expand DownExpand Up@@ -1317,6 +1324,15 @@ where
res?;
}

// Flush the monitor writes that were pending before we persisted the channel manager.
// Any writes that arrived after are left in the queue for the next iteration. There's
// no need to "chase the tail" by processing new updates that arrive during flushing -
// they'll be handled in the next round.
if pending_monitor_writes > 0 {
log_trace!(logger, "Flushing {} monitor writes", pending_monitor_writes);
chain_monitor.flush(pending_monitor_writes, &logger);
}

match check_and_reset_sleeper(&mut last_onion_message_handler_call, || {
sleeper(ONION_MESSAGE_HANDLER_TIMER)
}) {
Expand DownExpand Up@@ -1381,6 +1397,14 @@ where
channel_manager.get_cm().encode(),
)
.await?;

// Flush all pending monitor writes after final channel manager persistence.
let pending_monitor_writes = chain_monitor.pending_operation_count();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO we should move this above the CM write. A common cause of FCs is actually buggy shutdown logic where the node is still running while the BP is being shut down, causing monitor writes after the CM is written in the exit write above.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in #4351

if pending_monitor_writes > 0 {
log_trace!(logger, "Flushing {} monitor writes on shutdown", pending_monitor_writes);
chain_monitor.flush(pending_monitor_writes, &logger);
}

if let Some(ref scorer) = scorer {
kv_store
.write(
Expand DownExpand Up@@ -1461,7 +1485,9 @@ pub async fn process_events_async_with_kv_store_sync<
sleeper: Sleeper, mobile_interruptable_platform: bool, fetch_time: FetchTime,
) -> Result<(), lightning::io::Error>
where
M::Target: AChainMonitor<Signer = <CM::Target as AChannelManager>::Signer, Logger = L>,
M::Target: AChainMonitor<Signer = <CM::Target as AChannelManager>::Signer, Logger = L>
+ lightning::chain::Watch<<CM::Target as AChannelManager>::Signer>
+ lightning::events::EventsProvider,
CM::Target: AChannelManager,
OM::Target: AOnionMessenger,
PM::Target: APeerManager,
Expand DownExpand Up@@ -1570,8 +1596,11 @@ impl BackgroundProcessor {
liquidity_manager: Option<LM>, sweeper: Option<OS>, logger: L, scorer: Option<S>,
) -> Self
where
L::Target: 'static + Logger,
M::Target: AChainMonitor<Signer = <CM::Target as AChannelManager>::Signer, Logger = L>,
L::Target: 'static + Logger + Sized,
M::Target: 'static
+ AChainMonitor<Signer = <CM::Target as AChannelManager>::Signer, Logger = L>
+ lightning::chain::Watch<<CM::Target as AChannelManager>::Signer>
+ lightning::events::EventsProvider,
CM::Target: AChannelManager,
OM::Target: AOnionMessenger,
PM::Target: APeerManager,
Expand DownExpand Up@@ -1684,6 +1713,10 @@ impl BackgroundProcessor {
channel_manager.get_cm().timer_tick_occurred();
last_freshness_call = Instant::now();
}

// Capture the number of pending monitor writes before persisting the channel manager.
let pending_monitor_writes = chain_monitor.pending_operation_count();

if channel_manager.get_cm().get_and_clear_needs_persistence() {
log_trace!(logger, "Persisting ChannelManager...");
(kv_store.write(
Expand All@@ -1695,6 +1728,14 @@ impl BackgroundProcessor {
log_trace!(logger, "Done persisting ChannelManager.");
}

// Flush the monitor writes that were pending before we persisted the channel manager.
// There's no need to "chase the tail" by processing new updates that arrive during
// flushing - they'll be handled in the next round.
if pending_monitor_writes > 0 {
log_trace!(logger, "Flushing {} monitor writes", pending_monitor_writes);
chain_monitor.flush(pending_monitor_writes, &logger);
}

if let Some(liquidity_manager) = liquidity_manager.as_ref() {
log_trace!(logger, "Persisting LiquidityManager...");
let _ = liquidity_manager.get_lm().persist().map_err(|e| {
Expand DownExpand Up@@ -1815,6 +1856,18 @@ impl BackgroundProcessor {
CHANNEL_MANAGER_PERSISTENCE_KEY,
channel_manager.get_cm().encode(),
)?;

// Flush all pending monitor writes after final channel manager persistence.
let pending_monitor_writes = chain_monitor.pending_operation_count();
if pending_monitor_writes > 0 {
log_trace!(
logger,
"Flushing {} monitor writes on shutdown",
pending_monitor_writes
);
chain_monitor.flush(pending_monitor_writes, &logger);
}

if let Some(ref scorer) = scorer {
kv_store.write(
SCORER_PERSISTENCE_PRIMARY_NAMESPACE,
Expand DownExpand Up@@ -1896,9 +1949,10 @@ mod tests {
use bitcoin::transaction::{Transaction, TxOut};
use bitcoin::{Amount, ScriptBuf, Txid};
use core::sync::atomic::{AtomicBool, Ordering};
use lightning::chain::chainmonitor::AChainMonitor;
use lightning::chain::channelmonitor::ANTI_REORG_DELAY;
use lightning::chain::transaction::OutPoint;
use lightning::chain::{chainmonitor, BestBlock, Confirm, Filter};
use lightning::chain::{deferred, BestBlock, Confirm, Filter};
use lightning::events::{Event, PathFailure, ReplayEvent};
use lightning::ln::channelmanager;
use lightning::ln::channelmanager::{
Expand DownExpand Up@@ -2008,7 +2062,7 @@ mod tests {
Arc<test_utils::TestLogger>,
>;

type ChainMonitor = chainmonitor::ChainMonitor<
type ChainMonitor = deferred::DeferredChainMonitor<
InMemorySigner,
Arc<test_utils::TestChainSource>,
Arc<test_utils::TestBroadcaster>,
Expand DownExpand Up@@ -2436,7 +2490,7 @@ mod tests {
let now = Duration::from_secs(genesis_block.header.time as u64);
let keys_manager =
Arc::new(KeysManager::new(&seed, now.as_secs(), now.subsec_nanos(), true));
let chain_monitor = Arc::new(chainmonitor::ChainMonitor::new(
let chain_monitor = Arc::new(deferred::DeferredChainMonitor::new(
Some(Arc::clone(&chain_source)),
Arc::clone(&tx_broadcaster),
Arc::clone(&logger),
Expand DownExpand Up@@ -2580,19 +2634,31 @@ mod tests {
tx.clone(),
)
.unwrap();
// Flush deferred monitor operations so messages aren't held back
$node_a
.chain_monitor
.flush($node_a.chain_monitor.pending_operation_count(), &$node_a.logger);
let msg_a = get_event_msg!(
$node_a,
MessageSendEvent::SendFundingCreated,
$node_b.node.get_our_node_id()
);
$node_b.node.handle_funding_created($node_a.node.get_our_node_id(), &msg_a);
// Flush node_b's monitor so it releases the FundingSigned message
$node_b
.chain_monitor
.flush($node_b.chain_monitor.pending_operation_count(), &$node_b.logger);
get_event!($node_b, Event::ChannelPending);
let msg_b = get_event_msg!(
$node_b,
MessageSendEvent::SendFundingSigned,
$node_a.node.get_our_node_id()
);
$node_a.node.handle_funding_signed($node_b.node.get_our_node_id(), &msg_b);
// Flush node_a's monitor for the final update
$node_a
.chain_monitor
.flush($node_a.chain_monitor.pending_operation_count(), &$node_a.logger);
get_event!($node_a, Event::ChannelPending);
tx
}};
Expand DownExpand Up@@ -3039,11 +3105,23 @@ mod tests {
.node
.funding_transaction_generated(temporary_channel_id, node_1_id, funding_tx.clone())
.unwrap();
// Flush node_0's deferred monitor operations so the FundingCreated message is released
nodes[0]
.chain_monitor
.flush(nodes[0].chain_monitor.pending_operation_count(), &nodes[0].logger);
let msg_0 = get_event_msg!(nodes[0], MessageSendEvent::SendFundingCreated, node_1_id);
nodes[1].node.handle_funding_created(node_0_id, &msg_0);
// Flush node_1's deferred monitor operations so events and FundingSigned are released
nodes[1]
.chain_monitor
.flush(nodes[1].chain_monitor.pending_operation_count(), &nodes[1].logger);
get_event!(nodes[1], Event::ChannelPending);
let msg_1 = get_event_msg!(nodes[1], MessageSendEvent::SendFundingSigned, node_0_id);
nodes[0].node.handle_funding_signed(node_1_id, &msg_1);
// Flush node_0's monitor for the funding_signed update
nodes[0]
.chain_monitor
.flush(nodes[0].chain_monitor.pending_operation_count(), &nodes[0].logger);
channel_pending_recv
.recv_timeout(EVENT_DEADLINE)
.expect("ChannelPending not handled within deadline");
Expand DownExpand Up@@ -3104,6 +3182,10 @@ mod tests {
error_message.to_string(),
)
.unwrap();
// Flush the monitor update triggered by force close so the commitment tx is broadcasted
nodes[0]
.chain_monitor
.flush(nodes[0].chain_monitor.pending_operation_count(), &nodes[0].logger);
let commitment_tx = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().pop().unwrap();
confirm_transaction_depth(&mut nodes[0], &commitment_tx, BREAKDOWN_TIMEOUT as u32);

Expand Down
31 changes: 30 additions & 1 deletion lightning/src/chain/chainmonitor.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1488,10 +1488,12 @@ where
}
}

/// A trivial trait which describes any [`ChainMonitor`].
/// A trivial trait which describes any [`ChainMonitor`] or [`DeferredChainMonitor`].
///
/// This is not exported to bindings users as general cover traits aren't useful in other
/// languages.
///
/// [`DeferredChainMonitor`]: crate::chain::deferred::DeferredChainMonitor
pub trait AChainMonitor {
/// A type implementing [`EcdsaChannelSigner`].
type Signer: EcdsaChannelSigner + Sized;
Expand DownExpand Up@@ -1521,6 +1523,24 @@ pub trait AChainMonitor {
Self::Persister,
Self::EntropySource,
>;

/// Returns the number of pending monitor operations queued for later execution.
///
/// For monitors that process operations immediately (like [`ChainMonitor`]), this
/// always returns 0.
fn pending_operation_count(&self) -> usize;

/// Flushes pending monitor operations.
///
/// # Arguments
///
/// * `count` - The maximum number of operations to flush. If `count` is greater than
/// the number of pending operations, all pending operations are flushed.
/// * `logger` - Logger for error messages during flush operations.
///
/// For monitors that process operations immediately (like [`ChainMonitor`]), this
/// is a no-op.
fn flush(&self, count: usize, logger: &Self::Logger);
}

impl<
Expand All@@ -1546,6 +1566,15 @@ where
fn get_cm(&self) -> &ChainMonitor<ChannelSigner, C, T, F, L, P, ES> {
self
}

fn pending_operation_count(&self) -> usize {
// ChainMonitor processes operations immediately, so there are never any pending.
0
}

fn flush(&self, _count: usize, _logger: &L) {
// No-op: ChainMonitor processes operations immediately.
}
}

#[cfg(test)]
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Defer ChainMonitor updates and persistence to flush (wrapper approach) by joostjager · Pull Request #4345 · lightningdevkit/rust-lightning · GitHub
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 91 additions & 9 deletions lightning-background-processor/src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -103,7 +103,7 @@ use alloc::vec::Vec;
/// * Monitoring whether the [`ChannelManager`] needs to be re-persisted to disk, and if so,
/// writing it to disk/backups by invoking the callback given to it at startup.
/// [`ChannelManager`] persistence should be done in the background.
/// * Calling [`ChannelManager::timer_tick_occurred`], [`ChainMonitor::rebroadcast_pending_claims`]
/// * Calling [`ChannelManager::timer_tick_occurred`], [`lightning::chain::chainmonitor::ChainMonitor::rebroadcast_pending_claims`]
/// and [`PeerManager::timer_tick_occurred`] at the appropriate intervals.
/// * Calling [`NetworkGraph::remove_stale_channels_and_tracking`] (if a [`GossipSync`] with a
/// [`NetworkGraph`] is provided to [`BackgroundProcessor::start`]).
Expand DownExpand Up@@ -824,7 +824,7 @@ use futures_util::{dummy_waker, Joiner, OptionalSelector, Selector, SelectorOutp
/// # fn send_data(&mut self, _data: &[u8], _continue_read: bool) -> usize { 0 }
/// # fn disconnect_socket(&mut self) {}
/// # }
/// # type ChainMonitor<B, F, FE> = lightning::chain::chainmonitor::ChainMonitor<lightning::sign::InMemorySigner, Arc<F>, Arc<B>, Arc<FE>, Arc<Logger>, Arc<StoreSync>, Arc<lightning::sign::KeysManager>>;
/// # type ChainMonitor<B, F, FE> = lightning::chain::deferred::DeferredChainMonitor<lightning::sign::InMemorySigner, Arc<F>, Arc<B>, Arc<FE>, Arc<Logger>, Arc<StoreSync>, Arc<lightning::sign::KeysManager>>;
/// # type NetworkGraph = lightning::routing::gossip::NetworkGraph<Arc<Logger>>;
/// # type P2PGossipSync<UL> = lightning::routing::gossip::P2PGossipSync<Arc<NetworkGraph>, Arc<UL>, Arc<Logger>>;
/// # type ChannelManager<B, F, FE> = lightning::ln::channelmanager::SimpleArcChannelManager<ChainMonitor<B, F, FE>, B, FE, Logger>;
Expand DownExpand Up@@ -963,7 +963,9 @@ pub async fn process_events_async<
sleeper: Sleeper, mobile_interruptable_platform: bool, fetch_time: FetchTime,
) -> Result<(), lightning::io::Error>
where
M::Target: AChainMonitor<Signer = <CM::Target as AChannelManager>::Signer, Logger = L>,
M::Target: AChainMonitor<Signer = <CM::Target as AChannelManager>::Signer, Logger = L>
+ lightning::chain::Watch<<CM::Target as AChannelManager>::Signer>
+ lightning::events::EventsProvider,
CM::Target: AChannelManager,
OM::Target: AOnionMessenger,
PM::Target: APeerManager,
Expand DownExpand Up@@ -1120,6 +1122,11 @@ where

let mut futures = Joiner::new();

// Capture the number of pending monitor writes before persisting the channel manager.
// We'll only flush this many writes after the manager is persisted, to avoid flushing
// monitor updates that arrived after the manager state was captured.
let pending_monitor_writes = chain_monitor.pending_operation_count();

if channel_manager.get_cm().get_and_clear_needs_persistence() {
log_trace!(logger, "Persisting ChannelManager...");

Expand DownExpand Up@@ -1317,6 +1324,15 @@ where
res?;
}

// Flush the monitor writes that were pending before we persisted the channel manager.
// Any writes that arrived after are left in the queue for the next iteration. There's
// no need to "chase the tail" by processing new updates that arrive during flushing -
// they'll be handled in the next round.
if pending_monitor_writes > 0 {
log_trace!(logger, "Flushing {} monitor writes", pending_monitor_writes);
chain_monitor.flush(pending_monitor_writes, &logger);
}

match check_and_reset_sleeper(&mut last_onion_message_handler_call, || {
sleeper(ONION_MESSAGE_HANDLER_TIMER)
}) {
Expand DownExpand Up@@ -1381,6 +1397,14 @@ where
channel_manager.get_cm().encode(),
)
.await?;

// Flush all pending monitor writes after final channel manager persistence.
let pending_monitor_writes = chain_monitor.pending_operation_count();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO we should move this above the CM write. A common cause of FCs is actually buggy shutdown logic where the node is still running while the BP is being shut down, causing monitor writes after the CM is written in the exit write above.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in #4351

if pending_monitor_writes > 0 {
log_trace!(logger, "Flushing {} monitor writes on shutdown", pending_monitor_writes);
chain_monitor.flush(pending_monitor_writes, &logger);
}

if let Some(ref scorer) = scorer {
kv_store
.write(
Expand DownExpand Up@@ -1461,7 +1485,9 @@ pub async fn process_events_async_with_kv_store_sync<
sleeper: Sleeper, mobile_interruptable_platform: bool, fetch_time: FetchTime,
) -> Result<(), lightning::io::Error>
where
M::Target: AChainMonitor<Signer = <CM::Target as AChannelManager>::Signer, Logger = L>,
M::Target: AChainMonitor<Signer = <CM::Target as AChannelManager>::Signer, Logger = L>
+ lightning::chain::Watch<<CM::Target as AChannelManager>::Signer>
+ lightning::events::EventsProvider,
CM::Target: AChannelManager,
OM::Target: AOnionMessenger,
PM::Target: APeerManager,
Expand DownExpand Up@@ -1570,8 +1596,11 @@ impl BackgroundProcessor {
liquidity_manager: Option<LM>, sweeper: Option<OS>, logger: L, scorer: Option<S>,
) -> Self
where
L::Target: 'static + Logger,
M::Target: AChainMonitor<Signer = <CM::Target as AChannelManager>::Signer, Logger = L>,
L::Target: 'static + Logger + Sized,
M::Target: 'static
+ AChainMonitor<Signer = <CM::Target as AChannelManager>::Signer, Logger = L>
+ lightning::chain::Watch<<CM::Target as AChannelManager>::Signer>
+ lightning::events::EventsProvider,
CM::Target: AChannelManager,
OM::Target: AOnionMessenger,
PM::Target: APeerManager,
Expand DownExpand Up@@ -1684,6 +1713,10 @@ impl BackgroundProcessor {
channel_manager.get_cm().timer_tick_occurred();
last_freshness_call = Instant::now();
}

// Capture the number of pending monitor writes before persisting the channel manager.
let pending_monitor_writes = chain_monitor.pending_operation_count();

if channel_manager.get_cm().get_and_clear_needs_persistence() {
log_trace!(logger, "Persisting ChannelManager...");
(kv_store.write(
Expand All@@ -1695,6 +1728,14 @@ impl BackgroundProcessor {
log_trace!(logger, "Done persisting ChannelManager.");
}

// Flush the monitor writes that were pending before we persisted the channel manager.
// There's no need to "chase the tail" by processing new updates that arrive during
// flushing - they'll be handled in the next round.
if pending_monitor_writes > 0 {
log_trace!(logger, "Flushing {} monitor writes", pending_monitor_writes);
chain_monitor.flush(pending_monitor_writes, &logger);
}

if let Some(liquidity_manager) = liquidity_manager.as_ref() {
log_trace!(logger, "Persisting LiquidityManager...");
let _ = liquidity_manager.get_lm().persist().map_err(|e| {
Expand DownExpand Up@@ -1815,6 +1856,18 @@ impl BackgroundProcessor {
CHANNEL_MANAGER_PERSISTENCE_KEY,
channel_manager.get_cm().encode(),
)?;

// Flush all pending monitor writes after final channel manager persistence.
let pending_monitor_writes = chain_monitor.pending_operation_count();
if pending_monitor_writes > 0 {
log_trace!(
logger,
"Flushing {} monitor writes on shutdown",
pending_monitor_writes
);
chain_monitor.flush(pending_monitor_writes, &logger);
}

if let Some(ref scorer) = scorer {
kv_store.write(
SCORER_PERSISTENCE_PRIMARY_NAMESPACE,
Expand DownExpand Up@@ -1896,9 +1949,10 @@ mod tests {
use bitcoin::transaction::{Transaction, TxOut};
use bitcoin::{Amount, ScriptBuf, Txid};
use core::sync::atomic::{AtomicBool, Ordering};
use lightning::chain::chainmonitor::AChainMonitor;
use lightning::chain::channelmonitor::ANTI_REORG_DELAY;
use lightning::chain::transaction::OutPoint;
use lightning::chain::{chainmonitor, BestBlock, Confirm, Filter};
use lightning::chain::{deferred, BestBlock, Confirm, Filter};
use lightning::events::{Event, PathFailure, ReplayEvent};
use lightning::ln::channelmanager;
use lightning::ln::channelmanager::{
Expand DownExpand Up@@ -2008,7 +2062,7 @@ mod tests {
Arc<test_utils::TestLogger>,
>;

type ChainMonitor = chainmonitor::ChainMonitor<
type ChainMonitor = deferred::DeferredChainMonitor<
InMemorySigner,
Arc<test_utils::TestChainSource>,
Arc<test_utils::TestBroadcaster>,
Expand DownExpand Up@@ -2436,7 +2490,7 @@ mod tests {
let now = Duration::from_secs(genesis_block.header.time as u64);
let keys_manager =
Arc::new(KeysManager::new(&seed, now.as_secs(), now.subsec_nanos(), true));
let chain_monitor = Arc::new(chainmonitor::ChainMonitor::new(
let chain_monitor = Arc::new(deferred::DeferredChainMonitor::new(
Some(Arc::clone(&chain_source)),
Arc::clone(&tx_broadcaster),
Arc::clone(&logger),
Expand DownExpand Up@@ -2580,19 +2634,31 @@ mod tests {
tx.clone(),
)
.unwrap();
// Flush deferred monitor operations so messages aren't held back
$node_a
.chain_monitor
.flush($node_a.chain_monitor.pending_operation_count(), &$node_a.logger);
let msg_a = get_event_msg!(
$node_a,
MessageSendEvent::SendFundingCreated,
$node_b.node.get_our_node_id()
);
$node_b.node.handle_funding_created($node_a.node.get_our_node_id(), &msg_a);
// Flush node_b's monitor so it releases the FundingSigned message
$node_b
.chain_monitor
.flush($node_b.chain_monitor.pending_operation_count(), &$node_b.logger);
get_event!($node_b, Event::ChannelPending);
let msg_b = get_event_msg!(
$node_b,
MessageSendEvent::SendFundingSigned,
$node_a.node.get_our_node_id()
);
$node_a.node.handle_funding_signed($node_b.node.get_our_node_id(), &msg_b);
// Flush node_a's monitor for the final update
$node_a
.chain_monitor
.flush($node_a.chain_monitor.pending_operation_count(), &$node_a.logger);
get_event!($node_a, Event::ChannelPending);
tx
}};
Expand DownExpand Up@@ -3039,11 +3105,23 @@ mod tests {
.node
.funding_transaction_generated(temporary_channel_id, node_1_id, funding_tx.clone())
.unwrap();
// Flush node_0's deferred monitor operations so the FundingCreated message is released
nodes[0]
.chain_monitor
.flush(nodes[0].chain_monitor.pending_operation_count(), &nodes[0].logger);
let msg_0 = get_event_msg!(nodes[0], MessageSendEvent::SendFundingCreated, node_1_id);
nodes[1].node.handle_funding_created(node_0_id, &msg_0);
// Flush node_1's deferred monitor operations so events and FundingSigned are released
nodes[1]
.chain_monitor
.flush(nodes[1].chain_monitor.pending_operation_count(), &nodes[1].logger);
get_event!(nodes[1], Event::ChannelPending);
let msg_1 = get_event_msg!(nodes[1], MessageSendEvent::SendFundingSigned, node_0_id);
nodes[0].node.handle_funding_signed(node_1_id, &msg_1);
// Flush node_0's monitor for the funding_signed update
nodes[0]
.chain_monitor
.flush(nodes[0].chain_monitor.pending_operation_count(), &nodes[0].logger);
channel_pending_recv
.recv_timeout(EVENT_DEADLINE)
.expect("ChannelPending not handled within deadline");
Expand DownExpand Up@@ -3104,6 +3182,10 @@ mod tests {
error_message.to_string(),
)
.unwrap();
// Flush the monitor update triggered by force close so the commitment tx is broadcasted
nodes[0]
.chain_monitor
.flush(nodes[0].chain_monitor.pending_operation_count(), &nodes[0].logger);
let commitment_tx = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().pop().unwrap();
confirm_transaction_depth(&mut nodes[0], &commitment_tx, BREAKDOWN_TIMEOUT as u32);

Expand Down
31 changes: 30 additions & 1 deletion lightning/src/chain/chainmonitor.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1488,10 +1488,12 @@ where
}
}

/// A trivial trait which describes any [`ChainMonitor`].
/// A trivial trait which describes any [`ChainMonitor`] or [`DeferredChainMonitor`].
///
/// This is not exported to bindings users as general cover traits aren't useful in other
/// languages.
///
/// [`DeferredChainMonitor`]: crate::chain::deferred::DeferredChainMonitor
pub trait AChainMonitor {
/// A type implementing [`EcdsaChannelSigner`].
type Signer: EcdsaChannelSigner + Sized;
Expand DownExpand Up@@ -1521,6 +1523,24 @@ pub trait AChainMonitor {
Self::Persister,
Self::EntropySource,
>;

/// Returns the number of pending monitor operations queued for later execution.
///
/// For monitors that process operations immediately (like [`ChainMonitor`]), this
/// always returns 0.
fn pending_operation_count(&self) -> usize;

/// Flushes pending monitor operations.
///
/// # Arguments
///
/// * `count` - The maximum number of operations to flush. If `count` is greater than
/// the number of pending operations, all pending operations are flushed.
/// * `logger` - Logger for error messages during flush operations.
///
/// For monitors that process operations immediately (like [`ChainMonitor`]), this
/// is a no-op.
fn flush(&self, count: usize, logger: &Self::Logger);
}

impl<
Expand All@@ -1546,6 +1566,15 @@ where
fn get_cm(&self) -> &ChainMonitor<ChannelSigner, C, T, F, L, P, ES> {
self
}

fn pending_operation_count(&self) -> usize {
// ChainMonitor processes operations immediately, so there are never any pending.
0
}

fn flush(&self, _count: usize, _logger: &L) {
// No-op: ChainMonitor processes operations immediately.
}
}

#[cfg(test)]
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Defer ChainMonitor updates and persistence to flush (wrapper approach) by joostjager · Pull Request #4345 · lightningdevkit/rust-lightning · GitHub
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 91 additions & 9 deletions lightning-background-processor/src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -103,7 +103,7 @@ use alloc::vec::Vec;
/// * Monitoring whether the [`ChannelManager`] needs to be re-persisted to disk, and if so,
/// writing it to disk/backups by invoking the callback given to it at startup.
/// [`ChannelManager`] persistence should be done in the background.
/// * Calling [`ChannelManager::timer_tick_occurred`], [`ChainMonitor::rebroadcast_pending_claims`]
/// * Calling [`ChannelManager::timer_tick_occurred`], [`lightning::chain::chainmonitor::ChainMonitor::rebroadcast_pending_claims`]
/// and [`PeerManager::timer_tick_occurred`] at the appropriate intervals.
/// * Calling [`NetworkGraph::remove_stale_channels_and_tracking`] (if a [`GossipSync`] with a
/// [`NetworkGraph`] is provided to [`BackgroundProcessor::start`]).
Expand DownExpand Up@@ -824,7 +824,7 @@ use futures_util::{dummy_waker, Joiner, OptionalSelector, Selector, SelectorOutp
/// # fn send_data(&mut self, _data: &[u8], _continue_read: bool) -> usize { 0 }
/// # fn disconnect_socket(&mut self) {}
/// # }
/// # type ChainMonitor<B, F, FE> = lightning::chain::chainmonitor::ChainMonitor<lightning::sign::InMemorySigner, Arc<F>, Arc<B>, Arc<FE>, Arc<Logger>, Arc<StoreSync>, Arc<lightning::sign::KeysManager>>;
/// # type ChainMonitor<B, F, FE> = lightning::chain::deferred::DeferredChainMonitor<lightning::sign::InMemorySigner, Arc<F>, Arc<B>, Arc<FE>, Arc<Logger>, Arc<StoreSync>, Arc<lightning::sign::KeysManager>>;
/// # type NetworkGraph = lightning::routing::gossip::NetworkGraph<Arc<Logger>>;
/// # type P2PGossipSync<UL> = lightning::routing::gossip::P2PGossipSync<Arc<NetworkGraph>, Arc<UL>, Arc<Logger>>;
/// # type ChannelManager<B, F, FE> = lightning::ln::channelmanager::SimpleArcChannelManager<ChainMonitor<B, F, FE>, B, FE, Logger>;
Expand DownExpand Up@@ -963,7 +963,9 @@ pub async fn process_events_async<
sleeper: Sleeper, mobile_interruptable_platform: bool, fetch_time: FetchTime,
) -> Result<(), lightning::io::Error>
where
M::Target: AChainMonitor<Signer = <CM::Target as AChannelManager>::Signer, Logger = L>,
M::Target: AChainMonitor<Signer = <CM::Target as AChannelManager>::Signer, Logger = L>
+ lightning::chain::Watch<<CM::Target as AChannelManager>::Signer>
+ lightning::events::EventsProvider,
CM::Target: AChannelManager,
OM::Target: AOnionMessenger,
PM::Target: APeerManager,
Expand DownExpand Up@@ -1120,6 +1122,11 @@ where

let mut futures = Joiner::new();

// Capture the number of pending monitor writes before persisting the channel manager.
// We'll only flush this many writes after the manager is persisted, to avoid flushing
// monitor updates that arrived after the manager state was captured.
let pending_monitor_writes = chain_monitor.pending_operation_count();

if channel_manager.get_cm().get_and_clear_needs_persistence() {
log_trace!(logger, "Persisting ChannelManager...");

Expand DownExpand Up@@ -1317,6 +1324,15 @@ where
res?;
}

// Flush the monitor writes that were pending before we persisted the channel manager.
// Any writes that arrived after are left in the queue for the next iteration. There's
// no need to "chase the tail" by processing new updates that arrive during flushing -
// they'll be handled in the next round.
if pending_monitor_writes > 0 {
log_trace!(logger, "Flushing {} monitor writes", pending_monitor_writes);
chain_monitor.flush(pending_monitor_writes, &logger);
}

match check_and_reset_sleeper(&mut last_onion_message_handler_call, || {
sleeper(ONION_MESSAGE_HANDLER_TIMER)
}) {
Expand DownExpand Up@@ -1381,6 +1397,14 @@ where
channel_manager.get_cm().encode(),
)
.await?;

// Flush all pending monitor writes after final channel manager persistence.
let pending_monitor_writes = chain_monitor.pending_operation_count();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO we should move this above the CM write. A common cause of FCs is actually buggy shutdown logic where the node is still running while the BP is being shut down, causing monitor writes after the CM is written in the exit write above.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in #4351

if pending_monitor_writes > 0 {
log_trace!(logger, "Flushing {} monitor writes on shutdown", pending_monitor_writes);
chain_monitor.flush(pending_monitor_writes, &logger);
}

if let Some(ref scorer) = scorer {
kv_store
.write(
Expand DownExpand Up@@ -1461,7 +1485,9 @@ pub async fn process_events_async_with_kv_store_sync<
sleeper: Sleeper, mobile_interruptable_platform: bool, fetch_time: FetchTime,
) -> Result<(), lightning::io::Error>
where
M::Target: AChainMonitor<Signer = <CM::Target as AChannelManager>::Signer, Logger = L>,
M::Target: AChainMonitor<Signer = <CM::Target as AChannelManager>::Signer, Logger = L>
+ lightning::chain::Watch<<CM::Target as AChannelManager>::Signer>
+ lightning::events::EventsProvider,
CM::Target: AChannelManager,
OM::Target: AOnionMessenger,
PM::Target: APeerManager,
Expand DownExpand Up@@ -1570,8 +1596,11 @@ impl BackgroundProcessor {
liquidity_manager: Option<LM>, sweeper: Option<OS>, logger: L, scorer: Option<S>,
) -> Self
where
L::Target: 'static + Logger,
M::Target: AChainMonitor<Signer = <CM::Target as AChannelManager>::Signer, Logger = L>,
L::Target: 'static + Logger + Sized,
M::Target: 'static
+ AChainMonitor<Signer = <CM::Target as AChannelManager>::Signer, Logger = L>
+ lightning::chain::Watch<<CM::Target as AChannelManager>::Signer>
+ lightning::events::EventsProvider,
CM::Target: AChannelManager,
OM::Target: AOnionMessenger,
PM::Target: APeerManager,
Expand DownExpand Up@@ -1684,6 +1713,10 @@ impl BackgroundProcessor {
channel_manager.get_cm().timer_tick_occurred();
last_freshness_call = Instant::now();
}

// Capture the number of pending monitor writes before persisting the channel manager.
let pending_monitor_writes = chain_monitor.pending_operation_count();

if channel_manager.get_cm().get_and_clear_needs_persistence() {
log_trace!(logger, "Persisting ChannelManager...");
(kv_store.write(
Expand All@@ -1695,6 +1728,14 @@ impl BackgroundProcessor {
log_trace!(logger, "Done persisting ChannelManager.");
}

// Flush the monitor writes that were pending before we persisted the channel manager.
// There's no need to "chase the tail" by processing new updates that arrive during
// flushing - they'll be handled in the next round.
if pending_monitor_writes > 0 {
log_trace!(logger, "Flushing {} monitor writes", pending_monitor_writes);
chain_monitor.flush(pending_monitor_writes, &logger);
}

if let Some(liquidity_manager) = liquidity_manager.as_ref() {
log_trace!(logger, "Persisting LiquidityManager...");
let _ = liquidity_manager.get_lm().persist().map_err(|e| {
Expand DownExpand Up@@ -1815,6 +1856,18 @@ impl BackgroundProcessor {
CHANNEL_MANAGER_PERSISTENCE_KEY,
channel_manager.get_cm().encode(),
)?;

// Flush all pending monitor writes after final channel manager persistence.
let pending_monitor_writes = chain_monitor.pending_operation_count();
if pending_monitor_writes > 0 {
log_trace!(
logger,
"Flushing {} monitor writes on shutdown",
pending_monitor_writes
);
chain_monitor.flush(pending_monitor_writes, &logger);
}

if let Some(ref scorer) = scorer {
kv_store.write(
SCORER_PERSISTENCE_PRIMARY_NAMESPACE,
Expand DownExpand Up@@ -1896,9 +1949,10 @@ mod tests {
use bitcoin::transaction::{Transaction, TxOut};
use bitcoin::{Amount, ScriptBuf, Txid};
use core::sync::atomic::{AtomicBool, Ordering};
use lightning::chain::chainmonitor::AChainMonitor;
use lightning::chain::channelmonitor::ANTI_REORG_DELAY;
use lightning::chain::transaction::OutPoint;
use lightning::chain::{chainmonitor, BestBlock, Confirm, Filter};
use lightning::chain::{deferred, BestBlock, Confirm, Filter};
use lightning::events::{Event, PathFailure, ReplayEvent};
use lightning::ln::channelmanager;
use lightning::ln::channelmanager::{
Expand DownExpand Up@@ -2008,7 +2062,7 @@ mod tests {
Arc<test_utils::TestLogger>,
>;

type ChainMonitor = chainmonitor::ChainMonitor<
type ChainMonitor = deferred::DeferredChainMonitor<
InMemorySigner,
Arc<test_utils::TestChainSource>,
Arc<test_utils::TestBroadcaster>,
Expand DownExpand Up@@ -2436,7 +2490,7 @@ mod tests {
let now = Duration::from_secs(genesis_block.header.time as u64);
let keys_manager =
Arc::new(KeysManager::new(&seed, now.as_secs(), now.subsec_nanos(), true));
let chain_monitor = Arc::new(chainmonitor::ChainMonitor::new(
let chain_monitor = Arc::new(deferred::DeferredChainMonitor::new(
Some(Arc::clone(&chain_source)),
Arc::clone(&tx_broadcaster),
Arc::clone(&logger),
Expand DownExpand Up@@ -2580,19 +2634,31 @@ mod tests {
tx.clone(),
)
.unwrap();
// Flush deferred monitor operations so messages aren't held back
$node_a
.chain_monitor
.flush($node_a.chain_monitor.pending_operation_count(), &$node_a.logger);
let msg_a = get_event_msg!(
$node_a,
MessageSendEvent::SendFundingCreated,
$node_b.node.get_our_node_id()
);
$node_b.node.handle_funding_created($node_a.node.get_our_node_id(), &msg_a);
// Flush node_b's monitor so it releases the FundingSigned message
$node_b
.chain_monitor
.flush($node_b.chain_monitor.pending_operation_count(), &$node_b.logger);
get_event!($node_b, Event::ChannelPending);
let msg_b = get_event_msg!(
$node_b,
MessageSendEvent::SendFundingSigned,
$node_a.node.get_our_node_id()
);
$node_a.node.handle_funding_signed($node_b.node.get_our_node_id(), &msg_b);
// Flush node_a's monitor for the final update
$node_a
.chain_monitor
.flush($node_a.chain_monitor.pending_operation_count(), &$node_a.logger);
get_event!($node_a, Event::ChannelPending);
tx
}};
Expand DownExpand Up@@ -3039,11 +3105,23 @@ mod tests {
.node
.funding_transaction_generated(temporary_channel_id, node_1_id, funding_tx.clone())
.unwrap();
// Flush node_0's deferred monitor operations so the FundingCreated message is released
nodes[0]
.chain_monitor
.flush(nodes[0].chain_monitor.pending_operation_count(), &nodes[0].logger);
let msg_0 = get_event_msg!(nodes[0], MessageSendEvent::SendFundingCreated, node_1_id);
nodes[1].node.handle_funding_created(node_0_id, &msg_0);
// Flush node_1's deferred monitor operations so events and FundingSigned are released
nodes[1]
.chain_monitor
.flush(nodes[1].chain_monitor.pending_operation_count(), &nodes[1].logger);
get_event!(nodes[1], Event::ChannelPending);
let msg_1 = get_event_msg!(nodes[1], MessageSendEvent::SendFundingSigned, node_0_id);
nodes[0].node.handle_funding_signed(node_1_id, &msg_1);
// Flush node_0's monitor for the funding_signed update
nodes[0]
.chain_monitor
.flush(nodes[0].chain_monitor.pending_operation_count(), &nodes[0].logger);
channel_pending_recv
.recv_timeout(EVENT_DEADLINE)
.expect("ChannelPending not handled within deadline");
Expand DownExpand Up@@ -3104,6 +3182,10 @@ mod tests {
error_message.to_string(),
)
.unwrap();
// Flush the monitor update triggered by force close so the commitment tx is broadcasted
nodes[0]
.chain_monitor
.flush(nodes[0].chain_monitor.pending_operation_count(), &nodes[0].logger);
let commitment_tx = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().pop().unwrap();
confirm_transaction_depth(&mut nodes[0], &commitment_tx, BREAKDOWN_TIMEOUT as u32);

Expand Down
31 changes: 30 additions & 1 deletion lightning/src/chain/chainmonitor.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1488,10 +1488,12 @@ where
}
}

/// A trivial trait which describes any [`ChainMonitor`].
/// A trivial trait which describes any [`ChainMonitor`] or [`DeferredChainMonitor`].
///
/// This is not exported to bindings users as general cover traits aren't useful in other
/// languages.
///
/// [`DeferredChainMonitor`]: crate::chain::deferred::DeferredChainMonitor
pub trait AChainMonitor {
/// A type implementing [`EcdsaChannelSigner`].
type Signer: EcdsaChannelSigner + Sized;
Expand DownExpand Up@@ -1521,6 +1523,24 @@ pub trait AChainMonitor {
Self::Persister,
Self::EntropySource,
>;

/// Returns the number of pending monitor operations queued for later execution.
///
/// For monitors that process operations immediately (like [`ChainMonitor`]), this
/// always returns 0.
fn pending_operation_count(&self) -> usize;

/// Flushes pending monitor operations.
///
/// # Arguments
///
/// * `count` - The maximum number of operations to flush. If `count` is greater than
/// the number of pending operations, all pending operations are flushed.
/// * `logger` - Logger for error messages during flush operations.
///
/// For monitors that process operations immediately (like [`ChainMonitor`]), this
/// is a no-op.
fn flush(&self, count: usize, logger: &Self::Logger);
}

impl<
Expand All@@ -1546,6 +1566,15 @@ where
fn get_cm(&self) -> &ChainMonitor<ChannelSigner, C, T, F, L, P, ES> {
self
}

fn pending_operation_count(&self) -> usize {
// ChainMonitor processes operations immediately, so there are never any pending.
0
}

fn flush(&self, _count: usize, _logger: &L) {
// No-op: ChainMonitor processes operations immediately.
}
}

#[cfg(test)]
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Defer ChainMonitor updates and persistence to flush (wrapper approach) by joostjager · Pull Request #4345 · lightningdevkit/rust-lightning · GitHub
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 91 additions & 9 deletions lightning-background-processor/src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -103,7 +103,7 @@ use alloc::vec::Vec;
/// * Monitoring whether the [`ChannelManager`] needs to be re-persisted to disk, and if so,
/// writing it to disk/backups by invoking the callback given to it at startup.
/// [`ChannelManager`] persistence should be done in the background.
/// * Calling [`ChannelManager::timer_tick_occurred`], [`ChainMonitor::rebroadcast_pending_claims`]
/// * Calling [`ChannelManager::timer_tick_occurred`], [`lightning::chain::chainmonitor::ChainMonitor::rebroadcast_pending_claims`]
/// and [`PeerManager::timer_tick_occurred`] at the appropriate intervals.
/// * Calling [`NetworkGraph::remove_stale_channels_and_tracking`] (if a [`GossipSync`] with a
/// [`NetworkGraph`] is provided to [`BackgroundProcessor::start`]).
Expand DownExpand Up@@ -824,7 +824,7 @@ use futures_util::{dummy_waker, Joiner, OptionalSelector, Selector, SelectorOutp
/// # fn send_data(&mut self, _data: &[u8], _continue_read: bool) -> usize { 0 }
/// # fn disconnect_socket(&mut self) {}
/// # }
/// # type ChainMonitor<B, F, FE> = lightning::chain::chainmonitor::ChainMonitor<lightning::sign::InMemorySigner, Arc<F>, Arc<B>, Arc<FE>, Arc<Logger>, Arc<StoreSync>, Arc<lightning::sign::KeysManager>>;
/// # type ChainMonitor<B, F, FE> = lightning::chain::deferred::DeferredChainMonitor<lightning::sign::InMemorySigner, Arc<F>, Arc<B>, Arc<FE>, Arc<Logger>, Arc<StoreSync>, Arc<lightning::sign::KeysManager>>;
/// # type NetworkGraph = lightning::routing::gossip::NetworkGraph<Arc<Logger>>;
/// # type P2PGossipSync<UL> = lightning::routing::gossip::P2PGossipSync<Arc<NetworkGraph>, Arc<UL>, Arc<Logger>>;
/// # type ChannelManager<B, F, FE> = lightning::ln::channelmanager::SimpleArcChannelManager<ChainMonitor<B, F, FE>, B, FE, Logger>;
Expand DownExpand Up@@ -963,7 +963,9 @@ pub async fn process_events_async<
sleeper: Sleeper, mobile_interruptable_platform: bool, fetch_time: FetchTime,
) -> Result<(), lightning::io::Error>
where
M::Target: AChainMonitor<Signer = <CM::Target as AChannelManager>::Signer, Logger = L>,
M::Target: AChainMonitor<Signer = <CM::Target as AChannelManager>::Signer, Logger = L>
+ lightning::chain::Watch<<CM::Target as AChannelManager>::Signer>
+ lightning::events::EventsProvider,
CM::Target: AChannelManager,
OM::Target: AOnionMessenger,
PM::Target: APeerManager,
Expand DownExpand Up@@ -1120,6 +1122,11 @@ where

let mut futures = Joiner::new();

// Capture the number of pending monitor writes before persisting the channel manager.
// We'll only flush this many writes after the manager is persisted, to avoid flushing
// monitor updates that arrived after the manager state was captured.
let pending_monitor_writes = chain_monitor.pending_operation_count();

if channel_manager.get_cm().get_and_clear_needs_persistence() {
log_trace!(logger, "Persisting ChannelManager...");

Expand DownExpand Up@@ -1317,6 +1324,15 @@ where
res?;
}

// Flush the monitor writes that were pending before we persisted the channel manager.
// Any writes that arrived after are left in the queue for the next iteration. There's
// no need to "chase the tail" by processing new updates that arrive during flushing -
// they'll be handled in the next round.
if pending_monitor_writes > 0 {
log_trace!(logger, "Flushing {} monitor writes", pending_monitor_writes);
chain_monitor.flush(pending_monitor_writes, &logger);
}

match check_and_reset_sleeper(&mut last_onion_message_handler_call, || {
sleeper(ONION_MESSAGE_HANDLER_TIMER)
}) {
Expand DownExpand Up@@ -1381,6 +1397,14 @@ where
channel_manager.get_cm().encode(),
)
.await?;

// Flush all pending monitor writes after final channel manager persistence.
let pending_monitor_writes = chain_monitor.pending_operation_count();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO we should move this above the CM write. A common cause of FCs is actually buggy shutdown logic where the node is still running while the BP is being shut down, causing monitor writes after the CM is written in the exit write above.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in #4351

if pending_monitor_writes > 0 {
log_trace!(logger, "Flushing {} monitor writes on shutdown", pending_monitor_writes);
chain_monitor.flush(pending_monitor_writes, &logger);
}

if let Some(ref scorer) = scorer {
kv_store
.write(
Expand DownExpand Up@@ -1461,7 +1485,9 @@ pub async fn process_events_async_with_kv_store_sync<
sleeper: Sleeper, mobile_interruptable_platform: bool, fetch_time: FetchTime,
) -> Result<(), lightning::io::Error>
where
M::Target: AChainMonitor<Signer = <CM::Target as AChannelManager>::Signer, Logger = L>,
M::Target: AChainMonitor<Signer = <CM::Target as AChannelManager>::Signer, Logger = L>
+ lightning::chain::Watch<<CM::Target as AChannelManager>::Signer>
+ lightning::events::EventsProvider,
CM::Target: AChannelManager,
OM::Target: AOnionMessenger,
PM::Target: APeerManager,
Expand DownExpand Up@@ -1570,8 +1596,11 @@ impl BackgroundProcessor {
liquidity_manager: Option<LM>, sweeper: Option<OS>, logger: L, scorer: Option<S>,
) -> Self
where
L::Target: 'static + Logger,
M::Target: AChainMonitor<Signer = <CM::Target as AChannelManager>::Signer, Logger = L>,
L::Target: 'static + Logger + Sized,
M::Target: 'static
+ AChainMonitor<Signer = <CM::Target as AChannelManager>::Signer, Logger = L>
+ lightning::chain::Watch<<CM::Target as AChannelManager>::Signer>
+ lightning::events::EventsProvider,
CM::Target: AChannelManager,
OM::Target: AOnionMessenger,
PM::Target: APeerManager,
Expand DownExpand Up@@ -1684,6 +1713,10 @@ impl BackgroundProcessor {
channel_manager.get_cm().timer_tick_occurred();
last_freshness_call = Instant::now();
}

// Capture the number of pending monitor writes before persisting the channel manager.
let pending_monitor_writes = chain_monitor.pending_operation_count();

if channel_manager.get_cm().get_and_clear_needs_persistence() {
log_trace!(logger, "Persisting ChannelManager...");
(kv_store.write(
Expand All@@ -1695,6 +1728,14 @@ impl BackgroundProcessor {
log_trace!(logger, "Done persisting ChannelManager.");
}

// Flush the monitor writes that were pending before we persisted the channel manager.
// There's no need to "chase the tail" by processing new updates that arrive during
// flushing - they'll be handled in the next round.
if pending_monitor_writes > 0 {
log_trace!(logger, "Flushing {} monitor writes", pending_monitor_writes);
chain_monitor.flush(pending_monitor_writes, &logger);
}

if let Some(liquidity_manager) = liquidity_manager.as_ref() {
log_trace!(logger, "Persisting LiquidityManager...");
let _ = liquidity_manager.get_lm().persist().map_err(|e| {
Expand DownExpand Up@@ -1815,6 +1856,18 @@ impl BackgroundProcessor {
CHANNEL_MANAGER_PERSISTENCE_KEY,
channel_manager.get_cm().encode(),
)?;

// Flush all pending monitor writes after final channel manager persistence.
let pending_monitor_writes = chain_monitor.pending_operation_count();
if pending_monitor_writes > 0 {
log_trace!(
logger,
"Flushing {} monitor writes on shutdown",
pending_monitor_writes
);
chain_monitor.flush(pending_monitor_writes, &logger);
}

if let Some(ref scorer) = scorer {
kv_store.write(
SCORER_PERSISTENCE_PRIMARY_NAMESPACE,
Expand DownExpand Up@@ -1896,9 +1949,10 @@ mod tests {
use bitcoin::transaction::{Transaction, TxOut};
use bitcoin::{Amount, ScriptBuf, Txid};
use core::sync::atomic::{AtomicBool, Ordering};
use lightning::chain::chainmonitor::AChainMonitor;
use lightning::chain::channelmonitor::ANTI_REORG_DELAY;
use lightning::chain::transaction::OutPoint;
use lightning::chain::{chainmonitor, BestBlock, Confirm, Filter};
use lightning::chain::{deferred, BestBlock, Confirm, Filter};
use lightning::events::{Event, PathFailure, ReplayEvent};
use lightning::ln::channelmanager;
use lightning::ln::channelmanager::{
Expand DownExpand Up@@ -2008,7 +2062,7 @@ mod tests {
Arc<test_utils::TestLogger>,
>;

type ChainMonitor = chainmonitor::ChainMonitor<
type ChainMonitor = deferred::DeferredChainMonitor<
InMemorySigner,
Arc<test_utils::TestChainSource>,
Arc<test_utils::TestBroadcaster>,
Expand DownExpand Up@@ -2436,7 +2490,7 @@ mod tests {
let now = Duration::from_secs(genesis_block.header.time as u64);
let keys_manager =
Arc::new(KeysManager::new(&seed, now.as_secs(), now.subsec_nanos(), true));
let chain_monitor = Arc::new(chainmonitor::ChainMonitor::new(
let chain_monitor = Arc::new(deferred::DeferredChainMonitor::new(
Some(Arc::clone(&chain_source)),
Arc::clone(&tx_broadcaster),
Arc::clone(&logger),
Expand DownExpand Up@@ -2580,19 +2634,31 @@ mod tests {
tx.clone(),
)
.unwrap();
// Flush deferred monitor operations so messages aren't held back
$node_a
.chain_monitor
.flush($node_a.chain_monitor.pending_operation_count(), &$node_a.logger);
let msg_a = get_event_msg!(
$node_a,
MessageSendEvent::SendFundingCreated,
$node_b.node.get_our_node_id()
);
$node_b.node.handle_funding_created($node_a.node.get_our_node_id(), &msg_a);
// Flush node_b's monitor so it releases the FundingSigned message
$node_b
.chain_monitor
.flush($node_b.chain_monitor.pending_operation_count(), &$node_b.logger);
get_event!($node_b, Event::ChannelPending);
let msg_b = get_event_msg!(
$node_b,
MessageSendEvent::SendFundingSigned,
$node_a.node.get_our_node_id()
);
$node_a.node.handle_funding_signed($node_b.node.get_our_node_id(), &msg_b);
// Flush node_a's monitor for the final update
$node_a
.chain_monitor
.flush($node_a.chain_monitor.pending_operation_count(), &$node_a.logger);
get_event!($node_a, Event::ChannelPending);
tx
}};
Expand DownExpand Up@@ -3039,11 +3105,23 @@ mod tests {
.node
.funding_transaction_generated(temporary_channel_id, node_1_id, funding_tx.clone())
.unwrap();
// Flush node_0's deferred monitor operations so the FundingCreated message is released
nodes[0]
.chain_monitor
.flush(nodes[0].chain_monitor.pending_operation_count(), &nodes[0].logger);
let msg_0 = get_event_msg!(nodes[0], MessageSendEvent::SendFundingCreated, node_1_id);
nodes[1].node.handle_funding_created(node_0_id, &msg_0);
// Flush node_1's deferred monitor operations so events and FundingSigned are released
nodes[1]
.chain_monitor
.flush(nodes[1].chain_monitor.pending_operation_count(), &nodes[1].logger);
get_event!(nodes[1], Event::ChannelPending);
let msg_1 = get_event_msg!(nodes[1], MessageSendEvent::SendFundingSigned, node_0_id);
nodes[0].node.handle_funding_signed(node_1_id, &msg_1);
// Flush node_0's monitor for the funding_signed update
nodes[0]
.chain_monitor
.flush(nodes[0].chain_monitor.pending_operation_count(), &nodes[0].logger);
channel_pending_recv
.recv_timeout(EVENT_DEADLINE)
.expect("ChannelPending not handled within deadline");
Expand DownExpand Up@@ -3104,6 +3182,10 @@ mod tests {
error_message.to_string(),
)
.unwrap();
// Flush the monitor update triggered by force close so the commitment tx is broadcasted
nodes[0]
.chain_monitor
.flush(nodes[0].chain_monitor.pending_operation_count(), &nodes[0].logger);
let commitment_tx = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().pop().unwrap();
confirm_transaction_depth(&mut nodes[0], &commitment_tx, BREAKDOWN_TIMEOUT as u32);

Expand Down
31 changes: 30 additions & 1 deletion lightning/src/chain/chainmonitor.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1488,10 +1488,12 @@ where
}
}

/// A trivial trait which describes any [`ChainMonitor`].
/// A trivial trait which describes any [`ChainMonitor`] or [`DeferredChainMonitor`].
///
/// This is not exported to bindings users as general cover traits aren't useful in other
/// languages.
///
/// [`DeferredChainMonitor`]: crate::chain::deferred::DeferredChainMonitor
pub trait AChainMonitor {
/// A type implementing [`EcdsaChannelSigner`].
type Signer: EcdsaChannelSigner + Sized;
Expand DownExpand Up@@ -1521,6 +1523,24 @@ pub trait AChainMonitor {
Self::Persister,
Self::EntropySource,
>;

/// Returns the number of pending monitor operations queued for later execution.
///
/// For monitors that process operations immediately (like [`ChainMonitor`]), this
/// always returns 0.
fn pending_operation_count(&self) -> usize;

/// Flushes pending monitor operations.
///
/// # Arguments
///
/// * `count` - The maximum number of operations to flush. If `count` is greater than
/// the number of pending operations, all pending operations are flushed.
/// * `logger` - Logger for error messages during flush operations.
///
/// For monitors that process operations immediately (like [`ChainMonitor`]), this
/// is a no-op.
fn flush(&self, count: usize, logger: &Self::Logger);
}

impl<
Expand All@@ -1546,6 +1566,15 @@ where
fn get_cm(&self) -> &ChainMonitor<ChannelSigner, C, T, F, L, P, ES> {
self
}

fn pending_operation_count(&self) -> usize {
// ChainMonitor processes operations immediately, so there are never any pending.
0
}

fn flush(&self, _count: usize, _logger: &L) {
// No-op: ChainMonitor processes operations immediately.
}
}

#[cfg(test)]
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Defer ChainMonitor updates and persistence to flush (wrapper approach) by joostjager · Pull Request #4345 · lightningdevkit/rust-lightning · GitHub
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 91 additions & 9 deletions lightning-background-processor/src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -103,7 +103,7 @@ use alloc::vec::Vec;
/// * Monitoring whether the [`ChannelManager`] needs to be re-persisted to disk, and if so,
/// writing it to disk/backups by invoking the callback given to it at startup.
/// [`ChannelManager`] persistence should be done in the background.
/// * Calling [`ChannelManager::timer_tick_occurred`], [`ChainMonitor::rebroadcast_pending_claims`]
/// * Calling [`ChannelManager::timer_tick_occurred`], [`lightning::chain::chainmonitor::ChainMonitor::rebroadcast_pending_claims`]
/// and [`PeerManager::timer_tick_occurred`] at the appropriate intervals.
/// * Calling [`NetworkGraph::remove_stale_channels_and_tracking`] (if a [`GossipSync`] with a
/// [`NetworkGraph`] is provided to [`BackgroundProcessor::start`]).
Expand DownExpand Up@@ -824,7 +824,7 @@ use futures_util::{dummy_waker, Joiner, OptionalSelector, Selector, SelectorOutp
/// # fn send_data(&mut self, _data: &[u8], _continue_read: bool) -> usize { 0 }
/// # fn disconnect_socket(&mut self) {}
/// # }
/// # type ChainMonitor<B, F, FE> = lightning::chain::chainmonitor::ChainMonitor<lightning::sign::InMemorySigner, Arc<F>, Arc<B>, Arc<FE>, Arc<Logger>, Arc<StoreSync>, Arc<lightning::sign::KeysManager>>;
/// # type ChainMonitor<B, F, FE> = lightning::chain::deferred::DeferredChainMonitor<lightning::sign::InMemorySigner, Arc<F>, Arc<B>, Arc<FE>, Arc<Logger>, Arc<StoreSync>, Arc<lightning::sign::KeysManager>>;
/// # type NetworkGraph = lightning::routing::gossip::NetworkGraph<Arc<Logger>>;
/// # type P2PGossipSync<UL> = lightning::routing::gossip::P2PGossipSync<Arc<NetworkGraph>, Arc<UL>, Arc<Logger>>;
/// # type ChannelManager<B, F, FE> = lightning::ln::channelmanager::SimpleArcChannelManager<ChainMonitor<B, F, FE>, B, FE, Logger>;
Expand DownExpand Up@@ -963,7 +963,9 @@ pub async fn process_events_async<
sleeper: Sleeper, mobile_interruptable_platform: bool, fetch_time: FetchTime,
) -> Result<(), lightning::io::Error>
where
M::Target: AChainMonitor<Signer = <CM::Target as AChannelManager>::Signer, Logger = L>,
M::Target: AChainMonitor<Signer = <CM::Target as AChannelManager>::Signer, Logger = L>
+ lightning::chain::Watch<<CM::Target as AChannelManager>::Signer>
+ lightning::events::EventsProvider,
CM::Target: AChannelManager,
OM::Target: AOnionMessenger,
PM::Target: APeerManager,
Expand DownExpand Up@@ -1120,6 +1122,11 @@ where

let mut futures = Joiner::new();

// Capture the number of pending monitor writes before persisting the channel manager.
// We'll only flush this many writes after the manager is persisted, to avoid flushing
// monitor updates that arrived after the manager state was captured.
let pending_monitor_writes = chain_monitor.pending_operation_count();

if channel_manager.get_cm().get_and_clear_needs_persistence() {
log_trace!(logger, "Persisting ChannelManager...");

Expand DownExpand Up@@ -1317,6 +1324,15 @@ where
res?;
}

// Flush the monitor writes that were pending before we persisted the channel manager.
// Any writes that arrived after are left in the queue for the next iteration. There's
// no need to "chase the tail" by processing new updates that arrive during flushing -
// they'll be handled in the next round.
if pending_monitor_writes > 0 {
log_trace!(logger, "Flushing {} monitor writes", pending_monitor_writes);
chain_monitor.flush(pending_monitor_writes, &logger);
}

match check_and_reset_sleeper(&mut last_onion_message_handler_call, || {
sleeper(ONION_MESSAGE_HANDLER_TIMER)
}) {
Expand DownExpand Up@@ -1381,6 +1397,14 @@ where
channel_manager.get_cm().encode(),
)
.await?;

// Flush all pending monitor writes after final channel manager persistence.
let pending_monitor_writes = chain_monitor.pending_operation_count();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO we should move this above the CM write. A common cause of FCs is actually buggy shutdown logic where the node is still running while the BP is being shut down, causing monitor writes after the CM is written in the exit write above.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in #4351

if pending_monitor_writes > 0 {
log_trace!(logger, "Flushing {} monitor writes on shutdown", pending_monitor_writes);
chain_monitor.flush(pending_monitor_writes, &logger);
}

if let Some(ref scorer) = scorer {
kv_store
.write(
Expand DownExpand Up@@ -1461,7 +1485,9 @@ pub async fn process_events_async_with_kv_store_sync<
sleeper: Sleeper, mobile_interruptable_platform: bool, fetch_time: FetchTime,
) -> Result<(), lightning::io::Error>
where
M::Target: AChainMonitor<Signer = <CM::Target as AChannelManager>::Signer, Logger = L>,
M::Target: AChainMonitor<Signer = <CM::Target as AChannelManager>::Signer, Logger = L>
+ lightning::chain::Watch<<CM::Target as AChannelManager>::Signer>
+ lightning::events::EventsProvider,
CM::Target: AChannelManager,
OM::Target: AOnionMessenger,
PM::Target: APeerManager,
Expand DownExpand Up@@ -1570,8 +1596,11 @@ impl BackgroundProcessor {
liquidity_manager: Option<LM>, sweeper: Option<OS>, logger: L, scorer: Option<S>,
) -> Self
where
L::Target: 'static + Logger,
M::Target: AChainMonitor<Signer = <CM::Target as AChannelManager>::Signer, Logger = L>,
L::Target: 'static + Logger + Sized,
M::Target: 'static
+ AChainMonitor<Signer = <CM::Target as AChannelManager>::Signer, Logger = L>
+ lightning::chain::Watch<<CM::Target as AChannelManager>::Signer>
+ lightning::events::EventsProvider,
CM::Target: AChannelManager,
OM::Target: AOnionMessenger,
PM::Target: APeerManager,
Expand DownExpand Up@@ -1684,6 +1713,10 @@ impl BackgroundProcessor {
channel_manager.get_cm().timer_tick_occurred();
last_freshness_call = Instant::now();
}

// Capture the number of pending monitor writes before persisting the channel manager.
let pending_monitor_writes = chain_monitor.pending_operation_count();

if channel_manager.get_cm().get_and_clear_needs_persistence() {
log_trace!(logger, "Persisting ChannelManager...");
(kv_store.write(
Expand All@@ -1695,6 +1728,14 @@ impl BackgroundProcessor {
log_trace!(logger, "Done persisting ChannelManager.");
}

// Flush the monitor writes that were pending before we persisted the channel manager.
// There's no need to "chase the tail" by processing new updates that arrive during
// flushing - they'll be handled in the next round.
if pending_monitor_writes > 0 {
log_trace!(logger, "Flushing {} monitor writes", pending_monitor_writes);
chain_monitor.flush(pending_monitor_writes, &logger);
}

if let Some(liquidity_manager) = liquidity_manager.as_ref() {
log_trace!(logger, "Persisting LiquidityManager...");
let _ = liquidity_manager.get_lm().persist().map_err(|e| {
Expand DownExpand Up@@ -1815,6 +1856,18 @@ impl BackgroundProcessor {
CHANNEL_MANAGER_PERSISTENCE_KEY,
channel_manager.get_cm().encode(),
)?;

// Flush all pending monitor writes after final channel manager persistence.
let pending_monitor_writes = chain_monitor.pending_operation_count();
if pending_monitor_writes > 0 {
log_trace!(
logger,
"Flushing {} monitor writes on shutdown",
pending_monitor_writes
);
chain_monitor.flush(pending_monitor_writes, &logger);
}

if let Some(ref scorer) = scorer {
kv_store.write(
SCORER_PERSISTENCE_PRIMARY_NAMESPACE,
Expand DownExpand Up@@ -1896,9 +1949,10 @@ mod tests {
use bitcoin::transaction::{Transaction, TxOut};
use bitcoin::{Amount, ScriptBuf, Txid};
use core::sync::atomic::{AtomicBool, Ordering};
use lightning::chain::chainmonitor::AChainMonitor;
use lightning::chain::channelmonitor::ANTI_REORG_DELAY;
use lightning::chain::transaction::OutPoint;
use lightning::chain::{chainmonitor, BestBlock, Confirm, Filter};
use lightning::chain::{deferred, BestBlock, Confirm, Filter};
use lightning::events::{Event, PathFailure, ReplayEvent};
use lightning::ln::channelmanager;
use lightning::ln::channelmanager::{
Expand DownExpand Up@@ -2008,7 +2062,7 @@ mod tests {
Arc<test_utils::TestLogger>,
>;

type ChainMonitor = chainmonitor::ChainMonitor<
type ChainMonitor = deferred::DeferredChainMonitor<
InMemorySigner,
Arc<test_utils::TestChainSource>,
Arc<test_utils::TestBroadcaster>,
Expand DownExpand Up@@ -2436,7 +2490,7 @@ mod tests {
let now = Duration::from_secs(genesis_block.header.time as u64);
let keys_manager =
Arc::new(KeysManager::new(&seed, now.as_secs(), now.subsec_nanos(), true));
let chain_monitor = Arc::new(chainmonitor::ChainMonitor::new(
let chain_monitor = Arc::new(deferred::DeferredChainMonitor::new(
Some(Arc::clone(&chain_source)),
Arc::clone(&tx_broadcaster),
Arc::clone(&logger),
Expand DownExpand Up@@ -2580,19 +2634,31 @@ mod tests {
tx.clone(),
)
.unwrap();
// Flush deferred monitor operations so messages aren't held back
$node_a
.chain_monitor
.flush($node_a.chain_monitor.pending_operation_count(), &$node_a.logger);
let msg_a = get_event_msg!(
$node_a,
MessageSendEvent::SendFundingCreated,
$node_b.node.get_our_node_id()
);
$node_b.node.handle_funding_created($node_a.node.get_our_node_id(), &msg_a);
// Flush node_b's monitor so it releases the FundingSigned message
$node_b
.chain_monitor
.flush($node_b.chain_monitor.pending_operation_count(), &$node_b.logger);
get_event!($node_b, Event::ChannelPending);
let msg_b = get_event_msg!(
$node_b,
MessageSendEvent::SendFundingSigned,
$node_a.node.get_our_node_id()
);
$node_a.node.handle_funding_signed($node_b.node.get_our_node_id(), &msg_b);
// Flush node_a's monitor for the final update
$node_a
.chain_monitor
.flush($node_a.chain_monitor.pending_operation_count(), &$node_a.logger);
get_event!($node_a, Event::ChannelPending);
tx
}};
Expand DownExpand Up@@ -3039,11 +3105,23 @@ mod tests {
.node
.funding_transaction_generated(temporary_channel_id, node_1_id, funding_tx.clone())
.unwrap();
// Flush node_0's deferred monitor operations so the FundingCreated message is released
nodes[0]
.chain_monitor
.flush(nodes[0].chain_monitor.pending_operation_count(), &nodes[0].logger);
let msg_0 = get_event_msg!(nodes[0], MessageSendEvent::SendFundingCreated, node_1_id);
nodes[1].node.handle_funding_created(node_0_id, &msg_0);
// Flush node_1's deferred monitor operations so events and FundingSigned are released
nodes[1]
.chain_monitor
.flush(nodes[1].chain_monitor.pending_operation_count(), &nodes[1].logger);
get_event!(nodes[1], Event::ChannelPending);
let msg_1 = get_event_msg!(nodes[1], MessageSendEvent::SendFundingSigned, node_0_id);
nodes[0].node.handle_funding_signed(node_1_id, &msg_1);
// Flush node_0's monitor for the funding_signed update
nodes[0]
.chain_monitor
.flush(nodes[0].chain_monitor.pending_operation_count(), &nodes[0].logger);
channel_pending_recv
.recv_timeout(EVENT_DEADLINE)
.expect("ChannelPending not handled within deadline");
Expand DownExpand Up@@ -3104,6 +3182,10 @@ mod tests {
error_message.to_string(),
)
.unwrap();
// Flush the monitor update triggered by force close so the commitment tx is broadcasted
nodes[0]
.chain_monitor
.flush(nodes[0].chain_monitor.pending_operation_count(), &nodes[0].logger);
let commitment_tx = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().pop().unwrap();
confirm_transaction_depth(&mut nodes[0], &commitment_tx, BREAKDOWN_TIMEOUT as u32);

Expand Down
31 changes: 30 additions & 1 deletion lightning/src/chain/chainmonitor.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1488,10 +1488,12 @@ where
}
}

/// A trivial trait which describes any [`ChainMonitor`].
/// A trivial trait which describes any [`ChainMonitor`] or [`DeferredChainMonitor`].
///
/// This is not exported to bindings users as general cover traits aren't useful in other
/// languages.
///
/// [`DeferredChainMonitor`]: crate::chain::deferred::DeferredChainMonitor
pub trait AChainMonitor {
/// A type implementing [`EcdsaChannelSigner`].
type Signer: EcdsaChannelSigner + Sized;
Expand DownExpand Up@@ -1521,6 +1523,24 @@ pub trait AChainMonitor {
Self::Persister,
Self::EntropySource,
>;

/// Returns the number of pending monitor operations queued for later execution.
///
/// For monitors that process operations immediately (like [`ChainMonitor`]), this
/// always returns 0.
fn pending_operation_count(&self) -> usize;

/// Flushes pending monitor operations.
///
/// # Arguments
///
/// * `count` - The maximum number of operations to flush. If `count` is greater than
/// the number of pending operations, all pending operations are flushed.
/// * `logger` - Logger for error messages during flush operations.
///
/// For monitors that process operations immediately (like [`ChainMonitor`]), this
/// is a no-op.
fn flush(&self, count: usize, logger: &Self::Logger);
}

impl<
Expand All@@ -1546,6 +1566,15 @@ where
fn get_cm(&self) -> &ChainMonitor<ChannelSigner, C, T, F, L, P, ES> {
self
}

fn pending_operation_count(&self) -> usize {
// ChainMonitor processes operations immediately, so there are never any pending.
0
}

fn flush(&self, _count: usize, _logger: &L) {
// No-op: ChainMonitor processes operations immediately.
}
}

#[cfg(test)]
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); Defer ChainMonitor updates and persistence to flush (wrapper approach) by joostjager · Pull Request #4345 · lightningdevkit/rust-lightning · GitHub
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 91 additions & 9 deletions lightning-background-processor/src/lib.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -103,7 +103,7 @@ use alloc::vec::Vec;
/// * Monitoring whether the [`ChannelManager`] needs to be re-persisted to disk, and if so,
/// writing it to disk/backups by invoking the callback given to it at startup.
/// [`ChannelManager`] persistence should be done in the background.
/// * Calling [`ChannelManager::timer_tick_occurred`], [`ChainMonitor::rebroadcast_pending_claims`]
/// * Calling [`ChannelManager::timer_tick_occurred`], [`lightning::chain::chainmonitor::ChainMonitor::rebroadcast_pending_claims`]
/// and [`PeerManager::timer_tick_occurred`] at the appropriate intervals.
/// * Calling [`NetworkGraph::remove_stale_channels_and_tracking`] (if a [`GossipSync`] with a
/// [`NetworkGraph`] is provided to [`BackgroundProcessor::start`]).
Expand DownExpand Up@@ -824,7 +824,7 @@ use futures_util::{dummy_waker, Joiner, OptionalSelector, Selector, SelectorOutp
/// # fn send_data(&mut self, _data: &[u8], _continue_read: bool) -> usize { 0 }
/// # fn disconnect_socket(&mut self) {}
/// # }
/// # type ChainMonitor<B, F, FE> = lightning::chain::chainmonitor::ChainMonitor<lightning::sign::InMemorySigner, Arc<F>, Arc<B>, Arc<FE>, Arc<Logger>, Arc<StoreSync>, Arc<lightning::sign::KeysManager>>;
/// # type ChainMonitor<B, F, FE> = lightning::chain::deferred::DeferredChainMonitor<lightning::sign::InMemorySigner, Arc<F>, Arc<B>, Arc<FE>, Arc<Logger>, Arc<StoreSync>, Arc<lightning::sign::KeysManager>>;
/// # type NetworkGraph = lightning::routing::gossip::NetworkGraph<Arc<Logger>>;
/// # type P2PGossipSync<UL> = lightning::routing::gossip::P2PGossipSync<Arc<NetworkGraph>, Arc<UL>, Arc<Logger>>;
/// # type ChannelManager<B, F, FE> = lightning::ln::channelmanager::SimpleArcChannelManager<ChainMonitor<B, F, FE>, B, FE, Logger>;
Expand DownExpand Up@@ -963,7 +963,9 @@ pub async fn process_events_async<
sleeper: Sleeper, mobile_interruptable_platform: bool, fetch_time: FetchTime,
) -> Result<(), lightning::io::Error>
where
M::Target: AChainMonitor<Signer = <CM::Target as AChannelManager>::Signer, Logger = L>,
M::Target: AChainMonitor<Signer = <CM::Target as AChannelManager>::Signer, Logger = L>
+ lightning::chain::Watch<<CM::Target as AChannelManager>::Signer>
+ lightning::events::EventsProvider,
CM::Target: AChannelManager,
OM::Target: AOnionMessenger,
PM::Target: APeerManager,
Expand DownExpand Up@@ -1120,6 +1122,11 @@ where

let mut futures = Joiner::new();

// Capture the number of pending monitor writes before persisting the channel manager.
// We'll only flush this many writes after the manager is persisted, to avoid flushing
// monitor updates that arrived after the manager state was captured.
let pending_monitor_writes = chain_monitor.pending_operation_count();

if channel_manager.get_cm().get_and_clear_needs_persistence() {
log_trace!(logger, "Persisting ChannelManager...");

Expand DownExpand Up@@ -1317,6 +1324,15 @@ where
res?;
}

// Flush the monitor writes that were pending before we persisted the channel manager.
// Any writes that arrived after are left in the queue for the next iteration. There's
// no need to "chase the tail" by processing new updates that arrive during flushing -
// they'll be handled in the next round.
if pending_monitor_writes > 0 {
log_trace!(logger, "Flushing {} monitor writes", pending_monitor_writes);
chain_monitor.flush(pending_monitor_writes, &logger);
}

match check_and_reset_sleeper(&mut last_onion_message_handler_call, || {
sleeper(ONION_MESSAGE_HANDLER_TIMER)
}) {
Expand DownExpand Up@@ -1381,6 +1397,14 @@ where
channel_manager.get_cm().encode(),
)
.await?;

// Flush all pending monitor writes after final channel manager persistence.
let pending_monitor_writes = chain_monitor.pending_operation_count();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO we should move this above the CM write. A common cause of FCs is actually buggy shutdown logic where the node is still running while the BP is being shut down, causing monitor writes after the CM is written in the exit write above.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in #4351

if pending_monitor_writes > 0 {
log_trace!(logger, "Flushing {} monitor writes on shutdown", pending_monitor_writes);
chain_monitor.flush(pending_monitor_writes, &logger);
}

if let Some(ref scorer) = scorer {
kv_store
.write(
Expand DownExpand Up@@ -1461,7 +1485,9 @@ pub async fn process_events_async_with_kv_store_sync<
sleeper: Sleeper, mobile_interruptable_platform: bool, fetch_time: FetchTime,
) -> Result<(), lightning::io::Error>
where
M::Target: AChainMonitor<Signer = <CM::Target as AChannelManager>::Signer, Logger = L>,
M::Target: AChainMonitor<Signer = <CM::Target as AChannelManager>::Signer, Logger = L>
+ lightning::chain::Watch<<CM::Target as AChannelManager>::Signer>
+ lightning::events::EventsProvider,
CM::Target: AChannelManager,
OM::Target: AOnionMessenger,
PM::Target: APeerManager,
Expand DownExpand Up@@ -1570,8 +1596,11 @@ impl BackgroundProcessor {
liquidity_manager: Option<LM>, sweeper: Option<OS>, logger: L, scorer: Option<S>,
) -> Self
where
L::Target: 'static + Logger,
M::Target: AChainMonitor<Signer = <CM::Target as AChannelManager>::Signer, Logger = L>,
L::Target: 'static + Logger + Sized,
M::Target: 'static
+ AChainMonitor<Signer = <CM::Target as AChannelManager>::Signer, Logger = L>
+ lightning::chain::Watch<<CM::Target as AChannelManager>::Signer>
+ lightning::events::EventsProvider,
CM::Target: AChannelManager,
OM::Target: AOnionMessenger,
PM::Target: APeerManager,
Expand DownExpand Up@@ -1684,6 +1713,10 @@ impl BackgroundProcessor {
channel_manager.get_cm().timer_tick_occurred();
last_freshness_call = Instant::now();
}

// Capture the number of pending monitor writes before persisting the channel manager.
let pending_monitor_writes = chain_monitor.pending_operation_count();

if channel_manager.get_cm().get_and_clear_needs_persistence() {
log_trace!(logger, "Persisting ChannelManager...");
(kv_store.write(
Expand All@@ -1695,6 +1728,14 @@ impl BackgroundProcessor {
log_trace!(logger, "Done persisting ChannelManager.");
}

// Flush the monitor writes that were pending before we persisted the channel manager.
// There's no need to "chase the tail" by processing new updates that arrive during
// flushing - they'll be handled in the next round.
if pending_monitor_writes > 0 {
log_trace!(logger, "Flushing {} monitor writes", pending_monitor_writes);
chain_monitor.flush(pending_monitor_writes, &logger);
}

if let Some(liquidity_manager) = liquidity_manager.as_ref() {
log_trace!(logger, "Persisting LiquidityManager...");
let _ = liquidity_manager.get_lm().persist().map_err(|e| {
Expand DownExpand Up@@ -1815,6 +1856,18 @@ impl BackgroundProcessor {
CHANNEL_MANAGER_PERSISTENCE_KEY,
channel_manager.get_cm().encode(),
)?;

// Flush all pending monitor writes after final channel manager persistence.
let pending_monitor_writes = chain_monitor.pending_operation_count();
if pending_monitor_writes > 0 {
log_trace!(
logger,
"Flushing {} monitor writes on shutdown",
pending_monitor_writes
);
chain_monitor.flush(pending_monitor_writes, &logger);
}

if let Some(ref scorer) = scorer {
kv_store.write(
SCORER_PERSISTENCE_PRIMARY_NAMESPACE,
Expand DownExpand Up@@ -1896,9 +1949,10 @@ mod tests {
use bitcoin::transaction::{Transaction, TxOut};
use bitcoin::{Amount, ScriptBuf, Txid};
use core::sync::atomic::{AtomicBool, Ordering};
use lightning::chain::chainmonitor::AChainMonitor;
use lightning::chain::channelmonitor::ANTI_REORG_DELAY;
use lightning::chain::transaction::OutPoint;
use lightning::chain::{chainmonitor, BestBlock, Confirm, Filter};
use lightning::chain::{deferred, BestBlock, Confirm, Filter};
use lightning::events::{Event, PathFailure, ReplayEvent};
use lightning::ln::channelmanager;
use lightning::ln::channelmanager::{
Expand DownExpand Up@@ -2008,7 +2062,7 @@ mod tests {
Arc<test_utils::TestLogger>,
>;

type ChainMonitor = chainmonitor::ChainMonitor<
type ChainMonitor = deferred::DeferredChainMonitor<
InMemorySigner,
Arc<test_utils::TestChainSource>,
Arc<test_utils::TestBroadcaster>,
Expand DownExpand Up@@ -2436,7 +2490,7 @@ mod tests {
let now = Duration::from_secs(genesis_block.header.time as u64);
let keys_manager =
Arc::new(KeysManager::new(&seed, now.as_secs(), now.subsec_nanos(), true));
let chain_monitor = Arc::new(chainmonitor::ChainMonitor::new(
let chain_monitor = Arc::new(deferred::DeferredChainMonitor::new(
Some(Arc::clone(&chain_source)),
Arc::clone(&tx_broadcaster),
Arc::clone(&logger),
Expand DownExpand Up@@ -2580,19 +2634,31 @@ mod tests {
tx.clone(),
)
.unwrap();
// Flush deferred monitor operations so messages aren't held back
$node_a
.chain_monitor
.flush($node_a.chain_monitor.pending_operation_count(), &$node_a.logger);
let msg_a = get_event_msg!(
$node_a,
MessageSendEvent::SendFundingCreated,
$node_b.node.get_our_node_id()
);
$node_b.node.handle_funding_created($node_a.node.get_our_node_id(), &msg_a);
// Flush node_b's monitor so it releases the FundingSigned message
$node_b
.chain_monitor
.flush($node_b.chain_monitor.pending_operation_count(), &$node_b.logger);
get_event!($node_b, Event::ChannelPending);
let msg_b = get_event_msg!(
$node_b,
MessageSendEvent::SendFundingSigned,
$node_a.node.get_our_node_id()
);
$node_a.node.handle_funding_signed($node_b.node.get_our_node_id(), &msg_b);
// Flush node_a's monitor for the final update
$node_a
.chain_monitor
.flush($node_a.chain_monitor.pending_operation_count(), &$node_a.logger);
get_event!($node_a, Event::ChannelPending);
tx
}};
Expand DownExpand Up@@ -3039,11 +3105,23 @@ mod tests {
.node
.funding_transaction_generated(temporary_channel_id, node_1_id, funding_tx.clone())
.unwrap();
// Flush node_0's deferred monitor operations so the FundingCreated message is released
nodes[0]
.chain_monitor
.flush(nodes[0].chain_monitor.pending_operation_count(), &nodes[0].logger);
let msg_0 = get_event_msg!(nodes[0], MessageSendEvent::SendFundingCreated, node_1_id);
nodes[1].node.handle_funding_created(node_0_id, &msg_0);
// Flush node_1's deferred monitor operations so events and FundingSigned are released
nodes[1]
.chain_monitor
.flush(nodes[1].chain_monitor.pending_operation_count(), &nodes[1].logger);
get_event!(nodes[1], Event::ChannelPending);
let msg_1 = get_event_msg!(nodes[1], MessageSendEvent::SendFundingSigned, node_0_id);
nodes[0].node.handle_funding_signed(node_1_id, &msg_1);
// Flush node_0's monitor for the funding_signed update
nodes[0]
.chain_monitor
.flush(nodes[0].chain_monitor.pending_operation_count(), &nodes[0].logger);
channel_pending_recv
.recv_timeout(EVENT_DEADLINE)
.expect("ChannelPending not handled within deadline");
Expand DownExpand Up@@ -3104,6 +3182,10 @@ mod tests {
error_message.to_string(),
)
.unwrap();
// Flush the monitor update triggered by force close so the commitment tx is broadcasted
nodes[0]
.chain_monitor
.flush(nodes[0].chain_monitor.pending_operation_count(), &nodes[0].logger);
let commitment_tx = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().pop().unwrap();
confirm_transaction_depth(&mut nodes[0], &commitment_tx, BREAKDOWN_TIMEOUT as u32);

Expand Down
31 changes: 30 additions & 1 deletion lightning/src/chain/chainmonitor.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1488,10 +1488,12 @@ where
}
}

/// A trivial trait which describes any [`ChainMonitor`].
/// A trivial trait which describes any [`ChainMonitor`] or [`DeferredChainMonitor`].
///
/// This is not exported to bindings users as general cover traits aren't useful in other
/// languages.
///
/// [`DeferredChainMonitor`]: crate::chain::deferred::DeferredChainMonitor
pub trait AChainMonitor {
/// A type implementing [`EcdsaChannelSigner`].
type Signer: EcdsaChannelSigner + Sized;
Expand DownExpand Up@@ -1521,6 +1523,24 @@ pub trait AChainMonitor {
Self::Persister,
Self::EntropySource,
>;

/// Returns the number of pending monitor operations queued for later execution.
///
/// For monitors that process operations immediately (like [`ChainMonitor`]), this
/// always returns 0.
fn pending_operation_count(&self) -> usize;

/// Flushes pending monitor operations.
///
/// # Arguments
///
/// * `count` - The maximum number of operations to flush. If `count` is greater than
/// the number of pending operations, all pending operations are flushed.
/// * `logger` - Logger for error messages during flush operations.
///
/// For monitors that process operations immediately (like [`ChainMonitor`]), this
/// is a no-op.
fn flush(&self, count: usize, logger: &Self::Logger);
}

impl<
Expand All@@ -1546,6 +1566,15 @@ where
fn get_cm(&self) -> &ChainMonitor<ChannelSigner, C, T, F, L, P, ES> {
self
}

fn pending_operation_count(&self) -> usize {
// ChainMonitor processes operations immediately, so there are never any pending.
0
}

fn flush(&self, _count: usize, _logger: &L) {
// No-op: ChainMonitor processes operations immediately.
}
}

#[cfg(test)]
Expand Down
Loading
Loading