Skip to content

Commit cdeeb7f

Browse files
authored
Merge pull request #272 from tnull/2024-03-add-node-status
Introduce `status` method allowing to query the `Node`'s status
2 parents 1cf74c6 + b052f15 commit cdeeb7f

File tree

6 files changed

+186
-23
lines changed

6 files changed

+186
-23
lines changed

bindings/ldk_node.udl

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ interface LDKNode {
4242
void start();
4343
[Throws=NodeError]
4444
void stop();
45+
NodeStatus status();
46+
Config config();
4547
Event? next_event();
4648
Event wait_next_event();
4749
[Async]
@@ -97,7 +99,6 @@ interface LDKNode {
9799
[Throws=NodeError]
98100
string sign_message([ByRef]sequence<u8> msg);
99101
boolean verify_signature([ByRef]sequence<u8> msg, [ByRef]string sig, [ByRef]PublicKey pkey);
100-
boolean is_running();
101102
};
102103

103104
[Error]
@@ -137,6 +138,22 @@ enum NodeError {
137138
"LiquidityFeeTooHigh",
138139
};
139140

141+
dictionary NodeStatus {
142+
boolean is_running;
143+
boolean is_listening;
144+
BestBlock current_best_block;
145+
u64? latest_wallet_sync_timestamp;
146+
u64? latest_onchain_wallet_sync_timestamp;
147+
u64? latest_fee_rate_cache_update_timestamp;
148+
u64? latest_rgs_snapshot_timestamp;
149+
u64? latest_node_announcement_broadcast_timestamp;
150+
};
151+
152+
dictionary BestBlock {
153+
BlockHash block_hash;
154+
u32 height;
155+
};
156+
140157
[Error]
141158
enum BuildError {
142159
"InvalidSeedBytes",

src/builder.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ use std::fmt;
6565
use std::fs;
6666
use std::io::Cursor;
6767
use std::path::PathBuf;
68+
use std::sync::atomic::AtomicBool;
6869
use std::sync::{Arc, Mutex, RwLock};
6970
use std::time::SystemTime;
7071

@@ -945,6 +946,13 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
945946

946947
let (stop_sender, _) = tokio::sync::watch::channel(());
947948

949+
let is_listening = Arc::new(AtomicBool::new(false));
950+
let latest_wallet_sync_timestamp = Arc::new(RwLock::new(None));
951+
let latest_onchain_wallet_sync_timestamp = Arc::new(RwLock::new(None));
952+
let latest_fee_rate_cache_update_timestamp = Arc::new(RwLock::new(None));
953+
let latest_rgs_snapshot_timestamp = Arc::new(RwLock::new(None));
954+
let latest_node_announcement_broadcast_timestamp = Arc::new(RwLock::new(None));
955+
948956
Ok(Node {
949957
runtime,
950958
stop_sender,
@@ -968,6 +976,12 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
968976
scorer,
969977
peer_store,
970978
payment_store,
979+
is_listening,
980+
latest_wallet_sync_timestamp,
981+
latest_onchain_wallet_sync_timestamp,
982+
latest_fee_rate_cache_update_timestamp,
983+
latest_rgs_snapshot_timestamp,
984+
latest_node_announcement_broadcast_timestamp,
971985
})
972986
}
973987

src/lib.rs

Lines changed: 128 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ pub use error::Error as NodeError;
107107
use error::Error;
108108

109109
pub use event::Event;
110-
pub use types::ChannelConfig;
110+
pub use types::{BestBlock, ChannelConfig};
111111

112112
pub use io::utils::generate_entropy_mnemonic;
113113

@@ -167,8 +167,9 @@ use rand::Rng;
167167

168168
use std::default::Default;
169169
use std::net::ToSocketAddrs;
170+
use std::sync::atomic::{AtomicBool, Ordering};
170171
use std::sync::{Arc, Mutex, RwLock};
171-
use std::time::{Duration, Instant, SystemTime};
172+
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
172173

173174
#[cfg(feature = "uniffi")]
174175
uniffi::include_scaffolding!("ldk_node");
@@ -199,6 +200,12 @@ pub struct Node<K: KVStore + Sync + Send + 'static> {
199200
scorer: Arc<Mutex<Scorer>>,
200201
peer_store: Arc<PeerStore<K, Arc<FilesystemLogger>>>,
201202
payment_store: Arc<PaymentStore<K, Arc<FilesystemLogger>>>,
203+
is_listening: Arc<AtomicBool>,
204+
latest_wallet_sync_timestamp: Arc<RwLock<Option<u64>>>,
205+
latest_onchain_wallet_sync_timestamp: Arc<RwLock<Option<u64>>>,
206+
latest_fee_rate_cache_update_timestamp: Arc<RwLock<Option<u64>>>,
207+
latest_rgs_snapshot_timestamp: Arc<RwLock<Option<u64>>>,
208+
latest_node_announcement_broadcast_timestamp: Arc<RwLock<Option<u64>>>,
202209
}
203210

