diff --git a/.changelog/unreleased/improvements/4205-improve-next-epoch-info.md b/.changelog/unreleased/improvements/4205-improve-next-epoch-info.md new file mode 100644 index 0000000000..f90c642c30 --- /dev/null +++ b/.changelog/unreleased/improvements/4205-improve-next-epoch-info.md @@ -0,0 +1,2 @@ +- Improve namadac next-epoch-info to also estimate how much time is left in the + current epoch. ([\#4205](https://github.com/anoma/namada/pull/4205)) \ No newline at end of file diff --git a/crates/apps_lib/src/client/rpc.rs b/crates/apps_lib/src/client/rpc.rs index 65a1371630..449d35965a 100644 --- a/crates/apps_lib/src/client/rpc.rs +++ b/crates/apps_lib/src/client/rpc.rs @@ -49,6 +49,7 @@ use namada_sdk::rpc::{ }; use namada_sdk::storage::BlockResults; use namada_sdk::tendermint_rpc::endpoint::status; +use namada_sdk::time::DateTimeUtc; use namada_sdk::token::{ DenominatedAmount, MaspDigitPos, NATIVE_MAX_DECIMAL_PLACES, }; @@ -90,17 +91,30 @@ pub async fn query_and_print_masp_epoch(context: &impl Namada) -> MaspEpoch { /// Query and print some information to help discern when the next epoch will /// begin. pub async fn query_and_print_next_epoch_info(context: &impl Namada) { - query_block(context).await; + println!(); + let current_time = query_block(context).await.unwrap(); let current_epoch = query_epoch(context.client()).await.unwrap(); let (this_epoch_first_height, epoch_duration) = rpc::query_next_epoch_info(context.client()).await.unwrap(); - display_line!(context.io(), "Current epoch: {current_epoch}.\n"); + let this_epoch_first_height_header = + rpc::query_block_header(context.client(), this_epoch_first_height) + .await + .unwrap() + .unwrap(); + + let first_block_time = this_epoch_first_height_header.time; + let next_epoch_time = first_block_time + epoch_duration.min_duration; + + let seconds_left = next_epoch_time.time_diff(current_time).0; + let time_remaining_str = convert_to_hours(seconds_left); + + display_line!(context.io(), "\nCurrent epoch: {current_epoch}."); display_line!( context.io(), - "First block height of this epoch {current_epoch}: \ - {this_epoch_first_height}." + "First block height of epoch {current_epoch}: \ + {this_epoch_first_height}.\n" ); display_line!( context.io(), @@ -109,17 +123,33 @@ pub async fn query_and_print_next_epoch_info(context: &impl Namada) { ); display_line!( context.io(), - "Minimum amount of time for an epoch: {} seconds.", - epoch_duration.min_duration + "Minimum amount of time for an epoch: {}.", + convert_to_hours(epoch_duration.min_duration.0) ); display_line!( context.io(), - "\nEarliest height at which epoch {} can begin is block {}.", + "\nNext epoch ({}) begins in {} or at block height {}, whichever \ + occurs later.\n", current_epoch.next(), + time_remaining_str, this_epoch_first_height.0 + epoch_duration.min_num_of_blocks ); } +fn convert_to_hours(seconds: u64) -> String { + let hours = seconds / 3600; + let minutes = (seconds - 3600 * hours) / 60; + let seconds_unit = seconds - 3600 * hours - 60 * minutes; + + if hours > 0 { + format!("{}h-{}m-{}s", hours, minutes, seconds_unit) + } else if minutes > 0 { + format!("{}m-{}s", minutes, seconds_unit) + } else { + format!("{}s", seconds_unit) + } +} + /// Query and print node's status. pub async fn query_and_print_status( context: &impl Namada, @@ -138,7 +168,7 @@ pub async fn query_and_print_status( } /// Query the last committed block -pub async fn query_block(context: &impl Namada) { +pub async fn query_block(context: &impl Namada) -> Option { let block = namada_sdk::rpc::query_block(context.client()) .await .unwrap(); @@ -150,9 +180,11 @@ pub async fn query_block(context: &impl Namada) { block.height, block.time ); + Some(block.time) } None => { display_line!(context.io(), "No block has been committed yet."); + None } } } diff --git a/crates/core/src/time.rs b/crates/core/src/time.rs index fd27aa31aa..1f70321ae5 100644 --- a/crates/core/src/time.rs +++ b/crates/core/src/time.rs @@ -206,6 +206,16 @@ impl DateTimeUtc { pub fn next_second(&self) -> Self { *self + DurationSecs(1) } + + /// Returns the number of seconds in between two `DateTimeUtc` instances. + /// Assumes that `self` is later than `earlier`. + #[allow(clippy::arithmetic_side_effects)] + pub fn time_diff(&self, earlier: DateTimeUtc) -> DurationSecs { + (self.0 - earlier.0) + .to_std() + .map(DurationSecs::from) + .unwrap_or(DurationSecs(0)) + } } impl FromStr for DateTimeUtc { diff --git a/crates/sdk/src/rpc.rs b/crates/sdk/src/rpc.rs index d598ece8c3..2e9ce1b68a 100644 --- a/crates/sdk/src/rpc.rs +++ b/crates/sdk/src/rpc.rs @@ -47,7 +47,7 @@ use namada_proof_of_stake::types::{ BondsAndUnbondsDetails, CommissionPair, LivenessInfo, ValidatorMetaData, WeightedValidator, }; -use namada_state::LastBlock; +use namada_state::{BlockHeader, LastBlock}; use namada_token::masp::MaspTokenRewardData; use namada_tx::data::{BatchedTxResult, DryRunResult, ResultCode, TxResult}; use namada_tx::event::{Batch as BatchAttr, Code as CodeAttr}; @@ -153,6 +153,14 @@ pub async fn query_epoch( convert_response::(RPC.shell().epoch(client).await) } +/// Query the epoch of the last committed block +pub async fn query_block_header( + client: &C, + height: BlockHeight, +) -> Result, error::Error> { + convert_response::(RPC.shell().block_header(client, &height).await) +} + /// Query the masp epoch of the last committed block pub async fn query_masp_epoch( client: &C,