Skip to content

Commit 0670550

Browse files
authored
Merge pull request lightningdevkit#3996 from TheBlueMatt/2025-08-faster-0.1-load
Add a method to avoid re-persisting monitors during startup
2 parents ec54ea0 + d377958 commit 0670550

File tree

5 files changed

+99
-2
lines changed

5 files changed

+99
-2
lines changed

lightning/src/chain/chainmonitor.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -903,6 +903,53 @@ where
903903

904904
self.pending_send_only_events.lock().unwrap().push(send_peer_storage_event)
905905
}
906+
907+
/// Loads a [`ChannelMonitor`] which already exists on disk after startup.
908+
///
909+
/// Using this over [`chain::Watch::watch_channel`] avoids re-persisting a [`ChannelMonitor`]
910+
/// that hasn't changed, slowing down startup.
911+
///
912+
/// Note that this method *can* be used if additional blocks were replayed against the
913+
/// [`ChannelMonitor`] or if a [`ChannelMonitorUpdate`] loaded from disk was replayed such that
914+
/// it will replayed on startup, and in general can only *not* be used if you directly accessed
915+
/// the [`ChannelMonitor`] and changed its state in some way that will not be replayed again on
916+
/// a restart. Such direct access should generally never occur for most LDK-based nodes.
917+
///
918+
/// For [`ChannelMonitor`]s which were last serialized by an LDK version prior to 0.1 this will
919+
/// fall back to calling [`chain::Watch::watch_channel`] and persisting the [`ChannelMonitor`].
920+
/// See the release notes for LDK 0.1 for more information on this requirement.
921+
///
922+
/// [`ChannelMonitor`]s which do not need to be persisted (i.e. were last written by LDK 0.1 or
923+
/// later) will be loaded without persistence and this method will return
924+
/// [`ChannelMonitorUpdateStatus::Completed`].
925+
pub fn load_existing_monitor(
926+
&self, channel_id: ChannelId, monitor: ChannelMonitor<ChannelSigner>,
927+
) -> Result<ChannelMonitorUpdateStatus, ()> {
928+
if !monitor.written_by_0_1_or_later() {
929+
return chain::Watch::watch_channel(self, channel_id, monitor);
930+
}
931+
932+
let logger = WithChannelMonitor::from(&self.logger, &monitor, None);
933+
let mut monitors = self.monitors.write().unwrap();
934+
let entry = match monitors.entry(channel_id) {
935+
hash_map::Entry::Occupied(_) => {
936+
log_error!(logger, "Failed to add new channel data: channel monitor for given channel ID is already present");
937+
return Err(());
938+
},
939+
hash_map::Entry::Vacant(e) => e,
940+
};
941+
log_trace!(
942+
logger,
943+
"Loaded existing ChannelMonitor for channel {}",
944+
log_funding_info!(monitor)
945+
);
946+
if let Some(ref chain_source) = self.chain_source {
947+
monitor.load_outputs_to_watch(chain_source, &self.logger);
948+
}
949+
entry.insert(MonitorHolder { monitor, pending_monitor_updates: Mutex::new(Vec::new()) });
950+
951+
Ok(ChannelMonitorUpdateStatus::Completed)
952+
}
906953
}
907954

908955
impl<

lightning/src/chain/channelmonitor.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1305,6 +1305,11 @@ pub(crate) struct ChannelMonitorImpl<Signer: EcdsaChannelSigner> {
13051305
// found at `Self::funding`. We don't use the term "renegotiated", as the currently locked
13061306
// `FundingScope` could be one that was renegotiated.
13071307
alternative_funding_confirmed: Option<(Txid, u32)>,
1308+
1309+
/// [`ChannelMonitor`]s written by LDK prior to 0.1 need to be re-persisted after startup. To
1310+
/// make deciding whether to do so simple, here we track whether this monitor was last written
1311+
/// prior to 0.1.
1312+
written_by_0_1_or_later: bool,
13081313
}
13091314

13101315
// Returns a `&FundingScope` for the one we are currently observing/handling commitment transactions
@@ -1887,6 +1892,8 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
18871892
prev_holder_htlc_data: None,
18881893

18891894
alternative_funding_confirmed: None,
1895+
1896+
written_by_0_1_or_later: true,
18901897
})
18911898
}
18921899

@@ -2020,6 +2027,10 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
20202027
self.inner.lock().unwrap().get_funding_txo()
20212028
}
20222029