204211
impl<K: KVStore + Sync + Send + 'static> Node<K> {
@@ -222,6 +229,8 @@ impl<K: KVStore + Sync + Send + 'static> Node<K> {
222229
// Block to ensure we update our fee rate cache once on startup
223230
let fee_estimator = Arc::clone(&self.fee_estimator);
224231
let sync_logger = Arc::clone(&self.logger);
232+
let sync_fee_rate_update_timestamp =
233+
Arc::clone(&self.latest_fee_rate_cache_update_timestamp);
225234
let runtime_ref = &runtime;
226235
tokio::task::block_in_place(move || {
227236
runtime_ref.block_on(async move {
@@ -233,6 +242,9 @@ impl<K: KVStore + Sync + Send + 'static> Node<K> {
233242
"Initial fee rate cache update finished in {}ms.",
234243
now.elapsed().as_millis()
235244
);
245+
let unix_time_secs_opt =
246+
SystemTime::now().duration_since(UNIX_EPOCH).ok().map(|d| d.as_secs());
247+
*sync_fee_rate_update_timestamp.write().unwrap() = unix_time_secs_opt;
236248
Ok(())
237249
},
238250
Err(e) => {
@@ -246,6 +258,7 @@ impl<K: KVStore + Sync + Send + 'static> Node<K> {
246258
// Setup wallet sync
247259
let wallet = Arc::clone(&self.wallet);
248260
let sync_logger = Arc::clone(&self.logger);
261+
let sync_onchain_wallet_timestamp = Arc::clone(&self.latest_onchain_wallet_sync_timestamp);
249262
let mut stop_sync = self.stop_sender.subscribe();
250263
let onchain_wallet_sync_interval_secs = self
251264
.config
@@ -267,11 +280,16 @@ impl<K: KVStore + Sync + Send + 'static> Node<K> {
267280
_ = onchain_wallet_sync_interval.tick() => {
268281
let now = Instant::now();
269282
match wallet.sync().await {
270-
Ok(()) => log_trace!(
283+
Ok(()) => {
284+
log_trace!(
271285
sync_logger,
272286
"Background sync of on-chain wallet finished in {}ms.",
273287
now.elapsed().as_millis()
274-
),
288+
);
289+
let unix_time_secs_opt =
290+
SystemTime::now().duration_since(UNIX_EPOCH).ok().map(|d| d.as_secs());
291+
*sync_onchain_wallet_timestamp.write().unwrap() = unix_time_secs_opt;
292+
}
275293
Err(err) => {
276294
log_error!(
277295
sync_logger,
@@ -289,6 +307,7 @@ impl<K: KVStore + Sync + Send + 'static> Node<K> {
289307

290308
let mut stop_fee_updates = self.stop_sender.subscribe();
291309
let fee_update_logger = Arc::clone(&self.logger);
310+
let fee_update_timestamp = Arc::clone(&self.latest_fee_rate_cache_update_timestamp);
292311
let fee_estimator = Arc::clone(&self.fee_estimator);
293312
let fee_rate_cache_update_interval_secs =
294313
self.config.fee_rate_cache_update_interval_secs.max(WALLET_SYNC_INTERVAL_MINIMUM_SECS);
@@ -307,11 +326,16 @@ impl<K: KVStore + Sync + Send + 'static> Node<K> {
307326
_ = fee_rate_update_interval.tick() => {
308327
let now = Instant::now();
309328
match fee_estimator.update_fee_estimates().await {
310-
Ok(()) => log_trace!(
329+
Ok(()) => {
330+
log_trace!(
311331
fee_update_logger,
312332
"Background update of fee rate cache finished in {}ms.",
313333
now.elapsed().as_millis()
314-
),
334+
);
335+
let unix_time_secs_opt =
336+
SystemTime::now().duration_since(UNIX_EPOCH).ok().map(|d| d.as_secs());
337+
*fee_update_timestamp.write().unwrap() = unix_time_secs_opt;
338+
}
315339
Err(err) => {
316340
log_error!(
317341
fee_update_logger,
@@ -330,6 +354,7 @@ impl<K: KVStore + Sync + Send + 'static> Node<K> {
330354
let sync_cmon = Arc::clone(&self.chain_monitor);
331355
let sync_sweeper = Arc::clone(&self.output_sweeper);
332356
let sync_logger = Arc::clone(&self.logger);
357+
let sync_wallet_timestamp = Arc::clone(&self.latest_wallet_sync_timestamp);
333358
let mut stop_sync = self.stop_sender.subscribe();
334359
let wallet_sync_interval_secs =
335360
self.config.wallet_sync_interval_secs.max(WALLET_SYNC_INTERVAL_MINIMUM_SECS);
@@ -350,11 +375,16 @@ impl<K: KVStore + Sync + Send + 'static> Node<K> {
350375
];
351376
let now = Instant::now();
352377
match tx_sync.sync(confirmables).await {
353-
Ok(()) => log_trace!(
378+
Ok(()) => {
379+
log_trace!(
354380
sync_logger,
355381
"Background sync of Lightning wallet finished in {}ms.",
356382
now.elapsed().as_millis()
357-
),
383+
);
384+
let unix_time_secs_opt =
385+
SystemTime::now().duration_since(UNIX_EPOCH).ok().map(|d| d.as_secs());
386+
*sync_wallet_timestamp.write().unwrap() = unix_time_secs_opt;
387+
}
358388
Err(e) => {
359389
log_error!(sync_logger, "Background sync of Lightning wallet failed: {}", e)
360390
}
@@ -368,6 +398,7 @@ impl<K: KVStore + Sync + Send + 'static> Node<K> {
368398
let gossip_source = Arc::clone(&self.gossip_source);
369399
let gossip_sync_store = Arc::clone(&self.kv_store);
370400
let gossip_sync_logger = Arc::clone(&self.logger);
401+
let gossip_rgs_sync_timestamp = Arc::clone(&self.latest_rgs_snapshot_timestamp);
371402
let mut stop_gossip_sync = self.stop_sender.subscribe();
372403
runtime.spawn(async move {
373404
let mut interval = tokio::time::interval(RGS_SYNC_INTERVAL);
@@ -395,6 +426,7 @@ impl<K: KVStore + Sync + Send + 'static> Node<K> {
395426
log_error!(gossip_sync_logger, "Persistence failed: {}", e);
396427
panic!("Persistence failed");
397428
});
429+
*gossip_rgs_sync_timestamp.write().unwrap() = Some(updated_timestamp as u64);
398430
}
399431
Err(e) => log_error!(
400432
gossip_sync_logger,
@@ -413,6 +445,7 @@ impl<K: KVStore + Sync + Send + 'static> Node<K> {
413445
let peer_manager_connection_handler = Arc::clone(&self.peer_manager);
414446
let mut stop_listen = self.stop_sender.subscribe();
415447
let listening_logger = Arc::clone(&self.logger);
448+
let listening_indicator = Arc::clone(&self.is_listening);
416449

417450
let mut bind_addrs = Vec::with_capacity(listening_addresses.len());
418451

@@ -431,6 +464,7 @@ impl<K: KVStore + Sync + Send + 'static> Node<K> {
431464
}
432465

433466
runtime.spawn(async move {
467+
{
434468
let listener =
435469
tokio::net::TcpListener::bind(&*bind_addrs).await
436470
.unwrap_or_else(|e| {
@@ -440,11 +474,13 @@ impl<K: KVStore + Sync + Send + 'static> Node<K> {
440474
);
441475
});
442476

477+
listening_indicator.store(true, Ordering::Release);
478+
443479
loop {
444480
let peer_mgr = Arc::clone(&peer_manager_connection_handler);
445481
tokio::select! {
446482
_ = stop_listen.changed() => {
447-
return;
483+
break;
448484
}
449485
res = listener.accept() => {
450486
let tcp_stream = res.unwrap().0;
@@ -458,6 +494,9 @@ impl<K: KVStore + Sync + Send + 'static> Node<K> {
458494
}
459495
}
460496
}
497+
}
498+
499+
listening_indicator.store(false, Ordering::Release);
461500
});
462501
}
463502

@@ -508,6 +547,7 @@ impl<K: KVStore + Sync + Send + 'static> Node<K> {
508547
let bcast_config = Arc::clone(&self.config);
509548
let bcast_store = Arc::clone(&self.kv_store);
510549
let bcast_logger = Arc::clone(&self.logger);
550+
let bcast_ann_timestamp = Arc::clone(&self.latest_node_announcement_broadcast_timestamp);
511551
let mut stop_bcast = self.stop_sender.subscribe();
512552
runtime.spawn(async move {
513553
// We check every 30 secs whether our last broadcast is NODE_ANN_BCAST_INTERVAL away.
@@ -553,12 +593,17 @@ impl<K: KVStore + Sync + Send + 'static> Node<K> {
553593

554594
bcast_pm.broadcast_node_announcement([0; 3], [0; 32], addresses);
555595

556-
let unix_time_secs = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs();
557-
io::utils::write_latest_node_ann_bcast_timestamp(unix_time_secs, Arc::clone(&bcast_store), Arc::clone(&bcast_logger))
558-
.unwrap_or_else(|e| {
559-
log_error!(bcast_logger, "Persistence failed: {}", e);
560-
panic!("Persistence failed");
561-
});
596+
let unix_time_secs_opt =
597+
SystemTime::now().duration_since(UNIX_EPOCH).ok().map(|d| d.as_secs());
598+
*bcast_ann_timestamp.write().unwrap() = unix_time_secs_opt;
599+
600+
if let Some(unix_time_secs) = unix_time_secs_opt {
601+
io::utils::write_latest_node_ann_bcast_timestamp(unix_time_secs, Arc::clone(&bcast_store), Arc::clone(&bcast_logger))
602+
.unwrap_or_else(|e| {
603+
log_error!(bcast_logger, "Persistence failed: {}", e);
604+
panic!("Persistence failed");
605+
});
606+
}
562607
}
563608
}
564609
}
@@ -662,11 +707,6 @@ impl<K: KVStore + Sync + Send + 'static> Node<K> {
662707
Ok(())
663708
}
664709

665-
/// Returns whether the [`Node`] is running.
666-
pub fn is_running(&self) -> bool {
667-
self.runtime.read().unwrap().is_some()
668-
}
669-
670710
/// Disconnects all peers, stops all running background tasks, and shuts down [`Node`].
671711
///
672712
/// After this returns most API methods will return [`Error::NotRunning`].
@@ -697,6 +737,37 @@ impl<K: KVStore + Sync + Send + 'static> Node<K> {
697737
Ok(())
698738
}
699739

740+
/// Returns the status of the [`Node`].
741+
pub fn status(&self) -> NodeStatus {
742+
let is_running = self.runtime.read().unwrap().is_some();
743+
let is_listening = self.is_listening.load(Ordering::Acquire);
744+
let current_best_block = self.channel_manager.current_best_block().into();
745+
let latest_wallet_sync_timestamp = *self.latest_wallet_sync_timestamp.read().unwrap();
746+
let latest_onchain_wallet_sync_timestamp =
747+
*self.latest_onchain_wallet_sync_timestamp.read().unwrap();
748+
let latest_fee_rate_cache_update_timestamp =
749+
*self.latest_fee_rate_cache_update_timestamp.read().unwrap();
750+
let latest_rgs_snapshot_timestamp = *self.latest_rgs_snapshot_timestamp.read().unwrap();
751+
let latest_node_announcement_broadcast_timestamp =
752+
*self.latest_node_announcement_broadcast_timestamp.read().unwrap();
753+
754+
NodeStatus {
755+
is_running,
756+
is_listening,
757+
current_best_block,
758+
latest_wallet_sync_timestamp,
759+
latest_onchain_wallet_sync_timestamp,
760+
latest_fee_rate_cache_update_timestamp,
761+
latest_rgs_snapshot_timestamp,
762+
latest_node_announcement_broadcast_timestamp,
763+
}
764+
}
765+
766+
/// Returns the config with which the [`Node`] was initialized.
767+
pub fn config(&self) -> Config {
768+
self.config.as_ref().clone()
769+
}
770+
700771
/// Returns the next event in the event queue, if currently available.
701772
///
702773
/// Will return `Some(..)` if an event is available and `None` otherwise.
@@ -1746,6 +1817,43 @@ impl<K: KVStore + Sync + Send + 'static> Drop for Node<K> {
17461817
}
17471818
}
17481819

1820+
/// Represents the status of the [`Node`].
1821+
#[derive(Clone, Debug, PartialEq, Eq)]
1822+
pub struct NodeStatus {
1823+
/// Indicates whether the [`Node`] is running.
1824+
pub is_running: bool,
1825+
/// Indicates whether the [`Node`] is listening for incoming connections on the addresses
1826+
/// configured via [`Config::listening_addresses`].
1827+
pub is_listening: bool,
1828+
/// The best block to which our Lightning wallet is currently synced.
1829+
pub current_best_block: BestBlock,
1830+
/// The timestamp, in seconds since start of the UNIX epoch, when we last successfully synced
1831+
/// our Lightning wallet to the chain tip.
1832+
///
1833+
/// Will be `None` if the wallet hasn't been synced since the [`Node`] was initialized.
1834+
pub latest_wallet_sync_timestamp: Option<u64>,
1835+
/// The timestamp, in seconds since start of the UNIX epoch, when we last successfully synced
1836+
/// our on-chain wallet to the chain tip.
1837+
///
1838+
/// Will be `None` if the wallet hasn't been synced since the [`Node`] was initialized.
1839+
pub latest_onchain_wallet_sync_timestamp: Option<u64>,
1840+
/// The timestamp, in seconds since start of the UNIX epoch, when we last successfully update
1841+
/// our fee rate cache.
1842+
///
1843+
/// Will be `None` if the cache hasn't been updated since the [`Node`] was initialized.
1844+
pub latest_fee_rate_cache_update_timestamp: Option<u64>,
1845+
/// The timestamp, in seconds since start of the UNIX epoch, when the last rapid gossip sync
1846+
/// (RGS) snapshot we successfully applied was generated.
1847+
///
1848+
/// Will be `None` if RGS isn't configured or the snapshot hasn't been updated since the [`Node`] was initialized.
1849+
pub latest_rgs_snapshot_timestamp: Option<u64>,
1850+
/// The timestamp, in seconds since start of the UNIX epoch, when we last broadcasted a node
1851+
/// announcement.
1852+
///
1853+
/// Will be `None` if we have no public channels or we haven't broadcasted since the [`Node`] was initialized.
1854+
pub latest_node_announcement_broadcast_timestamp: Option<u64>,
1855+
}
1856+
17491857
async fn connect_peer_if_necessary<K: KVStore + Sync + Send + 'static>(
17501858
node_id: PublicKey, addr: SocketAddress, peer_manager: Arc<PeerManager<K>>,
17511859
logger: Arc<FilesystemLogger>,

0 commit comments

Comments
 (0)