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

Feature(cli): Add Config to CLI #1518

Draft
wants to merge 3 commits 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
4 changes: 4 additions & 0 deletions Cargo.lock

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

4 changes: 4 additions & 0 deletions crates/astria-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ homepage = "https://astria.org"
name = "astria-cli"

[dependencies]
home = "0.5"
toml = "0.7"
env_logger = "0.10"
log = "0.4"
color-eyre = "0.6"

astria-bridge-contracts = { path = "../astria-bridge-contracts" }
Expand Down
86 changes: 86 additions & 0 deletions crates/astria-cli/src/cli/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
use std::{
collections::HashMap,
fs,
};

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

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

impl Default for NetworkConfig {
fn default() -> Self {
Self {
sequencer_chain_id: "astria-dusk-10".to_string(),
sequencer_url: "https://rpc.sequencer.dusk-10.devnet.astria.org".to_string(),
asset: "TIA".to_string(),
fee_asset: "TIA".to_string(),
}
}
}

#[derive(Debug, Deserialize, Serialize)]
pub(crate) struct Config {
networks: HashMap<String, NetworkConfig>,
}
impl Config {
/// Validate if the parsed input for choosing a network config exists and is valid
pub(crate) fn validate_network(&self, network: String) -> bool {
for (network_name, _) in &self.networks {
if *network_name == network {
return true;
}
}
false
}

/// Get a list of valid network names from the config
pub(crate) fn get_valid_networks(&self) -> Vec<String> {
let mut valid_networks = Vec::new();
for (network_name, _) in &self.networks {
valid_networks.push(network_name.clone());
}
valid_networks
}

/// Get the network config for the selected network
pub(crate) fn get_network(&self, network: String) -> Option<&NetworkConfig> {
self.networks.get(&network)
}
}

pub(crate) fn get_networks_config() -> eyre::Result<Config> {
// try to get the home directory and build the 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");

// Read the TOML file
let toml_str = fs::read_to_string(path)?;
let config: Config = toml::from_str(&toml_str)?;

// for (network_name, network_config) in &config.networks {
// println!("Network: {}", network_name);
// println!(
// " Sequencer Chain ID: {}",
// network_config.sequencer_chain_id
// );
// println!(" Sequencer URL: {}", network_config.sequencer_url);
// println!(" Asset: {}", network_config.asset);
// println!(" Fee Asset: {}", network_config.fee_asset);
// println!();
// }

Ok(config)
}
48 changes: 43 additions & 5 deletions crates/astria-cli/src/cli/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub(crate) mod bridge;
pub(crate) mod config;
pub(crate) mod sequencer;

