From bce1f8e8914087b59919537a32891341de54b705 Mon Sep 17 00:00:00 2001 From: Aditya Sharma Date: Sat, 28 Jun 2025 15:24:59 +0530 Subject: [PATCH 01/16] Write structs to serialise-deserialise Channels inside Peer-storage 'PeerStorageMonitorHolder' is used to wrap a single ChannelMonitor, here we are adding some fields separetly so that we do not need to read the whole ChannelMonitor to identify if we have lost some states. `PeerStorageMonitorHolderList` is used to keep the list of all the channels which would be sent over the wire inside Peer Storage. --- lightning/src/ln/our_peer_storage.rs | 76 ++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/lightning/src/ln/our_peer_storage.rs b/lightning/src/ln/our_peer_storage.rs index 430c9f559f9..29de8e5ebbd 100644 --- a/lightning/src/ln/our_peer_storage.rs +++ b/lightning/src/ln/our_peer_storage.rs @@ -13,8 +13,13 @@ use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::hashes::{Hash, HashEngine, Hmac, HmacEngine}; +use bitcoin::secp256k1::PublicKey; +use crate::io; +use crate::ln::msgs::DecodeError; +use crate::ln::types::ChannelId; use crate::sign::PeerStorageKey; +use crate::util::ser::{Readable, Writeable, Writer}; use crate::crypto::chacha20poly1305rfc::ChaCha20Poly1305RFC; use crate::prelude::*; @@ -146,6 +151,77 @@ fn derive_nonce(key: &PeerStorageKey, random_bytes: &[u8]) -> [u8; 12] { nonce } +/// [`PeerStorageMonitorHolder`] represents a single channel sent over the wire. +/// This would be used inside [`ChannelManager`] to determine +/// if the user has lost channel states so that we can do something about it. +/// +/// The main idea here is to just enable node to figure out that it has lost some data +/// using peer storage backups. +/// +/// [`ChannelManager`]: crate::ln::channelmanager::ChannelManager +/// +/// TODO(aditya): Write FundRecoverer to use `monitor_bytes` to drop onchain. +pub(crate) struct PeerStorageMonitorHolder { + /// Channel Id of the channel. + pub(crate) channel_id: ChannelId, + /// Node Id of the channel partner. + pub(crate) counterparty_node_id: PublicKey, + /// Minimum seen secret to determine if we have lost state. + pub(crate) min_seen_secret: u64, + /// Whole serialised ChannelMonitor to recover funds. + pub(crate) monitor_bytes: Vec, +} + +impl Writeable for PeerStorageMonitorHolder { + fn write(&self, w: &mut W) -> Result<(), io::Error> { + self.channel_id.write(w)?; + self.counterparty_node_id.write(w)?; + self.min_seen_secret.write(w)?; + self.monitor_bytes.write(w) + } +} + +impl Readable for PeerStorageMonitorHolder { + fn read(r: &mut R) -> Result { + let channel_id = Readable::read(r)?; + let counterparty_node_id = Readable::read(r)?; + let min_seen_secret = Readable::read(r)?; + let monitor_bytes = Readable::read(r)?; + + Ok(PeerStorageMonitorHolder { + channel_id, + counterparty_node_id, + min_seen_secret, + monitor_bytes, + }) + } +} + +/// [`PeerStorageMonitorHolderList`] is used to serialise all the channels and send it over wire +/// wrapped inside [`PeerStorage`]. +/// +/// [`PeerStorage`]: crate::ln::msgs::PeerStorage +pub(crate) struct PeerStorageMonitorHolderList { + /// List of all the channels to be sent over the wire. + pub(crate) monitors: Vec, +} + +impl Writeable for PeerStorageMonitorHolderList { + fn write(&self, w: &mut W) -> Result<(), io::Error> { + encode_tlv_stream!(w, { (1, &self.monitors, required_vec) }); + Ok(()) + } +} + +impl Readable for PeerStorageMonitorHolderList { + fn read(r: &mut R) -> Result { + let mut monitors: Option> = None; + decode_tlv_stream!(r, { (1, monitors, optional_vec) }); + + Ok(PeerStorageMonitorHolderList { monitors: monitors.ok_or(DecodeError::InvalidValue)? }) + } +} + #[cfg(test)] mod tests { use crate::ln::our_peer_storage::{derive_nonce, DecryptedOurPeerStorage}; From 92ff1411c1a1fcd1a09944af38dc2afe7012aaa6 Mon Sep 17 00:00:00 2001 From: Aditya Sharma Date: Thu, 17 Jul 2025 00:33:31 +0530 Subject: [PATCH 02/16] Remove #[rustfmt::skip] from fn write Fixed formatting for write() in ChannelMonitorImpl. This would make the next commit cleaner by ensuring it only contains direct code shifts, without unrelated formatting changes. --- lightning/src/chain/channelmonitor.rs | 57 ++++++++++++++++++--------- 1 file changed, 39 insertions(+), 18 deletions(-) diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index 616de1f0e3f..4b4115ace67 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -1346,7 +1346,6 @@ const SERIALIZATION_VERSION: u8 = 1; const MIN_SERIALIZATION_VERSION: u8 = 1; impl Writeable for ChannelMonitorImpl { - #[rustfmt::skip] fn write(&self, writer: &mut W) -> Result<(), Error> { write_ver_prefix!(writer, SERIALIZATION_VERSION, MIN_SERIALIZATION_VERSION); @@ -1356,7 +1355,9 @@ impl Writeable for ChannelMonitorImpl { U48(self.commitment_transaction_number_obscure_factor).write(writer)?; self.destination_script.write(writer)?; - if let Some(ref broadcasted_holder_revokable_script) = self.broadcasted_holder_revokable_script { + if let Some(ref broadcasted_holder_revokable_script) = + self.broadcasted_holder_revokable_script + { writer.write_all(&[0; 1])?; broadcasted_holder_revokable_script.0.write(writer)?; broadcasted_holder_revokable_script.1.write(writer)?; @@ -1419,27 +1420,34 @@ impl Writeable for ChannelMonitorImpl { } } - writer.write_all(&(self.funding.counterparty_claimable_outpoints.len() as u64).to_be_bytes())?; + writer.write_all( + &(self.funding.counterparty_claimable_outpoints.len() as u64).to_be_bytes(), + )?; for (ref txid, ref htlc_infos) in self.funding.counterparty_claimable_outpoints.iter() { writer.write_all(&txid[..])?; writer.write_all(&(htlc_infos.len() as u64).to_be_bytes())?; for &(ref htlc_output, ref htlc_source) in htlc_infos.iter() { - debug_assert!(htlc_source.is_none() || Some(**txid) == self.funding.current_counterparty_commitment_txid + debug_assert!( + htlc_source.is_none() + || Some(**txid) == self.funding.current_counterparty_commitment_txid || Some(**txid) == self.funding.prev_counterparty_commitment_txid, - "HTLC Sources for all revoked commitment transactions should be none!"); + "HTLC Sources for all revoked commitment transactions should be none!" + ); serialize_htlc_in_commitment!(htlc_output); htlc_source.as_ref().map(|b| b.as_ref()).write(writer)?; } } - writer.write_all(&(self.counterparty_commitment_txn_on_chain.len() as u64).to_be_bytes())?; + writer + .write_all(&(self.counterparty_commitment_txn_on_chain.len() as u64).to_be_bytes())?; for (ref txid, commitment_number) in self.counterparty_commitment_txn_on_chain.iter() { writer.write_all(&txid[..])?; writer.write_all(&byte_utils::be48_to_array(*commitment_number))?; } writer.write_all(&(self.counterparty_hash_commitment_number.len() as u64).to_be_bytes())?; - for (ref payment_hash, commitment_number) in self.counterparty_hash_commitment_number.iter() { + for (ref payment_hash, commitment_number) in self.counterparty_hash_commitment_number.iter() + { writer.write_all(&payment_hash.0[..])?; writer.write_all(&byte_utils::be48_to_array(*commitment_number))?; } @@ -1447,17 +1455,22 @@ impl Writeable for ChannelMonitorImpl { if let Some(holder_commitment_tx) = &self.funding.prev_holder_commitment_tx { writer.write_all(&[1; 1])?; write_legacy_holder_commitment_data( - writer, holder_commitment_tx, &self.prev_holder_htlc_data.as_ref().unwrap(), + writer, + holder_commitment_tx, + &self.prev_holder_htlc_data.as_ref().unwrap(), )?; } else { writer.write_all(&[0; 1])?; } write_legacy_holder_commitment_data( - writer, &self.funding.current_holder_commitment_tx, &self.current_holder_htlc_data, + writer, + &self.funding.current_holder_commitment_tx, + &self.current_holder_htlc_data, )?; - writer.write_all(&byte_utils::be48_to_array(self.current_counterparty_commitment_number))?; + writer + .write_all(&byte_utils::be48_to_array(self.current_counterparty_commitment_number))?; writer.write_all(&byte_utils::be48_to_array(self.current_holder_commitment_number))?; writer.write_all(&(self.payment_preimages.len() as u64).to_be_bytes())?; @@ -1465,12 +1478,19 @@ impl Writeable for ChannelMonitorImpl { writer.write_all(&payment_preimage.0[..])?; } - writer.write_all(&(self.pending_monitor_events.iter().filter(|ev| match ev { - MonitorEvent::HTLCEvent(_) => true, - MonitorEvent::HolderForceClosed(_) => true, - MonitorEvent::HolderForceClosedWithInfo { .. } => true, - _ => false, - }).count() as u64).to_be_bytes())?; + writer.write_all( + &(self + .pending_monitor_events + .iter() + .filter(|ev| match ev { + MonitorEvent::HTLCEvent(_) => true, + MonitorEvent::HolderForceClosed(_) => true, + MonitorEvent::HolderForceClosedWithInfo { .. } => true, + _ => false, + }) + .count() as u64) + .to_be_bytes(), + )?; for event in self.pending_monitor_events.iter() { match event { MonitorEvent::HTLCEvent(upd) => { @@ -1494,7 +1514,8 @@ impl Writeable for ChannelMonitorImpl { self.best_block.block_hash.write(writer)?; writer.write_all(&self.best_block.height.to_be_bytes())?; - writer.write_all(&(self.onchain_events_awaiting_threshold_conf.len() as u64).to_be_bytes())?; + writer + .write_all(&(self.onchain_events_awaiting_threshold_conf.len() as u64).to_be_bytes())?; for ref entry in self.onchain_events_awaiting_threshold_conf.iter() { entry.write(writer)?; } @@ -1522,7 +1543,7 @@ impl Writeable for ChannelMonitorImpl { let mut pending_monitor_events = self.pending_monitor_events.clone(); pending_monitor_events.push(MonitorEvent::HolderForceClosed(*outpoint)); pending_monitor_events - } + }, _ => self.pending_monitor_events.clone(), }; From 2d756706c9bcaff24b81df001d0c039d53c7e61e Mon Sep 17 00:00:00 2001 From: Aditya Sharma Date: Thu, 17 Jul 2025 01:12:30 +0530 Subject: [PATCH 03/16] Serialise ChannelMonitors and send them over inside Peer Storage Create a utililty function to prevent code duplication while writing ChannelMonitors. Serialise them inside ChainMonitor::send_peer_storage and send them over. --- lightning/src/chain/chainmonitor.rs | 39 ++- lightning/src/chain/channelmonitor.rs | 399 ++++++++++++++------------ 2 files changed, 246 insertions(+), 192 deletions(-) diff --git a/lightning/src/chain/chainmonitor.rs b/lightning/src/chain/chainmonitor.rs index 98fd7e718e6..ad13dc3ff09 100644 --- a/lightning/src/chain/chainmonitor.rs +++ b/lightning/src/chain/chainmonitor.rs @@ -29,7 +29,7 @@ use bitcoin::hash_types::{BlockHash, Txid}; use crate::chain; use crate::chain::chaininterface::{BroadcasterInterface, FeeEstimator}; use crate::chain::channelmonitor::{ - Balance, ChannelMonitor, ChannelMonitorUpdate, MonitorEvent, TransactionOutputs, + write_util, Balance, ChannelMonitor, ChannelMonitorUpdate, MonitorEvent, TransactionOutputs, WithChannelMonitor, }; use crate::chain::transaction::{OutPoint, TransactionData}; @@ -37,7 +37,9 @@ use crate::chain::{ChannelMonitorUpdateStatus, Filter, WatchedOutput}; use crate::events::{self, Event, EventHandler, ReplayEvent}; use crate::ln::channel_state::ChannelDetails; use crate::ln::msgs::{self, BaseMessageHandler, Init, MessageSendEvent, SendOnlyMessageHandler}; -use crate::ln::our_peer_storage::DecryptedOurPeerStorage; +use crate::ln::our_peer_storage::{ + DecryptedOurPeerStorage, PeerStorageMonitorHolder, PeerStorageMonitorHolderList, +}; use crate::ln::types::ChannelId; use crate::prelude::*; use crate::sign::ecdsa::EcdsaChannelSigner; @@ -47,6 +49,7 @@ use crate::types::features::{InitFeatures, NodeFeatures}; use crate::util::errors::APIError; use crate::util::logger::{Logger, WithContext}; use crate::util::persist::MonitorName; +use crate::util::ser::{VecWriter, Writeable}; use crate::util::wakers::{Future, Notifier}; use bitcoin::secp256k1::PublicKey; use core::ops::Deref; @@ -810,10 +813,36 @@ where } fn send_peer_storage(&self, their_node_id: PublicKey) { - // TODO: Serialize `ChannelMonitor`s inside `our_peer_storage`. - let random_bytes = self.entropy_source.get_secure_random_bytes(); - let serialised_channels = Vec::new(); + + // TODO(aditya): Choose n random channels so that peer storage does not exceed 64k. + let monitors = self.monitors.read().unwrap(); + let mut monitors_list = PeerStorageMonitorHolderList { monitors: Vec::new() }; + + for (chan_id, mon) in monitors.iter() { + let mut ser_chan = VecWriter(Vec::new()); + let min_seen_secret = mon.monitor.get_min_seen_secret(); + let counterparty_node_id = mon.monitor.get_counterparty_node_id(); + + match write_util(&mon.monitor.inner.lock().unwrap(), true, &mut ser_chan) { + Ok(_) => { + let peer_storage_monitor = PeerStorageMonitorHolder { + channel_id: *chan_id, + min_seen_secret, + counterparty_node_id, + monitor_bytes: ser_chan.0, + }; + + monitors_list.monitors.push(peer_storage_monitor); + }, + Err(_) => { + panic!("Can not write monitor for {}", mon.monitor.channel_id()) + }, + } + } + + let mut serialised_channels = Vec::new(); + monitors_list.write(&mut serialised_channels).unwrap(); let our_peer_storage = DecryptedOurPeerStorage::new(serialised_channels); let cipher = our_peer_storage.encrypt(&self.our_peerstorage_encryption_key, &random_bytes); diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index 4b4115ace67..36717be32bd 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -1345,229 +1345,254 @@ impl Writeable for ChannelMonitor { const SERIALIZATION_VERSION: u8 = 1; const MIN_SERIALIZATION_VERSION: u8 = 1; -impl Writeable for ChannelMonitorImpl { - fn write(&self, writer: &mut W) -> Result<(), Error> { - write_ver_prefix!(writer, SERIALIZATION_VERSION, MIN_SERIALIZATION_VERSION); - - self.latest_update_id.write(writer)?; - - // Set in initial Channel-object creation, so should always be set by now: - U48(self.commitment_transaction_number_obscure_factor).write(writer)?; - - self.destination_script.write(writer)?; - if let Some(ref broadcasted_holder_revokable_script) = - self.broadcasted_holder_revokable_script - { - writer.write_all(&[0; 1])?; - broadcasted_holder_revokable_script.0.write(writer)?; - broadcasted_holder_revokable_script.1.write(writer)?; - broadcasted_holder_revokable_script.2.write(writer)?; - } else { - writer.write_all(&[1; 1])?; - } - - self.counterparty_payment_script.write(writer)?; - match &self.shutdown_script { - Some(script) => script.write(writer)?, - None => ScriptBuf::new().write(writer)?, - } +/// Utility function for writing [`ChannelMonitor`] to prevent code duplication in [`ChainMonitor`] while sending Peer Storage. +/// +/// NOTE: `is_stub` is true only when we are using this to serialise for Peer Storage. +/// +/// [`ChainMonitor`]: crate::chain::chainmonitor::ChainMonitor +pub(crate) fn write_util( + channel_monitor: &ChannelMonitorImpl, is_stub: bool, writer: &mut W, +) -> Result<(), Error> { + write_ver_prefix!(writer, SERIALIZATION_VERSION, MIN_SERIALIZATION_VERSION); - self.channel_keys_id.write(writer)?; - self.holder_revocation_basepoint.write(writer)?; - let funding_outpoint = self.get_funding_txo(); - writer.write_all(&funding_outpoint.txid[..])?; - writer.write_all(&funding_outpoint.index.to_be_bytes())?; - let redeem_script = self.funding.channel_parameters.make_funding_redeemscript(); - let script_pubkey = redeem_script.to_p2wsh(); - script_pubkey.write(writer)?; - self.funding.current_counterparty_commitment_txid.write(writer)?; - self.funding.prev_counterparty_commitment_txid.write(writer)?; - - self.counterparty_commitment_params.write(writer)?; - redeem_script.write(writer)?; - self.funding.channel_parameters.channel_value_satoshis.write(writer)?; + channel_monitor.latest_update_id.write(writer)?; - match self.their_cur_per_commitment_points { - Some((idx, pubkey, second_option)) => { - writer.write_all(&byte_utils::be48_to_array(idx))?; - writer.write_all(&pubkey.serialize())?; - match second_option { - Some(second_pubkey) => { - writer.write_all(&second_pubkey.serialize())?; - }, - None => { - writer.write_all(&[0; 33])?; - }, - } - }, - None => { - writer.write_all(&byte_utils::be48_to_array(0))?; - }, - } + // Set in initial Channel-object creation, so should always be set by now: + U48(channel_monitor.commitment_transaction_number_obscure_factor).write(writer)?; - writer.write_all(&self.on_holder_tx_csv.to_be_bytes())?; + channel_monitor.destination_script.write(writer)?; + if let Some(ref broadcasted_holder_revokable_script) = + channel_monitor.broadcasted_holder_revokable_script + { + writer.write_all(&[0; 1])?; + broadcasted_holder_revokable_script.0.write(writer)?; + broadcasted_holder_revokable_script.1.write(writer)?; + broadcasted_holder_revokable_script.2.write(writer)?; + } else { + writer.write_all(&[1; 1])?; + } + + channel_monitor.counterparty_payment_script.write(writer)?; + match &channel_monitor.shutdown_script { + Some(script) => script.write(writer)?, + None => ScriptBuf::new().write(writer)?, + } + + channel_monitor.channel_keys_id.write(writer)?; + channel_monitor.holder_revocation_basepoint.write(writer)?; + let funding_outpoint = channel_monitor.get_funding_txo(); + writer.write_all(&funding_outpoint.txid[..])?; + writer.write_all(&funding_outpoint.index.to_be_bytes())?; + let redeem_script = channel_monitor.funding.channel_parameters.make_funding_redeemscript(); + let script_pubkey = redeem_script.to_p2wsh(); + script_pubkey.write(writer)?; + channel_monitor.funding.current_counterparty_commitment_txid.write(writer)?; + channel_monitor.funding.prev_counterparty_commitment_txid.write(writer)?; + + channel_monitor.counterparty_commitment_params.write(writer)?; + redeem_script.write(writer)?; + channel_monitor.funding.channel_parameters.channel_value_satoshis.write(writer)?; + + match channel_monitor.their_cur_per_commitment_points { + Some((idx, pubkey, second_option)) => { + writer.write_all(&byte_utils::be48_to_array(idx))?; + writer.write_all(&pubkey.serialize())?; + match second_option { + Some(second_pubkey) => { + writer.write_all(&second_pubkey.serialize())?; + }, + None => { + writer.write_all(&[0; 33])?; + }, + } + }, + None => { + writer.write_all(&byte_utils::be48_to_array(0))?; + }, + } - self.commitment_secrets.write(writer)?; + writer.write_all(&channel_monitor.on_holder_tx_csv.to_be_bytes())?; - #[rustfmt::skip] - macro_rules! serialize_htlc_in_commitment { - ($htlc_output: expr) => { - writer.write_all(&[$htlc_output.offered as u8; 1])?; - writer.write_all(&$htlc_output.amount_msat.to_be_bytes())?; - writer.write_all(&$htlc_output.cltv_expiry.to_be_bytes())?; - writer.write_all(&$htlc_output.payment_hash.0[..])?; - $htlc_output.transaction_output_index.write(writer)?; - } - } + channel_monitor.commitment_secrets.write(writer)?; - writer.write_all( - &(self.funding.counterparty_claimable_outpoints.len() as u64).to_be_bytes(), - )?; - for (ref txid, ref htlc_infos) in self.funding.counterparty_claimable_outpoints.iter() { - writer.write_all(&txid[..])?; - writer.write_all(&(htlc_infos.len() as u64).to_be_bytes())?; - for &(ref htlc_output, ref htlc_source) in htlc_infos.iter() { - debug_assert!( - htlc_source.is_none() - || Some(**txid) == self.funding.current_counterparty_commitment_txid - || Some(**txid) == self.funding.prev_counterparty_commitment_txid, - "HTLC Sources for all revoked commitment transactions should be none!" - ); - serialize_htlc_in_commitment!(htlc_output); - htlc_source.as_ref().map(|b| b.as_ref()).write(writer)?; - } + #[rustfmt::skip] + macro_rules! serialize_htlc_in_commitment { + ($htlc_output: expr) => { + writer.write_all(&[$htlc_output.offered as u8; 1])?; + writer.write_all(&$htlc_output.amount_msat.to_be_bytes())?; + writer.write_all(&$htlc_output.cltv_expiry.to_be_bytes())?; + writer.write_all(&$htlc_output.payment_hash.0[..])?; + $htlc_output.transaction_output_index.write(writer)?; } + } - writer - .write_all(&(self.counterparty_commitment_txn_on_chain.len() as u64).to_be_bytes())?; - for (ref txid, commitment_number) in self.counterparty_commitment_txn_on_chain.iter() { - writer.write_all(&txid[..])?; - writer.write_all(&byte_utils::be48_to_array(*commitment_number))?; + writer.write_all( + &(channel_monitor.funding.counterparty_claimable_outpoints.len() as u64).to_be_bytes(), + )?; + for (ref txid, ref htlc_infos) in + channel_monitor.funding.counterparty_claimable_outpoints.iter() + { + writer.write_all(&txid[..])?; + writer.write_all(&(htlc_infos.len() as u64).to_be_bytes())?; + for &(ref htlc_output, ref htlc_source) in htlc_infos.iter() { + debug_assert!( + htlc_source.is_none() + || Some(**txid) == channel_monitor.funding.current_counterparty_commitment_txid + || Some(**txid) == channel_monitor.funding.prev_counterparty_commitment_txid, + "HTLC Sources for all revoked commitment transactions should be none!" + ); + serialize_htlc_in_commitment!(htlc_output); + htlc_source.as_ref().map(|b| b.as_ref()).write(writer)?; } + } - writer.write_all(&(self.counterparty_hash_commitment_number.len() as u64).to_be_bytes())?; - for (ref payment_hash, commitment_number) in self.counterparty_hash_commitment_number.iter() - { - writer.write_all(&payment_hash.0[..])?; - writer.write_all(&byte_utils::be48_to_array(*commitment_number))?; - } + writer.write_all( + &(channel_monitor.counterparty_commitment_txn_on_chain.len() as u64).to_be_bytes(), + )?; + for (ref txid, commitment_number) in channel_monitor.counterparty_commitment_txn_on_chain.iter() + { + writer.write_all(&txid[..])?; + writer.write_all(&byte_utils::be48_to_array(*commitment_number))?; + } - if let Some(holder_commitment_tx) = &self.funding.prev_holder_commitment_tx { - writer.write_all(&[1; 1])?; - write_legacy_holder_commitment_data( - writer, - holder_commitment_tx, - &self.prev_holder_htlc_data.as_ref().unwrap(), - )?; - } else { - writer.write_all(&[0; 1])?; - } + writer.write_all( + &(channel_monitor.counterparty_hash_commitment_number.len() as u64).to_be_bytes(), + )?; + for (ref payment_hash, commitment_number) in + channel_monitor.counterparty_hash_commitment_number.iter() + { + writer.write_all(&payment_hash.0[..])?; + writer.write_all(&byte_utils::be48_to_array(*commitment_number))?; + } + if let Some(holder_commitment_tx) = &channel_monitor.funding.prev_holder_commitment_tx { + writer.write_all(&[1; 1])?; write_legacy_holder_commitment_data( writer, - &self.funding.current_holder_commitment_tx, - &self.current_holder_htlc_data, + holder_commitment_tx, + &channel_monitor.prev_holder_htlc_data.as_ref().unwrap(), )?; + } else { + writer.write_all(&[0; 1])?; + } - writer - .write_all(&byte_utils::be48_to_array(self.current_counterparty_commitment_number))?; - writer.write_all(&byte_utils::be48_to_array(self.current_holder_commitment_number))?; + write_legacy_holder_commitment_data( + writer, + &channel_monitor.funding.current_holder_commitment_tx, + &channel_monitor.current_holder_htlc_data, + )?; - writer.write_all(&(self.payment_preimages.len() as u64).to_be_bytes())?; - for (payment_preimage, _) in self.payment_preimages.values() { - writer.write_all(&payment_preimage.0[..])?; - } + writer.write_all(&byte_utils::be48_to_array( + channel_monitor.current_counterparty_commitment_number, + ))?; + writer + .write_all(&byte_utils::be48_to_array(channel_monitor.current_holder_commitment_number))?; - writer.write_all( - &(self - .pending_monitor_events - .iter() - .filter(|ev| match ev { - MonitorEvent::HTLCEvent(_) => true, - MonitorEvent::HolderForceClosed(_) => true, - MonitorEvent::HolderForceClosedWithInfo { .. } => true, - _ => false, - }) - .count() as u64) - .to_be_bytes(), - )?; - for event in self.pending_monitor_events.iter() { - match event { - MonitorEvent::HTLCEvent(upd) => { - 0u8.write(writer)?; - upd.write(writer)?; - }, - MonitorEvent::HolderForceClosed(_) => 1u8.write(writer)?, - // `HolderForceClosedWithInfo` replaced `HolderForceClosed` in v0.0.122. To keep - // backwards compatibility, we write a `HolderForceClosed` event along with the - // `HolderForceClosedWithInfo` event. This is deduplicated in the reader. - MonitorEvent::HolderForceClosedWithInfo { .. } => 1u8.write(writer)?, - _ => {}, // Covered in the TLV writes below - } - } + writer.write_all(&(channel_monitor.payment_preimages.len() as u64).to_be_bytes())?; + for (payment_preimage, _) in channel_monitor.payment_preimages.values() { + writer.write_all(&payment_preimage.0[..])?; + } - writer.write_all(&(self.pending_events.len() as u64).to_be_bytes())?; - for event in self.pending_events.iter() { - event.write(writer)?; + writer.write_all( + &(channel_monitor + .pending_monitor_events + .iter() + .filter(|ev| match ev { + MonitorEvent::HTLCEvent(_) => true, + MonitorEvent::HolderForceClosed(_) => true, + MonitorEvent::HolderForceClosedWithInfo { .. } => true, + _ => false, + }) + .count() as u64) + .to_be_bytes(), + )?; + for event in channel_monitor.pending_monitor_events.iter() { + match event { + MonitorEvent::HTLCEvent(upd) => { + 0u8.write(writer)?; + upd.write(writer)?; + }, + MonitorEvent::HolderForceClosed(_) => 1u8.write(writer)?, + // `HolderForceClosedWithInfo` replaced `HolderForceClosed` in v0.0.122. To keep + // backwards compatibility, we write a `HolderForceClosed` event along with the + // `HolderForceClosedWithInfo` event. This is deduplicated in the reader. + MonitorEvent::HolderForceClosedWithInfo { .. } => 1u8.write(writer)?, + _ => {}, // Covered in the TLV writes below } + } - self.best_block.block_hash.write(writer)?; - writer.write_all(&self.best_block.height.to_be_bytes())?; + writer.write_all(&(channel_monitor.pending_events.len() as u64).to_be_bytes())?; + for event in channel_monitor.pending_events.iter() { + event.write(writer)?; + } - writer - .write_all(&(self.onchain_events_awaiting_threshold_conf.len() as u64).to_be_bytes())?; - for ref entry in self.onchain_events_awaiting_threshold_conf.iter() { - entry.write(writer)?; - } + channel_monitor.best_block.block_hash.write(writer)?; + writer.write_all(&channel_monitor.best_block.height.to_be_bytes())?; - (self.outputs_to_watch.len() as u64).write(writer)?; - for (txid, idx_scripts) in self.outputs_to_watch.iter() { - txid.write(writer)?; - (idx_scripts.len() as u64).write(writer)?; - for (idx, script) in idx_scripts.iter() { - idx.write(writer)?; - script.write(writer)?; - } + writer.write_all( + &(channel_monitor.onchain_events_awaiting_threshold_conf.len() as u64).to_be_bytes(), + )?; + for ref entry in channel_monitor.onchain_events_awaiting_threshold_conf.iter() { + entry.write(writer)?; + } + + (channel_monitor.outputs_to_watch.len() as u64).write(writer)?; + for (txid, idx_scripts) in channel_monitor.outputs_to_watch.iter() { + txid.write(writer)?; + (idx_scripts.len() as u64).write(writer)?; + for (idx, script) in idx_scripts.iter() { + idx.write(writer)?; + script.write(writer)?; } - self.onchain_tx_handler.write(writer)?; + } + + if !is_stub { + channel_monitor.onchain_tx_handler.write(writer)?; + } - self.lockdown_from_offchain.write(writer)?; - self.holder_tx_signed.write(writer)?; + channel_monitor.lockdown_from_offchain.write(writer)?; + channel_monitor.holder_tx_signed.write(writer)?; - // If we have a `HolderForceClosedWithInfo` event, we need to write the `HolderForceClosed` for backwards compatibility. - let pending_monitor_events = match self.pending_monitor_events.iter().find(|ev| match ev { + // If we have a `HolderForceClosedWithInfo` event, we need to write the `HolderForceClosed` for backwards compatibility. + let pending_monitor_events = + match channel_monitor.pending_monitor_events.iter().find(|ev| match ev { MonitorEvent::HolderForceClosedWithInfo { .. } => true, _ => false, }) { Some(MonitorEvent::HolderForceClosedWithInfo { outpoint, .. }) => { - let mut pending_monitor_events = self.pending_monitor_events.clone(); + let mut pending_monitor_events = channel_monitor.pending_monitor_events.clone(); pending_monitor_events.push(MonitorEvent::HolderForceClosed(*outpoint)); pending_monitor_events }, - _ => self.pending_monitor_events.clone(), + _ => channel_monitor.pending_monitor_events.clone(), }; - write_tlv_fields!(writer, { - (1, self.funding_spend_confirmed, option), - (3, self.htlcs_resolved_on_chain, required_vec), - (5, pending_monitor_events, required_vec), - (7, self.funding_spend_seen, required), - (9, self.counterparty_node_id, required), - (11, self.confirmed_commitment_tx_counterparty_output, option), - (13, self.spendable_txids_confirmed, required_vec), - (15, self.counterparty_fulfilled_htlcs, required), - (17, self.initial_counterparty_commitment_info, option), - (19, self.channel_id, required), - (21, self.balances_empty_height, option), - (23, self.holder_pays_commitment_tx_fee, option), - (25, self.payment_preimages, required), - (27, self.first_confirmed_funding_txo, required), - (29, self.initial_counterparty_commitment_tx, option), - (31, self.funding.channel_parameters, required), - (32, self.pending_funding, optional_vec), - }); + write_tlv_fields!(writer, { + (1, channel_monitor.funding_spend_confirmed, option), + (3, channel_monitor.htlcs_resolved_on_chain, required_vec), + (5, pending_monitor_events, required_vec), + (7, channel_monitor.funding_spend_seen, required), + (9, channel_monitor.counterparty_node_id, required), + (11, channel_monitor.confirmed_commitment_tx_counterparty_output, option), + (13, channel_monitor.spendable_txids_confirmed, required_vec), + (15, channel_monitor.counterparty_fulfilled_htlcs, required), + (17, channel_monitor.initial_counterparty_commitment_info, option), + (19, channel_monitor.channel_id, required), + (21, channel_monitor.balances_empty_height, option), + (23, channel_monitor.holder_pays_commitment_tx_fee, option), + (25, channel_monitor.payment_preimages, required), + (27, channel_monitor.first_confirmed_funding_txo, required), + (29, channel_monitor.initial_counterparty_commitment_tx, option), + (31, channel_monitor.funding.channel_parameters, required), + (32, channel_monitor.pending_funding, optional_vec), + }); - Ok(()) + Ok(()) +} + +impl Writeable for ChannelMonitorImpl { + fn write(&self, writer: &mut W) -> Result<(), Error> { + write_util(self, false, writer) } } From aff1e578802297ddd08ae1de1c99987c5cbb009f Mon Sep 17 00:00:00 2001 From: Aditya Sharma Date: Sat, 28 Jun 2025 15:37:29 +0530 Subject: [PATCH 04/16] Determine if we have lost data Deserialise the ChannelMonitors and compare the data to determine if we have lost some states. --- lightning/src/ln/channelmanager.rs | 54 +++++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 7f77f20e088..bbc41ac028d 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -82,7 +82,7 @@ use crate::ln::onion_utils::{ decode_fulfill_attribution_data, HTLCFailReason, LocalHTLCFailureReason, }; use crate::ln::onion_utils::{process_fulfill_attribution_data, AttributionData}; -use crate::ln::our_peer_storage::EncryptedOurPeerStorage; +use crate::ln::our_peer_storage::{EncryptedOurPeerStorage, PeerStorageMonitorHolderList}; #[cfg(test)] use crate::ln::outbound_payment; use crate::ln::outbound_payment::{ @@ -2963,6 +2963,7 @@ pub(super) const MAX_UNFUNDED_CHANNEL_PEERS: usize = 50; /// This constant defines the upper limit for the size of data /// that can be stored for a peer. It is set to 1024 bytes (1 kilobyte) /// to prevent excessive resource consumption. +#[cfg(not(test))] const MAX_PEER_STORAGE_SIZE: usize = 1024; /// The maximum number of peers which we do not have a (funded) channel with. Once we reach this @@ -9224,6 +9225,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ &self, peer_node_id: PublicKey, msg: msgs::PeerStorageRetrieval, ) -> Result<(), MsgHandleErrInternal> { // TODO: Check if have any stale or missing ChannelMonitor. + let per_peer_state = self.per_peer_state.read().unwrap(); let logger = WithContext::from(&self.logger, Some(peer_node_id), None, None); let err = || { MsgHandleErrInternal::from_chan_no_close( @@ -9250,6 +9252,55 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ log_trace!(logger, "Got valid {}-byte peer backup from {}", decrypted.len(), peer_node_id); + let mut cursor = io::Cursor::new(decrypted); + match ::read(&mut cursor) { + Ok(mon_list) => { + for mon_holder in mon_list.monitors.iter() { + let peer_state_mutex = + match per_peer_state.get(&mon_holder.counterparty_node_id) { + Some(mutex) => mutex, + None => { + log_debug!( + logger, + "Not able to find peer_state for the counterparty {}, channelId {}", + log_pubkey!(mon_holder.counterparty_node_id), + mon_holder.channel_id + ); + continue; + }, + }; + + let peer_state_lock = peer_state_mutex.lock().unwrap(); + let peer_state = &*peer_state_lock; + + match peer_state.channel_by_id.get(&mon_holder.channel_id) { + Some(chan) => { + if let Some(funded_chan) = chan.as_funded() { + if funded_chan + .get_revoked_counterparty_commitment_transaction_number() + > mon_holder.min_seen_secret + { + panic!( + "Lost channel state for channel {}. + Received peer storage with a more recent state than what our node had. + Use the FundRecoverer to initiate a force close and sweep the funds.", + &mon_holder.channel_id + ); + } + } + }, + None => { + // TODO: Figure out if this channel is so old that we have forgotten about it. + panic!("Lost a channel {}", &mon_holder.channel_id); + }, + } + } + }, + + Err(e) => { + panic!("Wrong serialisation of PeerStorageMonitorHolderList: {}", e); + }, + } Ok(()) } @@ -9275,6 +9326,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ ), ChannelId([0; 32]))); } + #[cfg(not(test))] if msg.data.len() > MAX_PEER_STORAGE_SIZE { log_debug!(logger, "Sending warning to peer and ignoring peer storage request from {} as its over 1KiB", log_pubkey!(counterparty_node_id)); From d86d30cae68be888f795064f0cb911937e967a5c Mon Sep 17 00:00:00 2001 From: Aditya Sharma Date: Sat, 28 Jun 2025 15:38:40 +0530 Subject: [PATCH 05/16] test: Modify test_peer_storage to check latest changes Node should now determine lost states using retrieved peer storage. --- lightning/src/ln/channelmanager.rs | 72 +++++++++++++----------------- lightning/src/util/test_utils.rs | 5 +++ 2 files changed, 36 insertions(+), 41 deletions(-) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index bbc41ac028d..6595a96cedc 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -17151,11 +17151,15 @@ mod tests { #[test] #[rustfmt::skip] + #[should_panic(expected = "Lost a channel ae3367da2c13bc1ceb86bf56418f62828f7ce9d6bfb15a46af5ba1f1ed8b124f")] fn test_peer_storage() { let chanmon_cfgs = create_chanmon_cfgs(2); + let (persister, chain_monitor); let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let nodes_0_deserialized; let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); - let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs); + let nodes_0_serialized = nodes[0].node.encode(); create_announced_chan_between_nodes(&nodes, 0, 1); @@ -17164,25 +17168,37 @@ mod tests { assert_ne!(peer_storage_msg_events_node0.len(), 0); assert_ne!(peer_storage_msg_events_node1.len(), 0); - match peer_storage_msg_events_node0[0] { - MessageSendEvent::SendPeerStorage { ref node_id, ref msg } => { - assert_eq!(*node_id, nodes[1].node.get_our_node_id()); - nodes[1].node.handle_peer_storage(nodes[0].node.get_our_node_id(), msg.clone()); + for ps_msg in peer_storage_msg_events_node0 { + match ps_msg { + MessageSendEvent::SendPeerStorage { ref node_id, ref msg } => { + assert_eq!(*node_id, nodes[1].node.get_our_node_id()); + nodes[1].node.handle_peer_storage(nodes[0].node.get_our_node_id(), msg.clone()); + } + _ => panic!("Unexpected event"), } - _ => panic!("Unexpected event"), } - match peer_storage_msg_events_node1[0] { - MessageSendEvent::SendPeerStorage { ref node_id, ref msg } => { - assert_eq!(*node_id, nodes[0].node.get_our_node_id()); - nodes[0].node.handle_peer_storage(nodes[1].node.get_our_node_id(), msg.clone()); + for ps_msg in peer_storage_msg_events_node1 { + match ps_msg { + MessageSendEvent::SendPeerStorage { ref node_id, ref msg } => { + assert_eq!(*node_id, nodes[0].node.get_our_node_id()); + nodes[0].node.handle_peer_storage(nodes[1].node.get_our_node_id(), msg.clone()); + } + _ => panic!("Unexpected event"), } - _ => panic!("Unexpected event"), } + send_payment(&nodes[0], &vec!(&nodes[1])[..], 1000); + send_payment(&nodes[0], &vec!(&nodes[1])[..], 10000); + send_payment(&nodes[0], &vec!(&nodes[1])[..], 9999); + nodes[0].node.peer_disconnected(nodes[1].node.get_our_node_id()); nodes[1].node.peer_disconnected(nodes[0].node.get_our_node_id()); + // Reload Node! + nodes[0].chain_source.clear_watched_txn_and_outputs(); + reload_node!(nodes[0], test_default_channel_config(), &nodes_0_serialized, &[], persister, chain_monitor, nodes_0_deserialized); + nodes[0].node.peer_connected(nodes[1].node.get_our_node_id(), &msgs::Init { features: nodes[1].node.init_features(), networks: None, remote_network_address: None }, true).unwrap(); @@ -17193,10 +17209,11 @@ mod tests { let node_1_events = nodes[1].node.get_and_clear_pending_msg_events(); assert_eq!(node_1_events.len(), 2); + // Since, node-0 does not have any memory it would not send any message. let node_0_events = nodes[0].node.get_and_clear_pending_msg_events(); - assert_eq!(node_0_events.len(), 2); + assert_eq!(node_0_events.len(), 0); - for msg in node_1_events{ + for msg in node_1_events { if let MessageSendEvent::SendChannelReestablish { ref node_id, ref msg } = msg { nodes[0].node.handle_channel_reestablish(nodes[1].node.get_our_node_id(), msg); assert_eq!(*node_id, nodes[0].node.get_our_node_id()); @@ -17208,35 +17225,8 @@ mod tests { } } - for msg in node_0_events{ - if let MessageSendEvent::SendChannelReestablish { ref node_id, ref msg } = msg { - nodes[1].node.handle_channel_reestablish(nodes[0].node.get_our_node_id(), msg); - assert_eq!(*node_id, nodes[1].node.get_our_node_id()); - } else if let MessageSendEvent::SendPeerStorageRetrieval { ref node_id, ref msg } = msg { - nodes[1].node.handle_peer_storage_retrieval(nodes[0].node.get_our_node_id(), msg.clone()); - assert_eq!(*node_id, nodes[1].node.get_our_node_id()); - } else { - panic!("Unexpected event") - } - } - - let node_1_msg_events = nodes[1].node.get_and_clear_pending_msg_events(); let node_0_msg_events = nodes[0].node.get_and_clear_pending_msg_events(); - - assert_eq!(node_1_msg_events.len(), 3); - assert_eq!(node_0_msg_events.len(), 3); - - for msg in node_1_msg_events { - if let MessageSendEvent::SendChannelReady { ref node_id, .. } = msg { - assert_eq!(*node_id, nodes[0].node.get_our_node_id()); - } else if let MessageSendEvent::SendAnnouncementSignatures { ref node_id, .. } = msg { - assert_eq!(*node_id, nodes[0].node.get_our_node_id()); - } else if let MessageSendEvent::SendChannelUpdate { ref node_id, .. } = msg { - assert_eq!(*node_id, nodes[0].node.get_our_node_id()); - } else { - panic!("Unexpected event") - } - } + assert_eq!(node_0_msg_events.len(), 2); for msg in node_0_msg_events { if let MessageSendEvent::SendChannelReady { ref node_id, .. } = msg { diff --git a/lightning/src/util/test_utils.rs b/lightning/src/util/test_utils.rs index 4165afea767..886164bf5ff 100644 --- a/lightning/src/util/test_utils.rs +++ b/lightning/src/util/test_utils.rs @@ -1814,6 +1814,11 @@ impl TestChainSource { self.watched_outputs.lock().unwrap().remove(&(outpoint, script_pubkey.clone())); self.watched_txn.lock().unwrap().remove(&(outpoint.txid, script_pubkey)); } + + pub fn clear_watched_txn_and_outputs(&self) { + self.watched_outputs.lock().unwrap().clear(); + self.watched_txn.lock().unwrap().clear(); + } } impl UtxoLookup for TestChainSource { From 895beba90918e825fe40cf3e7d1522a8c0ae5d15 Mon Sep 17 00:00:00 2001 From: Aditya Sharma Date: Fri, 11 Jul 2025 10:37:36 +0530 Subject: [PATCH 06/16] fixup: Serialise ChannelMonitors and send them over inside Peer Storage --- lightning/src/chain/chainmonitor.rs | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/lightning/src/chain/chainmonitor.rs b/lightning/src/chain/chainmonitor.rs index ad13dc3ff09..02560cc7896 100644 --- a/lightning/src/chain/chainmonitor.rs +++ b/lightning/src/chain/chainmonitor.rs @@ -813,25 +813,42 @@ where } fn send_peer_storage(&self, their_node_id: PublicKey) { + static MAX_PEER_STORAGE_SIZE: usize = 65000; let random_bytes = self.entropy_source.get_secure_random_bytes(); + let random_usize = usize::from_le_bytes(random_bytes[0..8].try_into().unwrap()); - // TODO(aditya): Choose n random channels so that peer storage does not exceed 64k. let monitors = self.monitors.read().unwrap(); let mut monitors_list = PeerStorageMonitorHolderList { monitors: Vec::new() }; + let mut curr_size = 0; - for (chan_id, mon) in monitors.iter() { + // Randomising Keys in the HashMap to fetch monitors without repetition. + let mut keys: Vec<&ChannelId> = monitors.keys().collect(); + for i in (1..keys.len()).rev() { + let j = random_usize % (i + 1); + keys.swap(i, j); + } + + for chan_id in keys { + let mon = &monitors[chan_id]; let mut ser_chan = VecWriter(Vec::new()); let min_seen_secret = mon.monitor.get_min_seen_secret(); let counterparty_node_id = mon.monitor.get_counterparty_node_id(); match write_util(&mon.monitor.inner.lock().unwrap(), true, &mut ser_chan) { Ok(_) => { + let mut ser_channel = Vec::new(); let peer_storage_monitor = PeerStorageMonitorHolder { channel_id: *chan_id, min_seen_secret, counterparty_node_id, monitor_bytes: ser_chan.0, }; + peer_storage_monitor.write(&mut ser_channel).unwrap(); + + curr_size += ser_channel.len(); + if curr_size > MAX_PEER_STORAGE_SIZE { + break; + } monitors_list.monitors.push(peer_storage_monitor); }, From 4c7b886a0e03f36b91960eaa3f926a7cf15a0e2d Mon Sep 17 00:00:00 2001 From: Aditya Sharma Date: Fri, 11 Jul 2025 10:38:20 +0530 Subject: [PATCH 07/16] fixup: Write structs to serialise-deserialise Channels inside Peer-storage --- lightning/src/ln/our_peer_storage.rs | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/lightning/src/ln/our_peer_storage.rs b/lightning/src/ln/our_peer_storage.rs index 29de8e5ebbd..4e1dd1a99c3 100644 --- a/lightning/src/ln/our_peer_storage.rs +++ b/lightning/src/ln/our_peer_storage.rs @@ -206,21 +206,9 @@ pub(crate) struct PeerStorageMonitorHolderList { pub(crate) monitors: Vec, } -impl Writeable for PeerStorageMonitorHolderList { - fn write(&self, w: &mut W) -> Result<(), io::Error> { - encode_tlv_stream!(w, { (1, &self.monitors, required_vec) }); - Ok(()) - } -} - -impl Readable for PeerStorageMonitorHolderList { - fn read(r: &mut R) -> Result { - let mut monitors: Option> = None; - decode_tlv_stream!(r, { (1, monitors, optional_vec) }); - - Ok(PeerStorageMonitorHolderList { monitors: monitors.ok_or(DecodeError::InvalidValue)? }) - } -} +impl_writeable_tlv_based!(PeerStorageMonitorHolderList, { + (1, monitors, required_vec), +}); #[cfg(test)] mod tests { From 23fea080b2f0a4097f3e6c007c697e675452b670 Mon Sep 17 00:00:00 2001 From: Aditya Sharma Date: Fri, 11 Jul 2025 10:39:03 +0530 Subject: [PATCH 08/16] fixup: Determine if we have lost data --- lightning/src/ln/channelmanager.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 6595a96cedc..6a6b17cbce4 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -9225,7 +9225,6 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ &self, peer_node_id: PublicKey, msg: msgs::PeerStorageRetrieval, ) -> Result<(), MsgHandleErrInternal> { // TODO: Check if have any stale or missing ChannelMonitor. - let per_peer_state = self.per_peer_state.read().unwrap(); let logger = WithContext::from(&self.logger, Some(peer_node_id), None, None); let err = || { MsgHandleErrInternal::from_chan_no_close( @@ -9251,6 +9250,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ }; log_trace!(logger, "Got valid {}-byte peer backup from {}", decrypted.len(), peer_node_id); + let per_peer_state = self.per_peer_state.read().unwrap(); let mut cursor = io::Cursor::new(decrypted); match ::read(&mut cursor) { From 993581d9aa764c3b5e7824c15272dc28b309bbc5 Mon Sep 17 00:00:00 2001 From: Aditya Sharma Date: Tue, 15 Jul 2025 11:09:53 +0530 Subject: [PATCH 09/16] fixup: Serialise ChannelMonitors and send them over inside Peer --- lightning/src/chain/chainmonitor.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/lightning/src/chain/chainmonitor.rs b/lightning/src/chain/chainmonitor.rs index 02560cc7896..4b695ffc320 100644 --- a/lightning/src/chain/chainmonitor.rs +++ b/lightning/src/chain/chainmonitor.rs @@ -813,9 +813,9 @@ where } fn send_peer_storage(&self, their_node_id: PublicKey) { - static MAX_PEER_STORAGE_SIZE: usize = 65000; + const MAX_PEER_STORAGE_SIZE: usize = 65531; let random_bytes = self.entropy_source.get_secure_random_bytes(); - let random_usize = usize::from_le_bytes(random_bytes[0..8].try_into().unwrap()); + let random_usize = usize::from_le_bytes(random_bytes[0..core::mem::size_of::()].try_into().unwrap()); let monitors = self.monitors.read().unwrap(); let mut monitors_list = PeerStorageMonitorHolderList { monitors: Vec::new() }; @@ -833,22 +833,22 @@ where let mut ser_chan = VecWriter(Vec::new()); let min_seen_secret = mon.monitor.get_min_seen_secret(); let counterparty_node_id = mon.monitor.get_counterparty_node_id(); + let chan_mon = mon.monitor.inner.lock().unwrap(); - match write_util(&mon.monitor.inner.lock().unwrap(), true, &mut ser_chan) { + match write_util(&chan_mon, true, &mut ser_chan) { Ok(_) => { - let mut ser_channel = Vec::new(); + // Adding size of peer_storage_monitor. + curr_size += ser_chan.0.len() + 32 + 8 + 32 + 14; + if curr_size > MAX_PEER_STORAGE_SIZE { + break; + } + let peer_storage_monitor = PeerStorageMonitorHolder { channel_id: *chan_id, min_seen_secret, counterparty_node_id, monitor_bytes: ser_chan.0, }; - peer_storage_monitor.write(&mut ser_channel).unwrap(); - - curr_size += ser_channel.len(); - if curr_size > MAX_PEER_STORAGE_SIZE { - break; - } monitors_list.monitors.push(peer_storage_monitor); }, From 2fb048683b2bf801c6c5196f1fcc5bf4b13cc97b Mon Sep 17 00:00:00 2001 From: Aditya Sharma Date: Tue, 15 Jul 2025 11:10:58 +0530 Subject: [PATCH 10/16] fixup: Write structs to serialise-deserialise Channels inside peer-storage --- lightning/src/ln/our_peer_storage.rs | 35 ++++++---------------------- 1 file changed, 7 insertions(+), 28 deletions(-) diff --git a/lightning/src/ln/our_peer_storage.rs b/lightning/src/ln/our_peer_storage.rs index 4e1dd1a99c3..f9b04ae9e4a 100644 --- a/lightning/src/ln/our_peer_storage.rs +++ b/lightning/src/ln/our_peer_storage.rs @@ -15,11 +15,8 @@ use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::hashes::{Hash, HashEngine, Hmac, HmacEngine}; use bitcoin::secp256k1::PublicKey; -use crate::io; -use crate::ln::msgs::DecodeError; use crate::ln::types::ChannelId; use crate::sign::PeerStorageKey; -use crate::util::ser::{Readable, Writeable, Writer}; use crate::crypto::chacha20poly1305rfc::ChaCha20Poly1305RFC; use crate::prelude::*; @@ -172,30 +169,12 @@ pub(crate) struct PeerStorageMonitorHolder { pub(crate) monitor_bytes: Vec, } -impl Writeable for PeerStorageMonitorHolder { - fn write(&self, w: &mut W) -> Result<(), io::Error> { - self.channel_id.write(w)?; - self.counterparty_node_id.write(w)?; - self.min_seen_secret.write(w)?; - self.monitor_bytes.write(w) - } -} - -impl Readable for PeerStorageMonitorHolder { - fn read(r: &mut R) -> Result { - let channel_id = Readable::read(r)?; - let counterparty_node_id = Readable::read(r)?; - let min_seen_secret = Readable::read(r)?; - let monitor_bytes = Readable::read(r)?; - - Ok(PeerStorageMonitorHolder { - channel_id, - counterparty_node_id, - min_seen_secret, - monitor_bytes, - }) - } -} +impl_writeable_tlv_based!(PeerStorageMonitorHolder, { + (0, channel_id, required), + (1, counterparty_node_id, required), + (2, min_seen_secret, required), + (3, monitor_bytes, required_vec), +}); /// [`PeerStorageMonitorHolderList`] is used to serialise all the channels and send it over wire /// wrapped inside [`PeerStorage`]. @@ -207,7 +186,7 @@ pub(crate) struct PeerStorageMonitorHolderList { } impl_writeable_tlv_based!(PeerStorageMonitorHolderList, { - (1, monitors, required_vec), + (0, monitors, required_vec), }); #[cfg(test)] From 6267a10ef3d0e3a2e37a4d0e54a2d04cdfa7251a Mon Sep 17 00:00:00 2001 From: Aditya Sharma Date: Thu, 17 Jul 2025 01:50:49 +0530 Subject: [PATCH 11/16] fixup: Serialise ChannelMonitors and send them over inside Peer Storage --- lightning/src/chain/chainmonitor.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lightning/src/chain/chainmonitor.rs b/lightning/src/chain/chainmonitor.rs index 4b695ffc320..f6905ea2d84 100644 --- a/lightning/src/chain/chainmonitor.rs +++ b/lightning/src/chain/chainmonitor.rs @@ -838,7 +838,11 @@ where match write_util(&chan_mon, true, &mut ser_chan) { Ok(_) => { // Adding size of peer_storage_monitor. - curr_size += ser_chan.0.len() + 32 + 8 + 32 + 14; + curr_size += ser_chan.0.serialized_length() + + min_seen_secret.serialized_length() + + chan_id.serialized_length() + + counterparty_node_id.serialized_length(); + if curr_size > MAX_PEER_STORAGE_SIZE { break; } @@ -858,8 +862,7 @@ where } } - let mut serialised_channels = Vec::new(); - monitors_list.write(&mut serialised_channels).unwrap(); + let serialised_channels = monitors_list.encode(); let our_peer_storage = DecryptedOurPeerStorage::new(serialised_channels); let cipher = our_peer_storage.encrypt(&self.our_peerstorage_encryption_key, &random_bytes); From 34eb62de900389696ac7f8b8e41a03282f616314 Mon Sep 17 00:00:00 2001 From: Aditya Sharma Date: Thu, 17 Jul 2025 02:09:21 +0530 Subject: [PATCH 12/16] fixup: Determine if we have lost data --- lightning/src/ln/channelmanager.rs | 86 +++++++++++++++--------------- 1 file changed, 42 insertions(+), 44 deletions(-) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 6a6b17cbce4..5a9b8fa19c2 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -9253,53 +9253,51 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ let per_peer_state = self.per_peer_state.read().unwrap(); let mut cursor = io::Cursor::new(decrypted); - match ::read(&mut cursor) { - Ok(mon_list) => { - for mon_holder in mon_list.monitors.iter() { - let peer_state_mutex = - match per_peer_state.get(&mon_holder.counterparty_node_id) { - Some(mutex) => mutex, - None => { - log_debug!( - logger, - "Not able to find peer_state for the counterparty {}, channelId {}", - log_pubkey!(mon_holder.counterparty_node_id), - mon_holder.channel_id - ); - continue; - }, - }; + let mon_list = ::read(&mut cursor).unwrap_or_else(|e| { + // This should NEVER happen. + log_debug!(self.logger, "Unable to unpack the retrieved peer storage {:?}", e); + PeerStorageMonitorHolderList { monitors: Vec::new() } + }); - let peer_state_lock = peer_state_mutex.lock().unwrap(); - let peer_state = &*peer_state_lock; + for mon_holder in mon_list.monitors.iter() { + let peer_state_mutex = + match per_peer_state.get(&mon_holder.counterparty_node_id) { + Some(mutex) => mutex, + None => { + log_debug!( + logger, + "Not able to find peer_state for the counterparty {}, channelId {}", + log_pubkey!(mon_holder.counterparty_node_id), + mon_holder.channel_id + ); + continue; + }, + }; - match peer_state.channel_by_id.get(&mon_holder.channel_id) { - Some(chan) => { - if let Some(funded_chan) = chan.as_funded() { - if funded_chan - .get_revoked_counterparty_commitment_transaction_number() - > mon_holder.min_seen_secret - { - panic!( - "Lost channel state for channel {}. - Received peer storage with a more recent state than what our node had. - Use the FundRecoverer to initiate a force close and sweep the funds.", - &mon_holder.channel_id - ); - } - } - }, - None => { - // TODO: Figure out if this channel is so old that we have forgotten about it. - panic!("Lost a channel {}", &mon_holder.channel_id); - }, - } - } - }, + let peer_state_lock = peer_state_mutex.lock().unwrap(); + let peer_state = &*peer_state_lock; - Err(e) => { - panic!("Wrong serialisation of PeerStorageMonitorHolderList: {}", e); - }, + match peer_state.channel_by_id.get(&mon_holder.channel_id) { + Some(chan) => { + if let Some(funded_chan) = chan.as_funded() { + if funded_chan + .get_revoked_counterparty_commitment_transaction_number() + > mon_holder.min_seen_secret + { + panic!( + "Lost channel state for channel {}. + Received peer storage with a more recent state than what our node had. + Use the FundRecoverer to initiate a force close and sweep the funds.", + &mon_holder.channel_id + ); + } + } + }, + None => { + // TODO: Figure out if this channel is so old that we have forgotten about it. + panic!("Lost a channel {}", &mon_holder.channel_id); + }, + } } Ok(()) } From 03bb2f0a2591685d0b93eddc4ba7856f2018eb92 Mon Sep 17 00:00:00 2001 From: Aditya Sharma Date: Thu, 17 Jul 2025 02:15:02 +0530 Subject: [PATCH 13/16] fixup: Serialise ChannelMonitors and send them over inside Peer Storage --- lightning/src/chain/chainmonitor.rs | 4 ++-- lightning/src/chain/channelmonitor.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lightning/src/chain/chainmonitor.rs b/lightning/src/chain/chainmonitor.rs index f6905ea2d84..bf114ff3874 100644 --- a/lightning/src/chain/chainmonitor.rs +++ b/lightning/src/chain/chainmonitor.rs @@ -29,7 +29,7 @@ use bitcoin::hash_types::{BlockHash, Txid}; use crate::chain; use crate::chain::chaininterface::{BroadcasterInterface, FeeEstimator}; use crate::chain::channelmonitor::{ - write_util, Balance, ChannelMonitor, ChannelMonitorUpdate, MonitorEvent, TransactionOutputs, + write_util_internal, Balance, ChannelMonitor, ChannelMonitorUpdate, MonitorEvent, TransactionOutputs, WithChannelMonitor, }; use crate::chain::transaction::{OutPoint, TransactionData}; @@ -835,7 +835,7 @@ where let counterparty_node_id = mon.monitor.get_counterparty_node_id(); let chan_mon = mon.monitor.inner.lock().unwrap(); - match write_util(&chan_mon, true, &mut ser_chan) { + match write_util_internal(&chan_mon, true, &mut ser_chan) { Ok(_) => { // Adding size of peer_storage_monitor. curr_size += ser_chan.0.serialized_length() diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index 36717be32bd..a445631e701 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -1350,7 +1350,7 @@ const MIN_SERIALIZATION_VERSION: u8 = 1; /// NOTE: `is_stub` is true only when we are using this to serialise for Peer Storage. /// /// [`ChainMonitor`]: crate::chain::chainmonitor::ChainMonitor -pub(crate) fn write_util( +pub(crate) fn write_util_internal( channel_monitor: &ChannelMonitorImpl, is_stub: bool, writer: &mut W, ) -> Result<(), Error> { write_ver_prefix!(writer, SERIALIZATION_VERSION, MIN_SERIALIZATION_VERSION); @@ -1592,7 +1592,7 @@ pub(crate) fn write_util( impl Writeable for ChannelMonitorImpl { fn write(&self, writer: &mut W) -> Result<(), Error> { - write_util(self, false, writer) + write_util_internal(self, false, writer) } } From 8766784de35f58b8abe4a016239506be75824d3e Mon Sep 17 00:00:00 2001 From: Aditya Sharma Date: Mon, 21 Jul 2025 18:26:10 +0530 Subject: [PATCH 14/16] fixup: Serialise ChannelMonitors and send them over inside Peer Storage --- lightning/src/chain/chainmonitor.rs | 38 +++++++++++---------------- lightning/src/chain/channelmonitor.rs | 4 +-- 2 files changed, 17 insertions(+), 25 deletions(-) diff --git a/lightning/src/chain/chainmonitor.rs b/lightning/src/chain/chainmonitor.rs index bf114ff3874..3f37a48b689 100644 --- a/lightning/src/chain/chainmonitor.rs +++ b/lightning/src/chain/chainmonitor.rs @@ -29,7 +29,7 @@ use bitcoin::hash_types::{BlockHash, Txid}; use crate::chain; use crate::chain::chaininterface::{BroadcasterInterface, FeeEstimator}; use crate::chain::channelmonitor::{ - write_util_internal, Balance, ChannelMonitor, ChannelMonitorUpdate, MonitorEvent, TransactionOutputs, + write_chanmon_internal, Balance, ChannelMonitor, ChannelMonitorUpdate, MonitorEvent, TransactionOutputs, WithChannelMonitor, }; use crate::chain::transaction::{OutPoint, TransactionData}; @@ -814,8 +814,9 @@ where fn send_peer_storage(&self, their_node_id: PublicKey) { const MAX_PEER_STORAGE_SIZE: usize = 65531; + const USIZE_LEN: usize = core::mem::size_of::(); let random_bytes = self.entropy_source.get_secure_random_bytes(); - let random_usize = usize::from_le_bytes(random_bytes[0..core::mem::size_of::()].try_into().unwrap()); + let random_usize = usize::from_le_bytes(random_bytes[0..USIZE_LEN].try_into().unwrap()); let monitors = self.monitors.read().unwrap(); let mut monitors_list = PeerStorageMonitorHolderList { monitors: Vec::new() }; @@ -835,31 +836,22 @@ where let counterparty_node_id = mon.monitor.get_counterparty_node_id(); let chan_mon = mon.monitor.inner.lock().unwrap(); - match write_util_internal(&chan_mon, true, &mut ser_chan) { - Ok(_) => { - // Adding size of peer_storage_monitor. - curr_size += ser_chan.0.serialized_length() - + min_seen_secret.serialized_length() - + chan_id.serialized_length() - + counterparty_node_id.serialized_length(); + write_chanmon_internal(&chan_mon, true, &mut ser_chan).expect("can not write Channel Monitor for peer storage message"); - if curr_size > MAX_PEER_STORAGE_SIZE { - break; - } + let peer_storage_monitor = PeerStorageMonitorHolder { + channel_id: *chan_id, + min_seen_secret, + counterparty_node_id, + monitor_bytes: ser_chan.0, + }; - let peer_storage_monitor = PeerStorageMonitorHolder { - channel_id: *chan_id, - min_seen_secret, - counterparty_node_id, - monitor_bytes: ser_chan.0, - }; + // Adding size of peer_storage_monitor. + curr_size += peer_storage_monitor.serialized_length(); - monitors_list.monitors.push(peer_storage_monitor); - }, - Err(_) => { - panic!("Can not write monitor for {}", mon.monitor.channel_id()) - }, + if curr_size > MAX_PEER_STORAGE_SIZE { + break; } + monitors_list.monitors.push(peer_storage_monitor); } let serialised_channels = monitors_list.encode(); diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index a445631e701..c4a37f2ec1e 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -1350,7 +1350,7 @@ const MIN_SERIALIZATION_VERSION: u8 = 1; /// NOTE: `is_stub` is true only when we are using this to serialise for Peer Storage. /// /// [`ChainMonitor`]: crate::chain::chainmonitor::ChainMonitor -pub(crate) fn write_util_internal( +pub(crate) fn write_chanmon_internal( channel_monitor: &ChannelMonitorImpl, is_stub: bool, writer: &mut W, ) -> Result<(), Error> { write_ver_prefix!(writer, SERIALIZATION_VERSION, MIN_SERIALIZATION_VERSION); @@ -1592,7 +1592,7 @@ pub(crate) fn write_util_internal( impl Writeable for ChannelMonitorImpl { fn write(&self, writer: &mut W) -> Result<(), Error> { - write_util_internal(self, false, writer) + write_chanmon_internal(self, false, writer) } } From e3de6983c4dea91213b8e9a3134cd7721d447d58 Mon Sep 17 00:00:00 2001 From: Aditya Sharma Date: Mon, 21 Jul 2025 18:26:42 +0530 Subject: [PATCH 15/16] fixup: Write structs to serialise-deserialise Channels inside peer-storage --- lightning/src/ln/our_peer_storage.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lightning/src/ln/our_peer_storage.rs b/lightning/src/ln/our_peer_storage.rs index f9b04ae9e4a..0501dd13cf3 100644 --- a/lightning/src/ln/our_peer_storage.rs +++ b/lightning/src/ln/our_peer_storage.rs @@ -171,9 +171,9 @@ pub(crate) struct PeerStorageMonitorHolder { impl_writeable_tlv_based!(PeerStorageMonitorHolder, { (0, channel_id, required), - (1, counterparty_node_id, required), - (2, min_seen_secret, required), - (3, monitor_bytes, required_vec), + (2, counterparty_node_id, required), + (4, min_seen_secret, required), + (6, monitor_bytes, required_vec), }); /// [`PeerStorageMonitorHolderList`] is used to serialise all the channels and send it over wire From c9baefb80672d56c2fe3073378d1e7114b66fb03 Mon Sep 17 00:00:00 2001 From: Aditya Sharma Date: Mon, 21 Jul 2025 18:29:01 +0530 Subject: [PATCH 16/16] fixup: Determine if we have lost data --- lightning/src/ln/channelmanager.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 5a9b8fa19c2..5f74e3e0573 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -9294,7 +9294,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ } }, None => { - // TODO: Figure out if this channel is so old that we have forgotten about it. + // TODO: Figure out if this channel is closed and we have forgotten about it. panic!("Lost a channel {}", &mon_holder.channel_id); }, }