diff --git a/CHANGELOG.md b/CHANGELOG.md index 6607ef5565c0..b546370bb877 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,8 @@ - [#4851](https://github.com/ChainSafe/forest/issues/4851) Add support for `FOREST_MAX_FILTER_RESULTS` in `Filecoin.EthGetLogs` RPC method. Add an `[events]` section to Forest configuration file. +- [#4954](https://github.com/ChainSafe/forest/issues/4954) Add `--format json` to `forest-cli chain head` command. + ### Changed ### Removed diff --git a/scripts/tests/calibnet_other_check.sh b/scripts/tests/calibnet_other_check.sh index c692bf3f7f49..f776a80ededa 100755 --- a/scripts/tests/calibnet_other_check.sh +++ b/scripts/tests/calibnet_other_check.sh @@ -32,6 +32,7 @@ $FOREST_CLI_PATH chain set-head --epoch -10 --force echo "Test subcommand: chain head" $FOREST_CLI_PATH chain head $FOREST_CLI_PATH chain head --tipsets 10 +$FOREST_CLI_PATH chain head --tipsets 5 --format json | jq 'length == 5' echo "Test subcommand: info show" $FOREST_CLI_PATH info show diff --git a/src/cli/subcommands/chain_cmd.rs b/src/cli/subcommands/chain_cmd.rs index 4ee47291ac42..da2178fc9d96 100644 --- a/src/cli/subcommands/chain_cmd.rs +++ b/src/cli/subcommands/chain_cmd.rs @@ -10,7 +10,13 @@ use cid::Cid; use clap::Subcommand; use nunny::Vec as NonEmpty; -use super::{print_pretty_lotus_json, print_rpc_res_cids}; +use super::print_pretty_lotus_json; + +#[derive(Debug, Clone, clap::ValueEnum)] +pub enum Format { + Json, + Text, +} #[derive(Debug, Subcommand)] pub enum ChainCommands { @@ -29,6 +35,9 @@ pub enum ChainCommands { /// Tipsets are categorized by epoch in descending order. #[arg(short = 'n', long, default_value = "1")] tipsets: u64, + /// Format of the output. `json` or `text`. + #[arg(long, default_value = "text")] + format: Format, }, /// Reads and prints out a message referenced by the specified CID from the @@ -68,7 +77,7 @@ impl ChainCommands { print_pretty_lotus_json(ChainGetBlock::call(&client, (cid,)).await?) } Self::Genesis => print_pretty_lotus_json(ChainGetGenesis::call(&client, ()).await?), - Self::Head { tipsets } => print_chain_head(&client, tipsets).await, + Self::Head { tipsets, format } => print_chain_head(&client, tipsets, format).await, Self::Message { cid } => { let bytes = ChainReadObj::call(&client, (cid,)).await?; match fvm_ipld_encoding::from_slice::(&bytes)? { @@ -151,15 +160,43 @@ fn maybe_confirm(no_confirm: bool, prompt: impl Into) -> anyhow::Result< } } -/// Print the first `n` tipsets from the head (inclusive). -async fn print_chain_head(client: &rpc::Client, n: u64) -> anyhow::Result<()> { +#[derive(Debug, serde::Serialize)] +struct TipsetInfo { + epoch: u64, + cids: Vec, +} + +/// Collects `n` tipsets from the head (inclusive) and returns them as a list of +/// [`TipsetInfo`] objects. +async fn collect_n_tipsets(client: &rpc::Client, n: u64) -> anyhow::Result> { ensure!(n > 0, "number of tipsets must be positive"); let current_epoch = ChainHead::call(client, ()).await?.epoch() as u64; - + let mut tipsets = Vec::with_capacity(n as usize); for epoch in (current_epoch.saturating_sub(n - 1)..=current_epoch).rev() { let tipset = tipset_by_epoch_or_offset(client, epoch.try_into()?).await?; - println!("[{}]", epoch); - print_rpc_res_cids(tipset)?; + tipsets.push(TipsetInfo { + epoch, + cids: tipset.cids().iter().map(|cid| cid.to_string()).collect(), + }); + } + Ok(tipsets) +} + +/// Print the first `n` tipsets from the head (inclusive). +async fn print_chain_head(client: &rpc::Client, n: u64, format: Format) -> anyhow::Result<()> { + let tipsets = collect_n_tipsets(client, n).await?; + match format { + Format::Json => { + println!("{}", serde_json::to_string_pretty(&tipsets)?); + } + Format::Text => { + tipsets.iter().for_each(|epoch_info| { + println!("[{}]", epoch_info.epoch); + epoch_info.cids.iter().for_each(|cid| { + println!("{}", cid); + }); + }); + } } Ok(()) } diff --git a/src/cli/subcommands/mod.rs b/src/cli/subcommands/mod.rs index 1411dc72269b..970abb5e6f59 100644 --- a/src/cli/subcommands/mod.rs +++ b/src/cli/subcommands/mod.rs @@ -24,8 +24,8 @@ use std::io::Write; pub(crate) use crate::cli_shared::cli::Config; use crate::cli_shared::cli::HELP_MESSAGE; +use crate::lotus_json::HasLotusJson; use crate::utils::version::FOREST_VERSION_STRING; -use crate::{blocks::Tipset, lotus_json::HasLotusJson}; use clap::Parser; use tracing::error; @@ -124,15 +124,6 @@ pub(super) fn print_pretty_lotus_json(obj: T) -> anyhow::Result Ok(()) } -/// Prints a tipset from a HTTP JSON-RPC response result -pub(super) fn print_rpc_res_cids(tipset: Tipset) -> anyhow::Result<()> { - for cid in &tipset.cids() { - println!("{cid}"); - } - - Ok(()) -} - /// Prints a bytes HTTP JSON-RPC response result pub(super) fn print_rpc_res_bytes(obj: Vec) -> anyhow::Result<()> { println!("{}", String::from_utf8(obj)?);