diff --git a/cli/src/cli.rs b/cli/src/cli.rs index 5c3293a432ebb8..7cd074289f0c9f 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -179,6 +179,7 @@ pub enum CliCommand { commitment_config: CommitmentConfig, follow: bool, }, + ClusterDate, ClusterVersion, CreateAddressWithSeed { from_pubkey: Option, @@ -187,7 +188,7 @@ pub enum CliCommand { }, Fees, GetBlockTime { - slot: Slot, + slot: Option, }, GetEpochInfo { commitment_config: CommitmentConfig, @@ -587,6 +588,10 @@ pub fn parse_command( let response = match matches.subcommand() { // Cluster Query Commands ("catchup", Some(matches)) => parse_catchup(matches, wallet_manager), + ("cluster-date", Some(_matches)) => Ok(CliCommandInfo { + command: CliCommand::ClusterDate, + signers: vec![], + }), ("cluster-version", Some(_matches)) => Ok(CliCommandInfo { command: CliCommand::ClusterVersion, signers: vec![], @@ -1685,6 +1690,7 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { *commitment_config, *follow, ), + CliCommand::ClusterDate => process_cluster_date(&rpc_client, config), CliCommand::ClusterVersion => process_cluster_version(&rpc_client), CliCommand::CreateAddressWithSeed { from_pubkey, @@ -1692,7 +1698,7 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { program_id, } => process_create_address_with_seed(config, from_pubkey.as_ref(), &seed, &program_id), CliCommand::Fees => process_fees(&rpc_client), - CliCommand::GetBlockTime { slot } => process_get_block_time(&rpc_client, *slot), + CliCommand::GetBlockTime { slot } => process_get_block_time(&rpc_client, config, *slot), CliCommand::GetGenesisHash => process_get_genesis_hash(&rpc_client), CliCommand::GetEpochInfo { commitment_config } => { process_get_epoch_info(&rpc_client, config, *commitment_config) diff --git a/cli/src/cli_output.rs b/cli/src/cli_output.rs index c691f37f0ea5b0..08e36d61354827 100644 --- a/cli/src/cli_output.rs +++ b/cli/src/cli_output.rs @@ -839,3 +839,26 @@ impl From<&Lockout> for CliLockout { } } } + +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CliBlockTime { + pub slot: Slot, + pub timestamp: UnixTimestamp, +} + +impl fmt::Display for CliBlockTime { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + writeln_name_value(f, "Block:", &self.slot.to_string())?; + writeln_name_value( + f, + "Date:", + &format!( + "{} (UnixTimestamp: {})", + DateTime::::from_utc(NaiveDateTime::from_timestamp(self.timestamp, 0), Utc) + .to_rfc3339_opts(SecondsFormat::Secs, true), + self.timestamp + ), + ) + } +} diff --git a/cli/src/cluster_query.rs b/cli/src/cluster_query.rs index 11c906ceeacb0e..6a6d29539b39be 100644 --- a/cli/src/cluster_query.rs +++ b/cli/src/cluster_query.rs @@ -1,12 +1,8 @@ use crate::{ cli::{check_account_for_fee, CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult}, - cli_output::{ - CliBlockProduction, CliBlockProductionEntry, CliEpochInfo, CliKeyedStakeState, - CliSlotStatus, CliStakeVec, CliValidator, CliValidators, - }, + cli_output::*, display::println_name_value, }; -use chrono::{DateTime, NaiveDateTime, SecondsFormat, Utc}; use clap::{value_t, value_t_or_exit, App, Arg, ArgMatches, SubCommand}; use console::{style, Emoji}; use indicatif::{ProgressBar, ProgressStyle}; @@ -24,7 +20,7 @@ use solana_client::{ use solana_remote_wallet::remote_wallet::RemoteWalletManager; use solana_sdk::{ account_utils::StateMut, - clock::{self, Slot}, + clock::{self, Clock, Slot}, commitment_config::CommitmentConfig, epoch_schedule::Epoch, hash::Hash, @@ -33,6 +29,7 @@ use solana_sdk::{ pubkey::Pubkey, signature::{Keypair, Signer}, system_instruction, + sysvar::{self, Sysvar}, transaction::Transaction, }; use std::{ @@ -81,6 +78,10 @@ impl ClusterQuerySubCommands for App<'_, '_> { ) .arg(commitment_arg()), ) + .subcommand( + SubCommand::with_name("cluster-date") + .about("Get current cluster date, computed from genesis creation time and network time") + ) .subcommand( SubCommand::with_name("cluster-version") .about("Get the version of the cluster entrypoint"), @@ -94,7 +95,6 @@ impl ClusterQuerySubCommands for App<'_, '_> { .index(1) .takes_value(true) .value_name("SLOT") - .required(true) .help("Slot number of the block to query") ) ) @@ -310,7 +310,7 @@ pub fn parse_cluster_ping( } pub fn parse_get_block_time(matches: &ArgMatches<'_>) -> Result { - let slot = value_t_or_exit!(matches, "slot", u64); + let slot = value_of(matches, "slot"); Ok(CliCommandInfo { command: CliCommand::GetBlockTime { slot }, signers: vec![], @@ -518,6 +518,24 @@ pub fn process_catchup( } } +pub fn process_cluster_date(rpc_client: &RpcClient, config: &CliConfig) -> ProcessResult { + let result = rpc_client + .get_account_with_commitment(&sysvar::clock::id(), CommitmentConfig::default())?; + if let Some(clock_account) = result.value { + let clock: Clock = Sysvar::from_account(&clock_account).ok_or_else(|| { + CliError::RpcRequestError("Failed to deserialize clock sysvar".to_string()) + })?; + let block_time = CliBlockTime { + slot: result.context.slot, + timestamp: clock.unix_timestamp, + }; + config.output_format.formatted_print(&block_time); + Ok("".to_string()) + } else { + Err(format!("AccountNotFound: pubkey={}", sysvar::clock::id()).into()) + } +} + pub fn process_cluster_version(rpc_client: &RpcClient) -> ProcessResult { let remote_version = rpc_client.get_version()?; Ok(remote_version.solana_core) @@ -567,15 +585,20 @@ pub fn process_leader_schedule(rpc_client: &RpcClient) -> ProcessResult { Ok("".to_string()) } -pub fn process_get_block_time(rpc_client: &RpcClient, slot: Slot) -> ProcessResult { +pub fn process_get_block_time( + rpc_client: &RpcClient, + config: &CliConfig, + slot: Option, +) -> ProcessResult { + let slot = if let Some(slot) = slot { + slot + } else { + rpc_client.get_slot()? + }; let timestamp = rpc_client.get_block_time(slot)?; - let result = format!( - "{} (UnixTimestamp: {})", - DateTime::::from_utc(NaiveDateTime::from_timestamp(timestamp, 0), Utc) - .to_rfc3339_opts(SecondsFormat::Secs, true), - timestamp - ); - Ok(result) + let block_time = CliBlockTime { slot, timestamp }; + config.output_format.formatted_print(&block_time); + Ok("".to_string()) } pub fn process_get_epoch_info( @@ -1196,6 +1219,17 @@ mod tests { let (default_keypair_file, mut tmp_file) = make_tmp_file(); write_keypair(&default_keypair, tmp_file.as_file_mut()).unwrap(); + let test_cluster_version = test_commands + .clone() + .get_matches_from(vec!["test", "cluster-date"]); + assert_eq!( + parse_command(&test_cluster_version, &default_keypair_file, &mut None).unwrap(), + CliCommandInfo { + command: CliCommand::ClusterDate, + signers: vec![], + } + ); + let test_cluster_version = test_commands .clone() .get_matches_from(vec!["test", "cluster-version"]); @@ -1224,7 +1258,7 @@ mod tests { assert_eq!( parse_command(&test_get_block_time, &default_keypair_file, &mut None).unwrap(), CliCommandInfo { - command: CliCommand::GetBlockTime { slot }, + command: CliCommand::GetBlockTime { slot: Some(slot) }, signers: vec![], } );