Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor(cli): Refactor CLI Command and Code Structure #1565

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion crates/astria-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ name = "astria-cli"

[dependencies]
color-eyre = "0.6"
home = "0.5"
toml = "0.7"

astria-bridge-contracts = { path = "../astria-bridge-contracts" }
astria-core = { path = "../astria-core", features = ["serde"] }

clap = { workspace = true, features = ["derive", "env"] }
clap = { workspace = true, features = ["derive", "env", "string"] }
ethers = { workspace = true, features = ["ws"] }
hex = { workspace = true }
ibc-types = { workspace = true }
Expand Down
50 changes: 50 additions & 0 deletions crates/astria-cli/src/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
use std::{
collections::HashMap,
fs,
path::Path,
};

use color_eyre::eyre;
use serde::{
Deserialize,
Serialize,
};
use toml;

pub const DEFAULT_SEQUENCER_URL: &str = "https://rpc.sequencer.dawn-0.astria.org";

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct NetworkConfig {
pub sequencer_chain_id: String,
pub sequencer_url: String,
pub asset: String,
pub fee_asset: String,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct SequencerNetworksConfig {
networks: HashMap<String, NetworkConfig>,
}

impl SequencerNetworksConfig {
/// Load the config from a file
pub fn load<P: AsRef<Path>>(path: P) -> eyre::Result<Self> {
let toml_str = fs::read_to_string(path)?;
let config: SequencerNetworksConfig = toml::from_str(&toml_str)?;
return Ok(config);
}

/// Get the network config for the selected network
pub fn get_network(&self, network: &String) -> eyre::Result<&NetworkConfig> {
if let Some(network_config) = self.networks.get(network) {
Ok(network_config)
} else {
let keys = self.networks.keys().collect::<Vec<&String>>();
Err(eyre::eyre!(
"'{}' not found: Expected one of the following: {:?}",
network,
keys
))
}
}
}
1 change: 1 addition & 0 deletions crates/astria-cli/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pub mod cli;
pub mod commands;
pub mod config;
22 changes: 19 additions & 3 deletions crates/astria-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,36 @@ use astria_cli::{
cli::Cli,
commands,
};
use clap::Command;
use color_eyre::eyre;

mod query;

#[tokio::main]
async fn main() -> ExitCode {
tracing_subscriber::fmt()
.pretty()
.with_writer(std::io::stderr)
.init();

if let Err(err) = run().await {
eprintln!("{err:?}");
return ExitCode::FAILURE;
let matches = Command::new("astria-cli")
.subcommand(query::command())
// .subcommand(command2::command(&config))
.get_matches();

match matches.subcommand() {
Some(("query", args)) => query::run(args).await.expect("Could not run query command"),
// Some(("command2", sub_matches)) => command2::run(sub_matches, &config),
_ => {
return ExitCode::FAILURE;
}
}

// if let Err(err) = run().await {
// eprintln!("{err:?}");
// return ExitCode::FAILURE;
// }

ExitCode::SUCCESS
}

Expand Down
109 changes: 109 additions & 0 deletions crates/astria-cli/src/query/blockheight.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
use astria_cli::config::{
SequencerNetworksConfig,
DEFAULT_SEQUENCER_URL,
};
use astria_sequencer_client::{
Client,
HttpClient,
};
use clap::{
builder::Str,
Arg,
ArgAction,
ArgMatches,
Command,
};
use color_eyre::{
eyre,
eyre::Context,
};
use home::home_dir;

pub(crate) fn command() -> Command {
let mut path = home_dir().expect("Could not determine the home directory.");
path.push(".astria");
path.push("sequencer-networks-config.toml");

Command::new("blockheight")
.about("Get the current blockheight from the sequencer")
.arg(
// flag input
Arg::new("sequencer-url")
.long("sequencer-url")
.help("URL of the sequencer")
.action(ArgAction::Set)
.default_value(DEFAULT_SEQUENCER_URL)
.env("SEQUENCER_URL"),
)
.arg(
// count bool flag
Arg::new("verbose")
.short('v')
.long("verbose")
.action(ArgAction::Count)
.help("Print debug information verbosely"),
)
.arg(
// flag input
Arg::new("network")
.long("network")
.action(ArgAction::Set)
.help("Select a network config preset"),
)
.arg(
// flag input
Arg::new("config")
.long("config")
.action(ArgAction::Set)
.help("Specify a network config file")
.default_value(Str::from(path.display().to_string())),
)
}

pub(crate) async fn run(matches: &ArgMatches) -> eyre::Result<()> {
// load and parse the config file
let config: SequencerNetworksConfig = {
let config_path = matches.get_one::<String>("config");
if let Some(path) = config_path {
SequencerNetworksConfig::load(path).expect("Could not load config file")
} else {
let mut path = home_dir().expect("Could not determine the home directory.");
path.push(".astria");
path.push("sequencer-networks-config.toml");
SequencerNetworksConfig::load(path).expect("Could not load config file")
}
};

// get verbosity cound (currently unused)
let verbose = matches.get_count("verbose");
println!("verbose count: {:?}", verbose);

// get the chosen network config
let network = matches.get_one::<String>("network");
println!("network: {:?}", network);

// get the correct sequencer_url based on all inputs
let sequenecer_url = if let Some(chosen_network) = network {
let net_config = config
.get_network(chosen_network)
.expect("network not found");
net_config.sequencer_url.clone()
} else {
let seq_url = matches.get_one::<String>("sequencer-url");
seq_url.unwrap().clone()
};

// submit the query to the sequencer
let sequencer_client = HttpClient::new(sequenecer_url.as_str())
.wrap_err("failed constructing http sequencer client")?;

let res = sequencer_client
.latest_block()
.await
.wrap_err("failed to get cometbft block")?;

println!("Block Height:");
println!(" {}", res.block.header.height);

Ok(())
}
24 changes: 24 additions & 0 deletions crates/astria-cli/src/query/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
mod blockheight;
mod nonce;

use clap::{
ArgMatches,
Command,
};
use color_eyre::eyre;

pub(crate) fn command() -> Command {
Command::new("query")
.about("Query the sequencer")
.subcommand(blockheight::command())
.subcommand(nonce::command())
}

pub(crate) async fn run(matches: &ArgMatches) -> eyre::Result<()> {
match matches.subcommand() {
Some(("blockheight", args)) => blockheight::run(args).await,
Some(("nonce", args)) => nonce::run(args).await,
// Some(("subcommand2", sub_matches)) => subcommand2::run(sub_matches, config),
_ => Err(eyre::eyre!("Unknown subcommand")),
}
}
125 changes: 125 additions & 0 deletions crates/astria-cli/src/query/nonce.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
use astria_cli::config::{
SequencerNetworksConfig,
DEFAULT_SEQUENCER_URL,
};
use astria_core::primitive::v1::Address;
use astria_sequencer_client::{
HttpClient,
SequencerClientExt,
};
use clap::{
builder::Str,
Arg,
ArgAction,
ArgMatches,
Command,
};
use color_eyre::{
eyre,
eyre::Context,
};
use home::home_dir;

pub(crate) fn command() -> Command {
// Create default path to the config file
let mut path = home_dir().expect("Could not determine the home directory.");
path.push(".astria");
path.push("sequencer-networks-config.toml");

Command::new("nonce")
.about("Get the nonce of an account")
.arg(
// flag input
Arg::new("sequencer-url")
.long("sequencer-url")
.help("URL of the sequencer")
.action(ArgAction::Set)
.default_value(DEFAULT_SEQUENCER_URL)
.env("SEQUENCER_URL"),
)
.arg(
// count bool flag
Arg::new("verbose")
.short('v')
.long("verbose")
.action(ArgAction::Count)
.help("Print debug information verbosely"),
)
.arg(
// flag input
Arg::new("network")
.long("network")
.action(ArgAction::Set)
.help("Select a network config preset"),
)
.arg(
// flag input
Arg::new("config")
.long("config")
.action(ArgAction::Set)
.help("Specify a network config file")
.default_value(Str::from(path.display().to_string())),
)
.arg(
// postional argument
Arg::new("address")
.action(ArgAction::Set)
.help("Specify a network config file")
.required(true),
)
}

pub(crate) async fn run(matches: &ArgMatches) -> eyre::Result<()> {
// load and parse the config file
let config: SequencerNetworksConfig = {
let config_path = matches.get_one::<String>("config");
if let Some(path) = config_path {
SequencerNetworksConfig::load(path).expect("Could not load config file")
} else {
let mut path = home_dir().expect("Could not determine the home directory.");
path.push(".astria");
path.push("sequencer-networks-config.toml");
SequencerNetworksConfig::load(path).expect("Could not load config file")
}
};

// get verbosity cound (currently unused)
let verbose = matches.get_count("verbose");
println!("verbose count: {:?}", verbose);

// get the chosen network config
let network = matches.get_one::<String>("network");
println!("network: {:?}", network);

// parse the input address
let address = matches
.get_one::<String>("address")
.expect("could not unwrap address");
println!("network: {:?}", address);
let address: Address = address.as_str().parse()?;

// get the correct sequencer_url based on all inputs
let sequenecer_url = if let Some(chosen_network) = network {
let net_config = config
.get_network(chosen_network)
.expect("network not found");
net_config.sequencer_url.clone()
} else {
let seq_url = matches.get_one::<String>("sequencer-url");
seq_url.unwrap().clone()
};

// submit the query to the sequencer
let sequencer_client = HttpClient::new(sequenecer_url.as_str())
.wrap_err("failed constructing http sequencer client")?;

let res = sequencer_client
.get_latest_nonce(address)
.await
.wrap_err("failed to get nonce")?;

println!("Nonce for address {}", address);
println!(" {} at height {}", res.nonce, res.height);

Ok(())
}
4 changes: 4 additions & 0 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,7 @@ _lint-proto:
buf breaking proto/sequencerblockapis --against 'buf.build/astria/sequencerblock-apis'
buf breaking proto/protocolapis --against 'buf.build/astria/protocol-apis'
buf breaking proto/composerapis --against 'buf.build/astria/composer-apis'

defaultargs := ''
cli *args=defaultargs:
cargo run -p astria-cli -- {{args}}
Loading