use clap::{
Expand All @@ -7,17 +8,32 @@ use clap::{
};
use color_eyre::eyre;

use crate::cli::sequencer::Command as SequencerCommand;

const DEFAULT_SEQUENCER_RPC: &str = "https://rpc.sequencer.dusk-10.devnet.astria.org";
const DEFAULT_SEQUENCER_CHAIN_ID: &str = "astria-dusk-10";
use super::*;
use crate::cli::{
config::{
Config,
NetworkConfig,
},
sequencer::Command as SequencerCommand,
};

/// A CLI for deploying and managing Astria services and related infrastructure.
#[derive(Debug, Parser)]
#[command(name = "astria-cli", version)]
pub struct Cli {
/// Sets the log level (e.g. error, warn, info, debug, trace)
#[arg(short, long, default_value = "info")]
pub(crate) log_level: String,

/// Select the network you want to use
#[arg(short, long, default_value = "dawn")]
pub network: String,

#[command(subcommand)]
pub(crate) command: Option<Command>,

#[clap(skip)]
pub(crate) network_config: Option<NetworkConfig>,
}

impl Cli {
Expand All @@ -27,9 +43,31 @@ impl Cli {
///
/// * If the arguments cannot be parsed
pub fn get_args() -> eyre::Result<Self> {
let args = Self::parse();
let mut args = Self::parse();

let config: Config = config::get_networks_config()?;

// Validate the selected network name
if config.validate_network(args.network.clone()) {
println!("network: {:?}", args.network);
if let Some(network_config) = config.get_network(args.network.clone()) {
args.set_network_config(network_config.clone());
} else {
println!("Network config not found");
}
} else {
println!(
"Network is not valid. Expected one of: {:?}",
config.get_valid_networks()
);
}

Ok(args)
}

pub fn set_network_config(&mut self, network_config: NetworkConfig) {
self.network_config = Some(network_config);
}
}

/// Commands that can be run
Expand Down
10 changes: 9 additions & 1 deletion crates/astria-cli/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use color_eyre::{
};

use crate::cli::{
config::NetworkConfig,
sequencer::{
AccountCommand,
AddressCommand,
Expand Down Expand Up @@ -48,7 +49,14 @@ pub async fn run(cli: Cli) -> eyre::Result<()> {
} => match command {
AccountCommand::Create => sequencer::create_account(),
AccountCommand::Balance(args) => sequencer::get_balance(&args).await?,
AccountCommand::Nonce(args) => sequencer::get_nonce(&args).await?,
AccountCommand::Nonce(args) => {
if let Some(network) = &cli.network_config {
sequencer::get_nonce(&args, network.clone()).await?
} else {
let network = NetworkConfig::default();
sequencer::get_nonce(&args, network).await?
}
}
},
SequencerCommand::Address {
command,
Expand Down
71 changes: 57 additions & 14 deletions crates/astria-cli/src/commands/sequencer.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::env;

use astria_core::{
crypto::SigningKey,
primitive::v1::{
Expand Down Expand Up @@ -36,17 +38,24 @@ use color_eyre::{
};
use rand::rngs::OsRng;

use crate::cli::sequencer::{
BasicAccountArgs,
Bech32mAddressArgs,
BlockHeightGetArgs,
BridgeLockArgs,
FeeAssetChangeArgs,
IbcRelayerChangeArgs,
InitBridgeAccountArgs,
SudoAddressChangeArgs,
TransferArgs,
ValidatorUpdateArgs,
// use serde::de;
use crate::{
cli::{
config::NetworkConfig,
sequencer::{
BasicAccountArgs,
Bech32mAddressArgs,
BlockHeightGetArgs,
BridgeLockArgs,
FeeAssetChangeArgs,
IbcRelayerChangeArgs,
InitBridgeAccountArgs,
SudoAddressChangeArgs,
TransferArgs,
ValidatorUpdateArgs,
},
},
DEFAULT_SEQUENCER_RPC,
};

/// Generate a new signing key (this is also called a secret key by other implementations)
Expand Down Expand Up @@ -123,9 +132,43 @@ pub(crate) async fn get_balance(args: &BasicAccountArgs) -> eyre::Result<()> {
///
/// * If the http client cannot be created
/// * If the balance cannot be retrieved
pub(crate) async fn get_nonce(args: &BasicAccountArgs) -> eyre::Result<()> {
let sequencer_client = HttpClient::new(args.sequencer_url.as_str())
.wrap_err("failed constructing http sequencer client")?;
pub(crate) async fn get_nonce(args: &BasicAccountArgs, network: NetworkConfig) -> eyre::Result<()> {
println!("Default value: {:?}", DEFAULT_SEQUENCER_RPC);
println!("Argument value: {:?}", network.sequencer_url);

let mut seq_url = String::from(DEFAULT_SEQUENCER_RPC);

let default = String::from(DEFAULT_SEQUENCER_RPC);

// Check if the environment variable is set
let env_var = env::var("SEQUENCER_URL")
.ok()
.unwrap_or_else(|| default.clone());
println!("Environment variable: {:?}", env_var);

// Compare the input value with the default and environment variable
// this will fail if the user uses the default value as a flag argument
if args.sequencer_url != DEFAULT_SEQUENCER_RPC {
println!("Using command line value: {}", DEFAULT_SEQUENCER_RPC);
seq_url = args.sequencer_url.clone();
} else if env_var != default.as_str() {
println!(
"Argument is using the value from the environment variable: {}",
args.sequencer_url
);
seq_url = env_var;
} else if network.sequencer_url != DEFAULT_SEQUENCER_RPC {
println!("Using network config value: {}", network.sequencer_url);
seq_url = network.sequencer_url.clone();
} else {
println!(
"Argument was explicitly set by the user: {}",
args.sequencer_url
);
}

let sequencer_client =
HttpClient::new(seq_url.as_str()).wrap_err("failed constructing http sequencer client")?;

let res = sequencer_client
.get_latest_nonce(args.address)
Expand Down
3 changes: 3 additions & 0 deletions crates/astria-cli/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
pub mod cli;
pub mod commands;

pub const DEFAULT_SEQUENCER_RPC: &str = "https://rpc.sequencer.dusk-10.devnet.astria.org";
pub const DEFAULT_SEQUENCER_CHAIN_ID: &str = "astria-dusk-10";
2 changes: 2 additions & 0 deletions crates/astria-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ async fn main() -> ExitCode {
}

async fn run() -> eyre::Result<()> {
// Parse the TOML string into our Config struct
let args = Cli::get_args()?;

commands::run(args).await
}
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