diff --git a/bindings/ldk_node.udl b/bindings/ldk_node.udl index 96490f2b..817fd06e 100644 --- a/bindings/ldk_node.udl +++ b/bindings/ldk_node.udl @@ -218,11 +218,12 @@ dictionary NodeStatus { boolean is_running; boolean is_listening; BestBlock current_best_block; - u64? latest_wallet_sync_timestamp; + u64? latest_lightning_wallet_sync_timestamp; u64? latest_onchain_wallet_sync_timestamp; u64? latest_fee_rate_cache_update_timestamp; u64? latest_rgs_snapshot_timestamp; u64? latest_node_announcement_broadcast_timestamp; + u32? latest_channel_monitor_archival_height; }; dictionary BestBlock { diff --git a/src/builder.rs b/src/builder.rs index 4905d97d..1cfd46ee 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -12,8 +12,8 @@ use crate::connection::ConnectionManager; use crate::event::EventQueue; use crate::fee_estimator::OnchainFeeEstimator; use crate::gossip::GossipSource; -use crate::io; use crate::io::sqlite_store::SqliteStore; +use crate::io::utils::{read_node_metrics, write_node_metrics}; #[cfg(any(vss, vss_test))] use crate::io::vss_store::VssStore; use crate::liquidity::LiquiditySource; @@ -28,6 +28,7 @@ use crate::types::{ }; use crate::wallet::persist::KVStoreWalletPersister; use crate::wallet::Wallet; +use crate::{io, NodeMetrics}; use crate::{LogLevel, Node}; use lightning::chain::{chainmonitor, BestBlock, Watch}; @@ -554,12 +555,16 @@ fn build_with_store_internal( ) -> Result { // Initialize the status fields. let is_listening = Arc::new(AtomicBool::new(false)); - let latest_wallet_sync_timestamp = Arc::new(RwLock::new(None)); - let latest_onchain_wallet_sync_timestamp = Arc::new(RwLock::new(None)); - let latest_fee_rate_cache_update_timestamp = Arc::new(RwLock::new(None)); - let latest_rgs_snapshot_timestamp = Arc::new(RwLock::new(None)); - let latest_node_announcement_broadcast_timestamp = Arc::new(RwLock::new(None)); - let latest_channel_monitor_archival_height = Arc::new(RwLock::new(None)); + let node_metrics = match read_node_metrics(Arc::clone(&kv_store), Arc::clone(&logger)) { + Ok(metrics) => Arc::new(RwLock::new(metrics)), + Err(e) => { + if e.kind() == std::io::ErrorKind::NotFound { + Arc::new(RwLock::new(NodeMetrics::default())) + } else { + return Err(BuildError::ReadFailed); + } + }, + }; // Initialize the on-chain wallet and chain access let xprv = bitcoin::bip32::Xpriv::new_master(config.network, &seed_bytes).map_err(|e| { @@ -608,12 +613,10 @@ fn build_with_store_internal( Arc::clone(&wallet), Arc::clone(&fee_estimator), Arc::clone(&tx_broadcaster), + Arc::clone(&kv_store), Arc::clone(&config), Arc::clone(&logger), - Arc::clone(&latest_wallet_sync_timestamp), - Arc::clone(&latest_onchain_wallet_sync_timestamp), - Arc::clone(&latest_fee_rate_cache_update_timestamp), - latest_channel_monitor_archival_height, + Arc::clone(&node_metrics), )), None => { // Default to Esplora client. @@ -623,12 +626,10 @@ fn build_with_store_internal( Arc::clone(&wallet), Arc::clone(&fee_estimator), Arc::clone(&tx_broadcaster), + Arc::clone(&kv_store), Arc::clone(&config), Arc::clone(&logger), - Arc::clone(&latest_wallet_sync_timestamp), - Arc::clone(&latest_onchain_wallet_sync_timestamp), - Arc::clone(&latest_fee_rate_cache_update_timestamp), - latest_channel_monitor_archival_height, + Arc::clone(&node_metrics), )) }, }; @@ -820,23 +821,24 @@ fn build_with_store_internal( Arc::new(GossipSource::new_p2p(Arc::clone(&network_graph), Arc::clone(&logger))); // Reset the RGS sync timestamp in case we somehow switch gossip sources - io::utils::write_latest_rgs_sync_timestamp( - 0, - Arc::clone(&kv_store), - Arc::clone(&logger), - ) - .map_err(|e| { - log_error!(logger, "Failed writing to store: {}", e); - BuildError::WriteFailed - })?; + { + let mut locked_node_metrics = node_metrics.write().unwrap(); + locked_node_metrics.latest_rgs_snapshot_timestamp = None; + write_node_metrics( + &*locked_node_metrics, + Arc::clone(&kv_store), + Arc::clone(&logger), + ) + .map_err(|e| { + log_error!(logger, "Failed writing to store: {}", e); + BuildError::WriteFailed + })?; + } p2p_source }, GossipSourceConfig::RapidGossipSync(rgs_server) => { - let latest_sync_timestamp = io::utils::read_latest_rgs_sync_timestamp( - Arc::clone(&kv_store), - Arc::clone(&logger), - ) - .unwrap_or(0); + let latest_sync_timestamp = + node_metrics.read().unwrap().latest_rgs_snapshot_timestamp.unwrap_or(0); Arc::new(GossipSource::new_rgs( rgs_server.clone(), latest_sync_timestamp, @@ -1021,11 +1023,7 @@ fn build_with_store_internal( peer_store, payment_store, is_listening, - latest_wallet_sync_timestamp, - latest_onchain_wallet_sync_timestamp, - latest_fee_rate_cache_update_timestamp, - latest_rgs_snapshot_timestamp, - latest_node_announcement_broadcast_timestamp, + node_metrics, }) } diff --git a/src/chain/mod.rs b/src/chain/mod.rs index 1d061397..8a12dc76 100644 --- a/src/chain/mod.rs +++ b/src/chain/mod.rs @@ -15,9 +15,10 @@ use crate::fee_estimator::{ apply_post_estimation_adjustments, get_all_conf_targets, get_num_block_defaults_for_target, OnchainFeeEstimator, }; +use crate::io::utils::write_node_metrics; use crate::logger::{log_bytes, log_error, log_info, log_trace, FilesystemLogger, Logger}; -use crate::types::{Broadcaster, ChainMonitor, ChannelManager, Sweeper, Wallet}; -use crate::Error; +use crate::types::{Broadcaster, ChainMonitor, ChannelManager, DynStore, Sweeper, Wallet}; +use crate::{Error, NodeMetrics}; use lightning::chain::{Confirm, Filter}; use lightning::util::ser::Writeable; @@ -102,23 +103,18 @@ pub(crate) enum ChainSource { lightning_wallet_sync_status: Mutex, fee_estimator: Arc, tx_broadcaster: Arc, + kv_store: Arc, config: Arc, logger: Arc, - latest_wallet_sync_timestamp: Arc>>, - latest_onchain_wallet_sync_timestamp: Arc>>, - latest_fee_rate_cache_update_timestamp: Arc>>, - latest_channel_monitor_archival_height: Arc>>, + node_metrics: Arc>, }, } impl ChainSource { pub(crate) fn new_esplora( server_url: String, onchain_wallet: Arc, fee_estimator: Arc, - tx_broadcaster: Arc, config: Arc, logger: Arc, - latest_wallet_sync_timestamp: Arc>>, - latest_onchain_wallet_sync_timestamp: Arc>>, - latest_fee_rate_cache_update_timestamp: Arc>>, - latest_channel_monitor_archival_height: Arc>>, + tx_broadcaster: Arc, kv_store: Arc, config: Arc, + logger: Arc, node_metrics: Arc>, ) -> Self { let mut client_builder = esplora_client::Builder::new(&server_url); client_builder = client_builder.timeout(DEFAULT_ESPLORA_CLIENT_TIMEOUT_SECS); @@ -135,12 +131,10 @@ impl ChainSource { lightning_wallet_sync_status, fee_estimator, tx_broadcaster, + kv_store, config, logger, - latest_wallet_sync_timestamp, - latest_onchain_wallet_sync_timestamp, - latest_fee_rate_cache_update_timestamp, - latest_channel_monitor_archival_height, + node_metrics, } } @@ -211,8 +205,9 @@ impl ChainSource { esplora_client, onchain_wallet, onchain_wallet_sync_status, + kv_store, logger, - latest_onchain_wallet_sync_timestamp, + node_metrics, .. } => { let receiver_res = { @@ -232,7 +227,7 @@ impl ChainSource { // If this is our first sync, do a full scan with the configured gap limit. // Otherwise just do an incremental sync. let incremental_sync = - latest_onchain_wallet_sync_timestamp.read().unwrap().is_some(); + node_metrics.read().unwrap().latest_onchain_wallet_sync_timestamp.is_some(); macro_rules! get_and_apply_wallet_update { ($sync_future: expr) => {{ @@ -251,8 +246,11 @@ impl ChainSource { .duration_since(UNIX_EPOCH) .ok() .map(|d| d.as_secs()); - *latest_onchain_wallet_sync_timestamp.write().unwrap() = - unix_time_secs_opt; + { + let mut locked_node_metrics = node_metrics.write().unwrap(); + locked_node_metrics.latest_onchain_wallet_sync_timestamp = unix_time_secs_opt; + write_node_metrics(&*locked_node_metrics, Arc::clone(&kv_store), Arc::clone(&logger))?; + } Ok(()) }, Err(e) => Err(e), @@ -327,9 +325,9 @@ impl ChainSource { Self::Esplora { tx_sync, lightning_wallet_sync_status, + kv_store, logger, - latest_wallet_sync_timestamp, - latest_channel_monitor_archival_height, + node_metrics, .. } => { let sync_cman = Arc::clone(&channel_manager); @@ -372,13 +370,24 @@ impl ChainSource { .duration_since(UNIX_EPOCH) .ok() .map(|d| d.as_secs()); - *latest_wallet_sync_timestamp.write().unwrap() = unix_time_secs_opt; + { + let mut locked_node_metrics = node_metrics.write().unwrap(); + locked_node_metrics.latest_lightning_wallet_sync_timestamp = + unix_time_secs_opt; + write_node_metrics( + &*locked_node_metrics, + Arc::clone(&kv_store), + Arc::clone(&logger), + )?; + } periodically_archive_fully_resolved_monitors( Arc::clone(&channel_manager), Arc::clone(&chain_monitor), - Arc::clone(&latest_channel_monitor_archival_height), - ); + Arc::clone(&kv_store), + Arc::clone(&logger), + Arc::clone(&node_metrics), + )?; Ok(()) }, Err(e) => { @@ -406,8 +415,9 @@ impl ChainSource { esplora_client, fee_estimator, config, + kv_store, logger, - latest_fee_rate_cache_update_timestamp, + node_metrics, .. } => { let now = Instant::now(); @@ -479,7 +489,15 @@ impl ChainSource { ); let unix_time_secs_opt = SystemTime::now().duration_since(UNIX_EPOCH).ok().map(|d| d.as_secs()); - *latest_fee_rate_cache_update_timestamp.write().unwrap() = unix_time_secs_opt; + { + let mut locked_node_metrics = node_metrics.write().unwrap(); + locked_node_metrics.latest_fee_rate_cache_update_timestamp = unix_time_secs_opt; + write_node_metrics( + &*locked_node_metrics, + Arc::clone(&kv_store), + Arc::clone(&logger), + )?; + } Ok(()) }, @@ -580,16 +598,19 @@ impl Filter for ChainSource { fn periodically_archive_fully_resolved_monitors( channel_manager: Arc, chain_monitor: Arc, - latest_channel_monitor_archival_height: Arc>>, -) { - let mut latest_archival_height_lock = latest_channel_monitor_archival_height.write().unwrap(); + kv_store: Arc, logger: Arc, node_metrics: Arc>, +) -> Result<(), Error> { + let mut locked_node_metrics = node_metrics.write().unwrap(); let cur_height = channel_manager.current_best_block().height; - let should_archive = latest_archival_height_lock + let should_archive = locked_node_metrics + .latest_channel_monitor_archival_height .as_ref() .map_or(true, |h| cur_height >= h + RESOLVED_CHANNEL_MONITOR_ARCHIVAL_INTERVAL); if should_archive { chain_monitor.archive_fully_resolved_channel_monitors(); - *latest_archival_height_lock = Some(cur_height); + locked_node_metrics.latest_channel_monitor_archival_height = Some(cur_height); + write_node_metrics(&*locked_node_metrics, kv_store, logger)?; } + Ok(()) } diff --git a/src/io/mod.rs b/src/io/mod.rs index 22caff50..fab0a27f 100644 --- a/src/io/mod.rs +++ b/src/io/mod.rs @@ -33,15 +33,10 @@ pub(crate) const DEPRECATED_SPENDABLE_OUTPUT_INFO_PERSISTENCE_PRIMARY_NAMESPACE: "spendable_outputs"; pub(crate) const DEPRECATED_SPENDABLE_OUTPUT_INFO_PERSISTENCE_SECONDARY_NAMESPACE: &str = ""; -/// RapidGossipSync's `latest_sync_timestamp` will be persisted under this key. -pub(crate) const LATEST_RGS_SYNC_TIMESTAMP_PRIMARY_NAMESPACE: &str = ""; -pub(crate) const LATEST_RGS_SYNC_TIMESTAMP_SECONDARY_NAMESPACE: &str = ""; -pub(crate) const LATEST_RGS_SYNC_TIMESTAMP_KEY: &str = "latest_rgs_sync_timestamp"; - -/// The last time we broadcast a node announcement will be persisted under this key. -pub(crate) const LATEST_NODE_ANN_BCAST_TIMESTAMP_PRIMARY_NAMESPACE: &str = ""; -pub(crate) const LATEST_NODE_ANN_BCAST_TIMESTAMP_SECONDARY_NAMESPACE: &str = ""; -pub(crate) const LATEST_NODE_ANN_BCAST_TIMESTAMP_KEY: &str = "latest_node_ann_bcast_timestamp"; +/// The node metrics will be persisted under this key. +pub(crate) const NODE_METRICS_PRIMARY_NAMESPACE: &str = ""; +pub(crate) const NODE_METRICS_SECONDARY_NAMESPACE: &str = ""; +pub(crate) const NODE_METRICS_KEY: &str = "node_metrics"; /// The BDK wallet's [`ChangeSet::descriptor`] will be persisted under this key. /// diff --git a/src/io/utils.rs b/src/io/utils.rs index 41192849..218fec47 100644 --- a/src/io/utils.rs +++ b/src/io/utils.rs @@ -10,12 +10,15 @@ use crate::config::WALLET_KEYS_SEED_LEN; use crate::chain::ChainSource; use crate::fee_estimator::OnchainFeeEstimator; +use crate::io::{ + NODE_METRICS_KEY, NODE_METRICS_PRIMARY_NAMESPACE, NODE_METRICS_SECONDARY_NAMESPACE, +}; use crate::logger::{log_error, FilesystemLogger}; use crate::peer_store::PeerStore; use crate::sweep::DeprecatedSpendableOutputInfo; use crate::types::{Broadcaster, DynStore, KeysManager, Sweeper}; use crate::wallet::ser::{ChangeSetDeserWrapper, ChangeSetSerWrapper}; -use crate::{Error, EventQueue, PaymentDetails}; +use crate::{Error, EventQueue, NodeMetrics, PaymentDetails}; use lightning::io::Cursor; use lightning::ln::msgs::DecodeError; @@ -342,98 +345,44 @@ where Ok(()) } -pub(crate) fn read_latest_rgs_sync_timestamp( +pub(crate) fn read_node_metrics( kv_store: Arc, logger: L, -) -> Result +) -> Result where L::Target: Logger, { let mut reader = Cursor::new(kv_store.read( - LATEST_RGS_SYNC_TIMESTAMP_PRIMARY_NAMESPACE, - LATEST_RGS_SYNC_TIMESTAMP_SECONDARY_NAMESPACE, - LATEST_RGS_SYNC_TIMESTAMP_KEY, + NODE_METRICS_PRIMARY_NAMESPACE, + NODE_METRICS_SECONDARY_NAMESPACE, + NODE_METRICS_KEY, )?); - u32::read(&mut reader).map_err(|e| { - log_error!(logger, "Failed to deserialize latest RGS sync timestamp: {}", e); - std::io::Error::new( - std::io::ErrorKind::InvalidData, - "Failed to deserialize latest RGS sync timestamp", - ) - }) -} - -pub(crate) fn write_latest_rgs_sync_timestamp( - updated_timestamp: u32, kv_store: Arc, logger: L, -) -> Result<(), Error> -where - L::Target: Logger, -{ - let data = updated_timestamp.encode(); - kv_store - .write( - LATEST_RGS_SYNC_TIMESTAMP_PRIMARY_NAMESPACE, - LATEST_RGS_SYNC_TIMESTAMP_SECONDARY_NAMESPACE, - LATEST_RGS_SYNC_TIMESTAMP_KEY, - &data, - ) - .map_err(|e| { - log_error!( - logger, - "Writing data to key {}/{}/{} failed due to: {}", - LATEST_RGS_SYNC_TIMESTAMP_PRIMARY_NAMESPACE, - LATEST_RGS_SYNC_TIMESTAMP_SECONDARY_NAMESPACE, - LATEST_RGS_SYNC_TIMESTAMP_KEY, - e - ); - Error::PersistenceFailed - }) -} - -pub(crate) fn read_latest_node_ann_bcast_timestamp( - kv_store: Arc, logger: L, -) -> Result -where - L::Target: Logger, -{ - let mut reader = Cursor::new(kv_store.read( - LATEST_NODE_ANN_BCAST_TIMESTAMP_PRIMARY_NAMESPACE, - LATEST_NODE_ANN_BCAST_TIMESTAMP_SECONDARY_NAMESPACE, - LATEST_NODE_ANN_BCAST_TIMESTAMP_KEY, - )?); - u64::read(&mut reader).map_err(|e| { - log_error!( - logger, - "Failed to deserialize latest node announcement broadcast timestamp: {}", - e - ); - std::io::Error::new( - std::io::ErrorKind::InvalidData, - "Failed to deserialize latest node announcement broadcast timestamp", - ) + NodeMetrics::read(&mut reader).map_err(|e| { + log_error!(logger, "Failed to deserialize NodeMetrics: {}", e); + std::io::Error::new(std::io::ErrorKind::InvalidData, "Failed to deserialize NodeMetrics") }) } -pub(crate) fn write_latest_node_ann_bcast_timestamp( - updated_timestamp: u64, kv_store: Arc, logger: L, +pub(crate) fn write_node_metrics( + node_metrics: &NodeMetrics, kv_store: Arc, logger: L, ) -> Result<(), Error> where L::Target: Logger, { - let data = updated_timestamp.encode(); + let data = node_metrics.encode(); kv_store .write( - LATEST_NODE_ANN_BCAST_TIMESTAMP_PRIMARY_NAMESPACE, - LATEST_NODE_ANN_BCAST_TIMESTAMP_SECONDARY_NAMESPACE, - LATEST_NODE_ANN_BCAST_TIMESTAMP_KEY, + NODE_METRICS_PRIMARY_NAMESPACE, + NODE_METRICS_SECONDARY_NAMESPACE, + NODE_METRICS_KEY, &data, ) .map_err(|e| { log_error!( logger, "Writing data to key {}/{}/{} failed due to: {}", - LATEST_NODE_ANN_BCAST_TIMESTAMP_PRIMARY_NAMESPACE, - LATEST_NODE_ANN_BCAST_TIMESTAMP_SECONDARY_NAMESPACE, - LATEST_NODE_ANN_BCAST_TIMESTAMP_KEY, + NODE_METRICS_PRIMARY_NAMESPACE, + NODE_METRICS_SECONDARY_NAMESPACE, + NODE_METRICS_KEY, e ); Error::PersistenceFailed diff --git a/src/lib.rs b/src/lib.rs index 58a653a6..4c7c1f95 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -122,12 +122,14 @@ pub use builder::NodeBuilder as Builder; use chain::ChainSource; use config::{ - default_user_config, may_announce_channel, NODE_ANN_BCAST_INTERVAL, PEER_RECONNECTION_INTERVAL, RGS_SYNC_INTERVAL, + default_user_config, may_announce_channel, NODE_ANN_BCAST_INTERVAL, PEER_RECONNECTION_INTERVAL, + RGS_SYNC_INTERVAL, }; use connection::ConnectionManager; use event::{EventHandler, EventQueue}; use gossip::GossipSource; use graph::NetworkGraph; +use io::utils::write_node_metrics; use liquidity::LiquiditySource; use payment::store::PaymentStore; use payment::{ @@ -145,6 +147,7 @@ use logger::{log_error, log_info, log_trace, FilesystemLogger, Logger}; use lightning::chain::BestBlock; use lightning::events::bump_transaction::Wallet as LdkWallet; +use lightning::impl_writeable_tlv_based; use lightning::ln::channel_state::ChannelShutdownState; use lightning::ln::channelmanager::PaymentId; use lightning::ln::msgs::SocketAddress; @@ -196,11 +199,7 @@ pub struct Node { peer_store: Arc>>, payment_store: Arc>>, is_listening: Arc, - latest_wallet_sync_timestamp: Arc>>, - latest_onchain_wallet_sync_timestamp: Arc>>, - latest_fee_rate_cache_update_timestamp: Arc>>, - latest_rgs_snapshot_timestamp: Arc>>, - latest_node_announcement_broadcast_timestamp: Arc>>, + node_metrics: Arc>, } impl Node { @@ -261,7 +260,7 @@ impl Node { let gossip_source = Arc::clone(&self.gossip_source); let gossip_sync_store = Arc::clone(&self.kv_store); let gossip_sync_logger = Arc::clone(&self.logger); - let gossip_rgs_sync_timestamp = Arc::clone(&self.latest_rgs_snapshot_timestamp); + let gossip_node_metrics = Arc::clone(&self.node_metrics); let mut stop_gossip_sync = self.stop_sender.subscribe(); runtime.spawn(async move { let mut interval = tokio::time::interval(RGS_SYNC_INTERVAL); @@ -284,22 +283,22 @@ impl Node { "Background sync of RGS gossip data finished in {}ms.", now.elapsed().as_millis() ); - io::utils::write_latest_rgs_sync_timestamp( - updated_timestamp, - Arc::clone(&gossip_sync_store), - Arc::clone(&gossip_sync_logger), - ) - .unwrap_or_else(|e| { - log_error!(gossip_sync_logger, "Persistence failed: {}", e); - panic!("Persistence failed"); - }); - *gossip_rgs_sync_timestamp.write().unwrap() = Some(updated_timestamp as u64); + { + let mut locked_node_metrics = gossip_node_metrics.write().unwrap(); + locked_node_metrics.latest_rgs_snapshot_timestamp = Some(updated_timestamp); + write_node_metrics(&*locked_node_metrics, Arc::clone(&gossip_sync_store), Arc::clone(&gossip_sync_logger)) + .unwrap_or_else(|e| { + log_error!(gossip_sync_logger, "Persistence failed: {}", e); + }); + } + } + Err(e) => { + log_error!( + gossip_sync_logger, + "Background sync of RGS gossip data failed: {}", + e + ) } - Err(e) => log_error!( - gossip_sync_logger, - "Background sync of RGS gossip data failed: {}", - e - ), } } } @@ -421,7 +420,7 @@ impl Node { let bcast_config = Arc::clone(&self.config); let bcast_store = Arc::clone(&self.kv_store); let bcast_logger = Arc::clone(&self.logger); - let bcast_ann_timestamp = Arc::clone(&self.latest_node_announcement_broadcast_timestamp); + let bcast_node_metrics = Arc::clone(&self.node_metrics); let mut stop_bcast = self.stop_sender.subscribe(); let node_alias = self.config.node_alias.clone(); if may_announce_channel(&self.config) { @@ -441,13 +440,13 @@ impl Node { return; } _ = interval.tick() => { - let skip_broadcast = match io::utils::read_latest_node_ann_bcast_timestamp(Arc::clone(&bcast_store), Arc::clone(&bcast_logger)) { - Ok(latest_bcast_time_secs) => { + let skip_broadcast = match bcast_node_metrics.read().unwrap().latest_node_announcement_broadcast_timestamp { + Some(latest_bcast_time_secs) => { // Skip if the time hasn't elapsed yet. let next_bcast_unix_time = SystemTime::UNIX_EPOCH + Duration::from_secs(latest_bcast_time_secs) + NODE_ANN_BCAST_INTERVAL; next_bcast_unix_time.elapsed().is_err() } - Err(_) => { + None => { // Don't skip if we haven't broadcasted before. false } @@ -479,20 +478,18 @@ impl Node { let unix_time_secs_opt = SystemTime::now().duration_since(UNIX_EPOCH).ok().map(|d| d.as_secs()); - *bcast_ann_timestamp.write().unwrap() = unix_time_secs_opt; - - if let Some(unix_time_secs) = unix_time_secs_opt { - io::utils::write_latest_node_ann_bcast_timestamp(unix_time_secs, Arc::clone(&bcast_store), Arc::clone(&bcast_logger)) + { + let mut locked_node_metrics = bcast_node_metrics.write().unwrap(); + locked_node_metrics.latest_node_announcement_broadcast_timestamp = unix_time_secs_opt; + write_node_metrics(&*locked_node_metrics, Arc::clone(&bcast_store), Arc::clone(&bcast_logger)) .unwrap_or_else(|e| { log_error!(bcast_logger, "Persistence failed: {}", e); - panic!("Persistence failed"); }); } } else { debug_assert!(false, "We checked whether the node may announce, so node alias should always be set"); continue } - } } } @@ -717,24 +714,30 @@ impl Node { let is_running = self.runtime.read().unwrap().is_some(); let is_listening = self.is_listening.load(Ordering::Acquire); let current_best_block = self.channel_manager.current_best_block().into(); - let latest_wallet_sync_timestamp = *self.latest_wallet_sync_timestamp.read().unwrap(); + let locked_node_metrics = self.node_metrics.read().unwrap(); + let latest_lightning_wallet_sync_timestamp = + locked_node_metrics.latest_lightning_wallet_sync_timestamp; let latest_onchain_wallet_sync_timestamp = - *self.latest_onchain_wallet_sync_timestamp.read().unwrap(); + locked_node_metrics.latest_onchain_wallet_sync_timestamp; let latest_fee_rate_cache_update_timestamp = - *self.latest_fee_rate_cache_update_timestamp.read().unwrap(); - let latest_rgs_snapshot_timestamp = *self.latest_rgs_snapshot_timestamp.read().unwrap(); + locked_node_metrics.latest_fee_rate_cache_update_timestamp; + let latest_rgs_snapshot_timestamp = + locked_node_metrics.latest_rgs_snapshot_timestamp.map(|val| val as u64); let latest_node_announcement_broadcast_timestamp = - *self.latest_node_announcement_broadcast_timestamp.read().unwrap(); + locked_node_metrics.latest_node_announcement_broadcast_timestamp; + let latest_channel_monitor_archival_height = + locked_node_metrics.latest_channel_monitor_archival_height; NodeStatus { is_running, is_listening, current_best_block, - latest_wallet_sync_timestamp, + latest_lightning_wallet_sync_timestamp, latest_onchain_wallet_sync_timestamp, latest_fee_rate_cache_update_timestamp, latest_rgs_snapshot_timestamp, latest_node_announcement_broadcast_timestamp, + latest_channel_monitor_archival_height, } } @@ -1495,30 +1498,67 @@ pub struct NodeStatus { /// The timestamp, in seconds since start of the UNIX epoch, when we last successfully synced /// our Lightning wallet to the chain tip. /// - /// Will be `None` if the wallet hasn't been synced since the [`Node`] was initialized. - pub latest_wallet_sync_timestamp: Option, + /// Will be `None` if the wallet hasn't been synced yet. + pub latest_lightning_wallet_sync_timestamp: Option, /// The timestamp, in seconds since start of the UNIX epoch, when we last successfully synced /// our on-chain wallet to the chain tip. /// - /// Will be `None` if the wallet hasn't been synced since the [`Node`] was initialized. + /// Will be `None` if the wallet hasn't been synced yet. pub latest_onchain_wallet_sync_timestamp: Option, /// The timestamp, in seconds since start of the UNIX epoch, when we last successfully update /// our fee rate cache. /// - /// Will be `None` if the cache hasn't been updated since the [`Node`] was initialized. + /// Will be `None` if the cache hasn't been updated yet. pub latest_fee_rate_cache_update_timestamp: Option, /// The timestamp, in seconds since start of the UNIX epoch, when the last rapid gossip sync /// (RGS) snapshot we successfully applied was generated. /// - /// Will be `None` if RGS isn't configured or the snapshot hasn't been updated since the [`Node`] was initialized. + /// Will be `None` if RGS isn't configured or the snapshot hasn't been updated yet. pub latest_rgs_snapshot_timestamp: Option, /// The timestamp, in seconds since start of the UNIX epoch, when we last broadcasted a node /// announcement. /// - /// Will be `None` if we have no public channels or we haven't broadcasted since the [`Node`] was initialized. + /// Will be `None` if we have no public channels or we haven't broadcasted yet. pub latest_node_announcement_broadcast_timestamp: Option, + /// The block height when we last archived closed channel monitor data. + /// + /// Will be `None` if we haven't archived any monitors of closed channels yet. + pub latest_channel_monitor_archival_height: Option, } +/// Status fields that are persisted across restarts. +#[derive(Clone, Debug, PartialEq, Eq)] +pub(crate) struct NodeMetrics { + latest_lightning_wallet_sync_timestamp: Option, + latest_onchain_wallet_sync_timestamp: Option, + latest_fee_rate_cache_update_timestamp: Option, + latest_rgs_snapshot_timestamp: Option, + latest_node_announcement_broadcast_timestamp: Option, + latest_channel_monitor_archival_height: Option, +} + +impl Default for NodeMetrics { + fn default() -> Self { + Self { + latest_lightning_wallet_sync_timestamp: None, + latest_onchain_wallet_sync_timestamp: None, + latest_fee_rate_cache_update_timestamp: None, + latest_rgs_snapshot_timestamp: None, + latest_node_announcement_broadcast_timestamp: None, + latest_channel_monitor_archival_height: None, + } + } +} + +impl_writeable_tlv_based!(NodeMetrics, { + (0, latest_lightning_wallet_sync_timestamp, option), + (2, latest_onchain_wallet_sync_timestamp, option), + (4, latest_fee_rate_cache_update_timestamp, option), + (6, latest_rgs_snapshot_timestamp, option), + (8, latest_node_announcement_broadcast_timestamp, option), + (10, latest_channel_monitor_archival_height, option), +}); + pub(crate) fn total_anchor_channels_reserve_sats( channel_manager: &ChannelManager, config: &Config, ) -> u64 {