diff --git a/Cargo.toml b/Cargo.toml index 945b368..779c6b7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ keywords = ["core blockchain", "xcb", "cli"] license = "MIT OR Apache-2.0" homepage = "https://github.com/core-coin/core-cli" repository = "https://github.com/core-coin/core-cli" -version = "0.0.3" +version = "0.0.4" [workspace.dependencies] # Workspace members diff --git a/crates/console/src/base.rs b/crates/console/src/base.rs index 0986eaf..5f52316 100644 --- a/crates/console/src/base.rs +++ b/crates/console/src/base.rs @@ -22,6 +22,7 @@ fn list() { println!(" 'get_block(||'latest')' - get block information by hash or number. Use 'latest' to get the latest block"); println!(" 'get_energy_price()' - get the current energy price to allow a timely execution of a transaction"); println!(" 'get_network_id()' - get the nework ID of the current network"); + println!(" 'syncing()' - get the syncing status of the node"); println!("'xcbkey' - XCB Key module commands:"); println!(" 'list()' - list all accounts"); diff --git a/crates/modules/Cargo.toml b/crates/modules/Cargo.toml index dcdb611..98e5d46 100644 --- a/crates/modules/Cargo.toml +++ b/crates/modules/Cargo.toml @@ -31,3 +31,6 @@ atoms-rpc-types.workspace = true xcb-keystore.workspace = true rpassword = "5.0" + +[dev-dependencies] +base-primitives.workspace = true \ No newline at end of file diff --git a/crates/modules/src/xcb.rs b/crates/modules/src/xcb.rs index 7ee4ac1..a79a4e6 100644 --- a/crates/modules/src/xcb.rs +++ b/crates/modules/src/xcb.rs @@ -86,6 +86,14 @@ impl XcbModule { Err(e) => Err(e), } } + + async fn syncing(&self) -> Result { + let syncing = self.client().await.lock().await.syncing().await; + match syncing { + Ok(syncing) => Ok(Response::SyncStatus(syncing)), + Err(e) => Err(e), + } + } } #[async_trait::async_trait] @@ -96,6 +104,7 @@ impl Module for XcbModule { "get_block" => self.block(args).await, "get_energy_price" => self.get_energy_price().await, "get_network_id" => self.get_network_id().await, + "syncing" => self.syncing().await, _ => Err(CliError::UnknownCommand), } } diff --git a/crates/modules/tests/xcb_tests.rs b/crates/modules/tests/xcb_tests.rs index f0f56f4..64bdb00 100644 --- a/crates/modules/tests/xcb_tests.rs +++ b/crates/modules/tests/xcb_tests.rs @@ -1,6 +1,7 @@ #[cfg(test)] mod tests { - use atoms_rpc_types::Block; + use atoms_rpc_types::{Block, SyncInfo}; + use base_primitives::U256; use cli_error::CliError; use modules::{Module, XcbModule}; use rpc::MockRpcClient; @@ -23,6 +24,11 @@ mod tests { XcbModule::new(client) } + fn get_module_with_rpc_client(client: MockRpcClient) -> XcbModule { + let client = Arc::new(Mutex::new(client)); + XcbModule::new(client) + } + #[tokio::test] async fn test_execute_get_block_height() { let mut module = get_module(); @@ -139,4 +145,49 @@ mod tests { Err(CliError::InvalidNumberOfArguments(_)) )); } + + #[tokio::test] + async fn test_syncing() { + let mut module = get_module(); + let response = module.execute("syncing".to_string(), vec![]).await.unwrap(); + assert_eq!( + response, + Response::SyncStatus(atoms_rpc_types::SyncStatus::None) + ); + + assert_eq!( + response.format(types::ResponseView::Human), + "RPC node is synced and data is up to date" + ); + } + + #[tokio::test] + async fn test_syncing_active() { + let mut module = get_module_with_rpc_client(MockRpcClient::new().with_syncing( + atoms_rpc_types::SyncStatus::Info(SyncInfo { + starting_block: U256::from(0), + current_block: U256::from(100), + highest_block: U256::from(1000), + warp_chunks_amount: None, + warp_chunks_processed: None, + }), + )); + + let response = module.execute("syncing".to_string(), vec![]).await.unwrap(); + assert_eq!( + response, + Response::SyncStatus(atoms_rpc_types::SyncStatus::Info(SyncInfo { + starting_block: U256::from(0), + current_block: U256::from(100), + highest_block: U256::from(1000), + warp_chunks_amount: None, + warp_chunks_processed: None, + })) + ); + + assert_eq!( + response.format(types::ResponseView::Human), + "RPC node is syncing now. Current block: 100, highest block: 1000, starting block: 0" + ); + } } diff --git a/crates/rpc/src/go_core.rs b/crates/rpc/src/go_core.rs index a5e1d5c..2836157 100644 --- a/crates/rpc/src/go_core.rs +++ b/crates/rpc/src/go_core.rs @@ -2,7 +2,7 @@ use crate::RpcClient; use async_trait::async_trait; use atoms_provider::{network::Ethereum, Provider, RootProvider}; use atoms_rpc_client::RpcClient as AtomsRpcClient; -use atoms_rpc_types::{Block, BlockId, BlockNumberOrTag, RpcBlockHash}; +use atoms_rpc_types::{Block, BlockId, BlockNumberOrTag, RpcBlockHash, SyncStatus}; use atoms_transport_http::{Client, Http}; use base_primitives::{hex::FromHex, FixedBytes}; use cli_error::CliError; @@ -90,4 +90,13 @@ impl RpcClient for GoCoreClient { .map_err(|e| CliError::RpcError(e.to_string()))?; Ok(response) } + + async fn syncing(&self) -> Result { + let response = self + .provider + .syncing() + .await + .map_err(|e| CliError::RpcError(e.to_string()))?; + Ok(response) + } } diff --git a/crates/rpc/src/lib.rs b/crates/rpc/src/lib.rs index fb10133..1987f2f 100644 --- a/crates/rpc/src/lib.rs +++ b/crates/rpc/src/lib.rs @@ -1,5 +1,5 @@ use async_trait::async_trait; -use atoms_rpc_types::Block; +use atoms_rpc_types::{Block, SyncStatus}; use cli_error::CliError; pub mod go_core; @@ -17,4 +17,6 @@ pub trait RpcClient { async fn get_energy_price(&self) -> Result; async fn get_network_id(&self) -> Result; + + async fn syncing(&self) -> Result; } diff --git a/crates/rpc/src/mock.rs b/crates/rpc/src/mock.rs index 8fb3130..3f466c8 100644 --- a/crates/rpc/src/mock.rs +++ b/crates/rpc/src/mock.rs @@ -9,6 +9,7 @@ pub struct MockRpcClient { pub block_latest: Block, pub energy_price: u128, pub network_id: u64, + pub syncing: atoms_rpc_types::SyncStatus, } impl MockRpcClient { @@ -20,6 +21,7 @@ impl MockRpcClient { block_latest: Block::default(), energy_price: 0, network_id: 0, + syncing: atoms_rpc_types::SyncStatus::None, } } @@ -52,6 +54,11 @@ impl MockRpcClient { self.network_id = network_id; self } + + pub fn with_syncing(mut self, syncing: atoms_rpc_types::SyncStatus) -> Self { + self.syncing = syncing; + self + } } impl Default for MockRpcClient { @@ -85,4 +92,8 @@ impl RpcClient for MockRpcClient { async fn get_network_id(&self) -> Result { Ok(self.network_id) } + + async fn syncing(&self) -> Result { + Ok(self.syncing) + } } diff --git a/crates/rpc/tests/go_core_tests.rs b/crates/rpc/tests/go_core_tests.rs index b7a86e0..577b472 100644 --- a/crates/rpc/tests/go_core_tests.rs +++ b/crates/rpc/tests/go_core_tests.rs @@ -2,6 +2,7 @@ #[cfg(test)] mod tests { + use atoms_rpc_types::SyncStatus; use cli_error::CliError; use rpc::{GoCoreClient, RpcClient}; use types::DEFAULT_BACKEND; @@ -92,4 +93,12 @@ mod tests { let response = go_core_client.get_block_by_number(999999999).await; assert!(matches!(response, Err(CliError::RpcError(_)))); } + + #[tokio::test] + async fn test_syncing() { + let go_core_client = gocore_client().await; + + let response = go_core_client.syncing().await.unwrap(); + assert_eq!(response, SyncStatus::None); + } } diff --git a/crates/types/src/response.rs b/crates/types/src/response.rs index e7b129c..284e746 100644 --- a/crates/types/src/response.rs +++ b/crates/types/src/response.rs @@ -2,7 +2,9 @@ use atoms_rpc_types::Block; use serde::Serialize; use crate::{account::KeyFile, Account}; +use atoms_rpc_types::SyncStatus; use std::str::FromStr; + /// ResponseView decided if response of call will be returned as a string, json object or human readable format #[derive(Debug, Clone, PartialEq, Default)] pub enum ResponseView { @@ -38,6 +40,7 @@ pub enum Response { Block(Block), Struct(serde_json::Value), // Use serde_json::Value for custom structs + SyncStatus(SyncStatus), Accounts(Vec), Keyfile(KeyFile), @@ -71,6 +74,14 @@ impl std::fmt::Display for Response { } Ok(()) } + Response::SyncStatus(sync_info) => { + if let SyncStatus::Info(sync_info) = sync_info { + write!(f, "RPC node is syncing now. Current block: {}, highest block: {}, starting block: {}", sync_info.current_block, sync_info.highest_block, sync_info.starting_block) + } else { + write!(f, "RPC node is synced and data is up to date") + } + } + Response::Keyfile(keyfile) => write!(f, "{}", keyfile), } } @@ -96,6 +107,7 @@ impl Response { Response::Struct(val) => format!("Struct value: {:#?}", val), Response::Accounts(_) => self.to_string(), Response::Keyfile(_) => self.to_string(), + Response::SyncStatus(_) => self.to_string(), } } }