diff --git a/CHANGELOG.md b/CHANGELOG.md index 0eb58ecaf33d..8f4c1b54aa40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,8 @@ ### Added +- [#6006](https://github.com/ChainSafe/forest/issues/6006) More strict checks for the address arguments in the `forest-cli` subcommands. + ### Changed ### Removed diff --git a/src/bin/forest-cli.rs b/src/bin/forest-cli.rs index b8c82feb82ad..9802c1edd985 100644 --- a/src/bin/forest-cli.rs +++ b/src/bin/forest-cli.rs @@ -1,6 +1,7 @@ // Copyright 2019-2025 ChainSafe Systems // SPDX-License-Identifier: Apache-2.0, MIT -fn main() -> anyhow::Result<()> { - forest::forest_main(std::env::args_os()) +#[tokio::main(flavor = "multi_thread")] +async fn main() -> anyhow::Result<()> { + forest::forest_main(std::env::args_os()).await } diff --git a/src/cli/main.rs b/src/cli/main.rs index c0b17e0e79e1..598d83188274 100644 --- a/src/cli/main.rs +++ b/src/cli/main.rs @@ -11,44 +11,41 @@ use clap::Parser; use std::ffi::OsString; use std::str::FromStr as _; -pub fn main(args: impl IntoIterator) -> anyhow::Result<()> +pub async fn main(args: impl IntoIterator) -> anyhow::Result<()> where ArgT: Into + Clone, { + // Preliminary client without the token to check network. This needs to occur before parsing to ensure the + // `StrictAddress` validation works correctly. + let client = rpc::Client::default_or_from_env(None)?; + if let Ok(name) = StateNetworkName::call(&client, ()).await + && !matches!(NetworkChain::from_str(&name), Ok(NetworkChain::Mainnet)) + { + CurrentNetwork::set_global(Network::Testnet); + } + // Capture Cli inputs let Cli { token, cmd } = Cli::parse_from(args); let client = rpc::Client::default_or_from_env(token.as_deref())?; - tokio::runtime::Builder::new_multi_thread() - .enable_all() - .build() - .unwrap() - .block_on(async { - logger::setup_logger(&crate::cli_shared::cli::CliOpts::default()); - - if let Ok(name) = StateNetworkName::call(&client, ()).await - && !matches!(NetworkChain::from_str(&name), Ok(NetworkChain::Mainnet)) - { - CurrentNetwork::set_global(Network::Testnet); - } + let (_bg_tasks, _guards) = logger::setup_logger(&crate::cli_shared::cli::CliOpts::default()); - // Run command - match cmd { - Subcommand::Chain(cmd) => cmd.run(client).await, - Subcommand::Auth(cmd) => cmd.run(client).await, - Subcommand::Net(cmd) => cmd.run(client).await, - Subcommand::Sync(cmd) => cmd.run(client).await, - Subcommand::Mpool(cmd) => cmd.run(client).await, - Subcommand::State(cmd) => cmd.run(client).await, - Subcommand::Config(cmd) => cmd.run(&mut std::io::stdout()), - Subcommand::Send(cmd) => cmd.run(client).await, - Subcommand::Info(cmd) => cmd.run(client).await, - Subcommand::Snapshot(cmd) => cmd.run(client).await, - Subcommand::Shutdown(cmd) => cmd.run(client).await, - Subcommand::Healthcheck(cmd) => cmd.run(client).await, - Subcommand::F3(cmd) => cmd.run(client).await, - Subcommand::WaitApi(cmd) => cmd.run(client).await, - } - }) + // Run command + match cmd { + Subcommand::Chain(cmd) => cmd.run(client).await, + Subcommand::Auth(cmd) => cmd.run(client).await, + Subcommand::Net(cmd) => cmd.run(client).await, + Subcommand::Sync(cmd) => cmd.run(client).await, + Subcommand::Mpool(cmd) => cmd.run(client).await, + Subcommand::State(cmd) => cmd.run(client).await, + Subcommand::Config(cmd) => cmd.run(&mut std::io::stdout()), + Subcommand::Send(cmd) => cmd.run(client).await, + Subcommand::Info(cmd) => cmd.run(client).await, + Subcommand::Snapshot(cmd) => cmd.run(client).await, + Subcommand::Shutdown(cmd) => cmd.run(client).await, + Subcommand::Healthcheck(cmd) => cmd.run(client).await, + Subcommand::F3(cmd) => cmd.run(client).await, + Subcommand::WaitApi(cmd) => cmd.run(client).await, + } } diff --git a/src/cli/subcommands/mpool_cmd.rs b/src/cli/subcommands/mpool_cmd.rs index 574ede2e7214..db64e1fa6963 100644 --- a/src/cli/subcommands/mpool_cmd.rs +++ b/src/cli/subcommands/mpool_cmd.rs @@ -1,8 +1,6 @@ // Copyright 2019-2025 ChainSafe Systems // SPDX-License-Identifier: Apache-2.0, MIT -use std::str::FromStr as _; - use crate::blocks::Tipset; use crate::lotus_json::{HasLotusJson as _, NotNullVec}; use crate::message::SignedMessage; @@ -27,15 +25,15 @@ pub enum MpoolCommands { cids: bool, /// Return messages to a given address #[arg(long)] - to: Option, + to: Option, /// Return messages from a given address #[arg(long)] - from: Option, + from: Option, }, /// Get the current nonce for an address Nonce { /// Address to check nonce for - address: Address, + address: StrictAddress, }, /// Print mempool stats Stat { @@ -48,24 +46,14 @@ pub enum MpoolCommands { }, } -fn to_addr(value: &Option) -> anyhow::Result> { - Ok(value - .as_ref() - .map(|s| StrictAddress::from_str(s)) - .transpose()?) -} - fn filter_messages( messages: Vec, local_addrs: Option>, - to: &Option, - from: &Option, + to: &Option, + from: &Option, ) -> anyhow::Result> { use crate::message::Message; - let to = to_addr(to)?; - let from = to_addr(from)?; - let filtered = messages .into_iter() .filter(|msg| { @@ -281,7 +269,7 @@ impl MpoolCommands { Ok(()) } Self::Nonce { address } => { - let nonce = MpoolGetNonce::call(&client, (address,)).await?; + let nonce = MpoolGetNonce::call(&client, (address.into(),)).await?; println!("{nonce}"); Ok(()) @@ -390,7 +378,7 @@ mod tests { // Filtering messages from sender2 let smsg_filtered: Vec = - filter_messages(smsg_json_vec, None, &None, &Some(sender2.to_string())) + filter_messages(smsg_json_vec, None, &None, &Some(sender2.into())) .unwrap() .into_iter() .collect(); @@ -423,7 +411,7 @@ mod tests { // Filtering messages to target2 let smsg_filtered: Vec = - filter_messages(smsg_json_vec, None, &Some(target2.to_string()), &None) + filter_messages(smsg_json_vec, None, &Some(target2.into()), &None) .unwrap() .into_iter() .collect(); @@ -437,6 +425,7 @@ mod tests { fn compute_statistics() { use crate::shim::message::Message; use fvm_ipld_encoding::RawBytes; + use std::str::FromStr; let addr0 = Address::from_str("t3urxivigpzih5f6ih3oq3lr2jlunw3m5oehbe5efts4ub5wy2oi4fbo5cw7333a4rrffo5535tjdq24wkc2aa").unwrap(); let addr1 = Address::from_str("t410fot3vkzzorqg4alowvghvxx4mhofhtazixbm6z2i").unwrap(); diff --git a/src/cli/subcommands/state_cmd.rs b/src/cli/subcommands/state_cmd.rs index b922d589eac0..3318469ea628 100644 --- a/src/cli/subcommands/state_cmd.rs +++ b/src/cli/subcommands/state_cmd.rs @@ -4,13 +4,12 @@ use crate::lotus_json::HasLotusJson; use crate::rpc::state::{ForestComputeStateOutput, ForestStateCompute}; use crate::rpc::{self, prelude::*}; -use crate::shim::address::{CurrentNetwork, Error, Network, StrictAddress}; +use crate::shim::address::StrictAddress; use crate::shim::clock::ChainEpoch; use cid::Cid; use clap::Subcommand; use std::num::NonZeroUsize; use std::path::PathBuf; -use std::str::FromStr; use std::time::Duration; #[derive(Debug, Subcommand)] @@ -36,7 +35,7 @@ pub enum StateCommands { /// Read the state of an actor ReadState { /// Actor address to read the state of - actor_address: String, + actor_address: StrictAddress, }, } @@ -76,21 +75,9 @@ impl StateCommands { } Self::ReadState { actor_address } => { let tipset = ChainHead::call(&client, ()).await?; - let address = match StrictAddress::from_str(&actor_address) { - Ok(address) => address.into(), - Err(Error::UnknownNetwork) => { - let expected = match CurrentNetwork::get() { - Network::Mainnet => 'f', - Network::Testnet => 't', - }; - anyhow::bail!("Invalid network prefix, expected '{}'", expected); - } - Err(e) => anyhow::bail!("Error parsing address: {e}"), - }; - let ret = client .call( - StateReadState::request((address, tipset.key().into()))? + StateReadState::request((actor_address.into(), tipset.key().into()))? .with_timeout(Duration::MAX), ) .await?;