2030+
pub(crate) fn written_by_0_1_or_later(&self) -> bool {
2031+
self.inner.lock().unwrap().written_by_0_1_or_later
2032+
}
2033+
20232034
/// Gets the funding script of the channel this ChannelMonitor is monitoring for.
20242035
pub fn get_funding_script(&self) -> ScriptBuf {
20252036
self.inner.lock().unwrap().get_funding_script()
@@ -6372,6 +6383,9 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP
63726383
(32, pending_funding, optional_vec),
63736384
(34, alternative_funding_confirmed, option),
63746385
});
6386+
// Note that `payment_preimages_with_info` was added (and is always written) in LDK 0.1, so
6387+
// we can use it to determine if this monitor was last written by LDK 0.1 or later.
6388+
let written_by_0_1_or_later = payment_preimages_with_info.is_some();
63756389
if let Some(payment_preimages_with_info) = payment_preimages_with_info {
63766390
if payment_preimages_with_info.len() != payment_preimages.len() {
63776391
return Err(DecodeError::InvalidValue);
@@ -6542,6 +6556,8 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP
65426556
prev_holder_htlc_data,
65436557

65446558
alternative_funding_confirmed,
6559+
6560+
written_by_0_1_or_later,
65456561
})))
65466562
}
65476563
}

lightning/src/ln/channelmanager.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15583,9 +15583,13 @@ impl Readable for VecDeque<(Event, Option<EventCompletionAction>)> {
1558315583
/// This is important if you have replayed a nontrivial number of blocks in step (4), allowing
1558415584
/// you to avoid having to replay the same blocks if you shut down quickly after startup. It is
1558515585
/// otherwise not required.
15586+
///
1558615587
/// Note that if you're using a [`ChainMonitor`] for your [`chain::Watch`] implementation, you
1558715588
/// will likely accomplish this as a side-effect of calling [`chain::Watch::watch_channel`] in
1558815589
/// the next step.
15590+
///
15591+
/// If you wish to avoid this for performance reasons, use
15592+
/// [`ChainMonitor::load_existing_monitor`].
1558915593
/// 7) Move the [`ChannelMonitor`]s into your local [`chain::Watch`]. If you're using a
1559015594
/// [`ChainMonitor`], this is done by calling [`chain::Watch::watch_channel`].
1559115595
///
@@ -15600,6 +15604,7 @@ impl Readable for VecDeque<(Event, Option<EventCompletionAction>)> {
1560015604
/// which you've already broadcasted the transaction.
1560115605
///
1560215606
/// [`ChainMonitor`]: crate::chain::chainmonitor::ChainMonitor
15607+
/// [`ChainMonitor::load_existing_monitor`]: crate::chain::chainmonitor::ChainMonitor::load_existing_monitor
1560315608
pub struct ChannelManagerReadArgs<
1560415609
'a,
1560515610
M: Deref,

lightning/src/ln/functional_test_utils.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1323,8 +1323,8 @@ pub fn _reload_node<'a, 'b, 'c>(
13231323
for monitor in monitors_read.drain(..) {
13241324
let channel_id = monitor.channel_id();
13251325
assert_eq!(
1326-
node.chain_monitor.watch_channel(channel_id, monitor),
1327-
Ok(ChannelMonitorUpdateStatus::Completed)
1326+
node.chain_monitor.load_existing_monitor(channel_id, monitor),
1327+
Ok(ChannelMonitorUpdateStatus::Completed),
13281328
);
13291329
check_added_monitors!(node, 1);
13301330
}

lightning/src/util/test_utils.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,35 @@ impl<'a> TestChainMonitor<'a> {
515515
self.latest_monitor_update_id.lock().unwrap().get(channel_id).unwrap().clone();
516516
self.chain_monitor.channel_monitor_updated(*channel_id, latest_update).unwrap();
517517
}
518+
519+
pub fn load_existing_monitor(
520+
&self, channel_id: ChannelId, monitor: ChannelMonitor<TestChannelSigner>,
521+
) -> Result<chain::ChannelMonitorUpdateStatus, ()> {
522+
#[cfg(feature = "std")]
523+
if let Some(blocker) = &*self.write_blocker.lock().unwrap() {
524+
blocker.recv().unwrap();
525+
}
526+
527+
// At every point where we get a monitor update, we should be able to send a useful monitor
528+
// to a watchtower and disk...
529+
let mut w = TestVecWriter(Vec::new());
530+
monitor.write(&mut w).unwrap();
531+
let new_monitor = <(BlockHash, ChannelMonitor<TestChannelSigner>)>::read(
532+
&mut io::Cursor::new(&w.0),
533+
(self.keys_manager, self.keys_manager),
534+
)
535+
.unwrap()
536+
.1;
537+
// Note that a ChannelMonitor might not round-trip exactly here as we have tests that were
538+
// serialized prior to LDK 0.1 and re-serializing them will flip the "written after LDK
539+
// 0.1" flag.
540+
self.latest_monitor_update_id
541+
.lock()
542+
.unwrap()
543+
.insert(channel_id, (monitor.get_latest_update_id(), monitor.get_latest_update_id()));
544+
self.added_monitors.lock().unwrap().push((channel_id, monitor));
545+
self.chain_monitor.load_existing_monitor(channel_id, new_monitor)
546+
}
518547
}
519548
impl<'a> chain::Watch<TestChannelSigner> for TestChainMonitor<'a> {
520549
fn watch_channel(

0 commit comments

Comments
 (0)