diff --git a/monero-wallet-ng/src/rpc.rs b/monero-wallet-ng/src/rpc.rs index be11d19ee..659fe4e83 100644 --- a/monero-wallet-ng/src/rpc.rs +++ b/monero-wallet-ng/src/rpc.rs @@ -40,6 +40,30 @@ pub trait ProvidesTransactionStatus: Sync { ) -> impl Send + Future>; } +#[derive(Debug, thiserror::Error)] +pub enum NodeInfoError { + #[error("Interface error: {0}")] + Interface(#[from] InterfaceError), + #[error("Failed to parse get_info response: {0}")] + Parse(String), +} + +/// Sync status of a remote daemon, as reported by `get_info`. +#[derive(Debug, Clone)] +pub struct NodeSyncStatus { + /// Whether the node considers itself on the same height as the network. + pub synchronized: bool, + /// The height the node has synced to. + pub height: u64, +} + +/// Provides the ability to query a remote daemon's sync status. +pub trait ProvidesNodeInfo: Sync { + fn node_sync_status( + &self, + ) -> impl Send + Future>; +} + /// Data structures we get back from the RPC server /// /// See: https://github.com/monero-project/monero/blob/48ad374b0d6d6e045128729534dc2508e6999afe/src/rpc/core_rpc_server_commands_defs.h#L358-L439 @@ -54,6 +78,14 @@ mod monerod { pub(crate) txs: Vec, } + // The daemon `get_info` endpoint. + // See: https://github.com/monero-project/monero/blob/48ad374b0d6d6e045128729534dc2508e6999afe/src/rpc/core_rpc_server_commands_defs.h#L633-L673 + #[derive(Deserialize)] + pub(crate) struct GetInfoResponse { + pub(crate) synchronized: bool, + pub(crate) height: u64, + } + // See: https://github.com/SNeedlewoods/seraphis_wallet/blob/dbbccecc89e1121762a4ad6b531638ece82aa0c7/src/rpc/core_rpc_server_commands_defs.h#L406-L428 #[derive(Deserialize)] pub(crate) struct TransactionInfo { @@ -113,3 +145,22 @@ impl ProvidesTransactionStatus for MoneroDaemon { } } } + +impl ProvidesNodeInfo for MoneroDaemon { + fn node_sync_status( + &self, + ) -> impl Send + Future> { + async move { + // 16kb, fairly arbitrary, but get_info is small + let response = self.rpc_call("get_info", None, 16384).await?; + + let info: monerod::GetInfoResponse = serde_json::from_str(&response) + .map_err(|e| NodeInfoError::Parse(e.to_string()))?; + + Ok(NodeSyncStatus { + synchronized: info.synchronized, + height: info.height, + }) + } + } +} diff --git a/monero-wallet/src/wallets.rs b/monero-wallet/src/wallets.rs index 3540af39c..9fe7a527e 100644 --- a/monero-wallet/src/wallets.rs +++ b/monero-wallet/src/wallets.rs @@ -6,7 +6,7 @@ //! - send money from one wallet to another. pub use monero_sys::{Daemon, WalletHandle as Wallet, WalletHandleListener}; -use anyhow::{Context, Result}; +use anyhow::{Context, Result, bail}; use monero_address::Network; use monero_daemon_rpc::MoneroDaemon; use monero_oxide_wallet::transaction::{NotPruned, Transaction}; @@ -299,12 +299,23 @@ impl Wallets { Ok(rpc_client) } - /// Check that the daemon RPC is reachable, connecting if necessary. + /// Check that the daemon RPC is reachable and the node is fully synced. pub async fn rpc_health_check(&self) -> Result<()> { - self.direct_rpc_block_height() + use monero_wallet_ng::rpc::ProvidesNodeInfo; + + let rpc_client = self.rpc_client().await?; + let status = rpc_client + .node_sync_status() .await .context("Monero daemon RPC health check failed")?; + if !status.synchronized { + bail!( + "Monero node is not fully synced (synced to height {})", + status.height + ); + } + Ok(()) }