diff --git a/substrate/client/rpc-api/src/system/helpers.rs b/substrate/client/rpc-api/src/system/helpers.rs index dd3294c243..c5dddedef9 100644 --- a/substrate/client/rpc-api/src/system/helpers.rs +++ b/substrate/client/rpc-api/src/system/helpers.rs @@ -86,6 +86,18 @@ pub enum NodeRole { Sentry, } +/// The state of the syncing of the node. +#[derive(Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SyncState { + /// Height of the block at which syncing started. + pub starting_block: Number, + /// Height of the current best block of the node. + pub current_block: Number, + /// Height of the highest block learned from the network. Missing if no block is known yet. + #[serde(default = "Default::default", skip_serializing_if = "Option::is_none")] + pub highest_block: Option, +} #[cfg(test)] mod tests { use super::*; @@ -114,4 +126,25 @@ mod tests { r#"{"peerId":"2","roles":"a","bestHash":5,"bestNumber":6}"#, ); } + + #[test] + fn should_serialize_sync_state() { + assert_eq!( + ::serde_json::to_string(&SyncState { + starting_block: 12u32, + current_block: 50u32, + highest_block: Some(128u32), + }).unwrap(), + r#"{"startingBlock":12,"currentBlock":50,"highestBlock":128}"#, + ); + + assert_eq!( + ::serde_json::to_string(&SyncState { + starting_block: 12u32, + current_block: 50u32, + highest_block: None, + }).unwrap(), + r#"{"startingBlock":12,"currentBlock":50}"#, + ); + } } diff --git a/substrate/client/rpc-api/src/system/mod.rs b/substrate/client/rpc-api/src/system/mod.rs index a7b746ee1b..fbeec23ea5 100644 --- a/substrate/client/rpc-api/src/system/mod.rs +++ b/substrate/client/rpc-api/src/system/mod.rs @@ -27,7 +27,7 @@ use futures::{future::BoxFuture, compat::Compat}; use self::error::Result as SystemResult; -pub use self::helpers::{SystemInfo, Health, PeerInfo, NodeRole}; +pub use self::helpers::{SystemInfo, Health, PeerInfo, NodeRole, SyncState}; pub use self::gen_client::Client as SystemClient; /// Substrate system RPC API @@ -103,4 +103,9 @@ pub trait SystemApi { /// Returns the roles the node is running as. #[rpc(name = "system_nodeRoles", returns = "Vec")] fn system_node_roles(&self) -> Receiver>; + + /// Returns the state of the syncing of the node: starting block, current best block, highest + /// known block. + #[rpc(name = "system_syncState", returns = "SyncState")] + fn system_sync_state(&self) -> Receiver>; } diff --git a/substrate/client/rpc/Cargo.toml b/substrate/client/rpc/Cargo.toml index 70d0005351..021c795fe5 100644 --- a/substrate/client/rpc/Cargo.toml +++ b/substrate/client/rpc/Cargo.toml @@ -44,6 +44,7 @@ lazy_static = { version = "1.4.0", optional = true } [dev-dependencies] assert_matches = "1.3.0" futures01 = { package = "futures", version = "0.1.29" } +lazy_static = "1.4.0" sc-network = { version = "0.8.0", path = "../network" } sp-io = { version = "2.0.0", path = "../../primitives/io" } substrate-test-runtime-client = { version = "2.0.0", path = "../../test-utils/runtime/client" } diff --git a/substrate/client/rpc/src/system/mod.rs b/substrate/client/rpc/src/system/mod.rs index 4b8abbe144..17fb6b77a5 100644 --- a/substrate/client/rpc/src/system/mod.rs +++ b/substrate/client/rpc/src/system/mod.rs @@ -30,7 +30,7 @@ use sp_runtime::traits::{self, Header as HeaderT}; use self::error::Result; pub use sc_rpc_api::system::*; -pub use self::helpers::{SystemInfo, Health, PeerInfo, NodeRole}; +pub use self::helpers::{SystemInfo, Health, PeerInfo, NodeRole, SyncState}; pub use self::gen_client::Client as SystemClient; macro_rules! bail_if_unsafe { @@ -66,7 +66,9 @@ pub enum Request { /// Must return any potential parse error. NetworkRemoveReservedPeer(String, oneshot::Sender>), /// Must return the node role. - NodeRoles(oneshot::Sender>) + NodeRoles(oneshot::Sender>), + /// Must return the state of the node syncing. + SyncState(oneshot::Sender::Number>>), } impl System { @@ -189,4 +191,10 @@ impl SystemApi::Number> for Sy let _ = self.send_back.unbounded_send(Request::NodeRoles(tx)); Receiver(Compat::new(rx)) } + + fn system_sync_state(&self) -> Receiver::Number>> { + let (tx, rx) = oneshot::channel(); + let _ = self.send_back.unbounded_send(Request::SyncState(tx)); + Receiver(Compat::new(rx)) + } } diff --git a/substrate/client/rpc/src/system/tests.rs b/substrate/client/rpc/src/system/tests.rs index f16d7da5b1..61f1940dc2 100644 --- a/substrate/client/rpc/src/system/tests.rs +++ b/substrate/client/rpc/src/system/tests.rs @@ -104,6 +104,13 @@ fn api>>(sync: T) -> System { Request::NodeRoles(sender) => { let _ = sender.send(vec![NodeRole::Authority]); } + Request::SyncState(sender) => { + let _ = sender.send(SyncState { + starting_block: 1, + current_block: 2, + highest_block: Some(3), + }); + } }; future::ready(()) @@ -291,6 +298,18 @@ fn system_node_roles() { ); } +#[test] +fn system_sync_state() { + assert_eq!( + wait_receiver(api(None).system_sync_state()), + SyncState { + starting_block: 1, + current_block: 2, + highest_block: Some(3), + } + ); +} + #[test] fn system_network_add_reserved() { let good_peer_id = "/ip4/198.51.100.19/tcp/30333/p2p/QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV"; diff --git a/substrate/client/service/src/lib.rs b/substrate/client/service/src/lib.rs index 1c67d3865f..cb741c2920 100644 --- a/substrate/client/service/src/lib.rs +++ b/substrate/client/service/src/lib.rs @@ -80,7 +80,7 @@ pub use sc_tracing::TracingReceiver; pub use task_manager::SpawnTaskHandle; pub use task_manager::TaskManager; pub use sp_consensus::import_queue::ImportQueue; -use sc_client_api::BlockchainEvents; +use sc_client_api::{blockchain::HeaderBackend, BlockchainEvents}; const DEFAULT_PROTOCOL_ID: &str = "sup"; @@ -199,7 +199,7 @@ pub struct PartialComponents, + C: BlockchainEvents + HeaderBackend, H: sc_network::ExHashT > ( role: Role, @@ -212,6 +212,9 @@ async fn build_network_future< ) { let mut imported_blocks_stream = client.import_notification_stream().fuse(); + // Current best block at initialization, to report to the RPC layer. + let starting_block = client.info().best_number; + // Stream of finalized blocks reported by the client. let mut finality_notification_stream = { let mut finality_notification_stream = client.finality_notification_stream().fuse(); @@ -323,6 +326,15 @@ async fn build_network_future< let _ = sender.send(vec![node_role]); } + sc_rpc::system::Request::SyncState(sender) => { + use sc_rpc::system::SyncState; + + let _ = sender.send(SyncState { + starting_block: starting_block, + current_block: client.info().best_number, + highest_block: network.best_seen_block(), + }); + } } }