From aef8d83443fefed25a232a6bcccc44b69c894fc3 Mon Sep 17 00:00:00 2001 From: Richard Janis Goldschmidt Date: Thu, 26 Sep 2024 14:22:11 +0200 Subject: [PATCH 1/6] refactor(cli): merge argument parsing and command execution --- .../src/{commands => }/bridge/collect.rs | 4 +- crates/astria-cli/src/bridge/mod.rs | 28 ++ .../src/{commands => }/bridge/submit.rs | 4 +- crates/astria-cli/src/cli/mod.rs | 46 --- crates/astria-cli/src/cli/sequencer.rs | 380 ------------------ crates/astria-cli/src/commands/bridge/mod.rs | 2 - crates/astria-cli/src/commands/mod.rs | 107 ----- crates/astria-cli/src/lib.rs | 55 ++- crates/astria-cli/src/main.rs | 20 +- crates/astria-cli/src/sequencer/account.rs | 128 ++++++ crates/astria-cli/src/sequencer/address.rs | 56 +++ crates/astria-cli/src/sequencer/balance.rs | 61 +++ .../astria-cli/src/sequencer/block_height.rs | 63 +++ .../astria-cli/src/sequencer/bridge_lock.rs | 80 ++++ .../src/sequencer/init_bridge_account.rs | 77 ++++ crates/astria-cli/src/sequencer/mod.rs | 54 +++ .../src/sequencer/sudo/fee_asset.rs | 117 ++++++ .../src/sequencer/sudo/ibc_relayer.rs | 114 ++++++ crates/astria-cli/src/sequencer/sudo/mod.rs | 31 ++ .../src/sequencer/sudo/sudo_address_change.rs | 63 +++ .../src/sequencer/sudo/validator_update.rs | 72 ++++ crates/astria-cli/src/sequencer/transfer.rs | 78 ++++ crates/astria-cli/src/utils.rs | 73 ++++ 23 files changed, 1154 insertions(+), 559 deletions(-) rename crates/astria-cli/src/{commands => }/bridge/collect.rs (99%) create mode 100644 crates/astria-cli/src/bridge/mod.rs rename crates/astria-cli/src/{commands => }/bridge/submit.rs (98%) delete mode 100644 crates/astria-cli/src/cli/mod.rs delete mode 100644 crates/astria-cli/src/cli/sequencer.rs delete mode 100644 crates/astria-cli/src/commands/bridge/mod.rs delete mode 100644 crates/astria-cli/src/commands/mod.rs create mode 100644 crates/astria-cli/src/sequencer/account.rs create mode 100644 crates/astria-cli/src/sequencer/address.rs create mode 100644 crates/astria-cli/src/sequencer/balance.rs create mode 100644 crates/astria-cli/src/sequencer/block_height.rs create mode 100644 crates/astria-cli/src/sequencer/bridge_lock.rs create mode 100644 crates/astria-cli/src/sequencer/init_bridge_account.rs create mode 100644 crates/astria-cli/src/sequencer/mod.rs create mode 100644 crates/astria-cli/src/sequencer/sudo/fee_asset.rs create mode 100644 crates/astria-cli/src/sequencer/sudo/ibc_relayer.rs create mode 100644 crates/astria-cli/src/sequencer/sudo/mod.rs create mode 100644 crates/astria-cli/src/sequencer/sudo/sudo_address_change.rs create mode 100644 crates/astria-cli/src/sequencer/sudo/validator_update.rs create mode 100644 crates/astria-cli/src/sequencer/transfer.rs create mode 100644 crates/astria-cli/src/utils.rs diff --git a/crates/astria-cli/src/commands/bridge/collect.rs b/crates/astria-cli/src/bridge/collect.rs similarity index 99% rename from crates/astria-cli/src/commands/bridge/collect.rs rename to crates/astria-cli/src/bridge/collect.rs index 531107a235..f8255fea9b 100644 --- a/crates/astria-cli/src/commands/bridge/collect.rs +++ b/crates/astria-cli/src/bridge/collect.rs @@ -50,7 +50,7 @@ use tracing::{ }; #[derive(Args, Debug)] -pub(crate) struct WithdrawalEvents { +pub(crate) struct WithdrawalEventsArgs { /// The websocket endpoint of a geth compatible rollup. #[arg(long)] rollup_endpoint: String, @@ -88,7 +88,7 @@ pub(crate) struct WithdrawalEvents { force: bool, } -impl WithdrawalEvents { +impl WithdrawalEventsArgs { pub(crate) async fn run(self) -> eyre::Result<()> { let Self { rollup_endpoint, diff --git a/crates/astria-cli/src/bridge/mod.rs b/crates/astria-cli/src/bridge/mod.rs new file mode 100644 index 0000000000..d8c2aebbd0 --- /dev/null +++ b/crates/astria-cli/src/bridge/mod.rs @@ -0,0 +1,28 @@ +pub(crate) mod collect; +pub(crate) mod submit; + +use clap::Subcommand; +use color_eyre::eyre; + +/// Interact with a Sequencer node +#[derive(Debug, clap::Args)] +pub(super) struct Args { + #[command(subcommand)] + command: Command, +} + +impl Args { + pub(super) async fn run(self) -> eyre::Result<()> { + match self.command { + Command::CollectWithdrawals(args) => args.run().await, + Command::SubmitWithdrawals(args) => args.run().await, + } + } +} + +#[derive(Debug, Subcommand)] +enum Command { + /// Commands for interacting with Sequencer accounts + CollectWithdrawals(collect::WithdrawalEventsArgs), + SubmitWithdrawals(submit::WithdrawalEventsArgs), +} diff --git a/crates/astria-cli/src/commands/bridge/submit.rs b/crates/astria-cli/src/bridge/submit.rs similarity index 98% rename from crates/astria-cli/src/commands/bridge/submit.rs rename to crates/astria-cli/src/bridge/submit.rs index c7a2a00e71..1a969426bb 100644 --- a/crates/astria-cli/src/commands/bridge/submit.rs +++ b/crates/astria-cli/src/bridge/submit.rs @@ -30,7 +30,7 @@ use tracing::{ }; #[derive(Args, Debug)] -pub(crate) struct WithdrawalEvents { +pub(crate) struct WithdrawalEventsArgs { #[arg(long, short)] input: PathBuf, #[arg(long)] @@ -43,7 +43,7 @@ pub(crate) struct WithdrawalEvents { sequencer_url: String, } -impl WithdrawalEvents { +impl WithdrawalEventsArgs { pub(crate) async fn run(self) -> eyre::Result<()> { let signing_key = read_signing_key(&self.signing_key).wrap_err_with(|| { format!( diff --git a/crates/astria-cli/src/cli/mod.rs b/crates/astria-cli/src/cli/mod.rs deleted file mode 100644 index 9b6af0119a..0000000000 --- a/crates/astria-cli/src/cli/mod.rs +++ /dev/null @@ -1,46 +0,0 @@ -pub(crate) mod bridge; -pub(crate) mod sequencer; - -use clap::{ - Parser, - Subcommand, -}; -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"; - -/// A CLI for deploying and managing Astria services and related infrastructure. -#[derive(Debug, Parser)] -#[command(name = "astria-cli", version)] -pub struct Cli { - #[command(subcommand)] - pub(crate) command: Option, -} - -impl Cli { - /// Parse the command line arguments - /// - /// # Errors - /// - /// * If the arguments cannot be parsed - pub fn get_args() -> eyre::Result { - let args = Self::parse(); - Ok(args) - } -} - -/// Commands that can be run -#[derive(Debug, Subcommand)] -pub(crate) enum Command { - Bridge { - #[command(subcommand)] - command: bridge::Command, - }, - Sequencer { - #[command(subcommand)] - command: SequencerCommand, - }, -} diff --git a/crates/astria-cli/src/cli/sequencer.rs b/crates/astria-cli/src/cli/sequencer.rs deleted file mode 100644 index 783282aedd..0000000000 --- a/crates/astria-cli/src/cli/sequencer.rs +++ /dev/null @@ -1,380 +0,0 @@ -use astria_core::primitive::v1::asset; -use astria_sequencer_client::Address; -use clap::{ - Args, - Subcommand, -}; - -/// Interact with a Sequencer node -#[derive(Debug, Subcommand)] -pub(crate) enum Command { - /// Commands for interacting with Sequencer accounts - Account { - #[command(subcommand)] - command: AccountCommand, - }, - /// Utilities for constructing and inspecting sequencer addresses - Address { - #[command(subcommand)] - command: AddressCommand, - }, - /// Commands for interacting with Sequencer balances - Balance { - #[command(subcommand)] - command: BalanceCommand, - }, - /// Commands for interacting with Sequencer block heights - #[command(name = "blockheight")] - BlockHeight { - #[command(subcommand)] - command: BlockHeightCommand, - }, - /// Commands requiring authority for Sequencer - Sudo { - #[command(subcommand)] - command: SudoCommand, - }, - /// Command for sending balance between accounts - Transfer(TransferArgs), - /// Command for initializing a bridge account - InitBridgeAccount(InitBridgeAccountArgs), - /// Command for transferring to a bridge account - BridgeLock(BridgeLockArgs), -} - -#[derive(Debug, Subcommand)] -pub(crate) enum AccountCommand { - /// Create a new Sequencer account - Create, - Balance(BasicAccountArgs), - Nonce(BasicAccountArgs), -} - -#[derive(Debug, Subcommand)] -pub(crate) enum AddressCommand { - /// Construct a bech32m Sequencer address given a public key - Bech32m(Bech32mAddressArgs), -} - -#[derive(Debug, Subcommand)] -pub(crate) enum BalanceCommand { - /// Get the balance of a Sequencer account - Get(BasicAccountArgs), -} - -#[derive(Debug, Subcommand)] -pub(crate) enum SudoCommand { - IbcRelayer { - #[command(subcommand)] - command: IbcRelayerChangeCommand, - }, - FeeAsset { - #[command(subcommand)] - command: FeeAssetChangeCommand, - }, - SudoAddressChange(SudoAddressChangeArgs), - ValidatorUpdate(ValidatorUpdateArgs), -} - -#[derive(Debug, Subcommand)] -pub(crate) enum IbcRelayerChangeCommand { - /// Add IBC Relayer - Add(IbcRelayerChangeArgs), - /// Remove IBC Relayer - Remove(IbcRelayerChangeArgs), -} - -#[derive(Debug, Subcommand)] -pub(crate) enum FeeAssetChangeCommand { - /// Add Fee Asset - Add(FeeAssetChangeArgs), - /// Remove Fee Asset - Remove(FeeAssetChangeArgs), -} - -#[derive(Args, Debug)] -pub(crate) struct BasicAccountArgs { - /// The url of the Sequencer node - #[arg( - long, - env = "SEQUENCER_URL", - default_value = crate::cli::DEFAULT_SEQUENCER_RPC - )] - pub(crate) sequencer_url: String, - /// The address of the Sequencer account - pub(crate) address: Address, -} - -#[derive(Args, Debug)] -pub(crate) struct Bech32mAddressArgs { - /// The hex formatted byte part of the bech32m address - #[arg(long)] - pub(crate) bytes: String, - /// The human readable prefix (Hrp) of the bech32m adress - #[arg(long, default_value = "astria")] - pub(crate) prefix: String, -} - -#[derive(Args, Debug)] -pub(crate) struct TransferArgs { - // The address of the Sequencer account to send amount to - pub(crate) to_address: Address, - // The amount being sent - #[arg(long)] - pub(crate) amount: u128, - /// The bech32m prefix that will be used for constructing addresses using the private key - #[arg(long, default_value = "astria")] - pub(crate) prefix: String, - /// The private key of account being sent from - #[arg(long, env = "SEQUENCER_PRIVATE_KEY")] - // TODO: https://github.com/astriaorg/astria/issues/594 - // Don't use a plain text private, prefer wrapper like from - // the secrecy crate with specialized `Debug` and `Drop` implementations - // that overwrite the key on drop and don't reveal it when printing. - pub(crate) private_key: String, - /// The url of the Sequencer node - #[arg( - long, - env = "SEQUENCER_URL", - default_value = crate::cli::DEFAULT_SEQUENCER_RPC - )] - pub(crate) sequencer_url: String, - /// The chain id of the sequencing chain being used - #[arg( - long = "sequencer.chain-id", - env = "ROLLUP_SEQUENCER_CHAIN_ID", - default_value = crate::cli::DEFAULT_SEQUENCER_CHAIN_ID - )] - pub(crate) sequencer_chain_id: String, - /// The asset to transer. - #[arg(long, default_value = "nria")] - pub(crate) asset: asset::Denom, - /// The asset to pay the transfer fees with. - #[arg(long, default_value = "nria")] - pub(crate) fee_asset: asset::Denom, -} - -#[derive(Args, Debug)] -pub(crate) struct FeeAssetChangeArgs { - /// The bech32m prefix that will be used for constructing addresses using the private key - #[arg(long, default_value = "astria")] - pub(crate) prefix: String, - // TODO: https://github.com/astriaorg/astria/issues/594 - // Don't use a plain text private, prefer wrapper like from - // the secrecy crate with specialized `Debug` and `Drop` implementations - // that overwrite the key on drop and don't reveal it when printing. - #[arg(long, env = "SEQUENCER_PRIVATE_KEY")] - pub(crate) private_key: String, - /// The url of the Sequencer node - #[arg( - long, - env = "SEQUENCER_URL", - default_value = crate::cli::DEFAULT_SEQUENCER_RPC - )] - pub(crate) sequencer_url: String, - /// The chain id of the sequencing chain being used - #[arg( - long = "sequencer.chain-id", - env = "ROLLUP_SEQUENCER_CHAIN_ID", - default_value = crate::cli::DEFAULT_SEQUENCER_CHAIN_ID - )] - pub(crate) sequencer_chain_id: String, - /// Asset's denomination string - #[arg(long)] - pub(crate) asset: asset::Denom, -} - -#[derive(Args, Debug)] -pub(crate) struct IbcRelayerChangeArgs { - /// The prefix to construct a bech32m address given the private key. - #[arg(long, default_value = "astria")] - pub(crate) prefix: String, - // TODO: https://github.com/astriaorg/astria/issues/594 - // Don't use a plain text private, prefer wrapper like from - // the secrecy crate with specialized `Debug` and `Drop` implementations - // that overwrite the key on drop and don't reveal it when printing. - #[arg(long, env = "SEQUENCER_PRIVATE_KEY")] - pub(crate) private_key: String, - /// The url of the Sequencer node - #[arg( - long, - env = "SEQUENCER_URL", - default_value = crate::cli::DEFAULT_SEQUENCER_RPC - )] - pub(crate) sequencer_url: String, - /// The chain id of the sequencing chain being used - #[arg( - long = "sequencer.chain-id", - env = "ROLLUP_SEQUENCER_CHAIN_ID", - default_value = crate::cli::DEFAULT_SEQUENCER_CHAIN_ID - )] - pub(crate) sequencer_chain_id: String, - /// The address to add or remove as an IBC relayer - #[arg(long)] - pub(crate) address: Address, -} - -#[derive(Args, Debug)] -pub(crate) struct InitBridgeAccountArgs { - /// The bech32m prefix that will be used for constructing addresses using the private key - #[arg(long, default_value = "astria")] - pub(crate) prefix: String, - // TODO: https://github.com/astriaorg/astria/issues/594 - // Don't use a plain text private, prefer wrapper like from - // the secrecy crate with specialized `Debug` and `Drop` implementations - // that overwrite the key on drop and don't reveal it when printing. - #[arg(long, env = "SEQUENCER_PRIVATE_KEY")] - pub(crate) private_key: String, - /// The url of the Sequencer node - #[arg( - long, - env = "SEQUENCER_URL", - default_value = crate::cli::DEFAULT_SEQUENCER_RPC - )] - pub(crate) sequencer_url: String, - /// The chain id of the sequencing chain being used - #[arg( - long = "sequencer.chain-id", - env = "ROLLUP_SEQUENCER_CHAIN_ID", - default_value = crate::cli::DEFAULT_SEQUENCER_CHAIN_ID - )] - pub(crate) sequencer_chain_id: String, - /// Plaintext rollup name (to be hashed into a rollup ID) - /// to initialize the bridge account with. - #[arg(long)] - pub(crate) rollup_name: String, - /// The asset to transer. - #[arg(long, default_value = "nria")] - pub(crate) asset: asset::Denom, - /// The asset to pay the transfer fees with. - #[arg(long, default_value = "nria")] - pub(crate) fee_asset: asset::Denom, -} - -#[derive(Args, Debug)] -pub(crate) struct BridgeLockArgs { - /// The address of the Sequencer account to lock amount to - pub(crate) to_address: Address, - /// The amount being locked - #[arg(long)] - pub(crate) amount: u128, - #[arg(long)] - pub(crate) destination_chain_address: String, - /// The prefix to construct a bech32m address given the private key. - #[arg(long, default_value = "astria")] - pub(crate) prefix: String, - // TODO: https://github.com/astriaorg/astria/issues/594 - // Don't use a plain text private, prefer wrapper like from - // the secrecy crate with specialized `Debug` and `Drop` implementations - // that overwrite the key on drop and don't reveal it when printing. - #[arg(long, env = "SEQUENCER_PRIVATE_KEY")] - pub(crate) private_key: String, - /// The url of the Sequencer node - #[arg( - long, - env = "SEQUENCER_URL", - default_value = crate::cli::DEFAULT_SEQUENCER_RPC - )] - pub(crate) sequencer_url: String, - /// The chain id of the sequencing chain being used - #[arg( - long = "sequencer.chain-id", - env = "ROLLUP_SEQUENCER_CHAIN_ID", - default_value = crate::cli::DEFAULT_SEQUENCER_CHAIN_ID - )] - pub(crate) sequencer_chain_id: String, - /// The asset to lock. - #[arg(long, default_value = "nria")] - pub(crate) asset: asset::Denom, - /// The asset to pay the transfer fees with. - #[arg(long, default_value = "nria")] - pub(crate) fee_asset: asset::Denom, -} - -#[derive(Debug, Subcommand)] -pub(crate) enum BlockHeightCommand { - /// Get the current block height of the Sequencer node - Get(BlockHeightGetArgs), -} - -#[derive(Args, Debug)] -pub(crate) struct BlockHeightGetArgs { - /// The url of the Sequencer node - #[arg( - long, - env = "SEQUENCER_URL", - default_value = crate::cli::DEFAULT_SEQUENCER_RPC - )] - pub(crate) sequencer_url: String, - /// The chain id of the sequencing chain being used - #[arg( - long = "sequencer.chain-id", - env = "ROLLUP_SEQUENCER_CHAIN_ID", - default_value = crate::cli::DEFAULT_SEQUENCER_CHAIN_ID - )] - pub(crate) sequencer_chain_id: String, -} - -#[derive(Args, Debug)] -pub(crate) struct SudoAddressChangeArgs { - /// The bech32m prefix that will be used for constructing addresses using the private key - #[arg(long, default_value = "astria")] - pub(crate) prefix: String, - // TODO: https://github.com/astriaorg/astria/issues/594 - // Don't use a plain text private, prefer wrapper like from - // the secrecy crate with specialized `Debug` and `Drop` implementations - // that overwrite the key on drop and don't reveal it when printing. - #[arg(long, env = "SEQUENCER_PRIVATE_KEY")] - pub(crate) private_key: String, - /// The url of the Sequencer node - #[arg( - long, - env = "SEQUENCER_URL", - default_value = crate::cli::DEFAULT_SEQUENCER_RPC - )] - pub(crate) sequencer_url: String, - /// The chain id of the sequencing chain being used - #[arg( - long = "sequencer.chain-id", - env = "ROLLUP_SEQUENCER_CHAIN_ID", - default_value = crate::cli::DEFAULT_SEQUENCER_CHAIN_ID - )] - pub(crate) sequencer_chain_id: String, - /// The new address to take over sudo privileges - #[arg(long)] - pub(crate) address: Address, -} - -#[derive(Args, Debug)] -pub(crate) struct ValidatorUpdateArgs { - /// The url of the Sequencer node - #[arg( - long, - env = "SEQUENCER_URL", - default_value = crate::cli::DEFAULT_SEQUENCER_RPC - )] - pub(crate) sequencer_url: String, - /// The chain id of the sequencing chain being used - #[arg( - long = "sequencer.chain-id", - env = "ROLLUP_SEQUENCER_CHAIN_ID", - default_value = crate::cli::DEFAULT_SEQUENCER_CHAIN_ID - )] - pub(crate) sequencer_chain_id: String, - /// The bech32m prefix that will be used for constructing addresses using the private key - #[arg(long, default_value = "astria")] - pub(crate) prefix: String, - /// The private key of the sudo account authorizing change - #[arg(long, env = "SEQUENCER_PRIVATE_KEY")] - // TODO: https://github.com/astriaorg/astria/issues/594 - // Don't use a plain text private, prefer wrapper like from - // the secrecy crate with specialized `Debug` and `Drop` implementations - // that overwrite the key on drop and don't reveal it when printing. - pub(crate) private_key: String, - /// The address of the Validator being updated - #[arg(long)] - pub(crate) validator_public_key: String, - /// The power the validator is being updated to - #[arg(long)] - pub(crate) power: u32, -} diff --git a/crates/astria-cli/src/commands/bridge/mod.rs b/crates/astria-cli/src/commands/bridge/mod.rs deleted file mode 100644 index 9f12fb87d5..0000000000 --- a/crates/astria-cli/src/commands/bridge/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub(crate) mod collect; -pub(crate) mod submit; diff --git a/crates/astria-cli/src/commands/mod.rs b/crates/astria-cli/src/commands/mod.rs deleted file mode 100644 index 436d5c74fc..0000000000 --- a/crates/astria-cli/src/commands/mod.rs +++ /dev/null @@ -1,107 +0,0 @@ -pub(crate) mod bridge; -mod sequencer; - -use color_eyre::{ - eyre, - eyre::eyre, -}; - -use crate::cli::{ - sequencer::{ - AccountCommand, - AddressCommand, - BalanceCommand, - BlockHeightCommand, - Command as SequencerCommand, - FeeAssetChangeCommand, - IbcRelayerChangeCommand, - SudoCommand, - }, - Cli, - Command, -}; - -/// Checks what function needs to be run and calls it with the appropriate arguments -/// -/// # Arguments -/// -/// * `cli` - The arguments passed to the command -/// -/// # Errors -/// -/// * If no command is specified -/// -/// # Panics -/// -/// * If the command is not recognized -pub async fn run(cli: Cli) -> eyre::Result<()> { - if let Some(command) = cli.command { - match command { - Command::Bridge { - command, - } => command.run().await?, - Command::Sequencer { - command, - } => match command { - SequencerCommand::Account { - command, - } => match command { - AccountCommand::Create => sequencer::create_account(), - AccountCommand::Balance(args) => sequencer::get_balance(&args).await?, - AccountCommand::Nonce(args) => sequencer::get_nonce(&args).await?, - }, - SequencerCommand::Address { - command, - } => match command { - AddressCommand::Bech32m(args) => sequencer::make_bech32m(&args)?, - }, - SequencerCommand::Balance { - command, - } => match command { - BalanceCommand::Get(args) => sequencer::get_balance(&args).await?, - }, - SequencerCommand::Sudo { - command, - } => match command { - SudoCommand::IbcRelayer { - command, - } => match command { - IbcRelayerChangeCommand::Add(args) => { - sequencer::ibc_relayer_add(&args).await?; - } - IbcRelayerChangeCommand::Remove(args) => { - sequencer::ibc_relayer_remove(&args).await?; - } - }, - SudoCommand::FeeAsset { - command, - } => match command { - FeeAssetChangeCommand::Add(args) => sequencer::fee_asset_add(&args).await?, - FeeAssetChangeCommand::Remove(args) => { - sequencer::fee_asset_remove(&args).await?; - } - }, - SudoCommand::ValidatorUpdate(args) => { - sequencer::validator_update(&args).await?; - } - SudoCommand::SudoAddressChange(args) => { - sequencer::sudo_address_change(&args).await?; - } - }, - SequencerCommand::Transfer(args) => sequencer::send_transfer(&args).await?, - SequencerCommand::BlockHeight { - command, - } => match command { - BlockHeightCommand::Get(args) => sequencer::get_block_height(&args).await?, - }, - SequencerCommand::InitBridgeAccount(args) => { - sequencer::init_bridge_account(&args).await?; - } - SequencerCommand::BridgeLock(args) => sequencer::bridge_lock(&args).await?, - }, - } - } else { - return Err(eyre!("Error: No command specified")); - } - Ok(()) -} diff --git a/crates/astria-cli/src/lib.rs b/crates/astria-cli/src/lib.rs index 18a4aecb56..f99295514c 100644 --- a/crates/astria-cli/src/lib.rs +++ b/crates/astria-cli/src/lib.rs @@ -1,2 +1,53 @@ -pub mod cli; -pub mod commands; +#![allow( + clippy::large_enum_variant, + reason = "the CLI contains enums with diverging variants. These are oneshot types that + are not expected to be copied, cloned, or passed around. Therefore large differences \ + between enum variants are not expected to cause performance issues." +)] + +mod bridge; +mod sequencer; +mod utils; + +use clap::{ + Parser, + Subcommand, +}; +use color_eyre::eyre; + +const DEFAULT_SEQUENCER_RPC: &str = "https://rpc.sequencer.dusk-10.devnet.astria.org"; +const DEFAULT_SEQUENCER_CHAIN_ID: &str = "astria-dusk-10"; + +/// Run commands against the Astria network. +#[derive(Debug, Parser)] +#[command(name = "astria-cli", version, about, propagate_version = true)] +pub struct Cli { + #[command(subcommand)] + command: Command, +} + +impl Cli { + /// Runs the Astria CLI. + /// + /// This is the only entry point into the Astria CLI. + /// + /// # Errors + /// + /// Returns various errors if executing a subcommand fails. The errors are + /// not explicitly listed here. + pub async fn run() -> eyre::Result<()> { + let cli = Self::parse(); + match cli.command { + Command::Bridge(bridge) => bridge.run().await, + Command::Sequencer(sequencer) => sequencer.run().await, + } + } +} + +#[derive(Debug, Subcommand)] +enum Command { + /// Collect events from a rollup and submit to Sequencer. + Bridge(bridge::Args), + /// Interact with Sequencer. + Sequencer(sequencer::Args), +} diff --git a/crates/astria-cli/src/main.rs b/crates/astria-cli/src/main.rs index c587580795..5e241d4ce8 100644 --- a/crates/astria-cli/src/main.rs +++ b/crates/astria-cli/src/main.rs @@ -1,27 +1,11 @@ -use std::process::ExitCode; - -use astria_cli::{ - cli::Cli, - commands, -}; use color_eyre::eyre; #[tokio::main] -async fn main() -> ExitCode { +async fn main() -> eyre::Result<()> { tracing_subscriber::fmt() .pretty() .with_writer(std::io::stderr) .init(); - if let Err(err) = run().await { - eprintln!("{err:?}"); - return ExitCode::FAILURE; - } - - ExitCode::SUCCESS -} - -async fn run() -> eyre::Result<()> { - let args = Cli::get_args()?; - commands::run(args).await + astria_cli::Cli::run().await } diff --git a/crates/astria-cli/src/sequencer/account.rs b/crates/astria-cli/src/sequencer/account.rs new file mode 100644 index 0000000000..3d76e1fabf --- /dev/null +++ b/crates/astria-cli/src/sequencer/account.rs @@ -0,0 +1,128 @@ +use astria_core::{ + crypto::SigningKey, + primitive::v1::Address, +}; +use astria_sequencer_client::{ + HttpClient, + SequencerClientExt as _, +}; +use clap::Subcommand; +use color_eyre::eyre::{ + self, + WrapErr as _, +}; +use rand::rngs::OsRng; + +#[derive(Debug, clap::Args)] +pub(super) struct Args { + #[command(subcommand)] + command: Command, +} + +impl Args { + pub(super) async fn run(self) -> eyre::Result<()> { + match self.command { + Command::Create(create) => create.run(), + Command::Balance(balance) => balance.run().await, + Command::Nonce(nonce) => nonce.run().await, + } + } +} + +#[derive(Debug, Subcommand)] +enum Command { + /// Generates a new ED25519 keypair. + Create(CreateArgs), + /// Queries the Sequencer for the balances of an account. + Balance(BalanceArgs), + /// Queries the Sequencer for the current nonce of an account. + Nonce(NonceArgs), +} + +#[derive(Debug, clap::Args)] +struct CreateArgs; + +impl CreateArgs { + #[expect( + clippy::unused_self, + clippy::unnecessary_wraps, + reason = "for consistency with all the other commands" + )] + fn run(self) -> eyre::Result<()> { + let signing_key = SigningKey::new(OsRng); + let pretty_signing_key = hex::encode(signing_key.as_bytes()); + let pretty_verifying_key = hex::encode(signing_key.verification_key().as_bytes()); + let pretty_address = hex::encode(signing_key.address_bytes()); + println!("Create Sequencer Account"); + println!(); + // TODO: don't print private keys to CLI, prefer writing to file: + // https://github.com/astriaorg/astria/issues/594 + println!("Private Key: {pretty_signing_key}"); + println!("Public Key: {pretty_verifying_key}"); + println!("Address: {pretty_address}"); + Ok(()) + } +} + +#[derive(Debug, clap::Args)] +struct BalanceArgs { + #[command(flatten)] + inner: ArgsInner, +} + +impl BalanceArgs { + async fn run(self) -> eyre::Result<()> { + let args = self.inner; + let sequencer_client = HttpClient::new(args.sequencer_url.as_str()) + .wrap_err("failed constructing http sequencer client")?; + + let res = sequencer_client + .get_latest_balance(args.address) + .await + .wrap_err("failed to get balance")?; + + println!("Balances for address: {}", args.address); + for balance in res.balances { + println!(" {} {}", balance.balance, balance.denom); + } + + Ok(()) + } +} + +#[derive(Debug, clap::Args)] +struct NonceArgs { + #[command(flatten)] + inner: ArgsInner, +} + +impl NonceArgs { + async fn run(self) -> eyre::Result<()> { + let args = self.inner; + let sequencer_client = HttpClient::new(args.sequencer_url.as_str()) + .wrap_err("failed constructing http sequencer client")?; + + let res = sequencer_client + .get_latest_nonce(args.address) + .await + .wrap_err("failed to get nonce")?; + + println!("Nonce for address {}", args.address); + println!(" {} at height {}", res.nonce, res.height); + + Ok(()) + } +} + +#[derive(clap::Args, Debug)] +struct ArgsInner { + /// The url of the Sequencer node + #[arg( + long, + env = "SEQUENCER_URL", + default_value = crate::DEFAULT_SEQUENCER_RPC + )] + sequencer_url: String, + /// The address of the Sequencer account + address: Address, +} diff --git a/crates/astria-cli/src/sequencer/address.rs b/crates/astria-cli/src/sequencer/address.rs new file mode 100644 index 0000000000..952211e52b --- /dev/null +++ b/crates/astria-cli/src/sequencer/address.rs @@ -0,0 +1,56 @@ +use astria_core::primitive::v1::{ + Address, + Bech32m, + ADDRESS_LEN, +}; +use color_eyre::eyre::{ + self, + WrapErr as _, +}; + +#[derive(Debug, clap::Args)] +pub(super) struct Args { + #[command(subcommand)] + command: Command, +} + +impl Args { + pub(super) fn run(self) -> eyre::Result<()> { + let Command::Bech32m(bech32m) = self.command; + bech32m.run() + } +} + +#[derive(Debug, clap::Subcommand)] +enum Command { + /// Returns a bech32m sequencer address given a prefix and hex-encoded byte slice + Bech32m(Bech32mArgs), +} + +#[derive(Debug, clap::Args)] +struct Bech32mArgs { + /// The hex formatted byte part of the bech32m address + #[arg(long)] + bytes: String, + /// The human readable prefix (Hrp) of the bech32m adress + #[arg(long, default_value = "astria")] + prefix: String, +} + +impl Bech32mArgs { + fn run(self) -> eyre::Result<()> { + use hex::FromHex as _; + let bytes = <[u8; ADDRESS_LEN]>::from_hex(&self.bytes) + .wrap_err("failed decoding provided hex bytes")?; + let address = Address::::builder() + .array(bytes) + .prefix(&self.prefix) + .try_build() + .wrap_err( + "failed constructing a valid bech32m address from the provided hex bytes and \ + prefix", + )?; + println!("{address}"); + Ok(()) + } +} diff --git a/crates/astria-cli/src/sequencer/balance.rs b/crates/astria-cli/src/sequencer/balance.rs new file mode 100644 index 0000000000..8d99bac8c2 --- /dev/null +++ b/crates/astria-cli/src/sequencer/balance.rs @@ -0,0 +1,61 @@ +use astria_core::primitive::v1::Address; +use astria_sequencer_client::{ + HttpClient, + SequencerClientExt as _, +}; +use clap::Subcommand; +use color_eyre::eyre::{ + self, + WrapErr as _, +}; + +#[derive(Debug, clap::Args)] +pub(super) struct Args { + #[command(subcommand)] + command: Command, +} + +impl Args { + pub(super) async fn run(self) -> eyre::Result<()> { + let Command::Get(get) = self.command; + get.run().await + } +} + +#[derive(Debug, Subcommand)] +enum Command { + /// Get the balance of a Sequencer account + Get(GetArgs), +} + +#[derive(clap::Args, Debug)] +struct GetArgs { + /// The url of the Sequencer node + #[arg( + long, + env = "SEQUENCER_URL", + default_value = crate::DEFAULT_SEQUENCER_RPC + )] + sequencer_url: String, + /// The address of the Sequencer account + address: Address, +} + +impl GetArgs { + async fn run(self) -> eyre::Result<()> { + let sequencer_client = HttpClient::new(self.sequencer_url.as_str()) + .wrap_err("failed constructing http sequencer client")?; + + let res = sequencer_client + .get_latest_balance(self.address) + .await + .wrap_err("failed to get balance")?; + + println!("Balances for address: {}", self.address); + for balance in res.balances { + println!(" {} {}", balance.balance, balance.denom); + } + + Ok(()) + } +} diff --git a/crates/astria-cli/src/sequencer/block_height.rs b/crates/astria-cli/src/sequencer/block_height.rs new file mode 100644 index 0000000000..1435f0977e --- /dev/null +++ b/crates/astria-cli/src/sequencer/block_height.rs @@ -0,0 +1,63 @@ +use astria_sequencer_client::{ + Client as _, + HttpClient, +}; +use clap::Subcommand; +use color_eyre::eyre::{ + self, + WrapErr as _, +}; + +#[derive(Debug, clap::Args)] +pub(super) struct Args { + #[command(subcommand)] + command: Command, +} + +impl Args { + pub(super) async fn run(self) -> eyre::Result<()> { + let Command::Get(get) = self.command; + get.run().await + } +} + +#[derive(Debug, Subcommand)] +enum Command { + /// Get the current block height of the Sequencer node + Get(GetArgs), +} + +#[derive(clap::Args, Debug)] +struct GetArgs { + /// The url of the Sequencer node + #[arg( + long, + env = "SEQUENCER_URL", + default_value = crate::DEFAULT_SEQUENCER_RPC + )] + sequencer_url: String, + /// The chain id of the sequencing chain being used + #[arg( + long = "sequencer.chain-id", + env = "ROLLUP_SEQUENCER_CHAIN_ID", + default_value = crate::DEFAULT_SEQUENCER_CHAIN_ID + )] + sequencer_chain_id: String, +} + +impl GetArgs { + async fn run(self) -> eyre::Result<()> { + let sequencer_client = HttpClient::new(self.sequencer_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(()) + } +} diff --git a/crates/astria-cli/src/sequencer/bridge_lock.rs b/crates/astria-cli/src/sequencer/bridge_lock.rs new file mode 100644 index 0000000000..4c964876d3 --- /dev/null +++ b/crates/astria-cli/src/sequencer/bridge_lock.rs @@ -0,0 +1,80 @@ +use astria_core::{ + primitive::v1::{ + asset, + Address, + }, + protocol::transaction::v1alpha1::{ + action::BridgeLockAction, + Action, + }, +}; +use color_eyre::eyre::{ + self, + WrapErr as _, +}; + +use crate::utils::submit_transaction; + +#[derive(clap::Args, Debug)] +pub(super) struct Args { + /// The address of the Sequencer account to lock amount to + to_address: Address, + /// The amount being locked + #[arg(long)] + amount: u128, + #[arg(long)] + destination_chain_address: String, + /// The prefix to construct a bech32m address given the private key. + #[arg(long, default_value = "astria")] + prefix: String, + // TODO: https://github.com/astriaorg/astria/issues/594 + // Don't use a plain text private, prefer wrapper like from + // the secrecy crate with specialized `Debug` and `Drop` implementations + // that overwrite the key on drop and don't reveal it when printing. + #[arg(long, env = "SEQUENCER_PRIVATE_KEY")] + private_key: String, + /// The url of the Sequencer node + #[arg( + long, + env = "SEQUENCER_URL", + default_value = crate::DEFAULT_SEQUENCER_RPC + )] + sequencer_url: String, + /// The chain id of the sequencing chain being used + #[arg( + long = "sequencer.chain-id", + env = "ROLLUP_SEQUENCER_CHAIN_ID", + default_value = crate::DEFAULT_SEQUENCER_CHAIN_ID + )] + sequencer_chain_id: String, + /// The asset to lock. + #[arg(long, default_value = "nria")] + asset: asset::Denom, + /// The asset to pay the transfer fees with. + #[arg(long, default_value = "nria")] + fee_asset: asset::Denom, +} + +impl Args { + pub(super) async fn run(self) -> eyre::Result<()> { + let res = submit_transaction( + self.sequencer_url.as_str(), + self.sequencer_chain_id.clone(), + &self.prefix, + self.private_key.as_str(), + Action::BridgeLock(BridgeLockAction { + to: self.to_address, + asset: self.asset.clone(), + amount: self.amount, + fee_asset: self.fee_asset.clone(), + destination_chain_address: self.destination_chain_address.clone(), + }), + ) + .await + .wrap_err("failed to submit BridgeLock transaction")?; + + println!("BridgeLock completed!"); + println!("Included in block: {}", res.height); + Ok(()) + } +} diff --git a/crates/astria-cli/src/sequencer/init_bridge_account.rs b/crates/astria-cli/src/sequencer/init_bridge_account.rs new file mode 100644 index 0000000000..a7b8f99bb4 --- /dev/null +++ b/crates/astria-cli/src/sequencer/init_bridge_account.rs @@ -0,0 +1,77 @@ +use astria_core::{ + primitive::v1::asset, + protocol::transaction::v1alpha1::{ + action::InitBridgeAccountAction, + Action, + }, +}; +use color_eyre::eyre::{ + self, + WrapErr as _, +}; + +#[derive(clap::Args, Debug)] +pub(super) struct Args { + /// The bech32m prefix that will be used for constructing addresses using the private key + #[arg(long, default_value = "astria")] + prefix: String, + // TODO: https://github.com/astriaorg/astria/issues/594 + // Don't use a plain text private, prefer wrapper like from + // the secrecy crate with specialized `Debug` and `Drop` implementations + // that overwrite the key on drop and don't reveal it when printing. + #[arg(long, env = "SEQUENCER_PRIVATE_KEY")] + private_key: String, + /// The url of the Sequencer node + #[arg( + long, + env = "SEQUENCER_URL", + default_value = crate::DEFAULT_SEQUENCER_RPC + )] + sequencer_url: String, + /// The chain id of the sequencing chain being used + #[arg( + long = "sequencer.chain-id", + env = "ROLLUP_SEQUENCER_CHAIN_ID", + default_value = crate::DEFAULT_SEQUENCER_CHAIN_ID + )] + sequencer_chain_id: String, + /// Plaintext rollup name (to be hashed into a rollup ID) + /// to initialize the bridge account with. + #[arg(long)] + rollup_name: String, + /// The asset to transer. + #[arg(long, default_value = "nria")] + asset: asset::Denom, + /// The asset to pay the transfer fees with. + #[arg(long, default_value = "nria")] + fee_asset: asset::Denom, +} + +impl Args { + pub(super) async fn run(self) -> eyre::Result<()> { + use astria_core::primitive::v1::RollupId; + + let rollup_id = RollupId::from_unhashed_bytes(self.rollup_name.as_bytes()); + let res = crate::utils::submit_transaction( + self.sequencer_url.as_str(), + self.sequencer_chain_id.clone(), + &self.prefix, + self.private_key.as_str(), + Action::InitBridgeAccount(InitBridgeAccountAction { + rollup_id, + asset: self.asset.clone(), + fee_asset: self.fee_asset.clone(), + sudo_address: None, + withdrawer_address: None, + }), + ) + .await + .wrap_err("failed to submit InitBridgeAccount transaction")?; + + println!("InitBridgeAccount completed!"); + println!("Included in block: {}", res.height); + println!("Rollup name: {}", self.rollup_name); + println!("Rollup ID: {rollup_id}"); + Ok(()) + } +} diff --git a/crates/astria-cli/src/sequencer/mod.rs b/crates/astria-cli/src/sequencer/mod.rs new file mode 100644 index 0000000000..8143197538 --- /dev/null +++ b/crates/astria-cli/src/sequencer/mod.rs @@ -0,0 +1,54 @@ +use clap::Subcommand; +use color_eyre::eyre; + +mod account; +mod address; +mod balance; +mod block_height; +mod bridge_lock; +mod init_bridge_account; +mod sudo; +mod transfer; + +#[derive(Debug, clap::Args)] +pub(super) struct Args { + #[command(subcommand)] + command: Command, +} + +impl Args { + pub(super) async fn run(self) -> eyre::Result<()> { + match self.command { + Command::Account(account) => account.run().await, + Command::Address(address) => address.run(), + Command::Balance(balance) => balance.run().await, + Command::BlockHeight(block_height) => block_height.run().await, + Command::BridgeLock(bridge_lock) => bridge_lock.run().await, + Command::InitBridgeAccount(init_bridge_account) => init_bridge_account.run().await, + Command::Sudo(sudo) => sudo.run().await, + Command::Transfer(transfer) => transfer.run().await, + } + } +} + +/// Interact with a Sequencer node +#[derive(Debug, Subcommand)] +enum Command { + /// Commands for interacting with Sequencer accounts + Account(account::Args), + /// Utilities for constructing and inspecting sequencer addresses + Address(address::Args), + /// Commands for interacting with Sequencer balances + Balance(balance::Args), + /// Commands for interacting with Sequencer block heights + #[command(name = "blockheight")] + BlockHeight(block_height::Args), + /// Command for transferring to a bridge account + BridgeLock(bridge_lock::Args), + /// Command for initializing a bridge account + InitBridgeAccount(init_bridge_account::Args), + /// Commands requiring authority for Sequencer + Sudo(sudo::Args), + /// Command for sending balance between accounts + Transfer(transfer::Args), +} diff --git a/crates/astria-cli/src/sequencer/sudo/fee_asset.rs b/crates/astria-cli/src/sequencer/sudo/fee_asset.rs new file mode 100644 index 0000000000..a1bde7b8b4 --- /dev/null +++ b/crates/astria-cli/src/sequencer/sudo/fee_asset.rs @@ -0,0 +1,117 @@ +use astria_core::{ + primitive::v1::asset, + protocol::transaction::v1alpha1::{ + action::FeeAssetChangeAction, + Action, + }, +}; +use clap::Subcommand; +use color_eyre::eyre::{ + self, + WrapErr as _, +}; + +use crate::utils::submit_transaction; + +#[derive(Debug, clap::Args)] +pub(super) struct Args { + #[command(subcommand)] + command: Command, +} + +impl Args { + pub(super) async fn run(self) -> eyre::Result<()> { + match self.command { + Command::Add(add) => add.run().await, + Command::Remove(remove) => remove.run().await, + } + } +} + +#[derive(Debug, Subcommand)] +enum Command { + /// Add Fee Asset + Add(AddArgs), + /// Remove Fee Asset + Remove(RemoveArgs), +} + +#[derive(Clone, Debug, clap::Args)] +struct AddArgs { + #[command(flatten)] + inner: ArgsInner, +} + +impl AddArgs { + async fn run(self) -> eyre::Result<()> { + let args = self.inner; + let res = submit_transaction( + args.sequencer_url.as_str(), + args.sequencer_chain_id.clone(), + &args.prefix, + args.private_key.as_str(), + Action::FeeAssetChange(FeeAssetChangeAction::Addition(args.asset.clone())), + ) + .await + .wrap_err("failed to submit FeeAssetChangeAction::Addition transaction")?; + + println!("FeeAssetChangeAction::Addition completed!"); + println!("Included in block: {}", res.height); + Ok(()) + } +} + +#[derive(Clone, Debug, clap::Args)] +struct RemoveArgs { + #[command(flatten)] + inner: ArgsInner, +} + +impl RemoveArgs { + async fn run(self) -> eyre::Result<()> { + let args = self.inner; + let res = submit_transaction( + args.sequencer_url.as_str(), + args.sequencer_chain_id.clone(), + &args.prefix, + args.private_key.as_str(), + Action::FeeAssetChange(FeeAssetChangeAction::Removal(args.asset.clone())), + ) + .await + .wrap_err("failed to submit FeeAssetChangeAction::Removal transaction")?; + + println!("FeeAssetChangeAction::Removal completed!"); + println!("Included in block: {}", res.height); + Ok(()) + } +} + +#[derive(Clone, Debug, clap::Args)] +struct ArgsInner { + /// The bech32m prefix that will be used for constructing addresses using the private key + #[arg(long, default_value = "astria")] + prefix: String, + // TODO: https://github.com/astriaorg/astria/issues/594 + // Don't use a plain text private, prefer wrapper like from + // the secrecy crate with specialized `Debug` and `Drop` implementations + // that overwrite the key on drop and don't reveal it when printing. + #[arg(long, env = "SEQUENCER_PRIVATE_KEY")] + private_key: String, + /// The url of the Sequencer node + #[arg( + long, + env = "SEQUENCER_URL", + default_value = crate::DEFAULT_SEQUENCER_RPC + )] + sequencer_url: String, + /// The chain id of the sequencing chain being used + #[arg( + long = "sequencer.chain-id", + env = "ROLLUP_SEQUENCER_CHAIN_ID", + default_value = crate::DEFAULT_SEQUENCER_CHAIN_ID + )] + sequencer_chain_id: String, + /// Asset's denomination string + #[arg(long)] + asset: asset::Denom, +} diff --git a/crates/astria-cli/src/sequencer/sudo/ibc_relayer.rs b/crates/astria-cli/src/sequencer/sudo/ibc_relayer.rs new file mode 100644 index 0000000000..426a8ddc70 --- /dev/null +++ b/crates/astria-cli/src/sequencer/sudo/ibc_relayer.rs @@ -0,0 +1,114 @@ +use astria_core::{ + primitive::v1::Address, + protocol::transaction::v1alpha1::{ + action::IbcRelayerChangeAction, + Action, + }, +}; +use color_eyre::{ + eyre, + eyre::WrapErr as _, +}; + +use crate::utils::submit_transaction; + +#[derive(Debug, clap::Args)] +pub(super) struct Args { + #[command(subcommand)] + command: Command, +} + +impl Args { + pub(super) async fn run(self) -> eyre::Result<()> { + match self.command { + Command::Add(add) => add.run().await, + Command::Remove(remove) => remove.run().await, + } + } +} + +#[derive(Debug, clap::Subcommand)] +enum Command { + Add(AddArgs), + Remove(RemoveArgs), +} + +#[derive(Debug, clap::Args)] +struct AddArgs { + #[command(flatten)] + inner: ArgsInner, +} + +impl AddArgs { + async fn run(self) -> eyre::Result<()> { + let args = self.inner; + let res = submit_transaction( + args.sequencer_url.as_str(), + args.sequencer_chain_id.clone(), + &args.prefix, + args.private_key.as_str(), + Action::IbcRelayerChange(IbcRelayerChangeAction::Addition(args.address)), + ) + .await + .wrap_err("failed to submit IbcRelayerChangeAction::Addition transaction")?; + + println!("IbcRelayerChangeAction::Addition completed!"); + println!("Included in block: {}", res.height); + Ok(()) + } +} + +#[derive(Debug, clap::Args)] +struct RemoveArgs { + #[command(flatten)] + inner: ArgsInner, +} + +impl RemoveArgs { + async fn run(self) -> eyre::Result<()> { + let args = self.inner; + let res = submit_transaction( + args.sequencer_url.as_str(), + args.sequencer_chain_id.clone(), + &args.prefix, + args.private_key.as_str(), + Action::IbcRelayerChange(IbcRelayerChangeAction::Removal(args.address)), + ) + .await + .wrap_err("failed to submit IbcRelayerChangeAction::Removal transaction")?; + + println!("IbcRelayerChangeAction::Removal completed!"); + println!("Included in block: {}", res.height); + Ok(()) + } +} + +#[derive(Debug, clap::Args)] +struct ArgsInner { + /// The prefix to construct a bech32m address given the private key. + #[arg(long, default_value = "astria")] + prefix: String, + // TODO: https://github.com/astriaorg/astria/issues/594 + // Don't use a plain text private, prefer wrapper like from + // the secrecy crate with specialized `Debug` and `Drop` implementations + // that overwrite the key on drop and don't reveal it when printing. + #[arg(long, env = "SEQUENCER_PRIVATE_KEY")] + private_key: String, + /// The url of the Sequencer node + #[arg( + long, + env = "SEQUENCER_URL", + default_value = crate::DEFAULT_SEQUENCER_RPC + )] + sequencer_url: String, + /// The chain id of the sequencing chain being used + #[arg( + long = "sequencer.chain-id", + env = "ROLLUP_SEQUENCER_CHAIN_ID", + default_value = crate::DEFAULT_SEQUENCER_CHAIN_ID + )] + sequencer_chain_id: String, + /// The address to add or remove as an IBC relayer + #[arg(long)] + address: Address, +} diff --git a/crates/astria-cli/src/sequencer/sudo/mod.rs b/crates/astria-cli/src/sequencer/sudo/mod.rs new file mode 100644 index 0000000000..368766bc45 --- /dev/null +++ b/crates/astria-cli/src/sequencer/sudo/mod.rs @@ -0,0 +1,31 @@ +use color_eyre::eyre; + +mod fee_asset; +mod ibc_relayer; +mod sudo_address_change; +mod validator_update; + +#[derive(Debug, clap::Args)] +pub(super) struct Args { + #[command(subcommand)] + command: Command, +} + +impl Args { + pub(super) async fn run(self) -> eyre::Result<()> { + match self.command { + Command::IbcRelayer(ibc_relayer) => ibc_relayer.run().await, + Command::FeeAsset(fee_asset) => fee_asset.run().await, + Command::SudoAddressChange(sudo_address_change) => sudo_address_change.run().await, + Command::ValidatorUpdate(validator_update) => validator_update.run().await, + } + } +} + +#[derive(Debug, clap::Subcommand)] +enum Command { + IbcRelayer(ibc_relayer::Args), + FeeAsset(fee_asset::Args), + SudoAddressChange(sudo_address_change::Args), + ValidatorUpdate(validator_update::Args), +} diff --git a/crates/astria-cli/src/sequencer/sudo/sudo_address_change.rs b/crates/astria-cli/src/sequencer/sudo/sudo_address_change.rs new file mode 100644 index 0000000000..70df073977 --- /dev/null +++ b/crates/astria-cli/src/sequencer/sudo/sudo_address_change.rs @@ -0,0 +1,63 @@ +use astria_core::{ + primitive::v1::Address, + protocol::transaction::v1alpha1::{ + action::SudoAddressChangeAction, + Action, + }, +}; +use color_eyre::eyre::{ + self, + WrapErr as _, +}; + +use crate::utils::submit_transaction; + +#[derive(Debug, clap::Args)] +pub(super) struct Args { + /// The bech32m prefix that will be used for constructing addresses using the private key + #[arg(long, default_value = "astria")] + prefix: String, + // TODO: https://github.com/astriaorg/astria/issues/594 + // Don't use a plain text private, prefer wrapper like from + // the secrecy crate with specialized `Debug` and `Drop` implementations + // that overwrite the key on drop and don't reveal it when printing. + #[arg(long, env = "SEQUENCER_PRIVATE_KEY")] + private_key: String, + /// The url of the Sequencer node + #[arg( + long, + env = "SEQUENCER_URL", + default_value = crate::DEFAULT_SEQUENCER_RPC + )] + sequencer_url: String, + /// The chain id of the sequencing chain being used + #[arg( + long = "sequencer.chain-id", + env = "ROLLUP_SEQUENCER_CHAIN_ID", + default_value = crate::DEFAULT_SEQUENCER_CHAIN_ID + )] + sequencer_chain_id: String, + /// The new address to take over sudo privileges + #[arg(long)] + address: Address, +} + +impl Args { + pub(super) async fn run(self) -> eyre::Result<()> { + let res = submit_transaction( + self.sequencer_url.as_str(), + self.sequencer_chain_id.clone(), + &self.prefix, + self.private_key.as_str(), + Action::SudoAddressChange(SudoAddressChangeAction { + new_address: self.address, + }), + ) + .await + .wrap_err("failed to submit SudoAddressChange transaction")?; + + println!("SudoAddressChange completed!"); + println!("Included in block: {}", res.height); + Ok(()) + } +} diff --git a/crates/astria-cli/src/sequencer/sudo/validator_update.rs b/crates/astria-cli/src/sequencer/sudo/validator_update.rs new file mode 100644 index 0000000000..15ba795487 --- /dev/null +++ b/crates/astria-cli/src/sequencer/sudo/validator_update.rs @@ -0,0 +1,72 @@ +use astria_core::protocol::transaction::v1alpha1::{ + action::ValidatorUpdate, + Action, +}; +use color_eyre::eyre::{ + self, + WrapErr as _, +}; + +use crate::utils::submit_transaction; + +#[derive(clap::Args, Debug)] +pub(super) struct Args { + /// The url of the Sequencer node + #[arg( + long, + env = "SEQUENCER_URL", + default_value = crate::DEFAULT_SEQUENCER_RPC + )] + sequencer_url: String, + /// The chain id of the sequencing chain being used + #[arg( + long = "sequencer.chain-id", + env = "ROLLUP_SEQUENCER_CHAIN_ID", + default_value = crate::DEFAULT_SEQUENCER_CHAIN_ID + )] + sequencer_chain_id: String, + /// The bech32m prefix that will be used for constructing addresses using the private key + #[arg(long, default_value = "astria")] + prefix: String, + /// The private key of the sudo account authorizing change + #[arg(long, env = "SEQUENCER_PRIVATE_KEY")] + // TODO: https://github.com/astriaorg/astria/issues/594 + // Don't use a plain text private, prefer wrapper like from + // the secrecy crate with specialized `Debug` and `Drop` implementations + // that overwrite the key on drop and don't reveal it when printing. + private_key: String, + /// The address of the Validator being updated + #[arg(long)] + validator_public_key: String, + /// The power the validator is being updated to + #[arg(long)] + power: u32, +} + +impl Args { + pub(super) async fn run(self) -> eyre::Result<()> { + let verification_key = astria_core::crypto::VerificationKey::try_from( + &*hex::decode(&self.validator_public_key) + .wrap_err("failed to decode public key bytes from argument")?, + ) + .wrap_err("failed to construct public key from bytes")?; + let validator_update = ValidatorUpdate { + power: self.power, + verification_key, + }; + + let res = submit_transaction( + self.sequencer_url.as_str(), + self.sequencer_chain_id.clone(), + &self.prefix, + self.private_key.as_str(), + Action::ValidatorUpdate(validator_update), + ) + .await + .wrap_err("failed to submit ValidatorUpdate transaction")?; + + println!("ValidatorUpdate completed!"); + println!("Included in block: {}", res.height); + Ok(()) + } +} diff --git a/crates/astria-cli/src/sequencer/transfer.rs b/crates/astria-cli/src/sequencer/transfer.rs new file mode 100644 index 0000000000..98ee7cb3a0 --- /dev/null +++ b/crates/astria-cli/src/sequencer/transfer.rs @@ -0,0 +1,78 @@ +use astria_core::{ + primitive::v1::{ + asset, + Address, + }, + protocol::transaction::v1alpha1::{ + action::TransferAction, + Action, + }, +}; +use color_eyre::eyre::{ + self, + WrapErr as _, +}; + +use crate::utils::submit_transaction; + +#[derive(clap::Args, Debug)] +pub(super) struct Args { + // The address of the Sequencer account to send amount to + to_address: Address, + // The amount being sent + #[arg(long)] + amount: u128, + /// The bech32m prefix that will be used for constructing addresses using the private key + #[arg(long, default_value = "astria")] + prefix: String, + /// The private key of account being sent from + #[arg(long, env = "SEQUENCER_PRIVATE_KEY")] + // TODO: https://github.com/astriaorg/astria/issues/594 + // Don't use a plain text private, prefer wrapper like from + // the secrecy crate with specialized `Debug` and `Drop` implementations + // that overwrite the key on drop and don't reveal it when printing. + private_key: String, + /// The url of the Sequencer node + #[arg( + long, + env = "SEQUENCER_URL", + default_value = crate::DEFAULT_SEQUENCER_RPC + )] + sequencer_url: String, + /// The chain id of the sequencing chain being used + #[arg( + long = "sequencer.chain-id", + env = "ROLLUP_SEQUENCER_CHAIN_ID", + default_value = crate::DEFAULT_SEQUENCER_CHAIN_ID + )] + sequencer_chain_id: String, + /// The asset to transer. + #[arg(long, default_value = "nria")] + asset: asset::Denom, + /// The asset to pay the transfer fees with. + #[arg(long, default_value = "nria")] + fee_asset: asset::Denom, +} + +impl Args { + pub(super) async fn run(self) -> eyre::Result<()> { + let res = submit_transaction( + self.sequencer_url.as_str(), + self.sequencer_chain_id.clone(), + &self.prefix, + self.private_key.as_str(), + Action::Transfer(TransferAction { + to: self.to_address, + amount: self.amount, + asset: self.asset.clone(), + fee_asset: self.fee_asset.clone(), + }), + ) + .await + .wrap_err("failed to submit transfer transaction")?; + + println!("Transfer completed!"); + println!("Included in block: {}", res.height); + Ok(()) + } +} diff --git a/crates/astria-cli/src/utils.rs b/crates/astria-cli/src/utils.rs new file mode 100644 index 0000000000..14b8be3600 --- /dev/null +++ b/crates/astria-cli/src/utils.rs @@ -0,0 +1,73 @@ +use astria_core::{ + crypto::SigningKey, + primitive::v1::Address, + protocol::transaction::v1alpha1::{ + Action, + TransactionParams, + UnsignedTransaction, + }, +}; +use astria_sequencer_client::{ + tendermint_rpc::endpoint::tx::Response, + HttpClient, + SequencerClientExt as _, +}; +use color_eyre::eyre::{ + self, + ensure, + eyre, + WrapErr as _, +}; + +pub(crate) async fn submit_transaction( + sequencer_url: &str, + chain_id: String, + prefix: &str, + private_key: &str, + action: Action, +) -> eyre::Result { + let sequencer_client = + HttpClient::new(sequencer_url).wrap_err("failed constructing http sequencer client")?; + + let private_key_bytes: [u8; 32] = hex::decode(private_key) + .wrap_err("failed to decode private key bytes from hex string")? + .try_into() + .map_err(|_| eyre!("invalid private key length; must be 32 bytes"))?; + let sequencer_key = SigningKey::from(private_key_bytes); + + let from_address = Address::builder() + .array(sequencer_key.verification_key().address_bytes()) + .prefix(prefix) + .try_build() + .wrap_err("failed constructing a valid from address from the provided prefix")?; + println!("sending tx from address: {from_address}"); + + let nonce_res = sequencer_client + .get_latest_nonce(from_address) + .await + .wrap_err("failed to get nonce")?; + + let tx = UnsignedTransaction { + params: TransactionParams::builder() + .nonce(nonce_res.nonce) + .chain_id(chain_id) + .build(), + actions: vec![action], + } + .into_signed(&sequencer_key); + let res = sequencer_client + .submit_transaction_sync(tx) + .await + .wrap_err("failed to submit transaction")?; + + let tx_response = sequencer_client.wait_for_tx_inclusion(res.hash).await; + + ensure!(res.code.is_ok(), "failed to check tx: {}", res.log); + + ensure!( + tx_response.tx_result.code.is_ok(), + "failed to execute tx: {}", + tx_response.tx_result.log + ); + Ok(tx_response) +} From e552a66080707b5b9cb253c74d13f45cfe55551e Mon Sep 17 00:00:00 2001 From: Richard Janis Goldschmidt Date: Thu, 26 Sep 2024 15:10:27 +0200 Subject: [PATCH 2/6] don't propagate version, don't introduce new features --- crates/astria-cli/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/astria-cli/src/lib.rs b/crates/astria-cli/src/lib.rs index f99295514c..41c4e5dadf 100644 --- a/crates/astria-cli/src/lib.rs +++ b/crates/astria-cli/src/lib.rs @@ -20,7 +20,7 @@ const DEFAULT_SEQUENCER_CHAIN_ID: &str = "astria-dusk-10"; /// Run commands against the Astria network. #[derive(Debug, Parser)] -#[command(name = "astria-cli", version, about, propagate_version = true)] +#[command(name = "astria-cli", version, about)] pub struct Cli { #[command(subcommand)] command: Command, From dcdad977e1b40fc47a2a95f6768894d4a1c1a48b Mon Sep 17 00:00:00 2001 From: Richard Janis Goldschmidt Date: Thu, 26 Sep 2024 15:20:31 +0200 Subject: [PATCH 3/6] have the bridge subcmd match the rest --- crates/astria-cli/src/bridge/collect.rs | 87 ++++--------------------- crates/astria-cli/src/bridge/mod.rs | 79 ++++++++++++++++++++-- crates/astria-cli/src/bridge/submit.rs | 9 ++- 3 files changed, 91 insertions(+), 84 deletions(-) diff --git a/crates/astria-cli/src/bridge/collect.rs b/crates/astria-cli/src/bridge/collect.rs index f8255fea9b..7d513aa4a1 100644 --- a/crates/astria-cli/src/bridge/collect.rs +++ b/crates/astria-cli/src/bridge/collect.rs @@ -1,9 +1,5 @@ use std::{ - collections::BTreeMap, - path::{ - Path, - PathBuf, - }, + path::PathBuf, sync::Arc, time::Duration, }; @@ -12,20 +8,15 @@ use astria_bridge_contracts::{ GetWithdrawalActions, GetWithdrawalActionsBuilder, }; -use astria_core::{ - primitive::v1::{ - asset::{ - self, - }, - Address, +use astria_core::primitive::v1::{ + asset::{ + self, }, - protocol::transaction::v1alpha1::Action, + Address, }; -use clap::Args; use color_eyre::eyre::{ self, bail, - ensure, eyre, OptionExt as _, WrapErr as _, @@ -49,8 +40,10 @@ use tracing::{ warn, }; -#[derive(Args, Debug)] -pub(crate) struct WithdrawalEventsArgs { +use super::ActionsByRollupHeight; + +#[derive(clap::Args, Debug)] +pub(super) struct Args { /// The websocket endpoint of a geth compatible rollup. #[arg(long)] rollup_endpoint: String, @@ -88,8 +81,8 @@ pub(crate) struct WithdrawalEventsArgs { force: bool, } -impl WithdrawalEventsArgs { - pub(crate) async fn run(self) -> eyre::Result<()> { +impl Args { + pub(super) async fn run(self) -> eyre::Result<()> { let Self { rollup_endpoint, contract_address, @@ -103,7 +96,8 @@ impl WithdrawalEventsArgs { force, } = self; - let output = open_output(&output, force).wrap_err("failed to open output for writing")?; + let output = + super::open_output(&output, force).wrap_err("failed to open output for writing")?; let block_provider = connect_to_rollup(&rollup_endpoint) .await @@ -209,36 +203,6 @@ async fn block_to_actions( actions_by_rollup_height.insert(rollup_height, actions) } -#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] -#[serde(transparent)] -pub(crate) struct ActionsByRollupHeight(BTreeMap>); - -impl ActionsByRollupHeight { - fn new() -> Self { - Self(BTreeMap::new()) - } - - pub(crate) fn into_inner(self) -> BTreeMap> { - self.0 - } - - #[instrument(skip_all, err)] - fn insert(&mut self, rollup_height: u64, actions: Vec) -> eyre::Result<()> { - ensure!( - self.0.insert(rollup_height, actions).is_none(), - "already collected actions for block at rollup height `{rollup_height}`; no 2 blocks \ - with the same height should have been seen", - ); - Ok(()) - } - - #[instrument(skip_all, fields(target = %output.path.display()), err)] - fn write_to_output(self, output: Output) -> eyre::Result<()> { - let writer = std::io::BufWriter::new(output.handle); - serde_json::to_writer(writer, &self.0).wrap_err("failed writing actions to file") - } -} - /// Constructs a block stream from `start` until `maybe_end`, if `Some`. /// Constructs an open ended stream from `start` if `None`. #[instrument(skip_all, fields(start, end = maybe_end), err)] @@ -292,31 +256,6 @@ async fn create_stream_of_blocks( Ok(subscription) } -#[derive(Debug)] -struct Output { - handle: std::fs::File, - path: PathBuf, -} - -#[instrument(skip(target), fields(target = %target.as_ref().display()), err)] -fn open_output>(target: P, overwrite: bool) -> eyre::Result { - let handle = if overwrite { - let mut options = std::fs::File::options(); - options.write(true).create(true).truncate(true); - options - } else { - let mut options = std::fs::File::options(); - options.write(true).create_new(true); - options - } - .open(&target) - .wrap_err("failed to open specified file for writing")?; - Ok(Output { - handle, - path: target.as_ref().to_path_buf(), - }) -} - #[instrument(err)] async fn connect_to_rollup(rollup_endpoint: &str) -> eyre::Result>> { let retry_config = tryhard::RetryFutureConfig::new(10) diff --git a/crates/astria-cli/src/bridge/mod.rs b/crates/astria-cli/src/bridge/mod.rs index d8c2aebbd0..c64f9e5065 100644 --- a/crates/astria-cli/src/bridge/mod.rs +++ b/crates/astria-cli/src/bridge/mod.rs @@ -1,8 +1,22 @@ -pub(crate) mod collect; -pub(crate) mod submit; +mod collect; +mod submit; +use std::{ + collections::BTreeMap, + path::{ + Path, + PathBuf, + }, +}; + +use astria_core::protocol::transaction::v1alpha1::Action; use clap::Subcommand; -use color_eyre::eyre; +use color_eyre::eyre::{ + self, + ensure, + WrapErr as _, +}; +use tracing::instrument; /// Interact with a Sequencer node #[derive(Debug, clap::Args)] @@ -23,6 +37,61 @@ impl Args { #[derive(Debug, Subcommand)] enum Command { /// Commands for interacting with Sequencer accounts - CollectWithdrawals(collect::WithdrawalEventsArgs), - SubmitWithdrawals(submit::WithdrawalEventsArgs), + CollectWithdrawals(collect::Args), + SubmitWithdrawals(submit::Args), +} + +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] +#[serde(transparent)] +struct ActionsByRollupHeight(BTreeMap>); + +impl ActionsByRollupHeight { + fn new() -> Self { + Self(BTreeMap::new()) + } + + fn into_inner(self) -> BTreeMap> { + self.0 + } + + #[instrument(skip_all, err)] + fn insert(&mut self, rollup_height: u64, actions: Vec) -> eyre::Result<()> { + ensure!( + self.0.insert(rollup_height, actions).is_none(), + "already collected actions for block at rollup height `{rollup_height}`; no 2 blocks \ + with the same height should have been seen", + ); + Ok(()) + } + + #[instrument(skip_all, fields(target = %output.path.display()), err)] + fn write_to_output(self, output: Output) -> eyre::Result<()> { + let writer = std::io::BufWriter::new(output.handle); + serde_json::to_writer(writer, &self.0).wrap_err("failed writing actions to file") + } +} + +#[derive(Debug)] +struct Output { + handle: std::fs::File, + path: PathBuf, +} + +#[instrument(skip(target), fields(target = %target.as_ref().display()), err)] +fn open_output>(target: P, overwrite: bool) -> eyre::Result { + let handle = if overwrite { + let mut options = std::fs::File::options(); + options.write(true).create(true).truncate(true); + options + } else { + let mut options = std::fs::File::options(); + options.write(true).create_new(true); + options + } + .open(&target) + .wrap_err("failed to open specified file for writing")?; + Ok(Output { + handle, + path: target.as_ref().to_path_buf(), + }) } diff --git a/crates/astria-cli/src/bridge/submit.rs b/crates/astria-cli/src/bridge/submit.rs index 1a969426bb..7baeec8e6e 100644 --- a/crates/astria-cli/src/bridge/submit.rs +++ b/crates/astria-cli/src/bridge/submit.rs @@ -16,7 +16,6 @@ use astria_sequencer_client::{ HttpClient, SequencerClientExt as _, }; -use clap::Args; use color_eyre::eyre::{ self, ensure, @@ -29,8 +28,8 @@ use tracing::{ warn, }; -#[derive(Args, Debug)] -pub(crate) struct WithdrawalEventsArgs { +#[derive(clap::Args, Debug)] +pub(crate) struct Args { #[arg(long, short)] input: PathBuf, #[arg(long)] @@ -43,7 +42,7 @@ pub(crate) struct WithdrawalEventsArgs { sequencer_url: String, } -impl WithdrawalEventsArgs { +impl Args { pub(crate) async fn run(self) -> eyre::Result<()> { let signing_key = read_signing_key(&self.signing_key).wrap_err_with(|| { format!( @@ -96,7 +95,7 @@ impl WithdrawalEventsArgs { } } -fn read_actions>(path: P) -> eyre::Result { +fn read_actions>(path: P) -> eyre::Result { let s = std::fs::read_to_string(path).wrap_err("failed buffering file contents as string")?; serde_json::from_str(&s) .wrap_err("failed deserializing file contents height-to-sequencer-actions serde object") From 0e14a194d9236c54473dea1c29948d31bac902c4 Mon Sep 17 00:00:00 2001 From: Richard Janis Goldschmidt Date: Mon, 30 Sep 2024 11:05:01 +0200 Subject: [PATCH 4/6] rename Command -> SubCommand, Args -> Command --- crates/astria-cli/src/sequencer/account.rs | 32 +++++++++---------- crates/astria-cli/src/sequencer/address.rs | 19 ++++++----- crates/astria-cli/src/sequencer/balance.rs | 16 +++++----- .../astria-cli/src/sequencer/block_height.rs | 16 +++++----- .../astria-cli/src/sequencer/bridge_lock.rs | 4 +-- .../src/sequencer/init_bridge_account.rs | 4 +-- crates/astria-cli/src/sequencer/mod.rs | 16 +++++----- .../src/sequencer/sudo/fee_asset.rs | 24 +++++++------- .../src/sequencer/sudo/ibc_relayer.rs | 24 +++++++------- crates/astria-cli/src/sequencer/sudo/mod.rs | 24 +++++++------- .../src/sequencer/sudo/sudo_address_change.rs | 4 +-- .../src/sequencer/sudo/validator_update.rs | 4 +-- crates/astria-cli/src/sequencer/transfer.rs | 4 +-- 13 files changed, 95 insertions(+), 96 deletions(-) diff --git a/crates/astria-cli/src/sequencer/account.rs b/crates/astria-cli/src/sequencer/account.rs index 3d76e1fabf..ba08428453 100644 --- a/crates/astria-cli/src/sequencer/account.rs +++ b/crates/astria-cli/src/sequencer/account.rs @@ -14,35 +14,35 @@ use color_eyre::eyre::{ use rand::rngs::OsRng; #[derive(Debug, clap::Args)] -pub(super) struct Args { +pub(super) struct Command { #[command(subcommand)] - command: Command, + command: SubCommand, } -impl Args { +impl Command { pub(super) async fn run(self) -> eyre::Result<()> { match self.command { - Command::Create(create) => create.run(), - Command::Balance(balance) => balance.run().await, - Command::Nonce(nonce) => nonce.run().await, + SubCommand::Create(create) => create.run(), + SubCommand::Balance(balance) => balance.run().await, + SubCommand::Nonce(nonce) => nonce.run().await, } } } #[derive(Debug, Subcommand)] -enum Command { +enum SubCommand { /// Generates a new ED25519 keypair. - Create(CreateArgs), + Create(Create), /// Queries the Sequencer for the balances of an account. - Balance(BalanceArgs), + Balance(Balance), /// Queries the Sequencer for the current nonce of an account. - Nonce(NonceArgs), + Nonce(Nonce), } #[derive(Debug, clap::Args)] -struct CreateArgs; +struct Create; -impl CreateArgs { +impl Create { #[expect( clippy::unused_self, clippy::unnecessary_wraps, @@ -65,12 +65,12 @@ impl CreateArgs { } #[derive(Debug, clap::Args)] -struct BalanceArgs { +struct Balance { #[command(flatten)] inner: ArgsInner, } -impl BalanceArgs { +impl Balance { async fn run(self) -> eyre::Result<()> { let args = self.inner; let sequencer_client = HttpClient::new(args.sequencer_url.as_str()) @@ -91,12 +91,12 @@ impl BalanceArgs { } #[derive(Debug, clap::Args)] -struct NonceArgs { +struct Nonce { #[command(flatten)] inner: ArgsInner, } -impl NonceArgs { +impl Nonce { async fn run(self) -> eyre::Result<()> { let args = self.inner; let sequencer_client = HttpClient::new(args.sequencer_url.as_str()) diff --git a/crates/astria-cli/src/sequencer/address.rs b/crates/astria-cli/src/sequencer/address.rs index 952211e52b..fa7dcee7cb 100644 --- a/crates/astria-cli/src/sequencer/address.rs +++ b/crates/astria-cli/src/sequencer/address.rs @@ -1,6 +1,5 @@ use astria_core::primitive::v1::{ Address, - Bech32m, ADDRESS_LEN, }; use color_eyre::eyre::{ @@ -9,26 +8,26 @@ use color_eyre::eyre::{ }; #[derive(Debug, clap::Args)] -pub(super) struct Args { +pub(super) struct Command { #[command(subcommand)] - command: Command, + command: SubCommand, } -impl Args { +impl Command { pub(super) fn run(self) -> eyre::Result<()> { - let Command::Bech32m(bech32m) = self.command; + let SubCommand::Bech32m(bech32m) = self.command; bech32m.run() } } #[derive(Debug, clap::Subcommand)] -enum Command { +enum SubCommand { /// Returns a bech32m sequencer address given a prefix and hex-encoded byte slice - Bech32m(Bech32mArgs), + Bech32m(Bech32m), } #[derive(Debug, clap::Args)] -struct Bech32mArgs { +struct Bech32m { /// The hex formatted byte part of the bech32m address #[arg(long)] bytes: String, @@ -37,12 +36,12 @@ struct Bech32mArgs { prefix: String, } -impl Bech32mArgs { +impl Bech32m { fn run(self) -> eyre::Result<()> { use hex::FromHex as _; let bytes = <[u8; ADDRESS_LEN]>::from_hex(&self.bytes) .wrap_err("failed decoding provided hex bytes")?; - let address = Address::::builder() + let address = Address::::builder() .array(bytes) .prefix(&self.prefix) .try_build() diff --git a/crates/astria-cli/src/sequencer/balance.rs b/crates/astria-cli/src/sequencer/balance.rs index 8d99bac8c2..2533dcc122 100644 --- a/crates/astria-cli/src/sequencer/balance.rs +++ b/crates/astria-cli/src/sequencer/balance.rs @@ -10,26 +10,26 @@ use color_eyre::eyre::{ }; #[derive(Debug, clap::Args)] -pub(super) struct Args { +pub(super) struct Command { #[command(subcommand)] - command: Command, + command: SubCommand, } -impl Args { +impl Command { pub(super) async fn run(self) -> eyre::Result<()> { - let Command::Get(get) = self.command; + let SubCommand::Get(get) = self.command; get.run().await } } #[derive(Debug, Subcommand)] -enum Command { +enum SubCommand { /// Get the balance of a Sequencer account - Get(GetArgs), + Get(Get), } #[derive(clap::Args, Debug)] -struct GetArgs { +struct Get { /// The url of the Sequencer node #[arg( long, @@ -41,7 +41,7 @@ struct GetArgs { address: Address, } -impl GetArgs { +impl Get { async fn run(self) -> eyre::Result<()> { let sequencer_client = HttpClient::new(self.sequencer_url.as_str()) .wrap_err("failed constructing http sequencer client")?; diff --git a/crates/astria-cli/src/sequencer/block_height.rs b/crates/astria-cli/src/sequencer/block_height.rs index 1435f0977e..6e9f1df628 100644 --- a/crates/astria-cli/src/sequencer/block_height.rs +++ b/crates/astria-cli/src/sequencer/block_height.rs @@ -9,26 +9,26 @@ use color_eyre::eyre::{ }; #[derive(Debug, clap::Args)] -pub(super) struct Args { +pub(super) struct Command { #[command(subcommand)] - command: Command, + command: SubCommand, } -impl Args { +impl Command { pub(super) async fn run(self) -> eyre::Result<()> { - let Command::Get(get) = self.command; + let SubCommand::Get(get) = self.command; get.run().await } } #[derive(Debug, Subcommand)] -enum Command { +enum SubCommand { /// Get the current block height of the Sequencer node - Get(GetArgs), + Get(Get), } #[derive(clap::Args, Debug)] -struct GetArgs { +struct Get { /// The url of the Sequencer node #[arg( long, @@ -45,7 +45,7 @@ struct GetArgs { sequencer_chain_id: String, } -impl GetArgs { +impl Get { async fn run(self) -> eyre::Result<()> { let sequencer_client = HttpClient::new(self.sequencer_url.as_str()) .wrap_err("failed constructing http sequencer client")?; diff --git a/crates/astria-cli/src/sequencer/bridge_lock.rs b/crates/astria-cli/src/sequencer/bridge_lock.rs index 4c964876d3..baeabbc717 100644 --- a/crates/astria-cli/src/sequencer/bridge_lock.rs +++ b/crates/astria-cli/src/sequencer/bridge_lock.rs @@ -16,7 +16,7 @@ use color_eyre::eyre::{ use crate::utils::submit_transaction; #[derive(clap::Args, Debug)] -pub(super) struct Args { +pub(super) struct Command { /// The address of the Sequencer account to lock amount to to_address: Address, /// The amount being locked @@ -55,7 +55,7 @@ pub(super) struct Args { fee_asset: asset::Denom, } -impl Args { +impl Command { pub(super) async fn run(self) -> eyre::Result<()> { let res = submit_transaction( self.sequencer_url.as_str(), diff --git a/crates/astria-cli/src/sequencer/init_bridge_account.rs b/crates/astria-cli/src/sequencer/init_bridge_account.rs index a7b8f99bb4..1e411457eb 100644 --- a/crates/astria-cli/src/sequencer/init_bridge_account.rs +++ b/crates/astria-cli/src/sequencer/init_bridge_account.rs @@ -11,7 +11,7 @@ use color_eyre::eyre::{ }; #[derive(clap::Args, Debug)] -pub(super) struct Args { +pub(super) struct Command { /// The bech32m prefix that will be used for constructing addresses using the private key #[arg(long, default_value = "astria")] prefix: String, @@ -47,7 +47,7 @@ pub(super) struct Args { fee_asset: asset::Denom, } -impl Args { +impl Command { pub(super) async fn run(self) -> eyre::Result<()> { use astria_core::primitive::v1::RollupId; diff --git a/crates/astria-cli/src/sequencer/mod.rs b/crates/astria-cli/src/sequencer/mod.rs index 8143197538..d0bc48982a 100644 --- a/crates/astria-cli/src/sequencer/mod.rs +++ b/crates/astria-cli/src/sequencer/mod.rs @@ -35,20 +35,20 @@ impl Args { #[derive(Debug, Subcommand)] enum Command { /// Commands for interacting with Sequencer accounts - Account(account::Args), + Account(account::Command), /// Utilities for constructing and inspecting sequencer addresses - Address(address::Args), + Address(address::Command), /// Commands for interacting with Sequencer balances - Balance(balance::Args), + Balance(balance::Command), /// Commands for interacting with Sequencer block heights #[command(name = "blockheight")] - BlockHeight(block_height::Args), + BlockHeight(block_height::Command), /// Command for transferring to a bridge account - BridgeLock(bridge_lock::Args), + BridgeLock(bridge_lock::Command), /// Command for initializing a bridge account - InitBridgeAccount(init_bridge_account::Args), + InitBridgeAccount(init_bridge_account::Command), /// Commands requiring authority for Sequencer - Sudo(sudo::Args), + Sudo(sudo::Command), /// Command for sending balance between accounts - Transfer(transfer::Args), + Transfer(transfer::Command), } diff --git a/crates/astria-cli/src/sequencer/sudo/fee_asset.rs b/crates/astria-cli/src/sequencer/sudo/fee_asset.rs index a1bde7b8b4..181c833cda 100644 --- a/crates/astria-cli/src/sequencer/sudo/fee_asset.rs +++ b/crates/astria-cli/src/sequencer/sudo/fee_asset.rs @@ -14,35 +14,35 @@ use color_eyre::eyre::{ use crate::utils::submit_transaction; #[derive(Debug, clap::Args)] -pub(super) struct Args { +pub(super) struct Command { #[command(subcommand)] - command: Command, + command: SubCommand, } -impl Args { +impl Command { pub(super) async fn run(self) -> eyre::Result<()> { match self.command { - Command::Add(add) => add.run().await, - Command::Remove(remove) => remove.run().await, + SubCommand::Add(add) => add.run().await, + SubCommand::Remove(remove) => remove.run().await, } } } #[derive(Debug, Subcommand)] -enum Command { +enum SubCommand { /// Add Fee Asset - Add(AddArgs), + Add(Add), /// Remove Fee Asset - Remove(RemoveArgs), + Remove(Remove), } #[derive(Clone, Debug, clap::Args)] -struct AddArgs { +struct Add { #[command(flatten)] inner: ArgsInner, } -impl AddArgs { +impl Add { async fn run(self) -> eyre::Result<()> { let args = self.inner; let res = submit_transaction( @@ -62,12 +62,12 @@ impl AddArgs { } #[derive(Clone, Debug, clap::Args)] -struct RemoveArgs { +struct Remove { #[command(flatten)] inner: ArgsInner, } -impl RemoveArgs { +impl Remove { async fn run(self) -> eyre::Result<()> { let args = self.inner; let res = submit_transaction( diff --git a/crates/astria-cli/src/sequencer/sudo/ibc_relayer.rs b/crates/astria-cli/src/sequencer/sudo/ibc_relayer.rs index 426a8ddc70..f8c190b157 100644 --- a/crates/astria-cli/src/sequencer/sudo/ibc_relayer.rs +++ b/crates/astria-cli/src/sequencer/sudo/ibc_relayer.rs @@ -13,33 +13,33 @@ use color_eyre::{ use crate::utils::submit_transaction; #[derive(Debug, clap::Args)] -pub(super) struct Args { +pub(super) struct Command { #[command(subcommand)] - command: Command, + command: SubCommand, } -impl Args { +impl Command { pub(super) async fn run(self) -> eyre::Result<()> { match self.command { - Command::Add(add) => add.run().await, - Command::Remove(remove) => remove.run().await, + SubCommand::Add(add) => add.run().await, + SubCommand::Remove(remove) => remove.run().await, } } } #[derive(Debug, clap::Subcommand)] -enum Command { - Add(AddArgs), - Remove(RemoveArgs), +enum SubCommand { + Add(Add), + Remove(Remove), } #[derive(Debug, clap::Args)] -struct AddArgs { +struct Add { #[command(flatten)] inner: ArgsInner, } -impl AddArgs { +impl Add { async fn run(self) -> eyre::Result<()> { let args = self.inner; let res = submit_transaction( @@ -59,12 +59,12 @@ impl AddArgs { } #[derive(Debug, clap::Args)] -struct RemoveArgs { +struct Remove { #[command(flatten)] inner: ArgsInner, } -impl RemoveArgs { +impl Remove { async fn run(self) -> eyre::Result<()> { let args = self.inner; let res = submit_transaction( diff --git a/crates/astria-cli/src/sequencer/sudo/mod.rs b/crates/astria-cli/src/sequencer/sudo/mod.rs index 368766bc45..b983d2eb03 100644 --- a/crates/astria-cli/src/sequencer/sudo/mod.rs +++ b/crates/astria-cli/src/sequencer/sudo/mod.rs @@ -6,26 +6,26 @@ mod sudo_address_change; mod validator_update; #[derive(Debug, clap::Args)] -pub(super) struct Args { +pub(super) struct Command { #[command(subcommand)] - command: Command, + command: SubCommand, } -impl Args { +impl Command { pub(super) async fn run(self) -> eyre::Result<()> { match self.command { - Command::IbcRelayer(ibc_relayer) => ibc_relayer.run().await, - Command::FeeAsset(fee_asset) => fee_asset.run().await, - Command::SudoAddressChange(sudo_address_change) => sudo_address_change.run().await, - Command::ValidatorUpdate(validator_update) => validator_update.run().await, + SubCommand::IbcRelayer(ibc_relayer) => ibc_relayer.run().await, + SubCommand::FeeAsset(fee_asset) => fee_asset.run().await, + SubCommand::SudoAddressChange(sudo_address_change) => sudo_address_change.run().await, + SubCommand::ValidatorUpdate(validator_update) => validator_update.run().await, } } } #[derive(Debug, clap::Subcommand)] -enum Command { - IbcRelayer(ibc_relayer::Args), - FeeAsset(fee_asset::Args), - SudoAddressChange(sudo_address_change::Args), - ValidatorUpdate(validator_update::Args), +enum SubCommand { + IbcRelayer(ibc_relayer::Command), + FeeAsset(fee_asset::Command), + SudoAddressChange(sudo_address_change::Command), + ValidatorUpdate(validator_update::Command), } diff --git a/crates/astria-cli/src/sequencer/sudo/sudo_address_change.rs b/crates/astria-cli/src/sequencer/sudo/sudo_address_change.rs index 70df073977..f93277440e 100644 --- a/crates/astria-cli/src/sequencer/sudo/sudo_address_change.rs +++ b/crates/astria-cli/src/sequencer/sudo/sudo_address_change.rs @@ -13,7 +13,7 @@ use color_eyre::eyre::{ use crate::utils::submit_transaction; #[derive(Debug, clap::Args)] -pub(super) struct Args { +pub(super) struct Command { /// The bech32m prefix that will be used for constructing addresses using the private key #[arg(long, default_value = "astria")] prefix: String, @@ -42,7 +42,7 @@ pub(super) struct Args { address: Address, } -impl Args { +impl Command { pub(super) async fn run(self) -> eyre::Result<()> { let res = submit_transaction( self.sequencer_url.as_str(), diff --git a/crates/astria-cli/src/sequencer/sudo/validator_update.rs b/crates/astria-cli/src/sequencer/sudo/validator_update.rs index 15ba795487..bcbca1832f 100644 --- a/crates/astria-cli/src/sequencer/sudo/validator_update.rs +++ b/crates/astria-cli/src/sequencer/sudo/validator_update.rs @@ -10,7 +10,7 @@ use color_eyre::eyre::{ use crate::utils::submit_transaction; #[derive(clap::Args, Debug)] -pub(super) struct Args { +pub(super) struct Command { /// The url of the Sequencer node #[arg( long, @@ -43,7 +43,7 @@ pub(super) struct Args { power: u32, } -impl Args { +impl Command { pub(super) async fn run(self) -> eyre::Result<()> { let verification_key = astria_core::crypto::VerificationKey::try_from( &*hex::decode(&self.validator_public_key) diff --git a/crates/astria-cli/src/sequencer/transfer.rs b/crates/astria-cli/src/sequencer/transfer.rs index 98ee7cb3a0..648156cd3e 100644 --- a/crates/astria-cli/src/sequencer/transfer.rs +++ b/crates/astria-cli/src/sequencer/transfer.rs @@ -16,7 +16,7 @@ use color_eyre::eyre::{ use crate::utils::submit_transaction; #[derive(clap::Args, Debug)] -pub(super) struct Args { +pub(super) struct Command { // The address of the Sequencer account to send amount to to_address: Address, // The amount being sent @@ -54,7 +54,7 @@ pub(super) struct Args { fee_asset: asset::Denom, } -impl Args { +impl Command { pub(super) async fn run(self) -> eyre::Result<()> { let res = submit_transaction( self.sequencer_url.as_str(), From 174b5fcb3c61fbfdfd0c6bff9999c786d424c30e Mon Sep 17 00:00:00 2001 From: Richard Janis Goldschmidt Date: Mon, 30 Sep 2024 19:36:17 +0200 Subject: [PATCH 5/6] overlooked renames --- crates/astria-cli/src/bridge/collect.rs | 4 ++-- crates/astria-cli/src/bridge/mod.rs | 16 ++++++++-------- crates/astria-cli/src/bridge/submit.rs | 4 ++-- crates/astria-cli/src/lib.rs | 4 ++-- crates/astria-cli/src/sequencer/mod.rs | 24 ++++++++++++------------ 5 files changed, 26 insertions(+), 26 deletions(-) diff --git a/crates/astria-cli/src/bridge/collect.rs b/crates/astria-cli/src/bridge/collect.rs index 7d513aa4a1..01fc605387 100644 --- a/crates/astria-cli/src/bridge/collect.rs +++ b/crates/astria-cli/src/bridge/collect.rs @@ -43,7 +43,7 @@ use tracing::{ use super::ActionsByRollupHeight; #[derive(clap::Args, Debug)] -pub(super) struct Args { +pub(super) struct Command { /// The websocket endpoint of a geth compatible rollup. #[arg(long)] rollup_endpoint: String, @@ -81,7 +81,7 @@ pub(super) struct Args { force: bool, } -impl Args { +impl Command { pub(super) async fn run(self) -> eyre::Result<()> { let Self { rollup_endpoint, diff --git a/crates/astria-cli/src/bridge/mod.rs b/crates/astria-cli/src/bridge/mod.rs index c64f9e5065..313b886f15 100644 --- a/crates/astria-cli/src/bridge/mod.rs +++ b/crates/astria-cli/src/bridge/mod.rs @@ -20,25 +20,25 @@ use tracing::instrument; /// Interact with a Sequencer node #[derive(Debug, clap::Args)] -pub(super) struct Args { +pub(super) struct Command { #[command(subcommand)] - command: Command, + command: SubCommand, } -impl Args { +impl Command { pub(super) async fn run(self) -> eyre::Result<()> { match self.command { - Command::CollectWithdrawals(args) => args.run().await, - Command::SubmitWithdrawals(args) => args.run().await, + SubCommand::CollectWithdrawals(args) => args.run().await, + SubCommand::SubmitWithdrawals(args) => args.run().await, } } } #[derive(Debug, Subcommand)] -enum Command { +enum SubCommand { /// Commands for interacting with Sequencer accounts - CollectWithdrawals(collect::Args), - SubmitWithdrawals(submit::Args), + CollectWithdrawals(collect::Command), + SubmitWithdrawals(submit::Command), } #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] diff --git a/crates/astria-cli/src/bridge/submit.rs b/crates/astria-cli/src/bridge/submit.rs index 7baeec8e6e..7d40ed1f87 100644 --- a/crates/astria-cli/src/bridge/submit.rs +++ b/crates/astria-cli/src/bridge/submit.rs @@ -29,7 +29,7 @@ use tracing::{ }; #[derive(clap::Args, Debug)] -pub(crate) struct Args { +pub(crate) struct Command { #[arg(long, short)] input: PathBuf, #[arg(long)] @@ -42,7 +42,7 @@ pub(crate) struct Args { sequencer_url: String, } -impl Args { +impl Command { pub(crate) async fn run(self) -> eyre::Result<()> { let signing_key = read_signing_key(&self.signing_key).wrap_err_with(|| { format!( diff --git a/crates/astria-cli/src/lib.rs b/crates/astria-cli/src/lib.rs index 41c4e5dadf..e4d7a90ba8 100644 --- a/crates/astria-cli/src/lib.rs +++ b/crates/astria-cli/src/lib.rs @@ -47,7 +47,7 @@ impl Cli { #[derive(Debug, Subcommand)] enum Command { /// Collect events from a rollup and submit to Sequencer. - Bridge(bridge::Args), + Bridge(bridge::Command), /// Interact with Sequencer. - Sequencer(sequencer::Args), + Sequencer(sequencer::Command), } diff --git a/crates/astria-cli/src/sequencer/mod.rs b/crates/astria-cli/src/sequencer/mod.rs index d0bc48982a..f4bb35f0c6 100644 --- a/crates/astria-cli/src/sequencer/mod.rs +++ b/crates/astria-cli/src/sequencer/mod.rs @@ -11,29 +11,29 @@ mod sudo; mod transfer; #[derive(Debug, clap::Args)] -pub(super) struct Args { +pub(super) struct Command { #[command(subcommand)] - command: Command, + command: SubCommand, } -impl Args { +impl Command { pub(super) async fn run(self) -> eyre::Result<()> { match self.command { - Command::Account(account) => account.run().await, - Command::Address(address) => address.run(), - Command::Balance(balance) => balance.run().await, - Command::BlockHeight(block_height) => block_height.run().await, - Command::BridgeLock(bridge_lock) => bridge_lock.run().await, - Command::InitBridgeAccount(init_bridge_account) => init_bridge_account.run().await, - Command::Sudo(sudo) => sudo.run().await, - Command::Transfer(transfer) => transfer.run().await, + SubCommand::Account(account) => account.run().await, + SubCommand::Address(address) => address.run(), + SubCommand::Balance(balance) => balance.run().await, + SubCommand::BlockHeight(block_height) => block_height.run().await, + SubCommand::BridgeLock(bridge_lock) => bridge_lock.run().await, + SubCommand::InitBridgeAccount(init_bridge_account) => init_bridge_account.run().await, + SubCommand::Sudo(sudo) => sudo.run().await, + SubCommand::Transfer(transfer) => transfer.run().await, } } } /// Interact with a Sequencer node #[derive(Debug, Subcommand)] -enum Command { +enum SubCommand { /// Commands for interacting with Sequencer accounts Account(account::Command), /// Utilities for constructing and inspecting sequencer addresses From d004a6bed792e2c25ec574a2c92e8a1f01fb97e5 Mon Sep 17 00:00:00 2001 From: Richard Janis Goldschmidt Date: Wed, 2 Oct 2024 17:17:43 +0200 Subject: [PATCH 6/6] fix after rebase --- crates/astria-cli/src/utils.rs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/crates/astria-cli/src/utils.rs b/crates/astria-cli/src/utils.rs index 14b8be3600..543b52e333 100644 --- a/crates/astria-cli/src/utils.rs +++ b/crates/astria-cli/src/utils.rs @@ -3,7 +3,6 @@ use astria_core::{ primitive::v1::Address, protocol::transaction::v1alpha1::{ Action, - TransactionParams, UnsignedTransaction, }, }; @@ -36,7 +35,7 @@ pub(crate) async fn submit_transaction( let sequencer_key = SigningKey::from(private_key_bytes); let from_address = Address::builder() - .array(sequencer_key.verification_key().address_bytes()) + .array(*sequencer_key.verification_key().address_bytes()) .prefix(prefix) .try_build() .wrap_err("failed constructing a valid from address from the provided prefix")?; @@ -47,14 +46,13 @@ pub(crate) async fn submit_transaction( .await .wrap_err("failed to get nonce")?; - let tx = UnsignedTransaction { - params: TransactionParams::builder() - .nonce(nonce_res.nonce) - .chain_id(chain_id) - .build(), - actions: vec![action], - } - .into_signed(&sequencer_key); + let tx = UnsignedTransaction::builder() + .nonce(nonce_res.nonce) + .chain_id(chain_id) + .actions(vec![action]) + .try_build() + .wrap_err("failed to construct a transaction")? + .into_signed(&sequencer_key); let res = sequencer_client .submit_transaction_sync(tx) .await