diff --git a/.changelog/unreleased/SDK/1963-sdk-refactor-rebased.md b/.changelog/unreleased/SDK/1963-sdk-refactor-rebased.md new file mode 100644 index 0000000000..6add26845e --- /dev/null +++ b/.changelog/unreleased/SDK/1963-sdk-refactor-rebased.md @@ -0,0 +1,2 @@ +- Improved the usability of the SDK and moved it to separate crate. + ([\#1963](https://github.com/anoma/namada/pull/1963)) \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 84cbd6f48e..63ae872e56 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4026,6 +4026,7 @@ dependencies = [ "ethbridge-bridge-contract", "ethers", "eyre", + "fd-lock", "futures", "itertools", "libsecp256k1 0.7.0", @@ -4035,6 +4036,7 @@ dependencies = [ "namada_core", "namada_ethereum_bridge", "namada_proof_of_stake", + "namada_sdk", "namada_test_utils", "num256", "orion", @@ -4116,6 +4118,7 @@ dependencies = [ "masp_primitives", "masp_proofs", "namada", + "namada_sdk", "namada_test_utils", "num-derive", "num-rational 0.4.1", @@ -4175,6 +4178,7 @@ dependencies = [ "masp_proofs", "namada", "namada_apps", + "namada_sdk", "namada_test_utils", "prost", "rand 0.8.5", @@ -4305,6 +4309,53 @@ dependencies = [ "tracing-subscriber 0.3.17", ] +[[package]] +name = "namada_sdk" +version = "0.23.0" +dependencies = [ + "assert_matches", + "async-trait", + "bimap", + "borsh 0.9.4", + "circular-queue", + "data-encoding", + "derivation-path", + "ethbridge-bridge-contract", + "ethers", + "fd-lock", + "futures", + "itertools", + "masp_primitives", + "masp_proofs", + "namada_core", + "namada_ethereum_bridge", + "namada_proof_of_stake", + "namada_test_utils", + "num256", + "orion", + "owo-colors 3.5.0", + "parse_duration", + "paste", + "prost", + "rand 0.8.5", + "rand_core 0.6.4", + "ripemd", + "serde 1.0.163", + "serde_json", + "sha2 0.9.9", + "slip10_ed25519", + "tempfile", + "tendermint-rpc", + "thiserror", + "tiny-bip39", + "tiny-hderive", + "tokio", + "toml 0.5.9", + "tracing 0.1.37", + "wasmtimer", + "zeroize", +] + [[package]] name = "namada_test_utils" version = "0.23.0" @@ -4338,6 +4389,7 @@ dependencies = [ "namada", "namada_apps", "namada_core", + "namada_sdk", "namada_test_utils", "namada_tx_prelude", "namada_vp_prelude", diff --git a/Cargo.toml b/Cargo.toml index 9c731f0fdf..813cf99d9a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ members = [ "macros", "vp_prelude", "encoding_spec", + "sdk", ] # wasm packages have to be built separately diff --git a/apps/Cargo.toml b/apps/Cargo.toml index 1d33f55df7..02bcf60373 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -55,7 +55,7 @@ mainnet = [ "namada/mainnet", ] dev = ["namada/dev"] -std = ["ed25519-consensus/std", "rand/std", "rand_core/std", "namada/std"] +std = ["ed25519-consensus/std", "rand/std", "rand_core/std", "namada/std", "namada_sdk/std"] # for integration tests and test utilies testing = ["dev"] @@ -67,6 +67,7 @@ abciplus = [ [dependencies] namada = {path = "../shared", features = ["ferveo-tpke", "masp-tx-gen", "multicore", "http-client"]} +namada_sdk = {path = "../sdk", default-features = false, features = ["wasm-runtime", "masp-tx-gen"]} ark-serialize.workspace = true ark-std.workspace = true arse-merkle-tree = { workspace = true, features = ["blake2b"] } diff --git a/apps/src/bin/namada-client/main.rs b/apps/src/bin/namada-client/main.rs index 9b43ca8f91..770dcf5367 100644 --- a/apps/src/bin/namada-client/main.rs +++ b/apps/src/bin/namada-client/main.rs @@ -13,9 +13,10 @@ async fn main() -> Result<()> { let _log_guard = logging::init_from_env_or(LevelFilter::INFO)?; // run the CLI - CliApi::::handle_client_command::( + CliApi::handle_client_command::( None, cli::namada_client_cli()?, + &CliIo, ) .await } diff --git a/apps/src/bin/namada-relayer/main.rs b/apps/src/bin/namada-relayer/main.rs index 05d2620bcb..f9d98a2a4e 100644 --- a/apps/src/bin/namada-relayer/main.rs +++ b/apps/src/bin/namada-relayer/main.rs @@ -14,5 +14,5 @@ async fn main() -> Result<()> { let cmd = cli::namada_relayer_cli()?; // run the CLI - CliApi::::handle_relayer_command::(None, cmd).await + CliApi::handle_relayer_command::(None, cmd, &CliIo).await } diff --git a/apps/src/bin/namada-wallet/main.rs b/apps/src/bin/namada-wallet/main.rs index 5e94831716..30d4a64156 100644 --- a/apps/src/bin/namada-wallet/main.rs +++ b/apps/src/bin/namada-wallet/main.rs @@ -6,5 +6,5 @@ pub fn main() -> Result<()> { color_eyre::install()?; let (cmd, ctx) = cli::namada_wallet_cli()?; // run the CLI - CliApi::::handle_wallet_command(cmd, ctx) + CliApi::handle_wallet_command(cmd, ctx, &CliIo) } diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 135ff1e3c5..421ada0e69 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -15,7 +15,7 @@ pub mod wallet; use clap::{ArgGroup, ArgMatches, ColorChoice}; use color_eyre::eyre::Result; -use namada::types::io::DefaultIo; +use namada::types::io::StdIo; use utils::*; pub use utils::{safe_exit, Cmd}; @@ -2517,7 +2517,6 @@ pub mod args { use std::str::FromStr; use namada::ibc::core::ics24_host::identifier::{ChannelId, PortId}; - pub use namada::sdk::args::*; use namada::types::address::Address; use namada::types::chain::{ChainId, ChainIdPrefix}; use namada::types::dec::Dec; @@ -2530,10 +2529,12 @@ pub mod args { use namada::types::token; use namada::types::token::NATIVE_MAX_DECIMAL_PLACES; use namada::types::transaction::GasLimit; + pub use namada_sdk::args::*; use super::context::*; use super::utils::*; use super::{ArgGroup, ArgMatches}; + use crate::cli::context::FromContext; use crate::config::{self, Action, ActionAtHeight}; use crate::facade::tendermint::Timeout; use crate::facade::tendermint_config::net::Address as TendermintAddress; @@ -3525,8 +3526,8 @@ pub mod args { target, token, amount, - native_token: (), tx_code_path, + native_token: (), } } @@ -3881,8 +3882,8 @@ pub mod args { validator, amount, source, - native_token: (), tx_code_path, + native_token: (), } } @@ -5799,7 +5800,7 @@ pub fn namada_relayer_cli() -> Result { cmds::EthBridgePool::WithContext(sub_cmd), ) => { let global_args = args::Global::parse(&matches); - let context = Context::new::(global_args)?; + let context = Context::new::(global_args)?; Ok(NamadaRelayer::EthBridgePoolWithCtx(Box::new(( sub_cmd, context, )))) diff --git a/apps/src/lib/cli/api.rs b/apps/src/lib/cli/api.rs index bb387c5d9a..79c8be3fa9 100644 --- a/apps/src/lib/cli/api.rs +++ b/apps/src/lib/cli/api.rs @@ -1,10 +1,8 @@ -use std::marker::PhantomData; - -use namada::sdk::queries::Client; -use namada::sdk::rpc::wait_until_node_is_synched; use namada::tendermint_rpc::HttpClient; use namada::types::control_flow::Halt; use namada::types::io::Io; +use namada_sdk::queries::Client; +use namada_sdk::rpc::wait_until_node_is_synched; use tendermint_config::net::Address as TendermintAddress; use crate::client::utils; @@ -13,7 +11,7 @@ use crate::client::utils; #[async_trait::async_trait(?Send)] pub trait CliClient: Client + Sync { fn from_tendermint_address(address: &mut TendermintAddress) -> Self; - async fn wait_until_node_is_synced(&self) -> Halt<()>; + async fn wait_until_node_is_synced(&self, io: &impl Io) -> Halt<()>; } #[async_trait::async_trait(?Send)] @@ -22,8 +20,8 @@ impl CliClient for HttpClient { HttpClient::new(utils::take_config_address(address)).unwrap() } - async fn wait_until_node_is_synced(&self) -> Halt<()> { - wait_until_node_is_synched::<_, IO>(self).await + async fn wait_until_node_is_synced(&self, io: &impl Io) -> Halt<()> { + wait_until_node_is_synched(self, io).await } } @@ -32,4 +30,4 @@ pub struct CliIo; #[async_trait::async_trait(?Send)] impl Io for CliIo {} -pub struct CliApi(PhantomData); +pub struct CliApi; diff --git a/apps/src/lib/cli/client.rs b/apps/src/lib/cli/client.rs index 1a7d9f534a..977442b9cb 100644 --- a/apps/src/lib/cli/client.rs +++ b/apps/src/lib/cli/client.rs @@ -1,9 +1,8 @@ use color_eyre::eyre::{eyre, Report, Result}; -use namada::ledger::eth_bridge::bridge_pool; -use namada::sdk::tx::dump_tx; -use namada::sdk::{signing, tx as sdk_tx}; use namada::types::control_flow::ProceedOrElse; use namada::types::io::Io; +use namada_sdk::tx::dump_tx; +use namada_sdk::{signing, Namada, NamadaImpl}; use crate::cli; use crate::cli::api::{CliApi, CliClient}; @@ -15,10 +14,11 @@ fn error() -> Report { eyre!("Fatal error") } -impl CliApi { - pub async fn handle_client_command( +impl CliApi { + pub async fn handle_client_command( client: Option, cmd: cli::NamadaClient, + io: &IO, ) -> Result<()> where C: CliClient, @@ -36,19 +36,22 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); + let namada = ctx.to_sdk(&client, io); let dry_run = args.tx.dry_run || args.tx.dry_run_wrapper; - tx::submit_custom::<_, IO>(&client, &mut ctx, args) - .await?; + tx::submit_custom(&namada, args).await?; if !dry_run { - crate::wallet::save(&ctx.wallet) + namada + .wallet() + .await + .save() .unwrap_or_else(|err| eprintln!("{}", err)); } else { - IO::println( + io.println( "Transaction dry run. No addresses have been \ saved.", ) @@ -61,12 +64,12 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - tx::submit_transfer::<_, IO>(&client, ctx, args) - .await?; + let namada = ctx.to_sdk(&client, io); + tx::submit_transfer(&namada, args).await?; } Sub::TxIbcTransfer(TxIbcTransfer(mut args)) => { let client = client.unwrap_or_else(|| { @@ -75,12 +78,12 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - tx::submit_ibc_transfer::<_, IO>(&client, ctx, args) - .await?; + let namada = ctx.to_sdk(&client, io); + tx::submit_ibc_transfer(&namada, args).await?; } Sub::TxUpdateAccount(TxUpdateAccount(mut args)) => { let client = client.unwrap_or_else(|| { @@ -89,14 +92,12 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - tx::submit_update_account::<_, IO>( - &client, &mut ctx, args, - ) - .await?; + let namada = ctx.to_sdk(&client, io); + tx::submit_update_account(&namada, args).await?; } Sub::TxInitAccount(TxInitAccount(mut args)) => { let client = client.unwrap_or_else(|| { @@ -105,21 +106,22 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); + let namada = ctx.to_sdk(&client, io); let dry_run = args.tx.dry_run || args.tx.dry_run_wrapper; - tx::submit_init_account::<_, IO>( - &client, &mut ctx, args, - ) - .await?; + tx::submit_init_account(&namada, args).await?; if !dry_run { - crate::wallet::save(&ctx.wallet) + namada + .wallet() + .await + .save() .unwrap_or_else(|err| eprintln!("{}", err)); } else { - IO::println( + io.println( "Transaction dry run. No addresses have been \ saved.", ) @@ -132,12 +134,23 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - tx::submit_init_validator::<_, IO>(&client, ctx, args) - .await?; + let namada = NamadaImpl::native_new( + &client, + &mut ctx.wallet, + &mut ctx.shielded, + io, + ctx.native_token, + ); + tx::submit_init_validator( + &namada, + &mut ctx.config, + args, + ) + .await?; } Sub::TxInitProposal(TxInitProposal(mut args)) => { let client = client.unwrap_or_else(|| { @@ -146,12 +159,12 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - tx::submit_init_proposal::<_, IO>(&client, ctx, args) - .await?; + let namada = ctx.to_sdk(&client, io); + tx::submit_init_proposal(&namada, args).await?; } Sub::TxVoteProposal(TxVoteProposal(mut args)) => { let client = client.unwrap_or_else(|| { @@ -160,12 +173,12 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - tx::submit_vote_proposal::<_, IO>(&client, ctx, args) - .await?; + let namada = ctx.to_sdk(&client, io); + tx::submit_vote_proposal(&namada, args).await?; } Sub::TxRevealPk(TxRevealPk(mut args)) => { let client = client.unwrap_or_else(|| { @@ -174,12 +187,12 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - tx::submit_reveal_pk::<_, IO>(&client, &mut ctx, args) - .await?; + let namada = ctx.to_sdk(&client, io); + tx::submit_reveal_pk(&namada, args).await?; } Sub::Bond(Bond(mut args)) => { let client = client.unwrap_or_else(|| { @@ -188,12 +201,12 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - tx::submit_bond::<_, IO>(&client, &mut ctx, args) - .await?; + let namada = ctx.to_sdk(&client, io); + tx::submit_bond(&namada, args).await?; } Sub::Unbond(Unbond(mut args)) => { let client = client.unwrap_or_else(|| { @@ -202,12 +215,12 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - tx::submit_unbond::<_, IO>(&client, &mut ctx, args) - .await?; + let namada = ctx.to_sdk(&client, io); + tx::submit_unbond(&namada, args).await?; } Sub::Withdraw(Withdraw(mut args)) => { let client = client.unwrap_or_else(|| { @@ -216,12 +229,12 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - tx::submit_withdraw::<_, IO>(&client, ctx, args) - .await?; + let namada = ctx.to_sdk(&client, io); + tx::submit_withdraw(&namada, args).await?; } Sub::TxCommissionRateChange(TxCommissionRateChange( mut args, @@ -232,14 +245,13 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - tx::submit_validator_commission_change::<_, IO>( - &client, ctx, args, - ) - .await?; + let namada = ctx.to_sdk(&client, io); + tx::submit_validator_commission_change(&namada, args) + .await?; } // Eth bridge Sub::AddToEthBridgePool(args) => { @@ -250,64 +262,32 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); + let namada = ctx.to_sdk(&client, io); let tx_args = args.tx.clone(); + let (mut tx, signing_data, _epoch) = + args.clone().build(&namada).await?; - let default_signer = Some(args.sender.clone()); - let signing_data = tx::aux_signing_data::<_, IO>( - &client, - &mut ctx.wallet, - &args.tx, - Some(args.sender.clone()), - default_signer, - ) - .await?; - - let (mut tx, _epoch) = - bridge_pool::build_bridge_pool_tx::<_, _, _, IO>( - &client, - &mut ctx.wallet, - &mut ctx.shielded, - args.clone(), - signing_data.fee_payer.clone(), - ) - .await?; - - signing::generate_test_vector::<_, _, IO>( - &client, - &mut ctx.wallet, - &tx, - ) - .await?; + signing::generate_test_vector(&namada, &tx).await?; if args.tx.dump_tx { - dump_tx::(&args.tx, tx); + dump_tx::(io, &args.tx, tx); } else { - tx::submit_reveal_aux::<_, IO>( - &client, - &mut ctx, + tx::submit_reveal_aux( + &namada, tx_args.clone(), &args.sender, ) .await?; - signing::sign_tx( - &mut ctx.wallet, - &tx_args, - &mut tx, - signing_data, - )?; + namada + .sign(&mut tx, &tx_args, signing_data) + .await?; - sdk_tx::process_tx::<_, _, IO>( - &client, - &mut ctx.wallet, - &tx_args, - tx, - ) - .await?; + namada.submit(tx, &tx_args).await?; } } Sub::TxUnjailValidator(TxUnjailValidator(mut args)) => { @@ -317,14 +297,12 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - tx::submit_unjail_validator::<_, IO>( - &client, ctx, args, - ) - .await?; + let namada = ctx.to_sdk(&client, io); + tx::submit_unjail_validator(&namada, args).await?; } Sub::TxUpdateStewardCommission( TxUpdateStewardCommission(mut args), @@ -335,14 +313,13 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - tx::submit_update_steward_commission::<_, IO>( - &client, ctx, args, - ) - .await?; + let namada = ctx.to_sdk(&client, io); + tx::submit_update_steward_commission(&namada, args) + .await?; } Sub::TxResignSteward(TxResignSteward(mut args)) => { let client = client.unwrap_or_else(|| { @@ -351,12 +328,12 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - tx::submit_resign_steward::<_, IO>(&client, ctx, args) - .await?; + let namada = ctx.to_sdk(&client, io); + tx::submit_resign_steward(&namada, args).await?; } // Ledger queries Sub::QueryEpoch(QueryEpoch(mut args)) => { @@ -364,10 +341,11 @@ impl CliApi { C::from_tendermint_address(&mut args.ledger_address) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; - rpc::query_and_print_epoch::<_, IO>(&client).await; + let namada = ctx.to_sdk(&client, io); + rpc::query_and_print_epoch(&namada).await; } Sub::QueryValidatorState(QueryValidatorState(mut args)) => { let client = client.unwrap_or_else(|| { @@ -376,16 +354,13 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - rpc::query_and_print_validator_state::<_, IO>( - &client, - &mut ctx.wallet, - args, - ) - .await; + let namada = ctx.to_sdk(&client, io); + rpc::query_and_print_validator_state(&namada, args) + .await; } Sub::QueryTransfers(QueryTransfers(mut args)) => { let client = client.unwrap_or_else(|| { @@ -394,17 +369,12 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - rpc::query_transfers::<_, _, IO>( - &client, - &mut ctx.wallet, - &mut ctx.shielded, - args, - ) - .await; + let namada = ctx.to_sdk(&client, io); + rpc::query_transfers(&namada, args).await; } Sub::QueryConversions(QueryConversions(mut args)) => { let client = client.unwrap_or_else(|| { @@ -413,26 +383,23 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - rpc::query_conversions::<_, IO>( - &client, - &mut ctx.wallet, - args, - ) - .await; + let namada = ctx.to_sdk(&client, io); + rpc::query_conversions(&namada, args).await; } Sub::QueryBlock(QueryBlock(mut args)) => { let client = client.unwrap_or_else(|| { C::from_tendermint_address(&mut args.ledger_address) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; - rpc::query_block::<_, IO>(&client).await; + let namada = ctx.to_sdk(&client, io); + rpc::query_block(&namada).await; } Sub::QueryBalance(QueryBalance(mut args)) => { let client = client.unwrap_or_else(|| { @@ -441,17 +408,12 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - rpc::query_balance::<_, _, IO>( - &client, - &mut ctx.wallet, - &mut ctx.shielded, - args, - ) - .await; + let namada = ctx.to_sdk(&client, io); + rpc::query_balance(&namada, args).await; } Sub::QueryBonds(QueryBonds(mut args)) => { let client = client.unwrap_or_else(|| { @@ -460,17 +422,14 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - rpc::query_bonds::<_, IO>( - &client, - &mut ctx.wallet, - args, - ) - .await - .expect("expected successful query of bonds"); + let namada = ctx.to_sdk(&client, io); + rpc::query_bonds(&namada, args) + .await + .expect("expected successful query of bonds"); } Sub::QueryBondedStake(QueryBondedStake(mut args)) => { let client = client.unwrap_or_else(|| { @@ -479,11 +438,12 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - rpc::query_bonded_stake::<_, IO>(&client, args).await; + let namada = ctx.to_sdk(&client, io); + rpc::query_bonded_stake(&namada, args).await; } Sub::QueryCommissionRate(QueryCommissionRate(mut args)) => { let client = client.unwrap_or_else(|| { @@ -492,16 +452,13 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - rpc::query_and_print_commission_rate::<_, IO>( - &client, - &mut ctx.wallet, - args, - ) - .await; + let namada = ctx.to_sdk(&client, io); + rpc::query_and_print_commission_rate(&namada, args) + .await; } Sub::QuerySlashes(QuerySlashes(mut args)) => { let client = client.unwrap_or_else(|| { @@ -510,16 +467,12 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - rpc::query_slashes::<_, IO>( - &client, - &mut ctx.wallet, - args, - ) - .await; + let namada = ctx.to_sdk(&client, io); + rpc::query_slashes(&namada, args).await; } Sub::QueryDelegations(QueryDelegations(mut args)) => { let client = client.unwrap_or_else(|| { @@ -528,16 +481,12 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - rpc::query_delegations::<_, IO>( - &client, - &mut ctx.wallet, - args, - ) - .await; + let namada = ctx.to_sdk(&client, io); + rpc::query_delegations(&namada, args).await; } Sub::QueryFindValidator(QueryFindValidator(mut args)) => { let client = client.unwrap_or_else(|| { @@ -546,11 +495,12 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - rpc::query_find_validator::<_, IO>(&client, args).await; + let namada = ctx.to_sdk(&client, io); + rpc::query_find_validator(&namada, args).await; } Sub::QueryResult(QueryResult(mut args)) => { let client = client.unwrap_or_else(|| { @@ -559,11 +509,12 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - rpc::query_result::<_, IO>(&client, args).await; + let namada = ctx.to_sdk(&client, io); + rpc::query_result(&namada, args).await; } Sub::QueryRawBytes(QueryRawBytes(mut args)) => { let client = client.unwrap_or_else(|| { @@ -572,11 +523,12 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - rpc::query_raw_bytes::<_, IO>(&client, args).await; + let namada = ctx.to_sdk(&client, io); + rpc::query_raw_bytes(&namada, args).await; } Sub::QueryProposal(QueryProposal(mut args)) => { let client = client.unwrap_or_else(|| { @@ -585,11 +537,12 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - rpc::query_proposal::<_, IO>(&client, args).await; + let namada = ctx.to_sdk(&client, io); + rpc::query_proposal(&namada, args).await; } Sub::QueryProposalResult(QueryProposalResult(mut args)) => { let client = client.unwrap_or_else(|| { @@ -598,12 +551,12 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - rpc::query_proposal_result::<_, IO>(&client, args) - .await; + let namada = ctx.to_sdk(&client, io); + rpc::query_proposal_result(&namada, args).await; } Sub::QueryProtocolParameters(QueryProtocolParameters( mut args, @@ -614,12 +567,12 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - rpc::query_protocol_parameters::<_, IO>(&client, args) - .await; + let namada = ctx.to_sdk(&client, io); + rpc::query_protocol_parameters(&namada, args).await; } Sub::QueryPgf(QueryPgf(mut args)) => { let client = client.unwrap_or_else(|| { @@ -628,11 +581,12 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - rpc::query_pgf::<_, IO>(&client, args).await; + let namada = ctx.to_sdk(&client, io); + rpc::query_pgf(&namada, args).await; } Sub::QueryAccount(QueryAccount(mut args)) => { let client = client.unwrap_or_else(|| { @@ -641,11 +595,12 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - rpc::query_account::<_, IO>(&client, args).await; + let namada = ctx.to_sdk(&client, io); + rpc::query_account(&namada, args).await; } Sub::SignTx(SignTx(mut args)) => { let client = client.unwrap_or_else(|| { @@ -654,11 +609,12 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - tx::sign_tx::<_, IO>(&client, &mut ctx, args).await?; + let namada = ctx.to_sdk(&client, io); + tx::sign_tx(&namada, args).await?; } } } @@ -692,11 +648,12 @@ impl CliApi { let client = C::from_tendermint_address(&mut ledger_address); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - rpc::epoch_sleep::<_, IO>(&client, args).await; + let namada = ctx.to_sdk(&client, io); + rpc::epoch_sleep(&namada, args).await; } }, } diff --git a/apps/src/lib/cli/context.rs b/apps/src/lib/cli/context.rs index 4aac8b1026..a65d5c1830 100644 --- a/apps/src/lib/cli/context.rs +++ b/apps/src/lib/cli/context.rs @@ -6,17 +6,18 @@ use std::path::{Path, PathBuf}; use std::str::FromStr; use color_eyre::eyre::Result; -use namada::sdk::masp::ShieldedContext; -use namada::sdk::wallet::Wallet; use namada::types::address::{Address, InternalAddress}; use namada::types::chain::ChainId; use namada::types::ethereum_events::EthAddress; use namada::types::io::Io; use namada::types::key::*; use namada::types::masp::*; +use namada_sdk::masp::fs::FsShieldedUtils; +use namada_sdk::masp::ShieldedContext; +use namada_sdk::wallet::Wallet; +use namada_sdk::{Namada, NamadaImpl}; use super::args; -use crate::client::tx::CLIShieldedUtils; #[cfg(any(test, feature = "dev"))] use crate::config::genesis; use crate::config::genesis::genesis_config; @@ -78,7 +79,7 @@ pub struct Context { /// The ledger configuration for a specific chain ID pub config: Config, /// The context fr shielded operations - pub shielded: ShieldedContext, + pub shielded: ShieldedContext, /// Native token's address pub native_token: Address, } @@ -145,11 +146,30 @@ impl Context { wallet, global_config, config, - shielded: CLIShieldedUtils::new::(chain_dir), + shielded: FsShieldedUtils::new(chain_dir), native_token, }) } + /// Make an implementation of Namada from this object and parameters. + pub fn to_sdk<'a, C, IO>( + &'a mut self, + client: &'a C, + io: &'a IO, + ) -> impl Namada + where + C: namada::ledger::queries::Client + Sync, + IO: Io, + { + NamadaImpl::native_new( + client, + &mut self.wallet, + &mut self.shielded, + io, + self.native_token.clone(), + ) + } + /// Parse and/or look-up the value from the context. pub fn get(&self, from_context: &FromContext) -> T where diff --git a/apps/src/lib/cli/relayer.rs b/apps/src/lib/cli/relayer.rs index 3322e84e2f..497c69c819 100644 --- a/apps/src/lib/cli/relayer.rs +++ b/apps/src/lib/cli/relayer.rs @@ -2,9 +2,9 @@ use std::sync::Arc; use color_eyre::eyre::{eyre, Report, Result}; use namada::eth_bridge::ethers::providers::{Http, Provider}; -use namada::ledger::eth_bridge::{bridge_pool, validator_set}; use namada::types::control_flow::ProceedOrElse; use namada::types::io::Io; +use namada_sdk::eth_bridge::{bridge_pool, validator_set}; use crate::cli; use crate::cli::api::{CliApi, CliClient}; @@ -15,10 +15,11 @@ fn error() -> Report { eyre!("Fatal error") } -impl CliApi { +impl CliApi { pub async fn handle_relayer_command( client: Option, cmd: cli::NamadaRelayer, + io: &impl Io, ) -> Result<()> where C: CliClient, @@ -36,11 +37,12 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - bridge_pool::recommend_batch::<_, IO>(&client, args) + let namada = ctx.to_sdk(&client, io); + bridge_pool::recommend_batch(&namada, args) .await .proceed_or_else(error)?; } @@ -56,11 +58,11 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk_ctxless(); - bridge_pool::construct_proof::<_, IO>(&client, args) + bridge_pool::construct_proof(&client, io, args) .await .proceed_or_else(error)?; } @@ -71,7 +73,7 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let eth_client = Arc::new( @@ -79,8 +81,8 @@ impl CliApi { .unwrap(), ); let args = args.to_sdk_ctxless(); - bridge_pool::relay_bridge_pool_proof::<_, _, IO>( - eth_client, &client, args, + bridge_pool::relay_bridge_pool_proof( + eth_client, &client, io, args, ) .await .proceed_or_else(error)?; @@ -92,10 +94,10 @@ impl CliApi { C::from_tendermint_address(&mut query.ledger_address) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; - bridge_pool::query_bridge_pool::<_, IO>(&client).await; + bridge_pool::query_bridge_pool(&client, io).await; } EthBridgePoolWithoutCtx::QuerySigned( QuerySignedBridgePool(mut query), @@ -104,10 +106,10 @@ impl CliApi { C::from_tendermint_address(&mut query.ledger_address) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; - bridge_pool::query_signed_bridge_pool::<_, IO>(&client) + bridge_pool::query_signed_bridge_pool(&client, io) .await .proceed_or_else(error)?; } @@ -118,10 +120,10 @@ impl CliApi { C::from_tendermint_address(&mut query.ledger_address) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; - bridge_pool::query_relay_progress::<_, IO>(&client).await; + bridge_pool::query_relay_progress(&client, io).await; } }, cli::NamadaRelayer::ValidatorSet(sub) => match sub { @@ -134,12 +136,12 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk_ctxless(); - validator_set::query_bridge_validator_set::<_, IO>( - &client, args, + validator_set::query_bridge_validator_set( + &client, io, args, ) .await; } @@ -152,12 +154,12 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk_ctxless(); - validator_set::query_governnace_validator_set::<_, IO>( - &client, args, + validator_set::query_governnace_validator_set( + &client, io, args, ) .await; } @@ -170,12 +172,12 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let args = args.to_sdk_ctxless(); - validator_set::query_validator_set_update_proof::<_, IO>( - &client, args, + validator_set::query_validator_set_update_proof( + &client, io, args, ) .await; } @@ -188,7 +190,7 @@ impl CliApi { ) }); client - .wait_until_node_is_synced::() + .wait_until_node_is_synced(io) .await .proceed_or_else(error)?; let eth_client = Arc::new( @@ -196,8 +198,8 @@ impl CliApi { .unwrap(), ); let args = args.to_sdk_ctxless(); - validator_set::relay_validator_set_update::<_, _, IO>( - eth_client, &client, args, + validator_set::relay_validator_set_update( + eth_client, &client, io, args, ) .await .proceed_or_else(error)?; diff --git a/apps/src/lib/cli/utils.rs b/apps/src/lib/cli/utils.rs index 26cc38ff7f..7c8bc4100c 100644 --- a/apps/src/lib/cli/utils.rs +++ b/apps/src/lib/cli/utils.rs @@ -8,8 +8,9 @@ use clap::{ArgAction, ArgMatches}; use color_eyre::eyre::Result; use super::args; -use super::context::{Context, FromContext}; +use super::context::Context; use crate::cli::api::CliIo; +use crate::cli::context::FromContext; // We only use static strings pub type App = clap::Command; diff --git a/apps/src/lib/cli/wallet.rs b/apps/src/lib/cli/wallet.rs index d039c7ef10..247835f46b 100644 --- a/apps/src/lib/cli/wallet.rs +++ b/apps/src/lib/cli/wallet.rs @@ -7,12 +7,16 @@ use borsh::BorshSerialize; use color_eyre::eyre::Result; use itertools::sorted; use masp_primitives::zip32::ExtendedFullViewingKey; -use namada::sdk::masp::find_valid_diversifier; -use namada::sdk::wallet::{DecryptionError, FindKeyError}; use namada::types::io::Io; use namada::types::key::*; use namada::types::masp::{MaspValue, PaymentAddress}; -use namada::{display, display_line, edisplay_line}; +use namada_sdk::masp::find_valid_diversifier; +use namada_sdk::wallet::{ + DecryptionError, FindKeyError, GenRestoreKeyError, Wallet, WalletIo, + WalletStorage, +}; +use namada_sdk::{display, display_line, edisplay_line}; +use rand::RngCore; use rand_core::OsRng; use crate::cli; @@ -21,65 +25,66 @@ use crate::cli::args::CliToSdk; use crate::cli::{args, cmds, Context}; use crate::wallet::{read_and_confirm_encryption_password, CliWalletUtils}; -impl CliApi { +impl CliApi { pub fn handle_wallet_command( cmd: cmds::NamadaWallet, mut ctx: Context, + io: &impl Io, ) -> Result<()> { match cmd { cmds::NamadaWallet::Key(sub) => match sub { cmds::WalletKey::Restore(cmds::KeyRestore(args)) => { - key_and_address_restore::(ctx, args) + key_and_address_restore(&mut ctx.wallet, io, args) } cmds::WalletKey::Gen(cmds::KeyGen(args)) => { - key_and_address_gen::(ctx, args) + key_and_address_gen(&mut ctx.wallet, io, &mut OsRng, args) } cmds::WalletKey::Find(cmds::KeyFind(args)) => { - key_find::(ctx, args) + key_find(&mut ctx.wallet, io, args) } cmds::WalletKey::List(cmds::KeyList(args)) => { - key_list::(ctx, args) + key_list(&mut ctx.wallet, io, args) } cmds::WalletKey::Export(cmds::Export(args)) => { - key_export::(ctx, args) + key_export(&mut ctx.wallet, io, args) } }, cmds::NamadaWallet::Address(sub) => match sub { cmds::WalletAddress::Gen(cmds::AddressGen(args)) => { - key_and_address_gen::(ctx, args) + key_and_address_gen(&mut ctx.wallet, io, &mut OsRng, args) } cmds::WalletAddress::Restore(cmds::AddressRestore(args)) => { - key_and_address_restore::(ctx, args) + key_and_address_restore(&mut ctx.wallet, io, args) } cmds::WalletAddress::Find(cmds::AddressOrAliasFind(args)) => { - address_or_alias_find::(ctx, args) + address_or_alias_find(&mut ctx.wallet, io, args) } cmds::WalletAddress::List(cmds::AddressList) => { - address_list::(ctx) + address_list(&mut ctx.wallet, io) } cmds::WalletAddress::Add(cmds::AddressAdd(args)) => { - address_add::(ctx, args) + address_add(&mut ctx.wallet, io, args) } }, cmds::NamadaWallet::Masp(sub) => match sub { cmds::WalletMasp::GenSpendKey(cmds::MaspGenSpendKey(args)) => { - spending_key_gen::(ctx, args) + spending_key_gen(&mut ctx.wallet, io, args) } cmds::WalletMasp::GenPayAddr(cmds::MaspGenPayAddr(args)) => { let args = args.to_sdk(&mut ctx); - payment_address_gen::(ctx, args) + payment_address_gen(&mut ctx.wallet, io, args) } cmds::WalletMasp::AddAddrKey(cmds::MaspAddAddrKey(args)) => { - address_key_add::(ctx, args) + address_key_add(&mut ctx.wallet, io, args) } cmds::WalletMasp::ListPayAddrs(cmds::MaspListPayAddrs) => { - payment_addresses_list::(ctx) + payment_addresses_list(&mut ctx.wallet, io) } cmds::WalletMasp::ListKeys(cmds::MaspListKeys(args)) => { - spending_keys_list::(ctx, args) + spending_keys_list(&mut ctx.wallet, io, args) } cmds::WalletMasp::FindAddrKey(cmds::MaspFindAddrKey(args)) => { - address_key_find::(ctx, args) + address_key_find(&mut ctx.wallet, io, args) } }, } @@ -88,35 +93,35 @@ impl CliApi { } /// Find shielded address or key -fn address_key_find( - ctx: Context, +fn address_key_find( + wallet: &mut Wallet, + io: &impl Io, args::AddrKeyFind { alias, unsafe_show_secret, }: args::AddrKeyFind, ) { - let mut wallet = ctx.wallet; let alias = alias.to_lowercase(); if let Ok(viewing_key) = wallet.find_viewing_key(&alias) { // Check if alias is a viewing key - display_line!(IO, "Viewing key: {}", viewing_key); + display_line!(io, "Viewing key: {}", viewing_key); if unsafe_show_secret { // Check if alias is also a spending key match wallet.find_spending_key(&alias, None) { Ok(spending_key) => { - display_line!(IO, "Spending key: {}", spending_key) + display_line!(io, "Spending key: {}", spending_key) } Err(FindKeyError::KeyNotFound) => {} - Err(err) => edisplay_line!(IO, "{}", err), + Err(err) => edisplay_line!(io, "{}", err), } } } else if let Some(payment_addr) = wallet.find_payment_addr(&alias) { // Failing that, check if alias is a payment address - display_line!(IO, "Payment address: {}", payment_addr); + display_line!(io, "Payment address: {}", payment_addr); } else { // Otherwise alias cannot be referring to any shielded value display_line!( - IO, + io, "No shielded address or key with alias {} found. Use the commands \ `masp list-addrs` and `masp list-keys` to see all the known \ addresses and keys.", @@ -126,44 +131,44 @@ fn address_key_find( } /// List spending keys. -fn spending_keys_list( - ctx: Context, +fn spending_keys_list( + wallet: &mut Wallet, + io: &impl Io, args::MaspKeysList { decrypt, unsafe_show_secret, }: args::MaspKeysList, ) { - let wallet = ctx.wallet; let known_view_keys = wallet.get_viewing_keys(); let known_spend_keys = wallet.get_spending_keys(); if known_view_keys.is_empty() { display_line!( - IO, + io, "No known keys. Try `masp add --alias my-addr --value ...` to add \ a new key to the wallet.", ); } else { let stdout = io::stdout(); let mut w = stdout.lock(); - display_line!(IO, &mut w; "Known keys:").unwrap(); + display_line!(io, &mut w; "Known keys:").unwrap(); for (alias, key) in known_view_keys { - display!(IO, &mut w; " Alias \"{}\"", alias).unwrap(); + display!(io, &mut w; " Alias \"{}\"", alias).unwrap(); let spending_key_opt = known_spend_keys.get(&alias); // If this alias is associated with a spending key, indicate whether // or not the spending key is encrypted // TODO: consider turning if let into match if let Some(spending_key) = spending_key_opt { if spending_key.is_encrypted() { - display_line!(IO, &mut w; " (encrypted):") + display_line!(io, &mut w; " (encrypted):") } else { - display_line!(IO, &mut w; " (not encrypted):") + display_line!(io, &mut w; " (not encrypted):") } .unwrap(); } else { - display_line!(IO, &mut w; ":").unwrap(); + display_line!(io, &mut w; ":").unwrap(); } // Always print the corresponding viewing key - display_line!(IO, &mut w; " Viewing Key: {}", key).unwrap(); + display_line!(io, &mut w; " Viewing Key: {}", key).unwrap(); // A subset of viewing keys will have corresponding spending keys. // Print those too if they are available and requested. if unsafe_show_secret { @@ -172,7 +177,7 @@ fn spending_keys_list( // Here the spending key is unencrypted or successfully // decrypted Ok(spending_key) => { - display_line!(IO, + display_line!(io, &mut w; " Spending key: {}", spending_key, ) @@ -186,7 +191,7 @@ fn spending_keys_list( // Here the key is encrypted but incorrect password has // been provided Err(err) => { - display_line!(IO, + display_line!(io, &mut w; " Couldn't decrypt the spending key: {}", err, @@ -201,49 +206,52 @@ fn spending_keys_list( } /// List payment addresses. -fn payment_addresses_list(ctx: Context) { - let wallet = ctx.wallet; +fn payment_addresses_list( + wallet: &mut Wallet, + io: &impl Io, +) { let known_addresses = wallet.get_payment_addrs(); if known_addresses.is_empty() { display_line!( - IO, + io, "No known payment addresses. Try `masp gen-addr --alias my-addr` \ to generate a new payment address.", ); } else { let stdout = io::stdout(); let mut w = stdout.lock(); - display_line!(IO, &mut w; "Known payment addresses:").unwrap(); + display_line!(io, &mut w; "Known payment addresses:").unwrap(); for (alias, address) in sorted(known_addresses) { - display_line!(IO, &mut w; " \"{}\": {}", alias, address).unwrap(); + display_line!(io, &mut w; " \"{}\": {}", alias, address).unwrap(); } } } /// Generate a spending key. -fn spending_key_gen( - ctx: Context, +fn spending_key_gen( + wallet: &mut Wallet, + io: &impl Io, args::MaspSpendKeyGen { alias, alias_force, unsafe_dont_encrypt, }: args::MaspSpendKeyGen, ) { - let mut wallet = ctx.wallet; let alias = alias.to_lowercase(); let password = read_and_confirm_encryption_password(unsafe_dont_encrypt); let (alias, _key) = wallet.gen_spending_key(alias, password, alias_force); - crate::wallet::save(&wallet).unwrap_or_else(|err| eprintln!("{}", err)); + wallet.save().unwrap_or_else(|err| eprintln!("{}", err)); display_line!( - IO, + io, "Successfully added a spending key with alias: \"{}\"", alias ); } /// Generate a shielded payment address from the given key. -fn payment_address_gen( - ctx: Context, +fn payment_address_gen( + wallet: &mut Wallet, + io: &impl Io, args::MaspPayAddrGen { alias, alias_force, @@ -257,7 +265,6 @@ fn payment_address_gen( let payment_addr = viewing_key .to_payment_address(div) .expect("a PaymentAddress"); - let mut wallet = ctx.wallet; let alias = wallet .insert_payment_addr( alias, @@ -265,20 +272,21 @@ fn payment_address_gen( alias_force, ) .unwrap_or_else(|| { - edisplay_line!(IO, "Payment address not added"); + edisplay_line!(io, "Payment address not added"); cli::safe_exit(1); }); - crate::wallet::save(&wallet).unwrap_or_else(|err| eprintln!("{}", err)); + wallet.save().unwrap_or_else(|err| eprintln!("{}", err)); display_line!( - IO, + io, "Successfully generated a payment address with the following alias: {}", alias, ); } /// Add a viewing key, spending key, or payment address to wallet. -fn address_key_add( - mut ctx: Context, +fn address_key_add( + wallet: &mut Wallet, + io: &impl Io, args::MaspAddrKeyAdd { alias, alias_force, @@ -289,11 +297,10 @@ fn address_key_add( let alias = alias.to_lowercase(); let (alias, typ) = match value { MaspValue::FullViewingKey(viewing_key) => { - let alias = ctx - .wallet + let alias = wallet .insert_viewing_key(alias, viewing_key, alias_force) .unwrap_or_else(|| { - edisplay_line!(IO, "Viewing key not added"); + edisplay_line!(io, "Viewing key not added"); cli::safe_exit(1); }); (alias, "viewing key") @@ -301,8 +308,7 @@ fn address_key_add( MaspValue::ExtendedSpendingKey(spending_key) => { let password = read_and_confirm_encryption_password(unsafe_dont_encrypt); - let alias = ctx - .wallet + let alias = wallet .encrypt_insert_spending_key( alias, spending_key, @@ -310,25 +316,24 @@ fn address_key_add( alias_force, ) .unwrap_or_else(|| { - edisplay_line!(IO, "Spending key not added"); + edisplay_line!(io, "Spending key not added"); cli::safe_exit(1); }); (alias, "spending key") } MaspValue::PaymentAddress(payment_addr) => { - let alias = ctx - .wallet + let alias = wallet .insert_payment_addr(alias, payment_addr, alias_force) .unwrap_or_else(|| { - edisplay_line!(IO, "Payment address not added"); + edisplay_line!(io, "Payment address not added"); cli::safe_exit(1); }); (alias, "payment address") } }; - crate::wallet::save(&ctx.wallet).unwrap_or_else(|err| eprintln!("{}", err)); + wallet.save().unwrap_or_else(|err| eprintln!("{}", err)); display_line!( - IO, + io, "Successfully added a {} with the following alias to wallet: {}", typ, alias, @@ -337,8 +342,9 @@ fn address_key_add( /// Restore a keypair and an implicit address from the mnemonic code in the /// wallet. -fn key_and_address_restore( - ctx: Context, +fn key_and_address_restore( + wallet: &mut Wallet, + io: &impl Io, args::KeyAndAddressRestore { scheme, alias, @@ -347,7 +353,6 @@ fn key_and_address_restore( derivation_path, }: args::KeyAndAddressRestore, ) { - let mut wallet = ctx.wallet; let encryption_password = read_and_confirm_encryption_password(unsafe_dont_encrypt); let (alias, _key) = wallet @@ -356,20 +361,22 @@ fn key_and_address_restore( alias, alias_force, derivation_path, + None, encryption_password, ) .unwrap_or_else(|err| { - edisplay_line!(IO, "{}", err); + edisplay_line!(io, "{}", err); cli::safe_exit(1) }) .unwrap_or_else(|| { - display_line!(IO, "No changes are persisted. Exiting."); + display_line!(io, "No changes are persisted. Exiting."); cli::safe_exit(0); }); - crate::wallet::save(&wallet) - .unwrap_or_else(|err| edisplay_line!(IO, "{}", err)); + wallet + .save() + .unwrap_or_else(|err| edisplay_line!(io, "{}", err)); display_line!( - IO, + io, "Successfully added a key and an address with alias: \"{}\"", alias ); @@ -377,8 +384,10 @@ fn key_and_address_restore( /// Generate a new keypair and derive implicit address from it and store them in /// the wallet. -fn key_and_address_gen( - ctx: Context, +fn key_and_address_gen( + wallet: &mut Wallet>, + io: &impl Io, + rng: &mut R, args::KeyAndAddressGen { scheme, alias, @@ -387,40 +396,42 @@ fn key_and_address_gen( derivation_path, }: args::KeyAndAddressGen, ) { - let mut wallet = ctx.wallet; let encryption_password = read_and_confirm_encryption_password(unsafe_dont_encrypt); - let mut rng = OsRng; - let derivation_path_and_mnemonic_rng = - derivation_path.map(|p| (p, &mut rng)); - let (alias, _key) = wallet + let derivation_path_and_mnemonic_rng = derivation_path.map(|p| (p, rng)); + let (alias, _key, _mnemonic) = wallet .gen_key( scheme, alias, alias_force, + None, encryption_password, derivation_path_and_mnemonic_rng, ) - .unwrap_or_else(|err| { - edisplay_line!(IO, "{}", err); - cli::safe_exit(1); - }) - .unwrap_or_else(|| { - display_line!(IO, "No changes are persisted. Exiting."); - cli::safe_exit(0); + .unwrap_or_else(|err| match err { + GenRestoreKeyError::KeyStorageError => { + println!("No changes are persisted. Exiting."); + cli::safe_exit(0); + } + _ => { + eprintln!("{}", err); + cli::safe_exit(1); + } }); - crate::wallet::save(&wallet) - .unwrap_or_else(|err| edisplay_line!(IO, "{}", err)); + wallet + .save() + .unwrap_or_else(|err| edisplay_line!(io, "{}", err)); display_line!( - IO, + io, "Successfully added a key and an address with alias: \"{}\"", alias ); } /// Find a keypair in the wallet store. -fn key_find( - ctx: Context, +fn key_find( + wallet: &mut Wallet, + io: &impl Io, args::KeyFind { public_key, alias, @@ -428,7 +439,6 @@ fn key_find( unsafe_show_secret, }: args::KeyFind, ) { - let mut wallet = ctx.wallet; let found_keypair = match public_key { Some(pk) => wallet.find_key_by_pk(&pk, None), None => { @@ -436,7 +446,7 @@ fn key_find( match alias { None => { edisplay_line!( - IO, + io, "An alias, public key or public key hash needs to be \ supplied", ); @@ -449,62 +459,62 @@ fn key_find( match found_keypair { Ok(keypair) => { let pkh: PublicKeyHash = (&keypair.ref_to()).into(); - display_line!(IO, "Public key hash: {}", pkh); - display_line!(IO, "Public key: {}", keypair.ref_to()); + display_line!(io, "Public key hash: {}", pkh); + display_line!(io, "Public key: {}", keypair.ref_to()); if unsafe_show_secret { - display_line!(IO, "Secret key: {}", keypair); + display_line!(io, "Secret key: {}", keypair); } } Err(err) => { - edisplay_line!(IO, "{}", err); + edisplay_line!(io, "{}", err); } } } /// List all known keys. -fn key_list( - ctx: Context, +fn key_list( + wallet: &mut Wallet, + io: &impl Io, args::KeyList { decrypt, unsafe_show_secret, }: args::KeyList, ) { - let wallet = ctx.wallet; let known_keys = wallet.get_keys(); if known_keys.is_empty() { display_line!( - IO, + io, "No known keys. Try `key gen --alias my-key` to generate a new \ key.", ); } else { let stdout = io::stdout(); let mut w = stdout.lock(); - display_line!(IO, &mut w; "Known keys:").unwrap(); + display_line!(io, &mut w; "Known keys:").unwrap(); for (alias, (stored_keypair, pkh)) in known_keys { let encrypted = if stored_keypair.is_encrypted() { "encrypted" } else { "not encrypted" }; - display_line!(IO, + display_line!(io, &mut w; " Alias \"{}\" ({}):", alias, encrypted, ) .unwrap(); if let Some(pkh) = pkh { - display_line!(IO, &mut w; " Public key hash: {}", pkh) + display_line!(io, &mut w; " Public key hash: {}", pkh) .unwrap(); } match stored_keypair.get::(decrypt, None) { Ok(keypair) => { - display_line!(IO, + display_line!(io, &mut w; " Public key: {}", keypair.ref_to(), ) .unwrap(); if unsafe_show_secret { - display_line!(IO, + display_line!(io, &mut w; " Secret key: {}", keypair, ) @@ -515,7 +525,7 @@ fn key_list( continue; } Err(err) => { - display_line!(IO, + display_line!(io, &mut w; " Couldn't decrypt the keypair: {}", err, ) @@ -527,11 +537,11 @@ fn key_list( } /// Export a keypair to a file. -fn key_export( - ctx: Context, +fn key_export( + wallet: &mut Wallet, + io: &impl Io, args::KeyExport { alias }: args::KeyExport, ) { - let mut wallet = ctx.wallet; wallet .find_key(alias.to_lowercase(), None) .map(|keypair| { @@ -542,30 +552,32 @@ fn key_export( let mut file = File::create(&file_name).unwrap(); file.write_all(file_data.as_ref()).unwrap(); - display_line!(IO, "Exported to file {}", file_name); + display_line!(io, "Exported to file {}", file_name); }) .unwrap_or_else(|err| { - edisplay_line!(IO, "{}", err); + edisplay_line!(io, "{}", err); cli::safe_exit(1) }) } /// List all known addresses. -fn address_list(ctx: Context) { - let wallet = ctx.wallet; +fn address_list( + wallet: &mut Wallet, + io: &impl Io, +) { let known_addresses = wallet.get_addresses(); if known_addresses.is_empty() { display_line!( - IO, + io, "No known addresses. Try `address gen --alias my-addr` to \ generate a new implicit address.", ); } else { let stdout = io::stdout(); let mut w = stdout.lock(); - display_line!(IO, &mut w; "Known addresses:").unwrap(); + display_line!(io, &mut w; "Known addresses:").unwrap(); for (alias, address) in sorted(known_addresses) { - display_line!(IO, + display_line!(io, &mut w; " \"{}\": {}", alias, address.to_pretty_string(), ) @@ -575,8 +587,11 @@ fn address_list(ctx: Context) { } /// Find address (alias) by its alias (address). -fn address_or_alias_find(ctx: Context, args: args::AddressOrAliasFind) { - let wallet = ctx.wallet; +fn address_or_alias_find( + wallet: &mut Wallet, + io: &impl Io, + args: args::AddressOrAliasFind, +) { if args.address.is_some() && args.alias.is_some() { panic!( "This should not be happening: clap should emit its own error \ @@ -585,10 +600,10 @@ fn address_or_alias_find(ctx: Context, args: args::AddressOrAliasFind) { } else if args.alias.is_some() { if let Some(address) = wallet.find_address(args.alias.as_ref().unwrap()) { - display_line!(IO, "Found address {}", address.to_pretty_string()); + display_line!(io, "Found address {}", address.to_pretty_string()); } else { display_line!( - IO, + io, "No address with alias {} found. Use the command `address \ list` to see all the known addresses.", args.alias.unwrap().to_lowercase() @@ -596,10 +611,10 @@ fn address_or_alias_find(ctx: Context, args: args::AddressOrAliasFind) { } } else if args.address.is_some() { if let Some(alias) = wallet.find_alias(args.address.as_ref().unwrap()) { - display_line!(IO, "Found alias {}", alias); + display_line!(io, "Found alias {}", alias); } else { display_line!( - IO, + io, "No alias with address {} found. Use the command `address \ list` to see all the known addresses.", args.address.unwrap() @@ -609,8 +624,11 @@ fn address_or_alias_find(ctx: Context, args: args::AddressOrAliasFind) { } /// Add an address to the wallet. -fn address_add(ctx: Context, args: args::AddressAdd) { - let mut wallet = ctx.wallet; +fn address_add( + wallet: &mut Wallet, + io: &impl Io, + args: args::AddressAdd, +) { if wallet .add_address( args.alias.clone().to_lowercase(), @@ -619,13 +637,14 @@ fn address_add(ctx: Context, args: args::AddressAdd) { ) .is_none() { - edisplay_line!(IO, "Address not added"); + edisplay_line!(io, "Address not added"); cli::safe_exit(1); } - crate::wallet::save(&wallet) - .unwrap_or_else(|err| edisplay_line!(IO, "{}", err)); + wallet + .save() + .unwrap_or_else(|err| edisplay_line!(io, "{}", err)); display_line!( - IO, + io, "Successfully added a key and an address with alias: \"{}\"", args.alias.to_lowercase() ); diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index d750ccc759..b3902e0900 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -34,16 +34,6 @@ use namada::ledger::pos::{CommissionPair, PosParams, Slash}; use namada::ledger::queries::RPC; use namada::ledger::storage::ConversionState; use namada::proof_of_stake::types::{ValidatorState, WeightedValidator}; -use namada::sdk::error; -use namada::sdk::error::{is_pinned_error, Error, PinnedBalanceError}; -use namada::sdk::masp::{ - Conversions, MaspAmount, MaspChange, ShieldedContext, ShieldedUtils, -}; -use namada::sdk::rpc::{ - self, enriched_bonds_and_unbonds, format_denominated_amount, query_epoch, - TxResponse, -}; -use namada::sdk::wallet::{AddressVpType, Wallet}; use namada::types::address::{masp, Address}; use namada::types::control_flow::ProceedOrElse; use namada::types::hash::Hash; @@ -53,52 +43,49 @@ use namada::types::masp::{BalanceOwner, ExtendedViewingKey, PaymentAddress}; use namada::types::storage::{BlockHeight, BlockResults, Epoch, Key, KeySeg}; use namada::types::token::{Change, MaspDenom}; use namada::types::{storage, token}; -use namada::{display, display_line, edisplay_line, prompt}; +use namada_sdk::error::{is_pinned_error, Error, PinnedBalanceError}; +use namada_sdk::masp::{Conversions, MaspAmount, MaspChange}; +use namada_sdk::rpc::{ + self, enriched_bonds_and_unbonds, query_epoch, TxResponse, +}; +use namada_sdk::wallet::AddressVpType; +use namada_sdk::{display, display_line, edisplay_line, error, prompt, Namada}; use tokio::time::Instant; use crate::cli::{self, args}; use crate::facade::tendermint::merkle::proof::Proof; use crate::facade::tendermint_rpc::error::Error as TError; -use crate::wallet::CliWalletUtils; /// Query the status of a given transaction. /// /// If a response is not delivered until `deadline`, we exit the cli with an /// error. -pub async fn query_tx_status< - C: namada::ledger::queries::Client + Sync, - IO: Io, ->( - client: &C, - status: namada::sdk::rpc::TxEventQuery<'_>, +pub async fn query_tx_status<'a>( + namada: &impl Namada<'a>, + status: namada_sdk::rpc::TxEventQuery<'_>, deadline: Instant, ) -> Event { - rpc::query_tx_status::<_, IO>(client, status, deadline) + rpc::query_tx_status(namada, status, deadline) .await .proceed() } /// Query and print the epoch of the last committed block -pub async fn query_and_print_epoch< - C: namada::ledger::queries::Client + Sync, - IO: Io, ->( - client: &C, -) -> Epoch { - let epoch = rpc::query_epoch(client).await.unwrap(); - display_line!(IO, "Last committed epoch: {}", epoch); +pub async fn query_and_print_epoch<'a>(context: &impl Namada<'a>) -> Epoch { + let epoch = rpc::query_epoch(context.client()).await.unwrap(); + display_line!(context.io(), "Last committed epoch: {}", epoch); epoch } /// Query the last committed block -pub async fn query_block( - client: &C, -) { - let block = namada::sdk::rpc::query_block(client).await.unwrap(); +pub async fn query_block<'a>(context: &impl Namada<'a>) { + let block = namada_sdk::rpc::query_block(context.client()) + .await + .unwrap(); match block { Some(block) => { display_line!( - IO, + context.io(), "Last committed block ID: {}, height: {}, time: {}", block.hash, block.height, @@ -106,7 +93,7 @@ pub async fn query_block( ); } None => { - display_line!(IO, "No block has been committed yet."); + display_line!(context.io(), "No block has been committed yet."); } } } @@ -122,26 +109,22 @@ pub async fn query_results( } /// Query the specified accepted transfers from the ledger -pub async fn query_transfers< - C: namada::ledger::queries::Client + Sync, - U: ShieldedUtils, - IO: Io, ->( - client: &C, - wallet: &mut Wallet, - shielded: &mut ShieldedContext, +pub async fn query_transfers<'a>( + context: &impl Namada<'a>, args: args::QueryTransfers, ) { let query_token = args.token; + let wallet = context.wallet().await; let query_owner = args.owner.map_or_else( || Either::Right(wallet.get_addresses().into_values().collect()), Either::Left, ); + let mut shielded = context.shielded_mut().await; let _ = shielded.load().await; // Obtain the effects of all shielded and transparent transactions let transfers = shielded .query_tx_deltas( - client, + context.client(), &query_owner, &query_token, &wallet.get_viewing_keys(), @@ -173,8 +156,9 @@ pub async fn query_transfers< // Realize the rewards that would have been attained upon the // transaction's reception let amt = shielded - .compute_exchanged_amount::<_, IO>( - client, + .compute_exchanged_amount( + context.client(), + context.io(), amt, epoch, Conversions::new(), @@ -182,7 +166,8 @@ pub async fn query_transfers< .await .unwrap() .0; - let dec = shielded.decode_amount(client, amt, epoch).await; + let dec = + shielded.decode_amount(context.client(), amt, epoch).await; shielded_accounts.insert(acc, dec); } // Check if this transfer pertains to the supplied token @@ -205,7 +190,7 @@ pub async fn query_transfers< continue; } display_line!( - IO, + context.io(), "Height: {}, Index: {}, Transparent Transfer:", height, idx @@ -213,7 +198,7 @@ pub async fn query_transfers< // Display the transparent changes first for (account, MaspChange { ref asset, change }) in tfer_delta { if account != masp() { - display!(IO, " {}:", account); + display!(context.io(), " {}:", account); let token_alias = wallet.lookup_alias(asset); let sign = match change.cmp(&Change::zero()) { Ordering::Greater => "+", @@ -221,26 +206,21 @@ pub async fn query_transfers< Ordering::Equal => "", }; display!( - IO, + context.io(), " {}{} {}", sign, - format_denominated_amount::<_, IO>( - client, - asset, - change.into(), - ) - .await, + context.format_amount(asset, change.into()).await, token_alias ); } - display_line!(IO, ""); + display_line!(context.io(), ""); } // Then display the shielded changes afterwards // TODO: turn this to a display impl // (account, amt) for (account, masp_change) in shielded_accounts { if fvk_map.contains_key(&account) { - display!(IO, " {}:", fvk_map[&account]); + display!(context.io(), " {}:", fvk_map[&account]); for (token_addr, val) in masp_change { let token_alias = wallet.lookup_alias(&token_addr); let sign = match val.cmp(&Change::zero()) { @@ -249,125 +229,107 @@ pub async fn query_transfers< Ordering::Equal => "", }; display!( - IO, + context.io(), " {}{} {}", sign, - format_denominated_amount::<_, IO>( - client, - &token_addr, - val.into(), - ) - .await, + context.format_amount(&token_addr, val.into()).await, token_alias, ); } - display_line!(IO, ""); + display_line!(context.io(), ""); } } } } /// Query the raw bytes of given storage key -pub async fn query_raw_bytes< - C: namada::ledger::queries::Client + Sync, - IO: Io, ->( - client: &C, +pub async fn query_raw_bytes<'a, N: Namada<'a>>( + context: &N, args: args::QueryRawBytes, ) { - let response = unwrap_client_response::( + let response = unwrap_client_response::( RPC.shell() - .storage_value(client, None, None, false, &args.storage_key) + .storage_value( + context.client(), + None, + None, + false, + &args.storage_key, + ) .await, ); if !response.data.is_empty() { - display_line!(IO, "Found data: 0x{}", HEXLOWER.encode(&response.data)); + display_line!( + context.io(), + "Found data: 0x{}", + HEXLOWER.encode(&response.data) + ); } else { - display_line!(IO, "No data found for key {}", args.storage_key); + display_line!( + context.io(), + "No data found for key {}", + args.storage_key + ); } } /// Query token balance(s) -pub async fn query_balance< - C: namada::ledger::queries::Client + Sync, - U: ShieldedUtils, - IO: Io, ->( - client: &C, - wallet: &mut Wallet, - shielded: &mut ShieldedContext, +pub async fn query_balance<'a>( + context: &impl Namada<'a>, args: args::QueryBalance, ) { // Query the balances of shielded or transparent account types depending on // the CLI arguments match &args.owner { Some(BalanceOwner::FullViewingKey(_viewing_key)) => { - query_shielded_balance::<_, _, IO>(client, wallet, shielded, args) - .await + query_shielded_balance(context, args).await } Some(BalanceOwner::Address(_owner)) => { - query_transparent_balance::<_, IO>(client, wallet, args).await + query_transparent_balance(context, args).await } Some(BalanceOwner::PaymentAddress(_owner)) => { - query_pinned_balance::<_, _, IO>(client, wallet, shielded, args) - .await + query_pinned_balance(context, args).await } None => { // Print pinned balance - query_pinned_balance::<_, _, IO>( - client, - wallet, - shielded, - args.clone(), - ) - .await; + query_pinned_balance(context, args.clone()).await; // Print shielded balance - query_shielded_balance::<_, _, IO>( - client, - wallet, - shielded, - args.clone(), - ) - .await; + query_shielded_balance(context, args.clone()).await; // Then print transparent balance - query_transparent_balance::<_, IO>(client, wallet, args).await; + query_transparent_balance(context, args).await; } }; } /// Query token balance(s) -pub async fn query_transparent_balance< - C: namada::ledger::queries::Client + Sync, - IO: Io, ->( - client: &C, - wallet: &mut Wallet, +pub async fn query_transparent_balance<'a>( + context: &impl Namada<'a>, args: args::QueryBalance, ) { let prefix = Key::from( Address::Internal(namada::types::address::InternalAddress::Multitoken) .to_db_key(), ); - let tokens = wallet.tokens_with_aliases(); + let tokens = context.wallet().await.tokens_with_aliases(); match (args.token, args.owner) { (Some(token), Some(owner)) => { let balance_key = token::balance_key(&token, &owner.address().unwrap()); - let token_alias = wallet.lookup_alias(&token); - match query_storage_value::(client, &balance_key) - .await + let token_alias = context.wallet().await.lookup_alias(&token); + match query_storage_value::<_, token::Amount>( + context.client(), + &balance_key, + ) + .await { Ok(balance) => { - let balance = format_denominated_amount::<_, IO>( - client, &token, balance, - ) - .await; - display_line!(IO, "{}: {}", token_alias, balance); + let balance = context.format_amount(&token, balance).await; + display_line!(context.io(), "{}: {}", token_alias, balance); } Err(e) => { - display_line!(IO, "Eror in querying: {e}"); + display_line!(context.io(), "Eror in querying: {e}"); display_line!( - IO, + context.io(), "No {} balance found for {}", token_alias, owner @@ -378,56 +340,38 @@ pub async fn query_transparent_balance< (None, Some(owner)) => { let owner = owner.address().unwrap(); for (token_alias, token) in tokens { - let balance = get_token_balance(client, &token, &owner).await; + let balance = + get_token_balance(context.client(), &token, &owner).await; if !balance.is_zero() { - let balance = format_denominated_amount::<_, IO>( - client, &token, balance, - ) - .await; - display_line!(IO, "{}: {}", token_alias, balance); + let balance = context.format_amount(&token, balance).await; + display_line!(context.io(), "{}: {}", token_alias, balance); } } } (Some(token), None) => { let prefix = token::balance_prefix(&token); let balances = - query_storage_prefix::(client, &prefix) - .await; + query_storage_prefix::(context, &prefix).await; if let Some(balances) = balances { - print_balances::<_, IO>( - client, - wallet, - balances, - Some(&token), - None, - ) - .await; + print_balances(context, balances, Some(&token), None).await; } } (None, None) => { - let balances = - query_storage_prefix::(client, &prefix) - .await; + let balances = query_storage_prefix(context, &prefix).await; if let Some(balances) = balances { - print_balances::<_, IO>(client, wallet, balances, None, None) - .await; + print_balances(context, balances, None, None).await; } } } } /// Query the token pinned balance(s) -pub async fn query_pinned_balance< - C: namada::ledger::queries::Client + Sync, - U: ShieldedUtils, - IO: Io, ->( - client: &C, - wallet: &mut Wallet, - shielded: &mut ShieldedContext, +pub async fn query_pinned_balance<'a>( + context: &impl Namada<'a>, args: args::QueryBalance, ) { // Map addresses to token names + let wallet = context.wallet().await; let tokens = wallet.get_addresses_with_vp_type(AddressVpType::Token); let owners = if let Some(pa) = args.owner.and_then(|x| x.payment_address()) { @@ -445,7 +389,7 @@ pub async fn query_pinned_balance< .values() .map(|fvk| ExtendedFullViewingKey::from(*fvk).fvk.vk) .collect(); - let _ = shielded.load().await; + let _ = context.shielded_mut().await.load().await; // Print the token balances by payment address let pinned_error = Err(Error::from(PinnedBalanceError::InvalidViewingKey)); for owner in owners { @@ -453,8 +397,10 @@ pub async fn query_pinned_balance< // Find the viewing key that can recognize payments the current payment // address for vk in &viewing_keys { - balance = shielded - .compute_exchanged_pinned_balance::<_, IO>(client, owner, vk) + balance = context + .shielded_mut() + .await + .compute_exchanged_pinned_balance(context, owner, vk) .await; if !is_pinned_error(&balance) { break; @@ -463,18 +409,21 @@ pub async fn query_pinned_balance< // If a suitable viewing key was not found, then demand it from the user if is_pinned_error(&balance) { let vk_str = - prompt!(IO, "Enter the viewing key for {}: ", owner).await; + prompt!(context.io(), "Enter the viewing key for {}: ", owner) + .await; let fvk = match ExtendedViewingKey::from_str(vk_str.trim()) { Ok(fvk) => fvk, _ => { - edisplay_line!(IO, "Invalid viewing key entered"); + edisplay_line!(context.io(), "Invalid viewing key entered"); continue; } }; let vk = ExtendedFullViewingKey::from(fvk).fvk.vk; // Use the given viewing key to decrypt pinned transaction data - balance = shielded - .compute_exchanged_pinned_balance::<_, IO>(client, owner, &vk) + balance = context + .shielded_mut() + .await + .compute_exchanged_pinned_balance(context, owner, &vk) .await } @@ -482,7 +431,7 @@ pub async fn query_pinned_balance< match (balance, args.token.as_ref()) { (Err(Error::Pinned(PinnedBalanceError::InvalidViewingKey)), _) => { display_line!( - IO, + context.io(), "Supplied viewing key cannot decode transactions to given \ payment address." ) @@ -492,13 +441,17 @@ pub async fn query_pinned_balance< _, ) => { display_line!( - IO, + context.io(), "Payment address {} has not yet been consumed.", owner ) } (Err(other), _) => { - display_line!(IO, "Error in Querying Pinned balance {}", other) + display_line!( + context.io(), + "Error in Querying Pinned balance {}", + other + ) } (Ok((balance, epoch)), Some(token)) => { let token_alias = wallet.lookup_alias(token); @@ -510,7 +463,7 @@ pub async fn query_pinned_balance< if total_balance.is_zero() { display_line!( - IO, + context.io(), "Payment address {} was consumed during epoch {}. \ Received no shielded {}", owner, @@ -518,14 +471,11 @@ pub async fn query_pinned_balance< token_alias ); } else { - let formatted = format_denominated_amount::<_, IO>( - client, - token, - total_balance.into(), - ) - .await; + let formatted = context + .format_amount(token, total_balance.into()) + .await; display_line!( - IO, + context.io(), "Payment address {} was consumed during epoch {}. \ Received {} {}", owner, @@ -544,7 +494,7 @@ pub async fn query_pinned_balance< { if !found_any { display_line!( - IO, + context.io(), "Payment address {} was consumed during epoch {}. \ Received:", owner, @@ -552,21 +502,23 @@ pub async fn query_pinned_balance< ); found_any = true; } - let formatted = format_denominated_amount::<_, IO>( - client, - token_addr, - (*value).into(), - ) - .await; + let formatted = context + .format_amount(token_addr, (*value).into()) + .await; let token_alias = tokens .get(token_addr) .map(|a| a.to_string()) .unwrap_or_else(|| token_addr.to_string()); - display_line!(IO, " {}: {}", token_alias, formatted,); + display_line!( + context.io(), + " {}: {}", + token_alias, + formatted, + ); } if !found_any { display_line!( - IO, + context.io(), "Payment address {} was consumed during epoch {}. \ Received no shielded assets.", owner, @@ -578,15 +530,15 @@ pub async fn query_pinned_balance< } } -async fn print_balances( - client: &C, - wallet: &Wallet, +async fn print_balances<'a>( + context: &impl Namada<'a>, balances: impl Iterator, token: Option<&Address>, target: Option<&Address>, ) { let stdout = io::stdout(); let mut w = stdout.lock(); + let wallet = context.wallet().await; let mut print_num = 0; let mut print_token = None; @@ -599,8 +551,7 @@ async fn print_balances( owner.clone(), format!( ": {}, owned by {}", - format_denominated_amount::<_, IO>(client, tok, balance) - .await, + context.format_amount(tok, balance).await, wallet.lookup_alias(owner) ), ), @@ -629,19 +580,20 @@ async fn print_balances( } _ => { let token_alias = wallet.lookup_alias(&t); - display_line!(IO, &mut w; "Token {}", token_alias).unwrap(); + display_line!(context.io(), &mut w; "Token {}", token_alias) + .unwrap(); print_token = Some(t); } } // Print the balance - display_line!(IO, &mut w; "{}", s).unwrap(); + display_line!(context.io(), &mut w; "{}", s).unwrap(); print_num += 1; } if print_num == 0 { match (token, target) { (Some(_), Some(target)) | (None, Some(target)) => display_line!( - IO, + context.io(), &mut w; "No balances owned by {}", wallet.lookup_alias(target) @@ -649,38 +601,38 @@ async fn print_balances( .unwrap(), (Some(token), None) => { let token_alias = wallet.lookup_alias(token); - display_line!(IO, &mut w; "No balances for token {}", token_alias).unwrap() + display_line!(context.io(), &mut w; "No balances for token {}", token_alias).unwrap() + } + (None, None) => { + display_line!(context.io(), &mut w; "No balances").unwrap() } - (None, None) => display_line!(IO, &mut w; "No balances").unwrap(), } } } /// Query Proposals -pub async fn query_proposal< - C: namada::ledger::queries::Client + Sync, - IO: Io, ->( - client: &C, +pub async fn query_proposal<'a>( + context: &impl Namada<'a>, args: args::QueryProposal, ) { - let current_epoch = query_and_print_epoch::<_, IO>(client).await; + let current_epoch = query_and_print_epoch(context).await; if let Some(id) = args.proposal_id { - let proposal = query_proposal_by_id(client, id).await.unwrap(); + let proposal = + query_proposal_by_id(context.client(), id).await.unwrap(); if let Some(proposal) = proposal { display_line!( - IO, + context.io(), "{}", proposal.to_string_with_status(current_epoch) ); } else { - edisplay_line!(IO, "No proposal found with id: {}", id); + edisplay_line!(context.io(), "No proposal found with id: {}", id); } } else { let last_proposal_id_key = governance_storage::get_counter_key(); - let last_proposal_id = - query_storage_value::(client, &last_proposal_id_key) + let last_proposal_id: u64 = + query_storage_value(context.client(), &last_proposal_id_key) .await .unwrap(); @@ -690,14 +642,14 @@ pub async fn query_proposal< 0 }; - display_line!(IO, "id: {}", last_proposal_id); + display_line!(context.io(), "id: {}", last_proposal_id); for id in from_id..last_proposal_id { - let proposal = query_proposal_by_id(client, id) + let proposal = query_proposal_by_id(context.client(), id) .await .unwrap() .expect("Proposal should be written to storage."); - display_line!(IO, "{}", proposal); + display_line!(context.io(), "{}", proposal); } } } @@ -707,18 +659,12 @@ pub async fn query_proposal_by_id( client: &C, proposal_id: u64, ) -> Result, error::Error> { - namada::sdk::rpc::query_proposal_by_id(client, proposal_id).await + namada_sdk::rpc::query_proposal_by_id(client, proposal_id).await } /// Query token shielded balance(s) -pub async fn query_shielded_balance< - C: namada::ledger::queries::Client + Sync, - U: ShieldedUtils, - IO: Io, ->( - client: &C, - wallet: &mut Wallet, - shielded: &mut ShieldedContext, +pub async fn query_shielded_balance<'a>( + context: &impl Namada<'a>, args: args::QueryBalance, ) { // Used to control whether balances for all keys or a specific key are @@ -730,20 +676,32 @@ pub async fn query_shielded_balance< // provided, then convert to a viewing key first. let viewing_keys = match owner { Some(viewing_key) => vec![viewing_key], - None => wallet.get_viewing_keys().values().copied().collect(), + None => context + .wallet() + .await + .get_viewing_keys() + .values() + .copied() + .collect(), }; - let _ = shielded.load().await; - let fvks: Vec<_> = viewing_keys - .iter() - .map(|fvk| ExtendedFullViewingKey::from(*fvk).fvk.vk) - .collect(); - shielded.fetch(client, &[], &fvks).await.unwrap(); - // Save the update state so that future fetches can be short-circuited - let _ = shielded.save().await; + { + let mut shielded = context.shielded_mut().await; + let _ = shielded.load().await; + let fvks: Vec<_> = viewing_keys + .iter() + .map(|fvk| ExtendedFullViewingKey::from(*fvk).fvk.vk) + .collect(); + shielded.fetch(context.client(), &[], &fvks).await.unwrap(); + // Save the update state so that future fetches can be short-circuited + let _ = shielded.save().await; + } // The epoch is required to identify timestamped tokens - let epoch = query_and_print_epoch::<_, IO>(client).await; + let epoch = query_and_print_epoch(context).await; // Map addresses to token names - let tokens = wallet.get_addresses_with_vp_type(AddressVpType::Token); + let tokens = context + .wallet() + .await + .get_addresses_with_vp_type(AddressVpType::Token); match (args.token, owner.is_some()) { // Here the user wants to know the balance for a specific token (Some(token), true) => { @@ -751,15 +709,20 @@ pub async fn query_shielded_balance< let viewing_key = ExtendedFullViewingKey::from(viewing_keys[0]).fvk.vk; let balance: MaspAmount = if no_conversions { - shielded - .compute_shielded_balance(client, &viewing_key) + context + .shielded_mut() + .await + .compute_shielded_balance(context.client(), &viewing_key) .await .unwrap() .expect("context should contain viewing key") } else { - shielded - .compute_exchanged_balance::<_, IO>( - client, + context + .shielded_mut() + .await + .compute_exchanged_balance( + context.client(), + context.io(), &viewing_key, epoch, ) @@ -768,7 +731,7 @@ pub async fn query_shielded_balance< .expect("context should contain viewing key") }; - let token_alias = wallet.lookup_alias(&token); + let token_alias = context.wallet().await.lookup_alias(&token); let total_balance = balance .get(&(epoch, token.clone())) @@ -776,21 +739,21 @@ pub async fn query_shielded_balance< .unwrap_or_default(); if total_balance.is_zero() { display_line!( - IO, + context.io(), "No shielded {} balance found for given key", token_alias ); } else { display_line!( - IO, + context.io(), "{}: {}", token_alias, - format_denominated_amount::<_, IO>( - client, - &token, - token::Amount::from(total_balance) - ) - .await + context + .format_amount( + &token, + token::Amount::from(total_balance), + ) + .await ); } } @@ -802,15 +765,23 @@ pub async fn query_shielded_balance< // Query the multi-asset balance at the given spending key let viewing_key = ExtendedFullViewingKey::from(fvk).fvk.vk; let balance = if no_conversions { - shielded - .compute_shielded_balance(client, &viewing_key) + context + .shielded_mut() + .await + .compute_shielded_balance( + context.client(), + &viewing_key, + ) .await .unwrap() .expect("context should contain viewing key") } else { - shielded - .compute_exchanged_balance::<_, IO>( - client, + context + .shielded_mut() + .await + .compute_exchanged_balance( + context.client(), + context.io(), &viewing_key, epoch, ) @@ -836,7 +807,7 @@ pub async fn query_shielded_balance< // hashtable creation any uglier if balances.is_empty() { display_line!( - IO, + context.io(), "No shielded {} balance found for any wallet key", &token_addr ); @@ -852,14 +823,15 @@ pub async fn query_shielded_balance< .get(&token) .map(|a| a.to_string()) .unwrap_or_else(|| token.to_string()); - display_line!(IO, "Shielded Token {}:", alias); - let formatted = format_denominated_amount::<_, IO>( - client, - &token, - token_balance.into(), - ) - .await; - display_line!(IO, " {}, owned by {}", formatted, fvk); + display_line!(context.io(), "Shielded Token {}:", alias); + let formatted = + context.format_amount(&token, token_balance.into()).await; + display_line!( + context.io(), + " {}, owned by {}", + formatted, + fvk + ); } } // Here the user wants to know the balance for a specific token across @@ -874,24 +846,32 @@ pub async fn query_shielded_balance< .as_ref(), ) .unwrap(); - let token_alias = wallet.lookup_alias(&token); - display_line!(IO, "Shielded Token {}:", token_alias); + let token_alias = context.wallet().await.lookup_alias(&token); + display_line!(context.io(), "Shielded Token {}:", token_alias); let mut found_any = false; - let token_alias = wallet.lookup_alias(&token); - display_line!(IO, "Shielded Token {}:", token_alias,); + let token_alias = context.wallet().await.lookup_alias(&token); + display_line!(context.io(), "Shielded Token {}:", token_alias,); for fvk in viewing_keys { // Query the multi-asset balance at the given spending key let viewing_key = ExtendedFullViewingKey::from(fvk).fvk.vk; let balance = if no_conversions { - shielded - .compute_shielded_balance(client, &viewing_key) + context + .shielded_mut() + .await + .compute_shielded_balance( + context.client(), + &viewing_key, + ) .await .unwrap() .expect("context should contain viewing key") } else { - shielded - .compute_exchanged_balance::<_, IO>( - client, + context + .shielded_mut() + .await + .compute_exchanged_balance( + context.client(), + context.io(), &viewing_key, epoch, ) @@ -904,18 +884,19 @@ pub async fn query_shielded_balance< if !val.is_zero() { found_any = true; } - let formatted = format_denominated_amount::<_, IO>( - client, - address, - (*val).into(), - ) - .await; - display_line!(IO, " {}, owned by {}", formatted, fvk); + let formatted = + context.format_amount(address, (*val).into()).await; + display_line!( + context.io(), + " {}, owned by {}", + formatted, + fvk + ); } } if !found_any { display_line!( - IO, + context.io(), "No shielded {} balance found for any wallet key", token_alias, ); @@ -927,20 +908,22 @@ pub async fn query_shielded_balance< let viewing_key = ExtendedFullViewingKey::from(viewing_keys[0]).fvk.vk; if no_conversions { - let balance = shielded - .compute_shielded_balance(client, &viewing_key) + let balance = context + .shielded_mut() + .await + .compute_shielded_balance(context.client(), &viewing_key) .await .unwrap() .expect("context should contain viewing key"); // Print balances by human-readable token names - print_decoded_balance_with_epoch::<_, IO>( - client, wallet, balance, - ) - .await; + print_decoded_balance_with_epoch(context, balance).await; } else { - let balance = shielded - .compute_exchanged_balance::<_, IO>( - client, + let balance = context + .shielded_mut() + .await + .compute_exchanged_balance( + context.client(), + context.io(), &viewing_key, epoch, ) @@ -948,55 +931,44 @@ pub async fn query_shielded_balance< .unwrap() .expect("context should contain viewing key"); // Print balances by human-readable token names - print_decoded_balance::<_, IO>(client, wallet, balance, epoch) - .await; + print_decoded_balance(context, balance, epoch).await; } } } } -pub async fn print_decoded_balance< - C: namada::ledger::queries::Client + Sync, - IO: Io, ->( - client: &C, - wallet: &mut Wallet, +pub async fn print_decoded_balance<'a>( + context: &impl Namada<'a>, decoded_balance: MaspAmount, epoch: Epoch, ) { if decoded_balance.is_empty() { - display_line!(IO, "No shielded balance found for given key"); + display_line!(context.io(), "No shielded balance found for given key"); } else { for ((_, token_addr), amount) in decoded_balance .iter() .filter(|((token_epoch, _), _)| *token_epoch == epoch) { display_line!( - IO, + context.io(), "{} : {}", - wallet.lookup_alias(token_addr), - format_denominated_amount::<_, IO>( - client, - token_addr, - (*amount).into() - ) - .await, + context.wallet().await.lookup_alias(token_addr), + context.format_amount(token_addr, (*amount).into()).await, ); } } } -pub async fn print_decoded_balance_with_epoch< - C: namada::ledger::queries::Client + Sync, - IO: Io, ->( - client: &C, - wallet: &mut Wallet, +pub async fn print_decoded_balance_with_epoch<'a>( + context: &impl Namada<'a>, decoded_balance: MaspAmount, ) { - let tokens = wallet.get_addresses_with_vp_type(AddressVpType::Token); + let tokens = context + .wallet() + .await + .get_addresses_with_vp_type(AddressVpType::Token); if decoded_balance.is_empty() { - display_line!(IO, "No shielded balance found for given key"); + display_line!(context.io(), "No shielded balance found for given key"); } for ((epoch, token_addr), value) in decoded_balance.iter() { let asset_value = (*value).into(); @@ -1005,12 +977,11 @@ pub async fn print_decoded_balance_with_epoch< .map(|a| a.to_string()) .unwrap_or_else(|| token_addr.to_string()); display_line!( - IO, + context.io(), "{} | {} : {}", alias, epoch, - format_denominated_amount::<_, IO>(client, token_addr, asset_value) - .await, + context.format_amount(token_addr, asset_value).await, ); } } @@ -1021,40 +992,42 @@ pub async fn get_token_balance( token: &Address, owner: &Address, ) -> token::Amount { - namada::sdk::rpc::get_token_balance(client, token, owner) + namada_sdk::rpc::get_token_balance(client, token, owner) .await .unwrap() } -pub async fn query_proposal_result< - C: namada::ledger::queries::Client + Sync, - IO: Io, ->( - client: &C, +pub async fn query_proposal_result<'a>( + context: &impl Namada<'a>, args: args::QueryProposalResult, ) { if args.proposal_id.is_some() { let proposal_id = args.proposal_id.expect("Proposal id should be defined."); let proposal = if let Some(proposal) = - query_proposal_by_id(client, proposal_id).await.unwrap() + query_proposal_by_id(context.client(), proposal_id) + .await + .unwrap() { proposal } else { - edisplay_line!(IO, "Proposal {} not found.", proposal_id); + edisplay_line!(context.io(), "Proposal {} not found.", proposal_id); return; }; - let is_author_steward = query_pgf_stewards(client) + let is_author_steward = query_pgf_stewards(context.client()) .await .iter() .any(|steward| steward.address.eq(&proposal.author)); let tally_type = proposal.get_tally_type(is_author_steward); - let total_voting_power = - get_total_staked_tokens(client, proposal.voting_end_epoch).await; + let total_voting_power = get_total_staked_tokens( + context.client(), + proposal.voting_end_epoch, + ) + .await; let votes = compute_proposal_votes( - client, + context.client(), proposal_id, proposal.voting_end_epoch, ) @@ -1063,8 +1036,8 @@ pub async fn query_proposal_result< let proposal_result = compute_proposal_result(votes, total_voting_power, tally_type); - display_line!(IO, "Proposal Id: {} ", proposal_id); - display_line!(IO, "{:4}{}", "", proposal_result); + display_line!(context.io(), "Proposal Id: {} ", proposal_id); + display_line!(context.io(), "{:4}{}", "", proposal_result); } else { let proposal_folder = args.proposal_folder.expect( "The argument --proposal-folder is required with --offline.", @@ -1085,11 +1058,13 @@ pub async fn query_proposal_result< serde_json::from_reader(proposal_file) .expect("file should be proper JSON"); - let author_account = - rpc::get_account_info(client, &proposal.proposal.author) - .await - .unwrap() - .expect("Account should exist."); + let author_account = rpc::get_account_info( + context.client(), + &proposal.proposal.author, + ) + .await + .unwrap() + .expect("Account should exist."); let proposal = proposal.validate( &author_account.public_keys_map, @@ -1100,12 +1075,15 @@ pub async fn query_proposal_result< if proposal.is_ok() { proposal.unwrap() } else { - edisplay_line!(IO, "The offline proposal is not valid."); + edisplay_line!( + context.io(), + "The offline proposal is not valid." + ); return; } } else { edisplay_line!( - IO, + context.io(), "Couldn't find a file name offline_proposal_*.json." ); return; @@ -1121,15 +1099,14 @@ pub async fn query_proposal_result< }) .collect::>(); - let proposal_votes = compute_offline_proposal_votes::<_, IO>( - client, - &proposal, - votes.clone(), + let proposal_votes = + compute_offline_proposal_votes(context, &proposal, votes.clone()) + .await; + let total_voting_power = get_total_staked_tokens( + context.client(), + proposal.proposal.tally_epoch, ) .await; - let total_voting_power = - get_total_staked_tokens(client, proposal.proposal.tally_epoch) - .await; let proposal_result = compute_proposal_result( proposal_votes, @@ -1137,51 +1114,54 @@ pub async fn query_proposal_result< TallyType::TwoThird, ); - display_line!(IO, "Proposal offline: {}", proposal.proposal.hash()); - display_line!(IO, "Parsed {} votes.", votes.len()); - display_line!(IO, "{:4}{}", "", proposal_result); + display_line!( + context.io(), + "Proposal offline: {}", + proposal.proposal.hash() + ); + display_line!(context.io(), "Parsed {} votes.", votes.len()); + display_line!(context.io(), "{:4}{}", "", proposal_result); } } -pub async fn query_account< - C: namada::ledger::queries::Client + Sync, - IO: Io, ->( - client: &C, +pub async fn query_account<'a>( + context: &impl Namada<'a>, args: args::QueryAccount, ) { - let account = rpc::get_account_info(client, &args.owner).await.unwrap(); + let account = rpc::get_account_info(context.client(), &args.owner) + .await + .unwrap(); if let Some(account) = account { - display_line!(IO, "Address: {}", account.address); - display_line!(IO, "Threshold: {}", account.threshold); - display_line!(IO, "Public keys:"); + display_line!(context.io(), "Address: {}", account.address); + display_line!(context.io(), "Threshold: {}", account.threshold); + display_line!(context.io(), "Public keys:"); for (public_key, _) in account.public_keys_map.pk_to_idx { - display_line!(IO, "- {}", public_key); + display_line!(context.io(), "- {}", public_key); } } else { - display_line!(IO, "No account exists for {}", args.owner); + display_line!(context.io(), "No account exists for {}", args.owner); } } -pub async fn query_pgf( - client: &C, - _args: args::QueryPgf, -) { - let stewards = query_pgf_stewards(client).await; - let fundings = query_pgf_fundings(client).await; +pub async fn query_pgf<'a>(context: &impl Namada<'a>, _args: args::QueryPgf) { + let stewards = query_pgf_stewards(context.client()).await; + let fundings = query_pgf_fundings(context.client()).await; match stewards.is_empty() { true => { - display_line!(IO, "Pgf stewards: no stewards are currectly set.") + display_line!( + context.io(), + "Pgf stewards: no stewards are currectly set." + ) } false => { - display_line!(IO, "Pgf stewards:"); + display_line!(context.io(), "Pgf stewards:"); for steward in stewards { - display_line!(IO, "{:4}- {}", "", steward.address); - display_line!(IO, "{:4} Reward distribution:", ""); + display_line!(context.io(), "{:4}- {}", "", steward.address); + display_line!(context.io(), "{:4} Reward distribution:", ""); for (address, percentage) in steward.reward_distribution { display_line!( - IO, + context.io(), "{:6}- {} to {}", "", percentage, @@ -1194,13 +1174,16 @@ pub async fn query_pgf( match fundings.is_empty() { true => { - display_line!(IO, "Pgf fundings: no fundings are currently set.") + display_line!( + context.io(), + "Pgf fundings: no fundings are currently set." + ) } false => { - display_line!(IO, "Pgf fundings:"); + display_line!(context.io(), "Pgf fundings:"); for funding in fundings { display_line!( - IO, + context.io(), "{:4}- {} for {}", "", funding.detail.target, @@ -1211,180 +1194,198 @@ pub async fn query_pgf( } } -pub async fn query_protocol_parameters< - C: namada::ledger::queries::Client + Sync, - IO: Io, ->( - client: &C, +pub async fn query_protocol_parameters<'a>( + context: &impl Namada<'a>, _args: args::QueryProtocolParameters, ) { - let governance_parameters = query_governance_parameters(client).await; - display_line!(IO, "Governance Parameters\n"); + let governance_parameters = + query_governance_parameters(context.client()).await; + display_line!(context.io(), "Governance Parameters\n"); display_line!( - IO, + context.io(), "{:4}Min. proposal fund: {}", "", governance_parameters.min_proposal_fund.to_string_native() ); display_line!( - IO, + context.io(), "{:4}Max. proposal code size: {}", "", governance_parameters.max_proposal_code_size ); display_line!( - IO, + context.io(), "{:4}Min. proposal voting period: {}", "", governance_parameters.min_proposal_voting_period ); display_line!( - IO, + context.io(), "{:4}Max. proposal period: {}", "", governance_parameters.max_proposal_period ); display_line!( - IO, + context.io(), "{:4}Max. proposal content size: {}", "", governance_parameters.max_proposal_content_size ); display_line!( - IO, + context.io(), "{:4}Min. proposal grace epochs: {}", "", governance_parameters.min_proposal_grace_epochs ); - let pgf_parameters = query_pgf_parameters(client).await; - display_line!(IO, "Public Goods Funding Parameters\n"); + let pgf_parameters = query_pgf_parameters(context.client()).await; + display_line!(context.io(), "Public Goods Funding Parameters\n"); display_line!( - IO, + context.io(), "{:4}Pgf inflation rate: {}", "", pgf_parameters.pgf_inflation_rate ); display_line!( - IO, + context.io(), "{:4}Steward inflation rate: {}", "", pgf_parameters.stewards_inflation_rate ); - display_line!(IO, "Protocol parameters"); + display_line!(context.io(), "Protocol parameters"); let key = param_storage::get_epoch_duration_storage_key(); - let epoch_duration = query_storage_value::(client, &key) - .await - .expect("Parameter should be definied."); + let epoch_duration: EpochDuration = + query_storage_value(context.client(), &key) + .await + .expect("Parameter should be definied."); display_line!( - IO, + context.io(), "{:4}Min. epoch duration: {}", "", epoch_duration.min_duration ); display_line!( - IO, + context.io(), "{:4}Min. number of blocks: {}", "", epoch_duration.min_num_of_blocks ); let key = param_storage::get_max_expected_time_per_block_key(); - let max_block_duration = query_storage_value::(client, &key) + let max_block_duration: u64 = query_storage_value(context.client(), &key) .await .expect("Parameter should be defined."); - display_line!(IO, "{:4}Max. block duration: {}", "", max_block_duration); + display_line!( + context.io(), + "{:4}Max. block duration: {}", + "", + max_block_duration + ); let key = param_storage::get_tx_whitelist_storage_key(); - let vp_whitelist = query_storage_value::>(client, &key) + let vp_whitelist: Vec = query_storage_value(context.client(), &key) .await .expect("Parameter should be defined."); - display_line!(IO, "{:4}VP whitelist: {:?}", "", vp_whitelist); + display_line!(context.io(), "{:4}VP whitelist: {:?}", "", vp_whitelist); let key = param_storage::get_tx_whitelist_storage_key(); - let tx_whitelist = query_storage_value::>(client, &key) + let tx_whitelist: Vec = query_storage_value(context.client(), &key) .await .expect("Parameter should be defined."); - display_line!(IO, "{:4}Transactions whitelist: {:?}", "", tx_whitelist); + display_line!( + context.io(), + "{:4}Transactions whitelist: {:?}", + "", + tx_whitelist + ); let key = param_storage::get_max_block_gas_key(); - let max_block_gas = query_storage_value::(client, &key) + let max_block_gas: u64 = query_storage_value(context.client(), &key) .await .expect("Parameter should be defined."); - display_line!(IO, "{:4}Max block gas: {:?}", "", max_block_gas); + display_line!(context.io(), "{:4}Max block gas: {:?}", "", max_block_gas); let key = param_storage::get_fee_unshielding_gas_limit_key(); - let fee_unshielding_gas_limit = query_storage_value::(client, &key) - .await - .expect("Parameter should be defined."); + let fee_unshielding_gas_limit: u64 = + query_storage_value(context.client(), &key) + .await + .expect("Parameter should be defined."); display_line!( - IO, + context.io(), "{:4}Fee unshielding gas limit: {:?}", "", fee_unshielding_gas_limit ); let key = param_storage::get_fee_unshielding_descriptions_limit_key(); - let fee_unshielding_descriptions_limit = - query_storage_value::(client, &key) + let fee_unshielding_descriptions_limit: u64 = + query_storage_value(context.client(), &key) .await .expect("Parameter should be defined."); display_line!( - IO, + context.io(), "{:4}Fee unshielding descriptions limit: {:?}", "", fee_unshielding_descriptions_limit ); let key = param_storage::get_gas_cost_key(); - let gas_cost_table = query_storage_value::< - C, - BTreeMap, - >(client, &key) - .await - .expect("Parameter should be defined."); - display_line!(IO, "{:4}Gas cost table:", ""); + let gas_cost_table: BTreeMap = + query_storage_value(context.client(), &key) + .await + .expect("Parameter should be defined."); + display_line!(context.io(), "{:4}Gas cost table:", ""); for (token, gas_cost) in gas_cost_table { - display_line!(IO, "{:8}{}: {:?}", "", token, gas_cost); + display_line!(context.io(), "{:8}{}: {:?}", "", token, gas_cost); } - display_line!(IO, "PoS parameters"); - let pos_params = query_pos_parameters(client).await; + display_line!(context.io(), "PoS parameters"); + let pos_params = query_pos_parameters(context.client()).await; display_line!( - IO, + context.io(), "{:4}Block proposer reward: {}", "", pos_params.block_proposer_reward ); display_line!( - IO, + context.io(), "{:4}Block vote reward: {}", "", pos_params.block_vote_reward ); display_line!( - IO, + context.io(), "{:4}Duplicate vote minimum slash rate: {}", "", pos_params.duplicate_vote_min_slash_rate ); display_line!( - IO, + context.io(), "{:4}Light client attack minimum slash rate: {}", "", pos_params.light_client_attack_min_slash_rate ); display_line!( - IO, + context.io(), "{:4}Max. validator slots: {}", "", pos_params.max_validator_slots ); - display_line!(IO, "{:4}Pipeline length: {}", "", pos_params.pipeline_len); - display_line!(IO, "{:4}Unbonding length: {}", "", pos_params.unbonding_len); display_line!( - IO, + context.io(), + "{:4}Pipeline length: {}", + "", + pos_params.pipeline_len + ); + display_line!( + context.io(), + "{:4}Unbonding length: {}", + "", + pos_params.unbonding_len + ); + display_line!( + context.io(), "{:4}Votes per token: {}", "", pos_params.tm_votes_per_token @@ -1443,16 +1444,14 @@ pub async fn query_pgf_parameters( unwrap_client_response::(RPC.vp().pgf().parameters(client).await) } -pub async fn query_and_print_unbonds< - C: namada::ledger::queries::Client + Sync, - IO: Io, ->( - client: &C, +pub async fn query_and_print_unbonds<'a>( + context: &impl Namada<'a>, source: &Address, validator: &Address, ) { - let unbonds = query_unbond_with_slashing(client, source, validator).await; - let current_epoch = query_epoch(client).await.unwrap(); + let unbonds = + query_unbond_with_slashing(context.client(), source, validator).await; + let current_epoch = query_epoch(context.client()).await.unwrap(); let mut total_withdrawable = token::Amount::default(); let mut not_yet_withdrawable = HashMap::::new(); @@ -1467,17 +1466,17 @@ pub async fn query_and_print_unbonds< } if total_withdrawable != token::Amount::default() { display_line!( - IO, + context.io(), "Total withdrawable now: {}.", total_withdrawable.to_string_native() ); } if !not_yet_withdrawable.is_empty() { - display_line!(IO, "Current epoch: {current_epoch}."); + display_line!(context.io(), "Current epoch: {current_epoch}."); } for (withdraw_epoch, amount) in not_yet_withdrawable { display_line!( - IO, + context.io(), "Amount {} withdrawable starting from epoch {withdraw_epoch}.", amount.to_string_native(), ); @@ -1501,12 +1500,11 @@ pub async fn query_withdrawable_tokens< } /// Query PoS bond(s) and unbond(s) -pub async fn query_bonds( - client: &C, - _wallet: &mut Wallet, +pub async fn query_bonds<'a>( + context: &impl Namada<'a>, args: args::QueryBonds, ) -> std::io::Result<()> { - let epoch = query_and_print_epoch::<_, IO>(client).await; + let epoch = query_and_print_epoch(context).await; let source = args.owner; let validator = args.validator; @@ -1514,10 +1512,14 @@ pub async fn query_bonds( let stdout = io::stdout(); let mut w = stdout.lock(); - let bonds_and_unbonds = - enriched_bonds_and_unbonds(client, epoch, &source, &validator) - .await - .unwrap(); + let bonds_and_unbonds = enriched_bonds_and_unbonds( + context.client(), + epoch, + &source, + &validator, + ) + .await + .unwrap(); for (bond_id, details) in &bonds_and_unbonds.data { let bond_type = if bond_id.source == bond_id.validator { @@ -1528,10 +1530,10 @@ pub async fn query_bonds( bond_id.source, bond_id.validator ) }; - display_line!(IO, &mut w; "{}:", bond_type)?; + display_line!(context.io(), &mut w; "{}:", bond_type)?; for bond in &details.data.bonds { display_line!( - IO, + context.io(), &mut w; " Remaining active bond from epoch {}: Δ {}", bond.start, @@ -1540,14 +1542,14 @@ pub async fn query_bonds( } if details.bonds_total != token::Amount::zero() { display_line!( - IO, + context.io(), &mut w; "Active (slashed) bonds total: {}", details.bonds_total_active().to_string_native() )?; } - display_line!(IO, &mut w; "Bonds total: {}", details.bonds_total.to_string_native())?; - display_line!(IO, &mut w; "")?; + display_line!(context.io(), &mut w; "Bonds total: {}", details.bonds_total.to_string_native())?; + display_line!(context.io(), &mut w; "")?; if !details.data.unbonds.is_empty() { let bond_type = if bond_id.source == bond_id.validator { @@ -1555,10 +1557,10 @@ pub async fn query_bonds( } else { format!("Unbonded delegations from {}", bond_id.source) }; - display_line!(IO, &mut w; "{}:", bond_type)?; + display_line!(context.io(), &mut w; "{}:", bond_type)?; for unbond in &details.data.unbonds { display_line!( - IO, + context.io(), &mut w; " Withdrawable from epoch {} (active from {}): Δ {}", unbond.withdraw, @@ -1567,30 +1569,30 @@ pub async fn query_bonds( )?; } display_line!( - IO, + context.io(), &mut w; "Unbonded total: {}", details.unbonds_total.to_string_native() )?; } display_line!( - IO, + context.io(), &mut w; "Withdrawable total: {}", details.total_withdrawable.to_string_native() )?; - display_line!(IO, &mut w; "")?; + display_line!(context.io(), &mut w; "")?; } if bonds_and_unbonds.bonds_total != bonds_and_unbonds.bonds_total_slashed { display_line!( - IO, + context.io(), &mut w; "All bonds total active: {}", bonds_and_unbonds.bonds_total_active().to_string_native() )?; } display_line!( - IO, + context.io(), &mut w; "All bonds total: {}", bonds_and_unbonds.bonds_total.to_string_native() @@ -1600,20 +1602,20 @@ pub async fn query_bonds( != bonds_and_unbonds.unbonds_total_slashed { display_line!( - IO, + context.io(), &mut w; "All unbonds total active: {}", bonds_and_unbonds.unbonds_total_active().to_string_native() )?; } display_line!( - IO, + context.io(), &mut w; "All unbonds total: {}", bonds_and_unbonds.unbonds_total.to_string_native() )?; display_line!( - IO, + context.io(), &mut w; "All unbonds total withdrawable: {}", bonds_and_unbonds.total_withdrawable.to_string_native() @@ -1622,51 +1624,55 @@ pub async fn query_bonds( } /// Query PoS bonded stake -pub async fn query_bonded_stake< - C: namada::ledger::queries::Client + Sync, - IO: Io, ->( - client: &C, +pub async fn query_bonded_stake<'a, N: Namada<'a>>( + context: &N, args: args::QueryBondedStake, ) { let epoch = match args.epoch { Some(epoch) => epoch, - None => query_and_print_epoch::<_, IO>(client).await, + None => query_and_print_epoch(context).await, }; match args.validator { Some(validator) => { let validator = validator; // Find bonded stake for the given validator - let stake = get_validator_stake(client, epoch, &validator).await; + let stake = + get_validator_stake(context.client(), epoch, &validator).await; match stake { Some(stake) => { // TODO: show if it's in consensus set, below capacity, or // below threshold set display_line!( - IO, + context.io(), "Bonded stake of validator {validator}: {}", stake.to_string_native() ) } None => { - display_line!(IO, "No bonded stake found for {validator}"); + display_line!( + context.io(), + "No bonded stake found for {validator}" + ); } } } None => { - let consensus = - unwrap_client_response::>( + let consensus: BTreeSet = + unwrap_client_response::( RPC.vp() .pos() - .consensus_validator_set(client, &Some(epoch)) + .consensus_validator_set(context.client(), &Some(epoch)) .await, ); - let below_capacity = - unwrap_client_response::>( + let below_capacity: BTreeSet = + unwrap_client_response::( RPC.vp() .pos() - .below_capacity_validator_set(client, &Some(epoch)) + .below_capacity_validator_set( + context.client(), + &Some(epoch), + ) .await, ); @@ -1674,10 +1680,11 @@ pub async fn query_bonded_stake< let stdout = io::stdout(); let mut w = stdout.lock(); - display_line!(IO, &mut w; "Consensus validators:").unwrap(); + display_line!(context.io(), &mut w; "Consensus validators:") + .unwrap(); for val in consensus.into_iter().rev() { display_line!( - IO, + context.io(), &mut w; " {}: {}", val.address.encode(), @@ -1686,11 +1693,11 @@ pub async fn query_bonded_stake< .unwrap(); } if !below_capacity.is_empty() { - display_line!(IO, &mut w; "Below capacity validators:") + display_line!(context.io(), &mut w; "Below capacity validators:") .unwrap(); for val in below_capacity.into_iter().rev() { display_line!( - IO, + context.io(), &mut w; " {}: {}", val.address.encode(), @@ -1702,9 +1709,10 @@ pub async fn query_bonded_stake< } } - let total_staked_tokens = get_total_staked_tokens(client, epoch).await; + let total_staked_tokens = + get_total_staked_tokens(context.client(), epoch).await; display_line!( - IO, + context.io(), "Total bonded stake: {}", total_staked_tokens.to_string_native() ); @@ -1744,47 +1752,43 @@ pub async fn query_validator_state< } /// Query a validator's state information -pub async fn query_and_print_validator_state< - C: namada::ledger::queries::Client + Sync, - IO: Io, ->( - client: &C, - _wallet: &mut Wallet, +pub async fn query_and_print_validator_state<'a>( + context: &impl Namada<'a>, args: args::QueryValidatorState, ) { let validator = args.validator; let state: Option = - query_validator_state(client, &validator, args.epoch).await; + query_validator_state(context.client(), &validator, args.epoch).await; match state { Some(state) => match state { ValidatorState::Consensus => { display_line!( - IO, + context.io(), "Validator {validator} is in the consensus set" ) } ValidatorState::BelowCapacity => { display_line!( - IO, + context.io(), "Validator {validator} is in the below-capacity set" ) } ValidatorState::BelowThreshold => { display_line!( - IO, + context.io(), "Validator {validator} is in the below-threshold set" ) } ValidatorState::Inactive => { - display_line!(IO, "Validator {validator} is inactive") + display_line!(context.io(), "Validator {validator} is inactive") } ValidatorState::Jailed => { - display_line!(IO, "Validator {validator} is jailed") + display_line!(context.io(), "Validator {validator} is jailed") } }, None => display_line!( - IO, + context.io(), "Validator {validator} is either not a validator, or an epoch \ before the current epoch has been queried (and the validator \ state information is no longer stored)" @@ -1793,25 +1797,21 @@ pub async fn query_and_print_validator_state< } /// Query PoS validator's commission rate information -pub async fn query_and_print_commission_rate< - C: namada::ledger::queries::Client + Sync, - IO: Io, ->( - client: &C, - _wallet: &mut Wallet, +pub async fn query_and_print_commission_rate<'a>( + context: &impl Namada<'a>, args: args::QueryCommissionRate, ) { let validator = args.validator; let info: Option = - query_commission_rate(client, &validator, args.epoch).await; + query_commission_rate(context.client(), &validator, args.epoch).await; match info { Some(CommissionPair { commission_rate: rate, max_commission_change_per_epoch: change, }) => { display_line!( - IO, + context.io(), "Validator {} commission rate: {}, max change per epoch: {}", validator.encode(), rate, @@ -1820,7 +1820,7 @@ pub async fn query_and_print_commission_rate< } None => { display_line!( - IO, + context.io(), "Address {} is not a validator (did not find commission rate \ and max change)", validator.encode(), @@ -1830,28 +1830,27 @@ pub async fn query_and_print_commission_rate< } /// Query PoS slashes -pub async fn query_slashes< - C: namada::ledger::queries::Client + Sync, - IO: Io, ->( - client: &C, - _wallet: &mut Wallet, +pub async fn query_slashes<'a, N: Namada<'a>>( + context: &N, args: args::QuerySlashes, ) { match args.validator { Some(validator) => { let validator = validator; // Find slashes for the given validator - let slashes: Vec = unwrap_client_response::>( - RPC.vp().pos().validator_slashes(client, &validator).await, + let slashes: Vec = unwrap_client_response::( + RPC.vp() + .pos() + .validator_slashes(context.client(), &validator) + .await, ); if !slashes.is_empty() { - display_line!(IO, "Processed slashes:"); + display_line!(context.io(), "Processed slashes:"); let stdout = io::stdout(); let mut w = stdout.lock(); for slash in slashes { display_line!( - IO, + context.io(), &mut w; "Infraction epoch {}, block height {}, type {}, rate \ {}", @@ -1864,7 +1863,7 @@ pub async fn query_slashes< } } else { display_line!( - IO, + context.io(), "No processed slashes found for {}", validator.encode() ) @@ -1874,20 +1873,26 @@ pub async fn query_slashes< let enqueued_slashes: HashMap< Address, BTreeMap>, - > = unwrap_client_response::< - C, - HashMap>>, - >(RPC.vp().pos().enqueued_slashes(client).await); + > = unwrap_client_response::( + RPC.vp().pos().enqueued_slashes(context.client()).await, + ); let enqueued_slashes = enqueued_slashes.get(&validator).cloned(); if let Some(enqueued) = enqueued_slashes { - display_line!(IO, "\nEnqueued slashes for future processing"); + display_line!( + context.io(), + "\nEnqueued slashes for future processing" + ); for (epoch, slashes) in enqueued { - display_line!(IO, "To be processed in epoch {}", epoch); + display_line!( + context.io(), + "To be processed in epoch {}", + epoch + ); for slash in slashes { let stdout = io::stdout(); let mut w = stdout.lock(); display_line!( - IO, + context.io(), &mut w; "Infraction epoch {}, block height {}, type {}", slash.epoch, slash.block_height, slash.r#type, @@ -1897,7 +1902,7 @@ pub async fn query_slashes< } } else { display_line!( - IO, + context.io(), "No enqueued slashes found for {}", validator.encode() ) @@ -1905,18 +1910,18 @@ pub async fn query_slashes< } None => { let all_slashes: HashMap> = - unwrap_client_response::>>( - RPC.vp().pos().slashes(client).await, + unwrap_client_response::( + RPC.vp().pos().slashes(context.client()).await, ); if !all_slashes.is_empty() { let stdout = io::stdout(); let mut w = stdout.lock(); - display_line!(IO, "Processed slashes:"); + display_line!(context.io(), "Processed slashes:"); for (validator, slashes) in all_slashes.into_iter() { for slash in slashes { display_line!( - IO, + context.io(), &mut w; "Infraction epoch {}, block height {}, rate {}, \ type {}, validator {}", @@ -1930,7 +1935,7 @@ pub async fn query_slashes< } } } else { - display_line!(IO, "No processed slashes found") + display_line!(context.io(), "No processed slashes found") } // Find enqueued slashes to be processed in the future for the given @@ -1938,16 +1943,18 @@ pub async fn query_slashes< let enqueued_slashes: HashMap< Address, BTreeMap>, - > = unwrap_client_response::< - C, - HashMap>>, - >(RPC.vp().pos().enqueued_slashes(client).await); + > = unwrap_client_response::( + RPC.vp().pos().enqueued_slashes(context.client()).await, + ); if !enqueued_slashes.is_empty() { - display_line!(IO, "\nEnqueued slashes for future processing"); + display_line!( + context.io(), + "\nEnqueued slashes for future processing" + ); for (validator, slashes_by_epoch) in enqueued_slashes { for (epoch, slashes) in slashes_by_epoch { display_line!( - IO, + context.io(), "\nTo be processed in epoch {}", epoch ); @@ -1955,7 +1962,7 @@ pub async fn query_slashes< let stdout = io::stdout(); let mut w = stdout.lock(); display_line!( - IO, + context.io(), &mut w; "Infraction epoch {}, block height {}, type \ {}, validator {}", @@ -1970,7 +1977,7 @@ pub async fn query_slashes< } } else { display_line!( - IO, + context.io(), "\nNo enqueued slashes found for future processing" ) } @@ -1978,55 +1985,57 @@ pub async fn query_slashes< } } -pub async fn query_delegations< - C: namada::ledger::queries::Client + Sync, - IO: Io, ->( - client: &C, - _wallet: &mut Wallet, +pub async fn query_delegations<'a, N: Namada<'a>>( + context: &N, args: args::QueryDelegations, ) { let owner = args.owner; - let delegations = unwrap_client_response::>( - RPC.vp().pos().delegation_validators(client, &owner).await, + let delegations: HashSet
= unwrap_client_response::( + RPC.vp() + .pos() + .delegation_validators(context.client(), &owner) + .await, ); if delegations.is_empty() { - display_line!(IO, "No delegations found"); + display_line!(context.io(), "No delegations found"); } else { - display_line!(IO, "Found delegations to:"); + display_line!(context.io(), "Found delegations to:"); for delegation in delegations { - display_line!(IO, " {delegation}"); + display_line!(context.io(), " {delegation}"); } } } -pub async fn query_find_validator< - C: namada::ledger::queries::Client + Sync, - IO: Io, ->( - client: &C, +pub async fn query_find_validator<'a, N: Namada<'a>>( + context: &N, args: args::QueryFindValidator, ) { let args::QueryFindValidator { query: _, tm_addr } = args; if tm_addr.len() != 40 { edisplay_line!( - IO, + context.io(), "Expected 40 characters in Tendermint address, got {}", tm_addr.len() ); cli::safe_exit(1); } let tm_addr = tm_addr.to_ascii_uppercase(); - let validator = unwrap_client_response::( - RPC.vp().pos().validator_by_tm_addr(client, &tm_addr).await, + let validator = unwrap_client_response::( + RPC.vp() + .pos() + .validator_by_tm_addr(context.client(), &tm_addr) + .await, ); match validator { Some(address) => { - display_line!(IO, "Found validator address \"{address}\".") + display_line!( + context.io(), + "Found validator address \"{address}\"." + ) } None => { display_line!( - IO, + context.io(), "No validator with Tendermint address {tm_addr} found." ) } @@ -2034,18 +2043,17 @@ pub async fn query_find_validator< } /// Dry run a transaction -pub async fn dry_run_tx( - client: &C, +pub async fn dry_run_tx<'a, N: Namada<'a>>( + context: &N, tx_bytes: Vec, ) -> Result<(), error::Error> where - C: namada::ledger::queries::Client + Sync, - C::Error: std::fmt::Display, + ::Error: std::fmt::Display, { display_line!( - IO, + context.io(), "Dry-run result: {}", - rpc::dry_run_tx::<_, IO>(client, tx_bytes).await? + rpc::dry_run_tx(context, tx_bytes).await? ); Ok(()) } @@ -2064,7 +2072,7 @@ pub async fn is_validator( client: &C, address: &Address, ) -> bool { - namada::sdk::rpc::is_validator(client, address) + namada_sdk::rpc::is_validator(client, address) .await .unwrap() } @@ -2074,7 +2082,7 @@ pub async fn is_delegator( client: &C, address: &Address, ) -> bool { - namada::sdk::rpc::is_delegator(client, address) + namada_sdk::rpc::is_delegator(client, address) .await .unwrap() } @@ -2084,7 +2092,7 @@ pub async fn is_delegator_at( address: &Address, epoch: Epoch, ) -> bool { - namada::sdk::rpc::is_delegator_at(client, address, epoch) + namada_sdk::rpc::is_delegator_at(client, address, epoch) .await .unwrap() } @@ -2096,31 +2104,30 @@ pub async fn known_address( client: &C, address: &Address, ) -> bool { - namada::sdk::rpc::known_address(client, address) + namada_sdk::rpc::known_address(client, address) .await .unwrap() } /// Query for all conversions. -pub async fn query_conversions< - C: namada::ledger::queries::Client + Sync, - IO: Io, ->( - client: &C, - wallet: &mut Wallet, +pub async fn query_conversions<'a>( + context: &impl Namada<'a>, args: args::QueryConversions, ) { // The chosen token type of the conversions let target_token = args.token; // To facilitate human readable token addresses - let tokens = wallet.get_addresses_with_vp_type(AddressVpType::Token); + let tokens = context + .wallet() + .await + .get_addresses_with_vp_type(AddressVpType::Token); let masp_addr = masp(); let key_prefix: Key = masp_addr.to_db_key().into(); let state_key = key_prefix .push(&(token::CONVERSION_KEY_PREFIX.to_owned())) .unwrap(); - let conv_state = - query_storage_value::(client, &state_key) + let conv_state: ConversionState = + query_storage_value(context.client(), &state_key) .await .expect("Conversions should be defined"); // Track whether any non-sentinel conversions are found @@ -2139,7 +2146,7 @@ pub async fn query_conversions< conversions_found = true; // Print the asset to which the conversion applies display!( - IO, + context.io(), "{}[{}]: ", tokens.get(addr).cloned().unwrap_or_else(|| addr.clone()), epoch, @@ -2152,7 +2159,7 @@ pub async fn query_conversions< let ((addr, _), epoch, _, _) = &conv_state.assets[asset_type]; // Now print out this component of the conversion display!( - IO, + context.io(), "{}{} {}[{}]", prefix, val, @@ -2163,11 +2170,11 @@ pub async fn query_conversions< prefix = " + "; } // Allowed conversions are always implicit equations - display_line!(IO, " = 0"); + display_line!(context.io(), " = 0"); } if !conversions_found { display_line!( - IO, + context.io(), "No conversions found satisfying specified criteria." ); } @@ -2184,18 +2191,15 @@ pub async fn query_conversion( masp_primitives::transaction::components::I32Sum, MerklePath, )> { - namada::sdk::rpc::query_conversion(client, asset_type).await + namada_sdk::rpc::query_conversion(client, asset_type).await } /// Query a wasm code hash -pub async fn query_wasm_code_hash< - C: namada::ledger::queries::Client + Sync, - IO: Io, ->( - client: &C, +pub async fn query_wasm_code_hash<'a>( + context: &impl Namada<'a>, code_path: impl AsRef, ) -> Result { - rpc::query_wasm_code_hash::<_, IO>(client, code_path).await + rpc::query_wasm_code_hash(context, code_path).await } /// Query a storage value and decode it with [`BorshDeserialize`]. @@ -2206,7 +2210,7 @@ pub async fn query_storage_value( where T: BorshDeserialize, { - namada::sdk::rpc::query_storage_value(client, key).await + namada_sdk::rpc::query_storage_value(client, key).await } /// Query a storage value and the proof without decoding. @@ -2218,7 +2222,7 @@ pub async fn query_storage_value_bytes< height: Option, prove: bool, ) -> (Option>, Option) { - namada::sdk::rpc::query_storage_value_bytes(client, key, height, prove) + namada_sdk::rpc::query_storage_value_bytes(client, key, height, prove) .await .unwrap() } @@ -2226,20 +2230,14 @@ pub async fn query_storage_value_bytes< /// Query a range of storage values with a matching prefix and decode them with /// [`BorshDeserialize`]. Returns an iterator of the storage keys paired with /// their associated values. -pub async fn query_storage_prefix< - C: namada::ledger::queries::Client + Sync, - T, - IO: Io, ->( - client: &C, +pub async fn query_storage_prefix<'a, 'b, T>( + context: &'b impl Namada<'a>, key: &storage::Key, -) -> Option> +) -> Option> where T: BorshDeserialize, { - rpc::query_storage_prefix::<_, IO, _>(client, key) - .await - .unwrap() + rpc::query_storage_prefix(context, key).await.unwrap() } /// Query to check if the given storage key exists. @@ -2249,7 +2247,7 @@ pub async fn query_has_storage_key< client: &C, key: &storage::Key, ) -> bool { - namada::sdk::rpc::query_has_storage_key(client, key) + namada_sdk::rpc::query_has_storage_key(client, key) .await .unwrap() } @@ -2258,39 +2256,39 @@ pub async fn query_has_storage_key< /// the current status of a transation. pub async fn query_tx_events( client: &C, - tx_event_query: namada::sdk::rpc::TxEventQuery<'_>, + tx_event_query: namada_sdk::rpc::TxEventQuery<'_>, ) -> std::result::Result< Option, ::Error, > { - namada::sdk::rpc::query_tx_events(client, tx_event_query).await + namada_sdk::rpc::query_tx_events(client, tx_event_query).await } /// Lookup the full response accompanying the specified transaction event // TODO: maybe remove this in favor of `query_tx_status` pub async fn query_tx_response( client: &C, - tx_query: namada::sdk::rpc::TxEventQuery<'_>, + tx_query: namada_sdk::rpc::TxEventQuery<'_>, ) -> Result { - namada::sdk::rpc::query_tx_response(client, tx_query).await + namada_sdk::rpc::query_tx_response(client, tx_query).await } /// Lookup the results of applying the specified transaction to the /// blockchain. -pub async fn query_result( - client: &C, +pub async fn query_result<'a>( + context: &impl Namada<'a>, args: args::QueryResult, ) { // First try looking up application event pertaining to given hash. let tx_response = query_tx_response( - client, - namada::sdk::rpc::TxEventQuery::Applied(&args.tx_hash), + context.client(), + namada_sdk::rpc::TxEventQuery::Applied(&args.tx_hash), ) .await; match tx_response { Ok(result) => { display_line!( - IO, + context.io(), "Transaction was applied with result: {}", serde_json::to_string_pretty(&result).unwrap() ) @@ -2298,19 +2296,19 @@ pub async fn query_result( Err(err1) => { // If this fails then instead look for an acceptance event. let tx_response = query_tx_response( - client, - namada::sdk::rpc::TxEventQuery::Accepted(&args.tx_hash), + context.client(), + namada_sdk::rpc::TxEventQuery::Accepted(&args.tx_hash), ) .await; match tx_response { Ok(result) => display_line!( - IO, + context.io(), "Transaction was accepted with result: {}", serde_json::to_string_pretty(&result).unwrap() ), Err(err2) => { // Print the errors that caused the lookups to fail - edisplay_line!(IO, "{}\n{}", err1, err2); + edisplay_line!(context.io(), "{}\n{}", err1, err2); cli::safe_exit(1) } } @@ -2318,16 +2316,13 @@ pub async fn query_result( } } -pub async fn epoch_sleep( - client: &C, - _args: args::Query, -) { - let start_epoch = query_and_print_epoch::<_, IO>(client).await; +pub async fn epoch_sleep<'a>(context: &impl Namada<'a>, _args: args::Query) { + let start_epoch = query_and_print_epoch(context).await; loop { tokio::time::sleep(core::time::Duration::from_secs(1)).await; - let current_epoch = query_epoch(client).await.unwrap(); + let current_epoch = query_epoch(context.client()).await.unwrap(); if current_epoch > start_epoch { - display_line!(IO, "Reached epoch {}", current_epoch); + display_line!(context.io(), "Reached epoch {}", current_epoch); break; } } @@ -2353,7 +2348,7 @@ pub async fn get_all_validators( client: &C, epoch: Epoch, ) -> HashSet
{ - namada::sdk::rpc::get_all_validators(client, epoch) + namada_sdk::rpc::get_all_validators(client, epoch) .await .unwrap() } @@ -2364,7 +2359,7 @@ pub async fn get_total_staked_tokens< client: &C, epoch: Epoch, ) -> token::Amount { - namada::sdk::rpc::get_total_staked_tokens(client, epoch) + namada_sdk::rpc::get_total_staked_tokens(client, epoch) .await .unwrap() } @@ -2392,7 +2387,7 @@ pub async fn get_delegators_delegation< client: &C, address: &Address, ) -> HashSet
{ - namada::sdk::rpc::get_delegators_delegation(client, address) + namada_sdk::rpc::get_delegators_delegation(client, address) .await .unwrap() } @@ -2404,7 +2399,7 @@ pub async fn get_delegators_delegation_at< address: &Address, epoch: Epoch, ) -> HashMap { - namada::sdk::rpc::get_delegators_delegation_at(client, address, epoch) + namada_sdk::rpc::get_delegators_delegation_at(client, address, epoch) .await .unwrap() } @@ -2414,7 +2409,7 @@ pub async fn query_governance_parameters< >( client: &C, ) -> GovernanceParameters { - namada::sdk::rpc::query_governance_parameters(client).await + namada_sdk::rpc::query_governance_parameters(client).await } /// A helper to unwrap client's response. Will shut down process on error. @@ -2427,11 +2422,8 @@ fn unwrap_client_response( }) } -pub async fn compute_offline_proposal_votes< - C: namada::ledger::queries::Client + Sync, - IO: Io, ->( - client: &C, +pub async fn compute_offline_proposal_votes<'a>( + context: &impl Namada<'a>, proposal: &OfflineSignedProposal, votes: Vec, ) -> ProposalVotes { @@ -2444,11 +2436,11 @@ pub async fn compute_offline_proposal_votes< HashMap, > = HashMap::default(); for vote in votes { - let is_validator = is_validator(client, &vote.address).await; - let is_delegator = is_delegator(client, &vote.address).await; + let is_validator = is_validator(context.client(), &vote.address).await; + let is_delegator = is_delegator(context.client(), &vote.address).await; if is_validator { let validator_stake = get_validator_stake( - client, + context.client(), proposal.proposal.tally_epoch, &vote.address, ) @@ -2459,7 +2451,7 @@ pub async fn compute_offline_proposal_votes< .insert(vote.address.clone(), validator_stake); } else if is_delegator { let validators = get_delegators_delegation_at( - client, + context.client(), &vote.address.clone(), proposal.proposal.tally_epoch, ) @@ -2478,7 +2470,7 @@ pub async fn compute_offline_proposal_votes< } } else { display_line!( - IO, + context.io(), "Skipping vote, not a validator/delegator at epoch {}.", proposal.proposal.tally_epoch ); @@ -2500,7 +2492,7 @@ pub async fn compute_proposal_votes< proposal_id: u64, epoch: Epoch, ) -> ProposalVotes { - let votes = namada::sdk::rpc::query_proposal_votes(client, proposal_id) + let votes = namada_sdk::rpc::query_proposal_votes(client, proposal_id) .await .unwrap(); diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index c8c42b190b..1afefab825 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -1,11 +1,5 @@ -use std::env; -use std::fmt::Debug; -use std::fs::{File, OpenOptions}; -use std::io::{Read, Write}; -use std::path::PathBuf; - -use borsh::{BorshDeserialize, BorshSerialize}; -use masp_proofs::prover::LocalTxProver; +use std::fs::File; + use namada::core::ledger::governance::cli::offline::{ OfflineProposal, OfflineSignedProposal, OfflineVote, }; @@ -15,60 +9,45 @@ use namada::core::ledger::governance::cli::onchain::{ use namada::ledger::pos; use namada::proof_of_stake::parameters::PosParams; use namada::proto::Tx; -use namada::sdk::rpc::{TxBroadcastData, TxResponse}; -use namada::sdk::wallet::{Wallet, WalletUtils}; -use namada::sdk::{error, masp, signing, tx}; -use namada::tendermint_rpc::HttpClient; use namada::types::address::{Address, ImplicitAddress}; use namada::types::dec::Dec; use namada::types::io::Io; use namada::types::key::{self, *}; use namada::types::transaction::pos::InitValidator; -use namada::{display_line, edisplay_line}; +use namada_sdk::rpc::{TxBroadcastData, TxResponse}; +use namada_sdk::{display_line, edisplay_line, error, signing, tx, Namada}; use super::rpc; -use crate::cli::{args, safe_exit, Context}; +use crate::cli::{args, safe_exit}; use crate::client::rpc::query_wasm_code_hash; use crate::client::tx::tx::ProcessTxResponse; use crate::config::TendermintMode; use crate::facade::tendermint_rpc::endpoint::broadcast::tx_sync::Response; use crate::node::ledger::tendermint_node; -use crate::wallet::{ - gen_validator_keys, read_and_confirm_encryption_password, CliWalletUtils, -}; +use crate::wallet::{gen_validator_keys, read_and_confirm_encryption_password}; /// Wrapper around `signing::aux_signing_data` that stores the optional /// disposable address to the wallet -pub async fn aux_signing_data< - C: namada::ledger::queries::Client + Sync, - IO: Io, ->( - client: &C, - wallet: &mut Wallet, +pub async fn aux_signing_data<'a>( + context: &impl Namada<'a>, args: &args::Tx, owner: Option
, default_signer: Option
, ) -> Result { - let signing_data = signing::aux_signing_data::<_, _, IO>( - client, - wallet, - args, - owner, - default_signer, - ) - .await?; + let signing_data = + signing::aux_signing_data(context, args, owner, default_signer).await?; if args.disposable_signing_key { if !(args.dry_run || args.dry_run_wrapper) { // Store the generated signing key to wallet in case of need - crate::wallet::save(wallet).map_err(|_| { + context.wallet().await.save().map_err(|_| { error::Error::Other( "Failed to save disposable address to wallet".to_string(), ) })?; } else { display_line!( - IO, + context.io(), "Transaction dry run. The disposable address will not be \ saved to wallet." ) @@ -79,12 +58,8 @@ pub async fn aux_signing_data< } // Build a transaction to reveal the signer of the given transaction. -pub async fn submit_reveal_aux< - C: namada::ledger::queries::Client + Sync, - IO: Io, ->( - client: &C, - ctx: &mut Context, +pub async fn submit_reveal_aux<'a>( + context: &impl Namada<'a>, args: args::Tx, address: &Address, ) -> Result<(), error::Error> { @@ -93,181 +68,103 @@ pub async fn submit_reveal_aux< } if let Address::Implicit(ImplicitAddress(pkh)) = address { - let key = ctx - .wallet + let key = context + .wallet_mut() + .await .find_key_by_pkh(pkh, args.clone().password) .map_err(|e| error::Error::Other(e.to_string()))?; let public_key = key.ref_to(); - if tx::is_reveal_pk_needed::(client, address, args.force).await? { - let signing_data = aux_signing_data::<_, IO>( - client, - &mut ctx.wallet, - &args, - None, - None, - ) - .await?; - - let (mut tx, _epoch) = tx::build_reveal_pk::<_, _, _, IO>( - client, - &mut ctx.wallet, - &mut ctx.shielded, - &args, - address, - &public_key, - &signing_data.fee_payer, - ) - .await?; + if tx::is_reveal_pk_needed(context.client(), address, args.force) + .await? + { + println!( + "Submitting a tx to reveal the public key for address \ + {address}..." + ); + let (mut tx, signing_data, _epoch) = + tx::build_reveal_pk(context, &args, &public_key).await?; - signing::generate_test_vector::<_, _, IO>( - client, - &mut ctx.wallet, - &tx, - ) - .await?; + signing::generate_test_vector(context, &tx).await?; - signing::sign_tx(&mut ctx.wallet, &args, &mut tx, signing_data)?; + context.sign(&mut tx, &args, signing_data).await?; - tx::process_tx::<_, _, IO>(client, &mut ctx.wallet, &args, tx) - .await?; + context.submit(tx, &args).await?; } } Ok(()) } -pub async fn submit_custom( - client: &C, - ctx: &mut Context, +pub async fn submit_custom<'a, N: Namada<'a>>( + namada: &N, args: args::TxCustom, ) -> Result<(), error::Error> where - C: namada::ledger::queries::Client + Sync, - C::Error: std::fmt::Display, + ::Error: std::fmt::Display, { - let default_signer = Some(args.owner.clone()); - let signing_data = aux_signing_data::<_, IO>( - client, - &mut ctx.wallet, - &args.tx, - Some(args.owner.clone()), - default_signer, - ) - .await?; - - submit_reveal_aux::<_, IO>(client, ctx, args.tx.clone(), &args.owner) - .await?; + submit_reveal_aux(namada, args.tx.clone(), &args.owner).await?; - let (mut tx, _epoch) = tx::build_custom::<_, _, _, IO>( - client, - &mut ctx.wallet, - &mut ctx.shielded, - args.clone(), - &signing_data.fee_payer, - ) - .await?; + let (mut tx, signing_data, _epoch) = args.build(namada).await?; - signing::generate_test_vector::<_, _, IO>(client, &mut ctx.wallet, &tx) - .await?; + signing::generate_test_vector(namada, &tx).await?; if args.tx.dump_tx { - tx::dump_tx::(&args.tx, tx); + tx::dump_tx(namada.io(), &args.tx, tx); } else { - signing::sign_tx(&mut ctx.wallet, &args.tx, &mut tx, signing_data)?; - tx::process_tx::<_, _, IO>(client, &mut ctx.wallet, &args.tx, tx) - .await?; + namada.sign(&mut tx, &args.tx, signing_data).await?; + namada.submit(tx, &args.tx).await?; } Ok(()) } -pub async fn submit_update_account( - client: &C, - ctx: &mut Context, +pub async fn submit_update_account<'a, N: Namada<'a>>( + namada: &N, args: args::TxUpdateAccount, ) -> Result<(), error::Error> where - C: namada::ledger::queries::Client + Sync, - C::Error: std::fmt::Display, + ::Error: std::fmt::Display, { - let default_signer = Some(args.addr.clone()); - let signing_data = aux_signing_data::<_, IO>( - client, - &mut ctx.wallet, - &args.tx, - Some(args.addr.clone()), - default_signer, - ) - .await?; + let (mut tx, signing_data, _epoch) = args.build(namada).await?; - let (mut tx, _epoch) = tx::build_update_account::<_, _, _, IO>( - client, - &mut ctx.wallet, - &mut ctx.shielded, - args.clone(), - signing_data.fee_payer.clone(), - ) - .await?; - - signing::generate_test_vector::<_, _, IO>(client, &mut ctx.wallet, &tx) - .await?; + signing::generate_test_vector(namada, &tx).await?; if args.tx.dump_tx { - tx::dump_tx::(&args.tx, tx); + tx::dump_tx(namada.io(), &args.tx, tx); } else { - signing::sign_tx(&mut ctx.wallet, &args.tx, &mut tx, signing_data)?; - tx::process_tx::<_, _, IO>(client, &mut ctx.wallet, &args.tx, tx) - .await?; + namada.sign(&mut tx, &args.tx, signing_data).await?; + namada.submit(tx, &args.tx).await?; } Ok(()) } -pub async fn submit_init_account( - client: &C, - ctx: &mut Context, +pub async fn submit_init_account<'a, N: Namada<'a>>( + namada: &N, args: args::TxInitAccount, ) -> Result<(), error::Error> where - C: namada::ledger::queries::Client + Sync, - C::Error: std::fmt::Display, + ::Error: std::fmt::Display, { - let signing_data = aux_signing_data::<_, IO>( - client, - &mut ctx.wallet, - &args.tx, - None, - None, - ) - .await?; + let (mut tx, signing_data, _epoch) = + tx::build_init_account(namada, &args).await?; - let (mut tx, _epoch) = tx::build_init_account::<_, _, _, IO>( - client, - &mut ctx.wallet, - &mut ctx.shielded, - args.clone(), - &signing_data.fee_payer, - ) - .await?; - - signing::generate_test_vector::<_, _, IO>(client, &mut ctx.wallet, &tx) - .await?; + signing::generate_test_vector(namada, &tx).await?; if args.tx.dump_tx { - tx::dump_tx::(&args.tx, tx); + tx::dump_tx(namada.io(), &args.tx, tx); } else { - signing::sign_tx(&mut ctx.wallet, &args.tx, &mut tx, signing_data)?; - tx::process_tx::<_, _, IO>(client, &mut ctx.wallet, &args.tx, tx) - .await?; + namada.sign(&mut tx, &args.tx, signing_data).await?; + namada.submit(tx, &args.tx).await?; } Ok(()) } -pub async fn submit_init_validator( - client: &C, - mut ctx: Context, +pub async fn submit_init_validator<'a>( + namada: &impl Namada<'a>, + config: &mut crate::config::Config, args::TxInitValidator { tx: tx_args, scheme, @@ -283,15 +180,12 @@ pub async fn submit_init_validator( unsafe_dont_encrypt, tx_code_path: _, }: args::TxInitValidator, -) -> Result<(), error::Error> -where - C: namada::ledger::queries::Client + Sync, -{ +) -> Result<(), error::Error> { let tx_args = args::Tx { chain_id: tx_args .clone() .chain_id - .or_else(|| Some(ctx.config.ledger.chain_id.clone())), + .or_else(|| Some(config.ledger.chain_id.clone())), ..tx_args.clone() }; let alias = tx_args @@ -316,29 +210,33 @@ where let eth_hot_key_alias = format!("{}-eth-hot-key", alias); let eth_cold_key_alias = format!("{}-eth-cold-key", alias); + let mut wallet = namada.wallet_mut().await; let consensus_key = consensus_key .map(|key| match key { common::SecretKey::Ed25519(_) => key, common::SecretKey::Secp256k1(_) => { - edisplay_line!(IO, "Consensus key can only be ed25519"); + edisplay_line!( + namada.io(), + "Consensus key can only be ed25519" + ); safe_exit(1) } }) .unwrap_or_else(|| { - display_line!(IO, "Generating consensus key..."); + display_line!(namada.io(), "Generating consensus key..."); let password = read_and_confirm_encryption_password(unsafe_dont_encrypt); - ctx.wallet + wallet .gen_key( // Note that TM only allows ed25519 for consensus key SchemeType::Ed25519, Some(consensus_key_alias.clone()), tx_args.wallet_alias_force, + None, password, None, ) .expect("Key generation should not fail.") - .expect("No existing alias expected.") .1 }); @@ -346,25 +244,28 @@ where .map(|key| match key { common::SecretKey::Secp256k1(_) => key.ref_to(), common::SecretKey::Ed25519(_) => { - edisplay_line!(IO, "Eth cold key can only be secp256k1"); + edisplay_line!( + namada.io(), + "Eth cold key can only be secp256k1" + ); safe_exit(1) } }) .unwrap_or_else(|| { - display_line!(IO, "Generating Eth cold key..."); + display_line!(namada.io(), "Generating Eth cold key..."); let password = read_and_confirm_encryption_password(unsafe_dont_encrypt); - ctx.wallet + wallet .gen_key( // Note that ETH only allows secp256k1 SchemeType::Secp256k1, Some(eth_cold_key_alias.clone()), tx_args.wallet_alias_force, + None, password, None, ) .expect("Key generation should not fail.") - .expect("No existing alias expected.") .1 .ref_to() }); @@ -373,35 +274,40 @@ where .map(|key| match key { common::SecretKey::Secp256k1(_) => key.ref_to(), common::SecretKey::Ed25519(_) => { - edisplay_line!(IO, "Eth hot key can only be secp256k1"); + edisplay_line!( + namada.io(), + "Eth hot key can only be secp256k1" + ); safe_exit(1) } }) .unwrap_or_else(|| { - display_line!(IO, "Generating Eth hot key..."); + display_line!(namada.io(), "Generating Eth hot key..."); let password = read_and_confirm_encryption_password(unsafe_dont_encrypt); - ctx.wallet + wallet .gen_key( // Note that ETH only allows secp256k1 SchemeType::Secp256k1, Some(eth_hot_key_alias.clone()), tx_args.wallet_alias_force, + None, password, None, ) .expect("Key generation should not fail.") - .expect("No existing alias expected.") .1 .ref_to() }); + // To avoid wallet deadlocks in following operations + drop(wallet); if protocol_key.is_none() { - display_line!(IO, "Generating protocol signing key..."); + display_line!(namada.io(), "Generating protocol signing key..."); } // Generate the validator keys let validator_keys = gen_validator_keys( - &mut ctx.wallet, + *namada.wallet_mut().await, Some(eth_hot_pk.clone()), protocol_key, scheme, @@ -414,17 +320,15 @@ where .expect("DKG sessions keys should have been created") .public(); - let validator_vp_code_hash = query_wasm_code_hash::( - client, - validator_vp_code_path.to_str().unwrap(), - ) - .await - .unwrap(); + let validator_vp_code_hash = + query_wasm_code_hash(namada, validator_vp_code_path.to_str().unwrap()) + .await + .unwrap(); // Validate the commission rate data if commission_rate > Dec::one() || commission_rate < Dec::zero() { edisplay_line!( - IO, + namada.io(), "The validator commission rate must not exceed 1.0 or 100%, and \ it must be 0 or positive" ); @@ -436,7 +340,7 @@ where || max_commission_rate_change < Dec::zero() { edisplay_line!( - IO, + namada.io(), "The validator maximum change in commission rate per epoch must \ not exceed 1.0 or 100%" ); @@ -445,7 +349,7 @@ where } } let tx_code_hash = - query_wasm_code_hash::<_, IO>(client, args::TX_INIT_VALIDATOR_WASM) + query_wasm_code_hash(namada, args::TX_INIT_VALIDATOR_WASM) .await .unwrap(); @@ -471,19 +375,10 @@ where tx.add_code_from_hash(tx_code_hash).add_data(data); - let signing_data = signing::aux_signing_data::<_, _, IO>( - client, - &mut ctx.wallet, - &tx_args, - None, - None, - ) - .await?; + let signing_data = aux_signing_data(namada, &tx_args, None, None).await?; - tx::prepare_tx::<_, _, _, IO>( - client, - &mut ctx.wallet, - &mut ctx.shielded, + tx::prepare_tx( + namada, &tx_args, &mut tx, signing_data.fee_payer.clone(), @@ -491,18 +386,14 @@ where ) .await?; - signing::generate_test_vector::<_, _, IO>(client, &mut ctx.wallet, &tx) - .await?; + signing::generate_test_vector(namada, &tx).await?; if tx_args.dump_tx { - tx::dump_tx::(&tx_args, tx); + tx::dump_tx(namada.io(), &tx_args, tx); } else { - signing::sign_tx(&mut ctx.wallet, &tx_args, &mut tx, signing_data)?; + namada.sign(&mut tx, &tx_args, signing_data).await?; - let result = - tx::process_tx::<_, _, IO>(client, &mut ctx.wallet, &tx_args, tx) - .await? - .initialized_accounts(); + let result = namada.submit(tx, &tx_args).await?.initialized_accounts(); if !tx_args.dry_run { let (validator_address_alias, validator_address) = match &result[..] @@ -510,29 +401,37 @@ where // There should be 1 account for the validator itself [validator_address] => { if let Some(alias) = - ctx.wallet.find_alias(validator_address) + namada.wallet().await.find_alias(validator_address) { (alias.clone(), validator_address.clone()) } else { edisplay_line!( - IO, + namada.io(), "Expected one account to be created" ); safe_exit(1) } } _ => { - edisplay_line!(IO, "Expected one account to be created"); + edisplay_line!( + namada.io(), + "Expected one account to be created" + ); safe_exit(1) } }; // add validator address and keys to the wallet - ctx.wallet + namada + .wallet_mut() + .await .add_validator_data(validator_address, validator_keys); - crate::wallet::save(&ctx.wallet) - .unwrap_or_else(|err| edisplay_line!(IO, "{}", err)); + namada + .wallet_mut() + .await + .save() + .unwrap_or_else(|err| edisplay_line!(namada.io(), "{}", err)); - let tendermint_home = ctx.config.ledger.cometbft_dir(); + let tendermint_home = config.ledger.cometbft_dir(); tendermint_node::write_validator_key( &tendermint_home, &consensus_key, @@ -541,51 +440,55 @@ where // Write Namada config stuff or figure out how to do the above // tendermint_node things two epochs in the future!!! - ctx.config.ledger.shell.tendermint_mode = TendermintMode::Validator; - ctx.config + config.ledger.shell.tendermint_mode = TendermintMode::Validator; + config .write( - &ctx.config.ledger.shell.base_dir, - &ctx.config.ledger.chain_id, + &config.ledger.shell.base_dir, + &config.ledger.chain_id, true, ) .unwrap(); let key = pos::params_key(); - let pos_params = - rpc::query_storage_value::(client, &key) + let pos_params: PosParams = + rpc::query_storage_value(namada.client(), &key) .await .expect("Pos parameter should be defined."); - display_line!(IO, ""); + display_line!(namada.io(), ""); display_line!( - IO, + namada.io(), "The validator's addresses and keys were stored in the wallet:" ); display_line!( - IO, + namada.io(), " Validator address \"{}\"", validator_address_alias ); display_line!( - IO, + namada.io(), " Validator account key \"{}\"", validator_key_alias ); - display_line!(IO, " Consensus key \"{}\"", consensus_key_alias); display_line!( - IO, + namada.io(), + " Consensus key \"{}\"", + consensus_key_alias + ); + display_line!( + namada.io(), "The ledger node has been setup to use this validator's \ address and consensus key." ); display_line!( - IO, + namada.io(), "Your validator will be active in {} epochs. Be sure to \ restart your node for the changes to take effect!", pos_params.pipeline_len ); } else { display_line!( - IO, + namada.io(), "Transaction dry run. No addresses have been saved." ); } @@ -593,172 +496,30 @@ where Ok(()) } -/// Shielded context file name -const FILE_NAME: &str = "shielded.dat"; -const TMP_FILE_NAME: &str = "shielded.tmp"; - -#[derive(Debug, BorshSerialize, BorshDeserialize, Clone)] -pub struct CLIShieldedUtils { - #[borsh_skip] - context_dir: PathBuf, -} - -impl CLIShieldedUtils { - /// Initialize a shielded transaction context that identifies notes - /// decryptable by any viewing key in the given set - pub fn new(context_dir: PathBuf) -> masp::ShieldedContext { - // Make sure that MASP parameters are downloaded to enable MASP - // transaction building and verification later on - let params_dir = masp::get_params_dir(); - let spend_path = params_dir.join(masp::SPEND_NAME); - let convert_path = params_dir.join(masp::CONVERT_NAME); - let output_path = params_dir.join(masp::OUTPUT_NAME); - if !(spend_path.exists() - && convert_path.exists() - && output_path.exists()) - { - display_line!(IO, "MASP parameters not present, downloading..."); - masp_proofs::download_masp_parameters(None) - .expect("MASP parameters not present or downloadable"); - display_line!( - IO, - "MASP parameter download complete, resuming execution..." - ); - } - // Finally initialize a shielded context with the supplied directory - let utils = Self { context_dir }; - masp::ShieldedContext { - utils, - ..Default::default() - } - } -} - -impl Default for CLIShieldedUtils { - fn default() -> Self { - Self { - context_dir: PathBuf::from(FILE_NAME), - } - } -} - -#[cfg_attr(feature = "async-send", async_trait::async_trait)] -#[cfg_attr(not(feature = "async-send"), async_trait::async_trait(?Send))] -impl masp::ShieldedUtils for CLIShieldedUtils { - fn local_tx_prover(&self) -> LocalTxProver { - if let Ok(params_dir) = env::var(masp::ENV_VAR_MASP_PARAMS_DIR) { - let params_dir = PathBuf::from(params_dir); - let spend_path = params_dir.join(masp::SPEND_NAME); - let convert_path = params_dir.join(masp::CONVERT_NAME); - let output_path = params_dir.join(masp::OUTPUT_NAME); - LocalTxProver::new(&spend_path, &output_path, &convert_path) - } else { - LocalTxProver::with_default_location() - .expect("unable to load MASP Parameters") - } - } - - /// Try to load the last saved shielded context from the given context - /// directory. If this fails, then leave the current context unchanged. - async fn load(self) -> std::io::Result> { - // Try to load shielded context from file - let mut ctx_file = File::open(self.context_dir.join(FILE_NAME))?; - let mut bytes = Vec::new(); - ctx_file.read_to_end(&mut bytes)?; - let mut new_ctx = masp::ShieldedContext::deserialize(&mut &bytes[..])?; - // Associate the originating context directory with the - // shielded context under construction - new_ctx.utils = self; - Ok(new_ctx) - } - - /// Save this shielded context into its associated context directory - async fn save( - &self, - ctx: &masp::ShieldedContext, - ) -> std::io::Result<()> { - // TODO: use mktemp crate? - let tmp_path = self.context_dir.join(TMP_FILE_NAME); - { - // First serialize the shielded context into a temporary file. - // Inability to create this file implies a simultaneuous write is in - // progress. In this case, immediately fail. This is unproblematic - // because the data intended to be stored can always be re-fetched - // from the blockchain. - let mut ctx_file = OpenOptions::new() - .write(true) - .create_new(true) - .open(tmp_path.clone())?; - let mut bytes = Vec::new(); - ctx.serialize(&mut bytes) - .expect("cannot serialize shielded context"); - ctx_file.write_all(&bytes[..])?; - } - // Atomically update the old shielded context file with new data. - // Atomicity is required to prevent other client instances from reading - // corrupt data. - std::fs::rename(tmp_path.clone(), self.context_dir.join(FILE_NAME))?; - // Finally, remove our temporary file to allow future saving of shielded - // contexts. - std::fs::remove_file(tmp_path)?; - Ok(()) - } -} - -pub async fn submit_transfer< - C: namada::ledger::queries::Client + Sync, - IO: Io, ->( - client: &C, - mut ctx: Context, +pub async fn submit_transfer<'a>( + namada: &impl Namada<'a>, args: args::TxTransfer, ) -> Result<(), error::Error> { for _ in 0..2 { - let default_signer = Some(args.source.effective_address()); - let signing_data = aux_signing_data::<_, IO>( - client, - &mut ctx.wallet, - &args.tx, - Some(args.source.effective_address()), - default_signer, - ) - .await?; - - submit_reveal_aux::<_, IO>( - client, - &mut ctx, + submit_reveal_aux( + namada, args.tx.clone(), &args.source.effective_address(), ) .await?; - let arg = args.clone(); - let (mut tx, tx_epoch) = tx::build_transfer::<_, _, _, IO>( - client, - &mut ctx.wallet, - &mut ctx.shielded, - arg, - signing_data.fee_payer.clone(), - ) - .await?; - signing::generate_test_vector::<_, _, IO>(client, &mut ctx.wallet, &tx) - .await?; + let (mut tx, signing_data, tx_epoch) = + args.clone().build(namada).await?; + signing::generate_test_vector(namada, &tx).await?; if args.tx.dump_tx { - tx::dump_tx::(&args.tx, tx); + tx::dump_tx(namada.io(), &args.tx, tx); break; } else { - signing::sign_tx(&mut ctx.wallet, &args.tx, &mut tx, signing_data)?; - let result = tx::process_tx::<_, _, IO>( - client, - &mut ctx.wallet, - &args.tx, - tx, - ) - .await?; + namada.sign(&mut tx, &args.tx, signing_data).await?; + let result = namada.submit(tx, &args.tx).await?; - let submission_epoch = - rpc::query_and_print_epoch::<_, IO>(client).await; + let submission_epoch = rpc::query_and_print_epoch(namada).await; match result { ProcessTxResponse::Applied(resp) if @@ -770,7 +531,7 @@ pub async fn submit_transfer< tx_epoch.unwrap() != submission_epoch => { // Then we probably straddled an epoch boundary. Let's retry... - edisplay_line!(IO, + edisplay_line!(namada.io(), "MASP transaction rejected and this may be due to the \ epoch changing. Attempting to resubmit transaction.", ); @@ -786,64 +547,38 @@ pub async fn submit_transfer< Ok(()) } -pub async fn submit_ibc_transfer( - client: &C, - mut ctx: Context, +pub async fn submit_ibc_transfer<'a, N: Namada<'a>>( + namada: &N, args: args::TxIbcTransfer, ) -> Result<(), error::Error> where - C: namada::ledger::queries::Client + Sync, - C::Error: std::fmt::Display, + ::Error: std::fmt::Display, { - let default_signer = Some(args.source.clone()); - let signing_data = aux_signing_data::<_, IO>( - client, - &mut ctx.wallet, - &args.tx, - Some(args.source.clone()), - default_signer, - ) - .await?; - - submit_reveal_aux::<_, IO>(client, &mut ctx, args.tx.clone(), &args.source) - .await?; - - let (mut tx, _epoch) = tx::build_ibc_transfer::<_, _, _, IO>( - client, - &mut ctx.wallet, - &mut ctx.shielded, - args.clone(), - signing_data.fee_payer.clone(), - ) - .await?; - signing::generate_test_vector::<_, _, IO>(client, &mut ctx.wallet, &tx) - .await?; + submit_reveal_aux(namada, args.tx.clone(), &args.source).await?; + let (mut tx, signing_data, _epoch) = args.build(namada).await?; + signing::generate_test_vector(namada, &tx).await?; if args.tx.dump_tx { - tx::dump_tx::(&args.tx, tx); + tx::dump_tx(namada.io(), &args.tx, tx); } else { - signing::sign_tx(&mut ctx.wallet, &args.tx, &mut tx, signing_data)?; - tx::process_tx::<_, _, IO>(client, &mut ctx.wallet, &args.tx, tx) - .await?; + namada.sign(&mut tx, &args.tx, signing_data).await?; + namada.submit(tx, &args.tx).await?; } Ok(()) } -pub async fn submit_init_proposal( - client: &C, - mut ctx: Context, +pub async fn submit_init_proposal<'a, N: Namada<'a>>( + namada: &N, args: args::InitProposal, ) -> Result<(), error::Error> where - C: namada::ledger::queries::Client + Sync, - C::Error: std::fmt::Display, + ::Error: std::fmt::Display, { - let current_epoch = rpc::query_and_print_epoch::<_, IO>(client).await; - let governance_parameters = rpc::query_governance_parameters(client).await; - - let ((mut tx_builder, _fee_unshield_epoch), signing_data) = if args - .is_offline + let current_epoch = rpc::query_and_print_epoch(namada).await; + let governance_parameters = + rpc::query_governance_parameters(namada.client()).await; + let (mut tx_builder, signing_data, _fee_unshield_epoch) = if args.is_offline { let proposal = OfflineProposal::try_from(args.proposal_data.as_ref()) .map_err(|e| { @@ -855,9 +590,8 @@ where .map_err(|e| error::TxError::InvalidProposal(e.to_string()))?; let default_signer = Some(proposal.author.clone()); - let signing_data = aux_signing_data::<_, IO>( - client, - &mut ctx.wallet, + let signing_data = aux_signing_data( + namada, &args.tx, Some(proposal.author.clone()), default_signer, @@ -876,7 +610,11 @@ where ) })?; - display_line!(IO, "Proposal serialized to: {}", output_file_path); + display_line!( + namada.io(), + "Proposal serialized to: {}", + output_file_path + ); return Ok(()); } else if args.is_pgf_funding { let proposal = @@ -889,36 +627,10 @@ where .validate(&governance_parameters, current_epoch, args.tx.force) .map_err(|e| error::TxError::InvalidProposal(e.to_string()))?; - let default_signer = Some(proposal.proposal.author.clone()); - let signing_data = aux_signing_data::<_, IO>( - client, - &mut ctx.wallet, - &args.tx, - Some(proposal.proposal.author.clone()), - default_signer, - ) - .await?; - - submit_reveal_aux::<_, IO>( - client, - &mut ctx, - args.tx.clone(), - &proposal.proposal.author, - ) - .await?; + submit_reveal_aux(namada, args.tx.clone(), &proposal.proposal.author) + .await?; - ( - tx::build_pgf_funding_proposal::<_, _, _, IO>( - client, - &mut ctx.wallet, - &mut ctx.shielded, - args.clone(), - proposal, - &signing_data.fee_payer.clone(), - ) - .await?, - signing_data, - ) + tx::build_pgf_funding_proposal(namada, &args, proposal).await? } else if args.is_pgf_stewards { let proposal = PgfStewardProposal::try_from( args.proposal_data.as_ref(), @@ -927,8 +639,8 @@ where error::TxError::FailedGovernaneProposalDeserialize(e.to_string()) })?; let author_balance = rpc::get_token_balance( - client, - &ctx.native_token, + namada.client(), + &namada.native_token(), &proposal.proposal.author, ) .await; @@ -941,44 +653,18 @@ where ) .map_err(|e| error::TxError::InvalidProposal(e.to_string()))?; - let default_signer = Some(proposal.proposal.author.clone()); - let signing_data = aux_signing_data::<_, IO>( - client, - &mut ctx.wallet, - &args.tx, - Some(proposal.proposal.author.clone()), - default_signer, - ) - .await?; - - submit_reveal_aux::<_, IO>( - client, - &mut ctx, - args.tx.clone(), - &proposal.proposal.author, - ) - .await?; + submit_reveal_aux(namada, args.tx.clone(), &proposal.proposal.author) + .await?; - ( - tx::build_pgf_stewards_proposal::<_, _, _, IO>( - client, - &mut ctx.wallet, - &mut ctx.shielded, - args.clone(), - proposal, - signing_data.fee_payer.clone(), - ) - .await?, - signing_data, - ) + tx::build_pgf_stewards_proposal(namada, &args, proposal).await? } else { let proposal = DefaultProposal::try_from(args.proposal_data.as_ref()) .map_err(|e| { error::TxError::FailedGovernaneProposalDeserialize(e.to_string()) })?; let author_balane = rpc::get_token_balance( - client, - &ctx.native_token, + namada.client(), + &namada.native_token(), &proposal.proposal.author, ) .await; @@ -991,87 +677,41 @@ where ) .map_err(|e| error::TxError::InvalidProposal(e.to_string()))?; - let default_signer = Some(proposal.proposal.author.clone()); - let signing_data = aux_signing_data::<_, IO>( - client, - &mut ctx.wallet, - &args.tx, - Some(proposal.proposal.author.clone()), - default_signer, - ) - .await?; - - submit_reveal_aux::<_, IO>( - client, - &mut ctx, - args.tx.clone(), - &proposal.proposal.author, - ) - .await?; + submit_reveal_aux(namada, args.tx.clone(), &proposal.proposal.author) + .await?; - ( - tx::build_default_proposal::<_, _, _, IO>( - client, - &mut ctx.wallet, - &mut ctx.shielded, - args.clone(), - proposal, - signing_data.fee_payer.clone(), - ) - .await?, - signing_data, - ) + tx::build_default_proposal(namada, &args, proposal).await? }; - signing::generate_test_vector::<_, _, IO>( - client, - &mut ctx.wallet, - &tx_builder, - ) - .await?; + signing::generate_test_vector(namada, &tx_builder).await?; if args.tx.dump_tx { - tx::dump_tx::(&args.tx, tx_builder); + tx::dump_tx(namada.io(), &args.tx, tx_builder); } else { - signing::sign_tx( - &mut ctx.wallet, - &args.tx, - &mut tx_builder, - signing_data, - )?; - tx::process_tx::<_, _, IO>( - client, - &mut ctx.wallet, - &args.tx, - tx_builder, - ) - .await?; + namada.sign(&mut tx_builder, &args.tx, signing_data).await?; + namada.submit(tx_builder, &args.tx).await?; } Ok(()) } -pub async fn submit_vote_proposal( - client: &C, - mut ctx: Context, +pub async fn submit_vote_proposal<'a, N: Namada<'a>>( + namada: &N, args: args::VoteProposal, ) -> Result<(), error::Error> where - C: namada::ledger::queries::Client + Sync, - C::Error: std::fmt::Display, + ::Error: std::fmt::Display, { - let current_epoch = rpc::query_and_print_epoch::<_, IO>(client).await; - - let default_signer = Some(args.voter.clone()); - let signing_data = aux_signing_data::<_, IO>( - client, - &mut ctx.wallet, - &args.tx, - Some(args.voter.clone()), - default_signer.clone(), - ) - .await?; + let (mut tx_builder, signing_data, _fee_unshield_epoch) = if args.is_offline + { + let default_signer = Some(args.voter.clone()); + let signing_data = aux_signing_data( + namada, + &args.tx, + Some(args.voter.clone()), + default_signer.clone(), + ) + .await?; - let (mut tx_builder, _fee_unshield_epoch) = if args.is_offline { let proposal_vote = ProposalVote::try_from(args.vote) .map_err(|_| error::TxError::InvalidProposalVote)?; @@ -1086,7 +726,7 @@ where ) .map_err(|e| error::TxError::InvalidProposal(e.to_string()))?; let delegations = rpc::get_delegators_delegation_at( - client, + namada.client(), &args.voter, proposal.proposal.tally_epoch, ) @@ -1110,50 +750,29 @@ where .serialize(args.tx.output_folder) .expect("Should be able to serialize the offline proposal"); - display_line!(IO, "Proposal vote serialized to: {}", output_file_path); + display_line!( + namada.io(), + "Proposal vote serialized to: {}", + output_file_path + ); return Ok(()); } else { - tx::build_vote_proposal::<_, _, _, IO>( - client, - &mut ctx.wallet, - &mut ctx.shielded, - args.clone(), - current_epoch, - signing_data.fee_payer.clone(), - ) - .await? + args.build(namada).await? }; - signing::generate_test_vector::<_, _, IO>( - client, - &mut ctx.wallet, - &tx_builder, - ) - .await?; + signing::generate_test_vector(namada, &tx_builder).await?; if args.tx.dump_tx { - tx::dump_tx::(&args.tx, tx_builder); + tx::dump_tx(namada.io(), &args.tx, tx_builder); } else { - signing::sign_tx( - &mut ctx.wallet, - &args.tx, - &mut tx_builder, - signing_data, - )?; - tx::process_tx::<_, _, IO>( - client, - &mut ctx.wallet, - &args.tx, - tx_builder, - ) - .await?; + namada.sign(&mut tx_builder, &args.tx, signing_data).await?; + namada.submit(tx_builder, &args.tx).await?; } Ok(()) } -pub async fn sign_tx( - client: &C, - ctx: &mut Context, +pub async fn sign_tx<'a, N: Namada<'a>>( + namada: &N, args::SignTx { tx: tx_args, tx_data, @@ -1161,37 +780,31 @@ pub async fn sign_tx( }: args::SignTx, ) -> Result<(), error::Error> where - C: namada::ledger::queries::Client + Sync, - C::Error: std::fmt::Display, + ::Error: std::fmt::Display, { let tx = if let Ok(transaction) = Tx::deserialize(tx_data.as_ref()) { transaction } else { - edisplay_line!(IO, "Couldn't decode the transaction."); + edisplay_line!(namada.io(), "Couldn't decode the transaction."); safe_exit(1) }; - let default_signer = Some(owner.clone()); - let signing_data = aux_signing_data::<_, IO>( - client, - &mut ctx.wallet, - &tx_args, - Some(owner.clone()), - default_signer, - ) - .await?; + let signing_data = + aux_signing_data(namada, &tx_args, Some(owner.clone()), default_signer) + .await?; + let mut wallet = namada.wallet_mut().await; let secret_keys = &signing_data .public_keys .iter() .filter_map(|public_key| { if let Ok(secret_key) = - signing::find_key_by_pk(&mut ctx.wallet, &tx_args, public_key) + signing::find_key_by_pk(&mut wallet, &tx_args, public_key) { Some(secret_key) } else { edisplay_line!( - IO, + namada.io(), "Couldn't find the secret key for {}. Skipping signature \ generation.", public_key @@ -1229,7 +842,7 @@ where ) .expect("Signature should be deserializable."); display_line!( - IO, + namada.io(), "Signature for {} serialized at {}", signature.pubkey, output_path.display() @@ -1239,357 +852,194 @@ where Ok(()) } -pub async fn submit_reveal_pk( - client: &C, - ctx: &mut Context, +pub async fn submit_reveal_pk<'a, N: Namada<'a>>( + namada: &N, args: args::RevealPk, ) -> Result<(), error::Error> where - C: namada::ledger::queries::Client + Sync, - C::Error: std::fmt::Display, + ::Error: std::fmt::Display, { - submit_reveal_aux::<_, IO>( - client, - ctx, - args.tx, - &(&args.public_key).into(), - ) - .await?; + submit_reveal_aux(namada, args.tx, &(&args.public_key).into()).await?; Ok(()) } -pub async fn submit_bond( - client: &C, - ctx: &mut Context, +pub async fn submit_bond<'a, N: Namada<'a>>( + namada: &N, args: args::Bond, ) -> Result<(), error::Error> where - C: namada::ledger::queries::Client + Sync, - C::Error: std::fmt::Display, + ::Error: std::fmt::Display, { let default_address = args.source.clone().unwrap_or(args.validator.clone()); - let default_signer = Some(default_address.clone()); - let signing_data = aux_signing_data::<_, IO>( - client, - &mut ctx.wallet, - &args.tx, - Some(default_address.clone()), - default_signer, - ) - .await?; + submit_reveal_aux(namada, args.tx.clone(), &default_address).await?; - submit_reveal_aux::<_, IO>(client, ctx, args.tx.clone(), &default_address) - .await?; - - let (mut tx, _fee_unshield_epoch) = tx::build_bond::<_, _, _, IO>( - client, - &mut ctx.wallet, - &mut ctx.shielded, - args.clone(), - signing_data.fee_payer.clone(), - ) - .await?; - signing::generate_test_vector::<_, _, IO>(client, &mut ctx.wallet, &tx) - .await?; + let (mut tx, signing_data, _fee_unshield_epoch) = + args.build(namada).await?; + signing::generate_test_vector(namada, &tx).await?; if args.tx.dump_tx { - tx::dump_tx::(&args.tx, tx); + tx::dump_tx(namada.io(), &args.tx, tx); } else { - signing::sign_tx(&mut ctx.wallet, &args.tx, &mut tx, signing_data)?; + namada.sign(&mut tx, &args.tx, signing_data).await?; - tx::process_tx::<_, _, IO>(client, &mut ctx.wallet, &args.tx, tx) - .await?; + namada.submit(tx, &args.tx).await?; } Ok(()) } -pub async fn submit_unbond( - client: &C, - ctx: &mut Context, +pub async fn submit_unbond<'a, N: Namada<'a>>( + namada: &N, args: args::Unbond, ) -> Result<(), error::Error> where - C: namada::ledger::queries::Client + Sync, - C::Error: std::fmt::Display, + ::Error: std::fmt::Display, { - let default_address = args.source.clone().unwrap_or(args.validator.clone()); - let default_signer = Some(default_address.clone()); - let signing_data = signing::aux_signing_data::<_, _, IO>( - client, - &mut ctx.wallet, - &args.tx, - Some(default_address), - default_signer, - ) - .await?; - - let (mut tx, _fee_unshield_epoch, latest_withdrawal_pre) = - tx::build_unbond::<_, _, _, IO>( - client, - &mut ctx.wallet, - &mut ctx.shielded, - args.clone(), - signing_data.fee_payer.clone(), - ) - .await?; - signing::generate_test_vector::<_, _, IO>(client, &mut ctx.wallet, &tx) - .await?; + let (mut tx, signing_data, _fee_unshield_epoch, latest_withdrawal_pre) = + args.build(namada).await?; + signing::generate_test_vector(namada, &tx).await?; if args.tx.dump_tx { - tx::dump_tx::(&args.tx, tx); + tx::dump_tx(namada.io(), &args.tx, tx); } else { - signing::sign_tx(&mut ctx.wallet, &args.tx, &mut tx, signing_data)?; + namada.sign(&mut tx, &args.tx, signing_data).await?; - tx::process_tx::<_, _, IO>(client, &mut ctx.wallet, &args.tx, tx) - .await?; + namada.submit(tx, &args.tx).await?; - tx::query_unbonds::<_, IO>(client, args.clone(), latest_withdrawal_pre) - .await?; + tx::query_unbonds(namada, args.clone(), latest_withdrawal_pre).await?; } Ok(()) } -pub async fn submit_withdraw( - client: &C, - mut ctx: Context, +pub async fn submit_withdraw<'a, N: Namada<'a>>( + namada: &N, args: args::Withdraw, ) -> Result<(), error::Error> where - C: namada::ledger::queries::Client + Sync, - C::Error: std::fmt::Display, + ::Error: std::fmt::Display, { - let default_address = args.source.clone().unwrap_or(args.validator.clone()); - let default_signer = Some(default_address.clone()); - let signing_data = aux_signing_data::<_, IO>( - client, - &mut ctx.wallet, - &args.tx, - Some(default_address), - default_signer, - ) - .await?; - - let (mut tx, _fee_unshield_epoch) = tx::build_withdraw::<_, _, _, IO>( - client, - &mut ctx.wallet, - &mut ctx.shielded, - args.clone(), - signing_data.fee_payer.clone(), - ) - .await?; - signing::generate_test_vector::<_, _, IO>(client, &mut ctx.wallet, &tx) - .await?; + let (mut tx, signing_data, _fee_unshield_epoch) = + args.build(namada).await?; + signing::generate_test_vector(namada, &tx).await?; if args.tx.dump_tx { - tx::dump_tx::(&args.tx, tx); + tx::dump_tx(namada.io(), &args.tx, tx); } else { - signing::sign_tx(&mut ctx.wallet, &args.tx, &mut tx, signing_data)?; + namada.sign(&mut tx, &args.tx, signing_data).await?; - tx::process_tx::<_, _, IO>(client, &mut ctx.wallet, &args.tx, tx) - .await?; + namada.submit(tx, &args.tx).await?; } Ok(()) } -pub async fn submit_validator_commission_change( - client: &C, - mut ctx: Context, +pub async fn submit_validator_commission_change<'a, N: Namada<'a>>( + namada: &N, args: args::CommissionRateChange, ) -> Result<(), error::Error> where - C: namada::ledger::queries::Client + Sync, + ::Error: std::fmt::Display, { - let default_signer = Some(args.validator.clone()); - let signing_data = aux_signing_data::<_, IO>( - client, - &mut ctx.wallet, - &args.tx, - Some(args.validator.clone()), - default_signer, - ) - .await?; - - let (mut tx, _fee_unshield_epoch) = - tx::build_validator_commission_change::<_, _, _, IO>( - client, - &mut ctx.wallet, - &mut ctx.shielded, - args.clone(), - signing_data.fee_payer.clone(), - ) - .await?; - signing::generate_test_vector::<_, _, IO>(client, &mut ctx.wallet, &tx) - .await?; + let (mut tx, signing_data, _fee_unshield_epoch) = + args.build(namada).await?; + signing::generate_test_vector(namada, &tx).await?; if args.tx.dump_tx { - tx::dump_tx::(&args.tx, tx); + tx::dump_tx(namada.io(), &args.tx, tx); } else { - signing::sign_tx(&mut ctx.wallet, &args.tx, &mut tx, signing_data)?; + namada.sign(&mut tx, &args.tx, signing_data).await?; - tx::process_tx::<_, _, IO>(client, &mut ctx.wallet, &args.tx, tx) - .await?; + namada.submit(tx, &args.tx).await?; } Ok(()) } -pub async fn submit_unjail_validator< - C: namada::ledger::queries::Client + Sync, - IO: Io, ->( - client: &C, - mut ctx: Context, +pub async fn submit_unjail_validator<'a, N: Namada<'a>>( + namada: &N, args: args::TxUnjailValidator, ) -> Result<(), error::Error> where - C::Error: std::fmt::Display, + ::Error: std::fmt::Display, { - let default_signer = Some(args.validator.clone()); - let signing_data = aux_signing_data::<_, IO>( - client, - &mut ctx.wallet, - &args.tx, - Some(args.validator.clone()), - default_signer, - ) - .await?; - - let (mut tx, _fee_unshield_epoch) = - tx::build_unjail_validator::<_, _, _, IO>( - client, - &mut ctx.wallet, - &mut ctx.shielded, - args.clone(), - signing_data.fee_payer.clone(), - ) - .await?; - signing::generate_test_vector::<_, _, IO>(client, &mut ctx.wallet, &tx) - .await?; + let (mut tx, signing_data, _fee_unshield_epoch) = + args.build(namada).await?; + signing::generate_test_vector(namada, &tx).await?; if args.tx.dump_tx { - tx::dump_tx::(&args.tx, tx); + tx::dump_tx(namada.io(), &args.tx, tx); } else { - signing::sign_tx(&mut ctx.wallet, &args.tx, &mut tx, signing_data)?; + namada.sign(&mut tx, &args.tx, signing_data).await?; - tx::process_tx::<_, _, IO>(client, &mut ctx.wallet, &args.tx, tx) - .await?; + namada.submit(tx, &args.tx).await?; } Ok(()) } -pub async fn submit_update_steward_commission< - C: namada::ledger::queries::Client + Sync, - IO: Io, ->( - client: &C, - mut ctx: Context, +pub async fn submit_update_steward_commission<'a, N: Namada<'a>>( + namada: &N, args: args::UpdateStewardCommission, ) -> Result<(), error::Error> where - C: namada::ledger::queries::Client + Sync, - C::Error: std::fmt::Display, + ::Error: std::fmt::Display, { - let default_signer = Some(args.steward.clone()); - let signing_data = signing::aux_signing_data::<_, _, IO>( - client, - &mut ctx.wallet, - &args.tx, - Some(args.steward.clone()), - default_signer, - ) - .await?; + let (mut tx, signing_data, _fee_unshield_epoch) = + args.build(namada).await?; - let (mut tx, _fee_unshield_epoch) = - tx::build_update_steward_commission::<_, _, _, IO>( - client, - &mut ctx.wallet, - &mut ctx.shielded, - args.clone(), - &signing_data.fee_payer, - ) - .await?; - - signing::generate_test_vector::<_, _, IO>(client, &mut ctx.wallet, &tx) - .await?; + signing::generate_test_vector(namada, &tx).await?; if args.tx.dump_tx { - tx::dump_tx::(&args.tx, tx); + tx::dump_tx(namada.io(), &args.tx, tx); } else { - signing::sign_tx(&mut ctx.wallet, &args.tx, &mut tx, signing_data)?; - tx::process_tx::<_, _, IO>(client, &mut ctx.wallet, &args.tx, tx) - .await?; + namada.sign(&mut tx, &args.tx, signing_data).await?; + namada.submit(tx, &args.tx).await?; } Ok(()) } -pub async fn submit_resign_steward( - client: &C, - mut ctx: Context, +pub async fn submit_resign_steward<'a, N: Namada<'a>>( + namada: &N, args: args::ResignSteward, ) -> Result<(), error::Error> where - C: namada::ledger::queries::Client + Sync, - C::Error: std::fmt::Display, + ::Error: std::fmt::Display, { - let default_signer = Some(args.steward.clone()); - let signing_data = signing::aux_signing_data::<_, _, IO>( - client, - &mut ctx.wallet, - &args.tx, - Some(args.steward.clone()), - default_signer, - ) - .await?; + let (mut tx, signing_data, _epoch) = args.build(namada).await?; - let (mut tx, _fee_unshield_epoch) = - tx::build_resign_steward::<_, _, _, IO>( - client, - &mut ctx.wallet, - &mut ctx.shielded, - args.clone(), - &signing_data.fee_payer, - ) - .await?; - - signing::generate_test_vector::<_, _, IO>(client, &mut ctx.wallet, &tx) - .await?; + signing::generate_test_vector(namada, &tx).await?; if args.tx.dump_tx { - tx::dump_tx::(&args.tx, tx); + tx::dump_tx(namada.io(), &args.tx, tx); } else { - signing::sign_tx(&mut ctx.wallet, &args.tx, &mut tx, signing_data)?; - tx::process_tx::<_, _, IO>(client, &mut ctx.wallet, &args.tx, tx) - .await?; + namada.sign(&mut tx, &args.tx, signing_data).await?; + namada.submit(tx, &args.tx).await?; } Ok(()) } /// Save accounts initialized from a tx into the wallet, if any. -pub async fn save_initialized_accounts( - wallet: &mut Wallet, +pub async fn save_initialized_accounts<'a>( + namada: &impl Namada<'a>, args: &args::Tx, initialized_accounts: Vec
, ) { - tx::save_initialized_accounts::(wallet, args, initialized_accounts) - .await + tx::save_initialized_accounts(namada, args, initialized_accounts).await } /// Broadcast a transaction to be included in the blockchain and checks that /// the tx has been successfully included into the mempool of a validator /// /// In the case of errors in any of those stages, an error message is returned -pub async fn broadcast_tx( - rpc_cli: &HttpClient, +pub async fn broadcast_tx<'a>( + namada: &impl Namada<'a>, to_broadcast: &TxBroadcastData, ) -> Result { - tx::broadcast_tx::<_, IO>(rpc_cli, to_broadcast).await + tx::broadcast_tx(namada, to_broadcast).await } /// Broadcast a transaction to be included in the blockchain. @@ -1600,9 +1050,9 @@ pub async fn broadcast_tx( /// 3. The decrypted payload of the tx has been included on the blockchain. /// /// In the case of errors in any of those stages, an error message is returned -pub async fn submit_tx( - client: &HttpClient, +pub async fn submit_tx<'a>( + namada: &impl Namada<'a>, to_broadcast: TxBroadcastData, ) -> Result { - tx::submit_tx::<_, IO>(client, to_broadcast).await + tx::submit_tx(namada, to_broadcast).await } diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index 0caab25d35..83fd499071 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -9,12 +9,12 @@ use borsh::BorshSerialize; use flate2::read::GzDecoder; use flate2::write::GzEncoder; use flate2::Compression; -use namada::sdk::wallet::Wallet; use namada::types::address; use namada::types::chain::ChainId; use namada::types::dec::Dec; use namada::types::key::*; use namada::vm::validate_untrusted_wasm; +use namada_sdk::wallet::Wallet; use prost::bytes::Bytes; use rand::prelude::ThreadRng; use rand::thread_rng; @@ -505,10 +505,16 @@ pub fn init_network( println!("Generating validator {} consensus key...", name); let password = read_and_confirm_encryption_password(unsafe_dont_encrypt); - let (_alias, keypair) = wallet - .gen_key(SchemeType::Ed25519, Some(alias), true, password, None) - .expect("Key generation should not fail.") - .expect("No existing alias expected."); + let (_alias, keypair, _mnemonic) = wallet + .gen_key( + SchemeType::Ed25519, + Some(alias), + true, + None, + password, + None, + ) + .expect("Key generation should not fail."); // Write consensus key for Tendermint tendermint_node::write_validator_key(&tm_home_dir, &keypair); @@ -525,10 +531,16 @@ pub fn init_network( println!("Generating validator {} account key...", name); let password = read_and_confirm_encryption_password(unsafe_dont_encrypt); - let (_alias, keypair) = wallet - .gen_key(SchemeType::Ed25519, Some(alias), true, password, None) - .expect("Key generation should not fail.") - .expect("No existing alias expected."); + let (_alias, keypair, _mnemonic) = wallet + .gen_key( + SchemeType::Ed25519, + Some(alias), + true, + None, + password, + None, + ) + .expect("Key generation should not fail."); keypair.ref_to() }); @@ -541,10 +553,16 @@ pub fn init_network( println!("Generating validator {} protocol signing key...", name); let password = read_and_confirm_encryption_password(unsafe_dont_encrypt); - let (_alias, keypair) = wallet - .gen_key(SchemeType::Ed25519, Some(alias), true, password, None) - .expect("Key generation should not fail.") - .expect("No existing alias expected."); + let (_alias, keypair, _mnemonic) = wallet + .gen_key( + SchemeType::Ed25519, + Some(alias), + true, + None, + password, + None, + ) + .expect("Key generation should not fail."); keypair.ref_to() }); @@ -557,16 +575,16 @@ pub fn init_network( println!("Generating validator {} eth hot key...", name); let password = read_and_confirm_encryption_password(unsafe_dont_encrypt); - let (_alias, keypair) = wallet + let (_alias, keypair, _mnemonic) = wallet .gen_key( SchemeType::Secp256k1, Some(alias), true, + None, password, None, ) - .expect("Key generation should not fail.") - .expect("No existing alias expected."); + .expect("Key generation should not fail."); keypair.ref_to() }); @@ -579,16 +597,16 @@ pub fn init_network( println!("Generating validator {} eth cold key...", name); let password = read_and_confirm_encryption_password(unsafe_dont_encrypt); - let (_alias, keypair) = wallet + let (_alias, keypair, _mnemonic) = wallet .gen_key( SchemeType::Secp256k1, Some(alias), true, + None, password, None, ) - .expect("Key generation should not fail.") - .expect("No existing alias expected."); + .expect("Key generation should not fail."); keypair.ref_to() }); @@ -675,16 +693,16 @@ pub fn init_network( ); let password = read_and_confirm_encryption_password(unsafe_dont_encrypt); - let (_alias, keypair) = wallet + let (_alias, keypair, _mnemonic) = wallet .gen_key( SchemeType::Ed25519, Some(name.clone()), true, + None, password, None, ) - .expect("Key generation should not fail.") - .expect("No existing alias expected."); + .expect("Key generation should not fail."); let public_key = genesis_config::HexString(keypair.ref_to().to_string()); config.public_key = Some(public_key); @@ -938,16 +956,16 @@ fn init_established_account( println!("Generating established account {} key...", name.as_ref()); let password = read_and_confirm_encryption_password(unsafe_dont_encrypt); - let (_alias, keypair) = wallet + let (_alias, keypair, _mnemonic) = wallet .gen_key( SchemeType::Ed25519, Some(format!("{}-key", name.as_ref())), true, + None, password, None, // do not use mnemonic code / HD derivation path ) - .expect("Key generation should not fail.") - .expect("No existing alias expected."); + .expect("Key generation should not fail."); let public_key = genesis_config::HexString(keypair.ref_to().to_string()); config.public_key = Some(public_key); diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index b7281fd4ce..222e7b4f1f 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -6,7 +6,6 @@ use borsh::{BorshDeserialize, BorshSerialize}; use derivative::Derivative; use namada::core::ledger::governance::parameters::GovernanceParameters; use namada::core::ledger::pgf::parameters::PgfParameters; -use namada::ledger::eth_bridge::EthereumBridgeConfig; use namada::ledger::parameters::EpochDuration; use namada::ledger::pos::{Dec, GenesisValidator, PosParams}; use namada::types::address::Address; @@ -17,6 +16,7 @@ use namada::types::time::{DateTimeUtc, DurationSecs}; use namada::types::token::Denomination; use namada::types::uint::Uint; use namada::types::{storage, token}; +use namada_sdk::eth_bridge::EthereumBridgeConfig; /// Genesis configuration file format pub mod genesis_config { @@ -900,14 +900,14 @@ pub fn genesis( } #[cfg(any(test, feature = "dev"))] pub fn genesis(num_validators: u64) -> Genesis { - use namada::ledger::eth_bridge::{ - Contracts, Erc20WhitelistEntry, UpgradeableContract, - }; use namada::types::address::{ self, apfel, btc, dot, eth, kartoffel, nam, schnitzel, wnam, }; use namada::types::ethereum_events::testing::DAI_ERC20_ETH_ADDRESS; use namada::types::ethereum_events::EthAddress; + use namada_sdk::eth_bridge::{ + Contracts, Erc20WhitelistEntry, UpgradeableContract, + }; use crate::wallet; diff --git a/apps/src/lib/node/ledger/ethereum_oracle/mod.rs b/apps/src/lib/node/ledger/ethereum_oracle/mod.rs index 6980778c07..300ea85347 100644 --- a/apps/src/lib/node/ledger/ethereum_oracle/mod.rs +++ b/apps/src/lib/node/ledger/ethereum_oracle/mod.rs @@ -12,9 +12,9 @@ use namada::core::types::ethereum_structs; use namada::eth_bridge::ethers; use namada::eth_bridge::ethers::providers::{Http, Middleware, Provider}; use namada::eth_bridge::oracle::config::Config; -use namada::ledger::eth_bridge::{eth_syncing_status_timeout, SyncStatus}; use namada::types::control_flow::time::{Constant, Duration, Instant, Sleep}; use namada::types::ethereum_events::EthereumEvent; +use namada_sdk::eth_bridge::{eth_syncing_status_timeout, SyncStatus}; use num256::Uint256; use thiserror::Error; use tokio::sync::mpsc::error::TryRecvError; diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 53252065f1..9a32f6dd60 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -1049,7 +1049,6 @@ mod test_finalize_block { self, get_key_from_hash, get_nonce_key, get_signed_root_key, }; use namada::eth_bridge::storage::min_confirmations_key; - use namada::ledger::eth_bridge::MinimumConfirmations; use namada::ledger::gas::VpGasMeter; use namada::ledger::native_vp::parameters::ParametersVp; use namada::ledger::native_vp::NativeVp; @@ -1087,6 +1086,7 @@ mod test_finalize_block { use namada::types::transaction::{Fee, WrapperTx}; use namada::types::uint::Uint; use namada::types::vote_extensions::ethereum_events; + use namada_sdk::eth_bridge::MinimumConfirmations; use namada_test_utils::TestWasms; use test_log::test; diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index d6b2efe4dd..6faf48f2d1 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -2,7 +2,6 @@ use std::collections::HashMap; use std::hash::Hash; -use namada::ledger::eth_bridge::EthBridgeStatus; use namada::ledger::parameters::{self, Parameters}; use namada::ledger::pos::{staking_token_address, PosParams}; use namada::ledger::storage::traits::StorageHasher; @@ -17,6 +16,7 @@ use namada::types::hash::Hash as CodeHash; use namada::types::key::*; use namada::types::time::{DateTimeUtc, TimeZone, Utc}; use namada::vm::validate_untrusted_wasm; +use namada_sdk::eth_bridge::EthBridgeStatus; use super::*; use crate::facade::tendermint_proto::google::protobuf; diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index a1c17fe450..eecf6aea06 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -30,7 +30,6 @@ use borsh::{BorshDeserialize, BorshSerialize}; use masp_primitives::transaction::Transaction; use namada::core::hints; use namada::core::ledger::eth_bridge; -use namada::ledger::eth_bridge::{EthBridgeQueries, EthereumOracleConfig}; use namada::ledger::events::log::EventLog; use namada::ledger::events::Event; use namada::ledger::gas::{Gas, TxGasMeter}; @@ -66,6 +65,7 @@ use namada::types::transaction::{ use namada::types::{address, hash, token}; use namada::vm::wasm::{TxCache, VpCache}; use namada::vm::{WasmCacheAccess, WasmCacheRwAccess}; +use namada_sdk::eth_bridge::{EthBridgeQueries, EthereumOracleConfig}; use num_derive::{FromPrimitive, ToPrimitive}; use num_traits::{FromPrimitive, ToPrimitive}; use thiserror::Error; diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index ab544de3f8..e5b1e94e13 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -4,7 +4,6 @@ use data_encoding::HEXUPPER; use namada::core::hints; use namada::core::ledger::storage::WlStorage; -use namada::ledger::eth_bridge::{EthBridgeQueries, SendValsetUpd}; use namada::ledger::pos::PosQueries; use namada::ledger::protocol::get_fee_unshielding_transaction; use namada::ledger::storage::TempWlStorage; @@ -15,6 +14,7 @@ use namada::types::transaction::protocol::{ }; #[cfg(feature = "abcipp")] use namada::types::voting_power::FractionalVotingPower; +use namada_sdk::eth_bridge::{EthBridgeQueries, SendValsetUpd}; use super::block_alloc::{BlockSpace, EncryptedTxsBins}; use super::*; diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index a62c3ec4b4..f1e56c295f 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -2,6 +2,7 @@ use borsh::BorshSerialize; use ferveo_common::TendermintValidator; +use namada::ledger::dry_run_tx; use namada::ledger::pos::into_tm_voting_power; use namada::ledger::queries::{RequestCtx, ResponseQuery}; use namada::ledger::storage_api::token; @@ -49,7 +50,11 @@ where }; // Invoke the root RPC handler - returns borsh-encoded data on success - let result = namada::ledger::queries::handle_path(ctx, &request); + let result = if request.path == "/shell/dry_run_tx" { + dry_run_tx(ctx, &request) + } else { + namada::ledger::queries::handle_path(ctx, &request) + }; match result { Ok(ResponseQuery { data, info, proof }) => response::Query { value: data, @@ -137,10 +142,10 @@ where #[cfg(not(feature = "abcipp"))] mod test_queries { use namada::core::ledger::storage::EPOCH_SWITCH_BLOCKS_DELAY; - use namada::ledger::eth_bridge::{EthBridgeQueries, SendValsetUpd}; use namada::ledger::pos::PosQueries; use namada::proof_of_stake::types::WeightedValidator; use namada::types::storage::Epoch; + use namada_sdk::eth_bridge::{EthBridgeQueries, SendValsetUpd}; use super::*; use crate::facade::tendermint_proto::abci::VoteInfo; diff --git a/apps/src/lib/node/ledger/shell/testing/client.rs b/apps/src/lib/node/ledger/shell/testing/client.rs index 9ebc825f54..7649156b8e 100644 --- a/apps/src/lib/node/ledger/shell/testing/client.rs +++ b/apps/src/lib/node/ledger/shell/testing/client.rs @@ -47,9 +47,10 @@ pub fn run( NamadaClient::WithoutContext(sub_cmd, global) } }; - rt.block_on(CliApi::::handle_client_command( + rt.block_on(CliApi::handle_client_command( Some(node), cmd, + &TestingIo, )) } Bin::Wallet => { @@ -60,7 +61,7 @@ pub fn run( let cmd = cmds::NamadaWallet::parse(&matches) .expect("Could not parse wallet command"); - CliApi::::handle_wallet_command(cmd, ctx) + CliApi::handle_wallet_command(cmd, ctx, &TestingIo) } Bin::Relayer => { args.insert(0, "relayer"); @@ -82,9 +83,10 @@ pub fn run( NamadaRelayer::ValidatorSet(sub_cmd) } }; - rt.block_on(CliApi::::handle_relayer_command( + rt.block_on(CliApi::handle_relayer_command( Some(node), cmd, + &TestingIo, )) } } @@ -96,7 +98,7 @@ impl<'a> CliClient for &'a MockNode { unreachable!("MockNode should always be instantiated at test start.") } - async fn wait_until_node_is_synced(&self) -> Halt<()> { + async fn wait_until_node_is_synced(&self, _io: &impl Io) -> Halt<()> { ControlFlow::Continue(()) } } diff --git a/apps/src/lib/node/ledger/shell/testing/node.rs b/apps/src/lib/node/ledger/shell/testing/node.rs index 034ac80845..7ebf3ce0b8 100644 --- a/apps/src/lib/node/ledger/shell/testing/node.rs +++ b/apps/src/lib/node/ledger/shell/testing/node.rs @@ -6,6 +6,7 @@ use std::sync::{Arc, Mutex}; use color_eyre::eyre::{Report, Result}; use data_encoding::HEXUPPER; use lazy_static::lazy_static; +use namada::ledger::dry_run_tx; use namada::ledger::events::log::dumb_queries; use namada::ledger::queries::{ EncodedResponseQuery, RequestCtx, RequestQuery, Router, RPC, @@ -19,7 +20,6 @@ use namada::proof_of_stake::{ read_consensus_validator_set_addresses_with_stake, validator_consensus_key_handle, }; -use namada::sdk::queries::Client; use namada::tendermint_proto::abci::VoteInfo; use namada::tendermint_rpc::endpoint::abci_info; use namada::tendermint_rpc::SimpleRequest; @@ -27,6 +27,7 @@ use namada::types::hash::Hash; use namada::types::key::tm_consensus_key_raw_hash; use namada::types::storage::{BlockHash, BlockHeight, Epoch, Header}; use namada::types::time::DateTimeUtc; +use namada_sdk::queries::Client; use num_traits::cast::FromPrimitive; use regex::Regex; use tokio::sync::mpsc::UnboundedReceiver; @@ -352,7 +353,12 @@ impl<'a> Client for &'a MockNode { tx_wasm_cache: borrowed.tx_wasm_cache.read_only(), storage_read_past_height_limit: None, }; - rpc.handle(ctx, &request).map_err(Report::new) + if request.path == "/shell/dry_run_tx" { + dry_run_tx(ctx, &request) + } else { + rpc.handle(ctx, &request) + } + .map_err(Report::new) } async fn perform( diff --git a/apps/src/lib/node/ledger/shell/testing/utils.rs b/apps/src/lib/node/ledger/shell/testing/utils.rs index bfcb7f50ab..451e20c2df 100644 --- a/apps/src/lib/node/ledger/shell/testing/utils.rs +++ b/apps/src/lib/node/ledger/shell/testing/utils.rs @@ -74,13 +74,13 @@ pub struct TestingIo; #[async_trait::async_trait(?Send)] impl Io for TestingIo { - fn print(output: impl AsRef) { + fn print(&self, output: impl AsRef) { let mut testout = TESTOUT.lock().unwrap(); testout.append(output.as_ref().as_bytes().to_vec()); print!("{}", output.as_ref()); } - fn println(output: impl AsRef) { + fn println(&self, output: impl AsRef) { let mut testout = TESTOUT.lock().unwrap(); let mut bytes = output.as_ref().as_bytes().to_vec(); bytes.extend_from_slice("\n".as_bytes()); @@ -89,22 +89,24 @@ impl Io for TestingIo { } fn write( + &self, _: W, output: impl AsRef, ) -> std::io::Result<()> { - Self::print(output); + self.print(output); Ok(()) } fn writeln( + &self, _: W, output: impl AsRef, ) -> std::io::Result<()> { - Self::println(output); + self.println(output); Ok(()) } - fn eprintln(output: impl AsRef) { + fn eprintln(&self, output: impl AsRef) { let mut testout = TESTOUT.lock().unwrap(); let mut bytes = output.as_ref().as_bytes().to_vec(); bytes.extend_from_slice("\n".as_bytes()); @@ -112,11 +114,11 @@ impl Io for TestingIo { eprintln!("{}", output.as_ref()); } - async fn read() -> tokio::io::Result { + async fn read(&self) -> tokio::io::Result { read_aux(&*TESTIN).await } - async fn prompt(question: impl AsRef) -> String { + async fn prompt(&self, question: impl AsRef) -> String { prompt_aux(&*TESTIN, tokio::io::stdout(), question.as_ref()).await } } diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index 658c35a121..e7e19bf96d 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -4,7 +4,6 @@ pub mod bridge_pool_vext; pub mod eth_events; pub mod val_set_update; -use namada::ledger::eth_bridge::{EthBridgeQueries, SendValsetUpd}; #[cfg(feature = "abcipp")] use namada::ledger::pos::PosQueries; use namada::proto::{SignableEthMessage, Signed}; @@ -15,6 +14,7 @@ use namada::types::vote_extensions::VoteExtensionDigest; use namada::types::vote_extensions::{ bridge_pool_roots, ethereum_events, validator_set_update, VoteExtension, }; +use namada_sdk::eth_bridge::{EthBridgeQueries, SendValsetUpd}; use super::*; #[cfg(feature = "abcipp")] diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/bridge_pool_vext.rs b/apps/src/lib/node/ledger/shell/vote_extensions/bridge_pool_vext.rs index 002bd18904..201c96983f 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/bridge_pool_vext.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/bridge_pool_vext.rs @@ -273,8 +273,6 @@ mod test_bp_vote_extensions { use borsh::BorshSerialize; #[cfg(not(feature = "abcipp"))] use namada::core::ledger::eth_bridge::storage::bridge_pool::get_key_from_hash; - #[cfg(not(feature = "abcipp"))] - use namada::ledger::eth_bridge::EthBridgeQueries; use namada::ledger::pos::PosQueries; use namada::ledger::storage_api::StorageWrite; use namada::proof_of_stake::types::{ @@ -297,6 +295,8 @@ mod test_bp_vote_extensions { use namada::types::vote_extensions::bridge_pool_roots; #[cfg(feature = "abcipp")] use namada::types::vote_extensions::VoteExtension; + #[cfg(not(feature = "abcipp"))] + use namada_sdk::eth_bridge::EthBridgeQueries; #[cfg(feature = "abcipp")] use tendermint_proto_abcipp::abci::response_verify_vote_extension::VerifyStatus; #[cfg(feature = "abcipp")] diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs index 0dd85bfd70..e7dbdc255b 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs @@ -2,7 +2,6 @@ use std::collections::{BTreeMap, HashMap}; -use namada::ledger::eth_bridge::EthBridgeQueries; use namada::ledger::pos::PosQueries; use namada::ledger::storage::traits::StorageHasher; use namada::ledger::storage::{DBIter, DB}; @@ -15,6 +14,7 @@ use namada::types::vote_extensions::ethereum_events::{ }; #[cfg(feature = "abcipp")] use namada::types::voting_power::FractionalVotingPower; +use namada_sdk::eth_bridge::EthBridgeQueries; use super::*; use crate::node::ledger::shell::{Shell, ShellMode}; diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs index 03843b4717..4888c10be1 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs @@ -311,7 +311,6 @@ mod test_vote_extensions { use namada::core::ledger::storage_api::collections::lazy_map::{ NestedSubKey, SubKey, }; - use namada::ledger::eth_bridge::EthBridgeQueries; use namada::ledger::pos::PosQueries; use namada::proof_of_stake::types::WeightedValidator; use namada::proof_of_stake::{ @@ -337,6 +336,7 @@ mod test_vote_extensions { use namada::types::vote_extensions::validator_set_update; #[cfg(feature = "abcipp")] use namada::types::vote_extensions::VoteExtension; + use namada_sdk::eth_bridge::EthBridgeQueries; #[cfg(feature = "abcipp")] use crate::facade::tendermint_proto::abci::response_verify_vote_extension::VerifyStatus; diff --git a/apps/src/lib/wallet/cli_utils.rs b/apps/src/lib/wallet/cli_utils.rs index 72bb0acaab..46349167ef 100644 --- a/apps/src/lib/wallet/cli_utils.rs +++ b/apps/src/lib/wallet/cli_utils.rs @@ -4,10 +4,10 @@ use std::io::{self, Write}; use borsh::BorshSerialize; use itertools::sorted; use masp_primitives::zip32::ExtendedFullViewingKey; -use namada::sdk::masp::find_valid_diversifier; -use namada::sdk::wallet::{DecryptionError, FindKeyError}; use namada::types::key::{PublicKeyHash, RefTo}; use namada::types::masp::{MaspValue, PaymentAddress}; +use namada_sdk::masp::find_valid_diversifier; +use namada_sdk::wallet::{DecryptionError, FindKeyError, GenRestoreKeyError}; use rand_core::OsRng; use crate::cli; @@ -271,6 +271,7 @@ pub fn key_and_address_restore( alias, alias_force, derivation_path, + None, encryption_password, ) .unwrap_or_else(|err| { @@ -306,21 +307,24 @@ pub fn key_and_address_gen( let mut rng = OsRng; let derivation_path_and_mnemonic_rng = derivation_path.map(|p| (p, &mut rng)); - let (alias, _key) = wallet + let (alias, _key, _mnemonic) = wallet .gen_key( scheme, alias, alias_force, + None, encryption_password, derivation_path_and_mnemonic_rng, ) - .unwrap_or_else(|err| { - eprintln!("{}", err); - cli::safe_exit(1); - }) - .unwrap_or_else(|| { - println!("No changes are persisted. Exiting."); - cli::safe_exit(0); + .unwrap_or_else(|err| match err { + GenRestoreKeyError::KeyStorageError => { + println!("No changes are persisted. Exiting."); + cli::safe_exit(0); + } + _ => { + eprintln!("{}", err); + cli::safe_exit(1); + } }); crate::wallet::save(&wallet).unwrap_or_else(|err| eprintln!("{}", err)); println!( diff --git a/apps/src/lib/wallet/defaults.rs b/apps/src/lib/wallet/defaults.rs index 00b0f49d26..82a9524daa 100644 --- a/apps/src/lib/wallet/defaults.rs +++ b/apps/src/lib/wallet/defaults.rs @@ -8,10 +8,10 @@ pub use dev::{ validator_keys, }; use namada::core::ledger::eth_bridge::storage::bridge_pool::BRIDGE_POOL_ADDRESS; -use namada::ledger::{eth_bridge, governance, pgf, pos}; -use namada::sdk::wallet::alias::Alias; +use namada::ledger::{governance, pgf, pos}; use namada::types::address::Address; use namada::types::key::*; +use namada_sdk::wallet::alias::Alias; use crate::config::genesis::genesis_config::GenesisConfig; @@ -22,7 +22,7 @@ pub fn addresses_from_genesis(genesis: GenesisConfig) -> Vec<(Alias, Address)> { ("pos".into(), pos::ADDRESS), ("pos_slash_pool".into(), pos::SLASH_POOL_ADDRESS), ("governance".into(), governance::ADDRESS), - ("eth_bridge".into(), eth_bridge::ADDRESS), + ("eth_bridge".into(), namada_sdk::eth_bridge::ADDRESS), ("bridge_pool".into(), BRIDGE_POOL_ADDRESS), ("pgf".into(), pgf::ADDRESS), ]; @@ -78,12 +78,12 @@ mod dev { use borsh::BorshDeserialize; use namada::ledger::{governance, pgf, pos}; - use namada::sdk::wallet::alias::Alias; use namada::types::address::{ apfel, btc, dot, eth, kartoffel, nam, schnitzel, Address, }; use namada::types::key::dkg_session_keys::DkgKeypair; use namada::types::key::*; + use namada_sdk::wallet::alias::Alias; /// Generate a new protocol signing keypair, eth hot key and DKG session /// keypair diff --git a/apps/src/lib/wallet/mod.rs b/apps/src/lib/wallet/mod.rs index f6611ebe18..18818daef5 100644 --- a/apps/src/lib/wallet/mod.rs +++ b/apps/src/lib/wallet/mod.rs @@ -9,14 +9,16 @@ use std::str::FromStr; use std::{env, fs}; use namada::bip39::{Language, Mnemonic}; -pub use namada::sdk::wallet::alias::Alias; -use namada::sdk::wallet::{ - AddressVpType, ConfirmationResponse, FindKeyError, GenRestoreKeyError, - Wallet, WalletUtils, -}; -pub use namada::sdk::wallet::{ValidatorData, ValidatorKeys}; use namada::types::address::Address; use namada::types::key::*; +pub use namada_sdk::wallet::alias::Alias; +use namada_sdk::wallet::fs::FsWalletStorage; +use namada_sdk::wallet::store::Store; +use namada_sdk::wallet::{ + AddressVpType, ConfirmationResponse, FindKeyError, GenRestoreKeyError, + Wallet, WalletIo, +}; +pub use namada_sdk::wallet::{ValidatorData, ValidatorKeys}; use rand_core::OsRng; pub use store::wallet_file; use zeroize::Zeroizing; @@ -24,32 +26,28 @@ use zeroize::Zeroizing; use crate::cli; use crate::config::genesis::genesis_config::GenesisConfig; -#[derive(Debug)] -pub struct CliWalletUtils; +#[derive(Debug, Clone)] +pub struct CliWalletUtils { + store_dir: PathBuf, +} -impl WalletUtils for CliWalletUtils { - type Rng = OsRng; - type Storage = PathBuf; +impl CliWalletUtils { + /// Initialize a wallet at the given directory + pub fn new(store_dir: PathBuf) -> Wallet { + Wallet::new(Self { store_dir }, Store::default()) + } +} - fn read_decryption_password() -> Zeroizing { - match env::var("NAMADA_WALLET_PASSWORD_FILE") { - Ok(path) => Zeroizing::new( - fs::read_to_string(path) - .expect("Something went wrong reading the file"), - ), - Err(_) => match env::var("NAMADA_WALLET_PASSWORD") { - Ok(password) => Zeroizing::new(password), - Err(_) => { - let prompt = "Enter your decryption password: "; - rpassword::read_password_from_tty(Some(prompt)) - .map(Zeroizing::new) - .expect("Failed reading password from tty.") - } - }, - } +impl FsWalletStorage for CliWalletUtils { + fn store_dir(&self) -> &PathBuf { + &self.store_dir } +} + +impl WalletIo for CliWalletUtils { + type Rng = OsRng; - fn read_encryption_password() -> Zeroizing { + fn read_password(confirm: bool) -> Zeroizing { let pwd = match env::var("NAMADA_WALLET_PASSWORD_FILE") { Ok(path) => Zeroizing::new( fs::read_to_string(path) @@ -57,7 +55,7 @@ impl WalletUtils for CliWalletUtils { ), Err(_) => match env::var("NAMADA_WALLET_PASSWORD") { Ok(password) => Zeroizing::new(password), - Err(_) => { + Err(_) if confirm => { let prompt = "Enter your encryption password: "; read_and_confirm_passphrase_tty(prompt).unwrap_or_else( |e| { @@ -69,9 +67,15 @@ impl WalletUtils for CliWalletUtils { }, ) } + Err(_) => { + let prompt = "Enter your decryption password: "; + rpassword::read_password_from_tty(Some(prompt)) + .map(Zeroizing::new) + .expect("Failed reading password from tty.") + } }, }; - if pwd.as_str().is_empty() { + if confirm && pwd.as_str().is_empty() { eprintln!("Password cannot be empty"); eprintln!("Action cancelled, no changes persisted."); cli::safe_exit(1) @@ -190,7 +194,7 @@ pub fn read_and_confirm_passphrase_tty( /// for signing protocol txs and for the DKG (which will also be stored) /// A protocol keypair may be optionally provided, indicating that /// we should re-use a keypair already in the wallet -pub fn gen_validator_keys( +pub fn gen_validator_keys( wallet: &mut Wallet, eth_bridge_pk: Option, protocol_pk: Option, @@ -221,7 +225,7 @@ fn find_secret_key( ) -> Result, FindKeyError> where F: Fn(&ValidatorData) -> common::SecretKey, - U: WalletUtils, + U: WalletIo, { maybe_pk .map(|pk| { @@ -254,19 +258,19 @@ pub fn add_genesis_addresses( /// Save the wallet store to a file. pub fn save(wallet: &Wallet) -> std::io::Result<()> { - self::store::save(wallet.store(), wallet.store_dir()) + wallet + .save() + .map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err)) } /// Load a wallet from the store file. pub fn load(store_dir: &Path) -> Option> { - let store = self::store::load(store_dir).unwrap_or_else(|err| { + let mut wallet = CliWalletUtils::new(store_dir.to_path_buf()); + wallet.load().unwrap_or_else(|err| { eprintln!("Unable to load the wallet: {}", err); cli::safe_exit(1) }); - Some(Wallet::::new( - store_dir.to_path_buf(), - store, - )) + Some(wallet) } /// Load a wallet from the store file or create a new wallet without any @@ -276,7 +280,9 @@ pub fn load_or_new(store_dir: &Path) -> Wallet { eprintln!("Unable to load the wallet: {}", err); cli::safe_exit(1) }); - Wallet::::new(store_dir.to_path_buf(), store) + let mut wallet = CliWalletUtils::new(store_dir.to_path_buf()); + *wallet.store_mut() = store; + wallet } /// Load a wallet from the store file or create a new one with the default @@ -290,7 +296,9 @@ pub fn load_or_new_from_genesis( eprintln!("Unable to load the wallet: {}", err); cli::safe_exit(1) }); - Wallet::::new(store_dir.to_path_buf(), store) + let mut wallet = CliWalletUtils::new(store_dir.to_path_buf()); + *wallet.store_mut() = store; + wallet } /// Read the password for encryption from the file/env/stdin, with @@ -302,14 +310,14 @@ pub fn read_and_confirm_encryption_password( println!("Warning: The keypair will NOT be encrypted."); None } else { - Some(CliWalletUtils::read_encryption_password()) + Some(CliWalletUtils::read_password(true)) } } #[cfg(test)] mod tests { use namada::bip39::MnemonicType; - use namada::sdk::wallet::WalletUtils; + use namada_sdk::wallet::WalletIo; use rand_core; use super::CliWalletUtils; diff --git a/apps/src/lib/wallet/pre_genesis.rs b/apps/src/lib/wallet/pre_genesis.rs index 21a80267f1..da12c2dcce 100644 --- a/apps/src/lib/wallet/pre_genesis.rs +++ b/apps/src/lib/wallet/pre_genesis.rs @@ -3,11 +3,11 @@ use std::path::{Path, PathBuf}; use ark_serialize::{Read, Write}; use fd_lock::RwLock; -use namada::sdk::wallet::pre_genesis::{ +use namada::types::key::SchemeType; +use namada_sdk::wallet::pre_genesis::{ ReadError, ValidatorStore, ValidatorWallet, }; -use namada::sdk::wallet::{gen_key_to_store, WalletUtils}; -use namada::types::key::SchemeType; +use namada_sdk::wallet::{gen_key_to_store, WalletIo}; use zeroize::Zeroizing; use crate::wallet::store::gen_validator_keys; @@ -75,7 +75,7 @@ pub fn load(store_dir: &Path) -> Result { || store.consensus_key.is_encrypted() || store.account_key.is_encrypted() { - Some(CliWalletUtils::read_decryption_password()) + Some(CliWalletUtils::read_password(false)) } else { None }; diff --git a/apps/src/lib/wallet/store.rs b/apps/src/lib/wallet/store.rs index 0f2aa86b7b..62eae8ac0e 100644 --- a/apps/src/lib/wallet/store.rs +++ b/apps/src/lib/wallet/store.rs @@ -1,37 +1,22 @@ -use std::fs; -use std::io::prelude::*; -use std::io::Write; use std::path::{Path, PathBuf}; #[cfg(not(feature = "dev"))] use std::str::FromStr; use ark_std::rand::prelude::*; use ark_std::rand::SeedableRng; -use fd_lock::RwLock; -#[cfg(not(feature = "dev"))] -use namada::sdk::wallet::store::AddressVpType; -#[cfg(feature = "dev")] -use namada::sdk::wallet::StoredKeypair; -use namada::sdk::wallet::{gen_sk_rng, Store, ValidatorKeys}; #[cfg(not(feature = "dev"))] use namada::types::address::Address; use namada::types::key::*; use namada::types::transaction::EllipticCurve; -use thiserror::Error; +#[cfg(not(feature = "dev"))] +use namada_sdk::wallet::store::AddressVpType; +#[cfg(feature = "dev")] +use namada_sdk::wallet::StoredKeypair; +use namada_sdk::wallet::{gen_sk_rng, LoadStoreError, Store, ValidatorKeys}; use crate::config::genesis::genesis_config::GenesisConfig; use crate::wallet::CliWalletUtils; -#[derive(Error, Debug)] -pub enum LoadStoreError { - #[error("Failed decoding the wallet store: {0}")] - Decode(toml::de::Error), - #[error("Failed to read the wallet store from {0}: {1}")] - ReadWallet(String, String), - #[error("Failed to write the wallet store: {0}")] - StoreNewWallet(String), -} - /// Wallet file name const FILE_NAME: &str = "wallet.toml"; @@ -40,28 +25,12 @@ pub fn wallet_file(store_dir: impl AsRef) -> PathBuf { store_dir.as_ref().join(FILE_NAME) } -/// Save the wallet store to a file. -pub fn save(store: &Store, store_dir: &Path) -> std::io::Result<()> { - let data = store.encode(); - let wallet_path = wallet_file(store_dir); - // Make sure the dir exists - let wallet_dir = wallet_path.parent().unwrap(); - fs::create_dir_all(wallet_dir)?; - // Write the file - let mut options = fs::OpenOptions::new(); - options.create(true).write(true).truncate(true); - let mut lock = RwLock::new(options.open(wallet_path)?); - let mut guard = lock.write()?; - guard.write_all(&data) -} - /// Load the store file or create a new one without any keys or addresses. pub fn load_or_new(store_dir: &Path) -> Result { load(store_dir).or_else(|_| { - let store = Store::default(); - save(&store, store_dir) - .map_err(|err| LoadStoreError::StoreNewWallet(err.to_string()))?; - Ok(store) + let wallet = CliWalletUtils::new(store_dir.to_path_buf()); + wallet.save()?; + Ok(wallet.into()) }) } @@ -80,37 +49,18 @@ pub fn load_or_new_from_genesis( let _ = genesis_cfg; new() }; - save(&store, store_dir) - .map_err(|err| LoadStoreError::StoreNewWallet(err.to_string()))?; - Ok(store) + let mut wallet = CliWalletUtils::new(store_dir.to_path_buf()); + *wallet.store_mut() = store; + wallet.save()?; + Ok(wallet.into()) }) } /// Attempt to load the store file. pub fn load(store_dir: &Path) -> Result { - let wallet_file = wallet_file(store_dir); - let mut options = fs::OpenOptions::new(); - options.read(true).write(false); - let lock = RwLock::new(options.open(&wallet_file).map_err(|err| { - LoadStoreError::ReadWallet( - wallet_file.to_string_lossy().into_owned(), - err.to_string(), - ) - })?); - let guard = lock.read().map_err(|err| { - LoadStoreError::ReadWallet( - wallet_file.to_string_lossy().into_owned(), - err.to_string(), - ) - })?; - let mut store = Vec::::new(); - (&*guard).read_to_end(&mut store).map_err(|err| { - LoadStoreError::ReadWallet( - store_dir.to_str().unwrap().parse().unwrap(), - err.to_string(), - ) - })?; - Store::decode(store).map_err(LoadStoreError::Decode) + let mut wallet = CliWalletUtils::new(store_dir.to_path_buf()); + wallet.load()?; + Ok(wallet.into()) } /// Add addresses from a genesis configuration. diff --git a/benches/Cargo.toml b/benches/Cargo.toml index 91a5d45333..ebd99eedda 100644 --- a/benches/Cargo.toml +++ b/benches/Cargo.toml @@ -49,6 +49,7 @@ masp_primitives.workspace = true masp_proofs.workspace = true namada = { path = "../shared", features = ["testing"] } namada_apps = { path = "../apps", features = ["testing"] } +namada_sdk = {path = "../sdk", features = ["testing"] } namada_test_utils = { path = "../test_utils" } prost.workspace = true rand.workspace = true diff --git a/benches/lib.rs b/benches/lib.rs index 47645abdf4..038d7017f0 100644 --- a/benches/lib.rs +++ b/benches/lib.rs @@ -63,6 +63,7 @@ use namada::ibc::core::Msg; use namada::ibc::Height as IbcHeight; use namada::ibc_proto::google::protobuf::Any; use namada::ibc_proto::protobuf::Protobuf; +use namada::ledger::dry_run_tx; use namada::ledger::gas::TxGasMeter; use namada::ledger::ibc::storage::{channel_key, connection_key}; use namada::ledger::queries::{ @@ -71,16 +72,11 @@ use namada::ledger::queries::{ use namada::ledger::storage_api::StorageRead; use namada::proof_of_stake; use namada::proto::{Code, Data, Section, Signature, Tx}; -use namada::sdk::args::InputAmount; -use namada::sdk::masp::{ - self, ShieldedContext, ShieldedTransfer, ShieldedUtils, -}; -use namada::sdk::wallet::Wallet; use namada::tendermint::Hash; use namada::tendermint_rpc::{self}; use namada::types::address::InternalAddress; use namada::types::chain::ChainId; -use namada::types::io::DefaultIo; +use namada::types::io::StdIo; use namada::types::masp::{ ExtendedViewingKey, PaymentAddress, TransferSource, TransferTarget, }; @@ -100,6 +96,12 @@ use namada_apps::facade::tendermint_proto::google::protobuf::Timestamp; use namada_apps::node::ledger::shell::Shell; use namada_apps::wallet::{defaults, CliWalletUtils}; use namada_apps::{config, wasm_loader}; +use namada_sdk::args::InputAmount; +use namada_sdk::masp::{ + self, ShieldedContext, ShieldedTransfer, ShieldedUtils, +}; +use namada_sdk::wallet::Wallet; +use namada_sdk::NamadaImpl; use namada_test_utils::tx_data::TxWriteData; use rand_core::OsRng; use sha2::{Digest, Sha256}; @@ -585,22 +587,29 @@ impl ShieldedUtils for BenchShieldedUtils { /// Try to load the last saved shielded context from the given context /// directory. If this fails, then leave the current context unchanged. - async fn load(self) -> std::io::Result> { + async fn load( + &self, + ctx: &mut ShieldedContext, + ) -> std::io::Result<()> { // Try to load shielded context from file let mut ctx_file = File::open( self.context_dir.0.path().to_path_buf().join(FILE_NAME), )?; let mut bytes = Vec::new(); ctx_file.read_to_end(&mut bytes)?; - let mut new_ctx = ShieldedContext::deserialize(&mut &bytes[..])?; - // Associate the originating context directory with the - // shielded context under construction - new_ctx.utils = self; - Ok(new_ctx) + // Fill the supplied context with the deserialized object + *ctx = ShieldedContext { + utils: ctx.utils.clone(), + ..ShieldedContext::deserialize(&mut &bytes[..])? + }; + Ok(()) } /// Save this shielded context into its associated context directory - async fn save(&self, ctx: &ShieldedContext) -> std::io::Result<()> { + async fn save( + &self, + ctx: &ShieldedContext, + ) -> std::io::Result<()> { let tmp_path = self.context_dir.0.path().to_path_buf().join(TMP_FILE_NAME); { @@ -662,8 +671,12 @@ impl Client for BenchShell { storage_read_past_height_limit: None, }; - RPC.handle(ctx, &request) - .map_err(|_| std::io::Error::from(std::io::ErrorKind::NotFound)) + if request.path == "/shell/dry_run_tx" { + dry_run_tx(ctx, &request) + } else { + RPC.handle(ctx, &request) + } + .map_err(|_| std::io::Error::from(std::io::ErrorKind::NotFound)) } async fn perform( @@ -681,13 +694,12 @@ impl Default for BenchShieldedCtx { fn default() -> Self { let mut shell = BenchShell::default(); - let mut ctx = - Context::new::(namada_apps::cli::args::Global { - chain_id: None, - base_dir: shell.tempdir.as_ref().canonicalize().unwrap(), - wasm_dir: Some(WASM_DIR.into()), - }) - .unwrap(); + let mut ctx = Context::new::(namada_apps::cli::args::Global { + chain_id: None, + base_dir: shell.tempdir.as_ref().canonicalize().unwrap(), + wasm_dir: Some(WASM_DIR.into()), + }) + .unwrap(); // Generate spending key for Albert and Bertha ctx.wallet.gen_spending_key( @@ -720,7 +732,7 @@ impl Default for BenchShieldedCtx { .fvk .vk; let (div, _g_d) = - namada::sdk::masp::find_valid_diversifier(&mut OsRng); + namada_sdk::masp::find_valid_diversifier(&mut OsRng); let payment_addr = viewing_key.to_payment_address(div).unwrap(); let _ = ctx .wallet @@ -803,10 +815,18 @@ impl BenchShieldedCtx { &[], )) .unwrap(); + let namada = NamadaImpl::native_new( + &self.shell, + &mut self.wallet, + &mut self.shielded, + &StdIo, + self.shell.wl_storage.storage.native_token.clone(), + ); let shielded = async_runtime .block_on( - self.shielded - .gen_shielded_transfer::<_, DefaultIo>(&self.shell, args), + ShieldedContext::::gen_shielded_transfer( + &namada, &args, + ), ) .unwrap() .map( diff --git a/sdk/Cargo.toml b/sdk/Cargo.toml new file mode 100644 index 0000000000..f0eb9289ed --- /dev/null +++ b/sdk/Cargo.toml @@ -0,0 +1,121 @@ +[package] +name = "namada_sdk" +description = "The main Namada SDK crate" +resolver = "2" +authors.workspace = true +edition.workspace = true +documentation.workspace = true +homepage.workspace = true +keywords.workspace = true +license.workspace = true +readme.workspace = true +repository.workspace = true +version.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[features] +abciplus = [ + "namada_core/abciplus", + "namada_proof_of_stake/abciplus", + "namada_ethereum_bridge/abciplus", +] + +ferveo-tpke = [ + "namada_core/ferveo-tpke", +] + +masp-tx-gen = [ + "rand", + "rand_core", +] + +multicore = ["masp_proofs/multicore"] + +namada-sdk = [ + "tendermint-rpc", + "masp-tx-gen", + "ferveo-tpke", + "masp_primitives/transparent-inputs" +] + +std = ["fd-lock"] + +# tendermint-rpc support +tendermint-rpc = [ + "async-client", + "dep:tendermint-rpc", +] + +wasm-runtime = [ + "namada_core/wasm-runtime", +] + +# Enable queries support for an async client +async-client = [ + "async-trait", +] + +ibc-mocks = [ + "namada_core/ibc-mocks", +] + +# for integration tests and test utilies +testing = [ + "namada_core/testing", + "namada_ethereum_bridge/testing", + "namada_proof_of_stake/testing", + "async-client", + "rand_core", + "rand", +] + +[dependencies] +async-trait = {version = "0.1.51", optional = true} +bimap.workspace = true +borsh.workspace = true +circular-queue.workspace = true +data-encoding.workspace = true +derivation-path.workspace = true +ethbridge-bridge-contract.workspace = true +ethers.workspace = true +fd-lock = { workspace = true, optional = true } +futures.workspace = true +itertools.workspace = true +masp_primitives.workspace = true +masp_proofs = { workspace = true, features = ["download-params"] } +namada_core = {path = "../core", default-features = false, features = ["secp256k1-sign"]} +namada_ethereum_bridge = {path = "../ethereum_bridge", default-features = false} +namada_proof_of_stake = {path = "../proof_of_stake", default-features = false} +num256.workspace = true +orion.workspace = true +owo-colors = "3.5.0" +parse_duration = "2.1.1" +paste.workspace = true +prost.workspace = true +rand = {optional = true, workspace = true} +rand_core = {optional = true, workspace = true} +ripemd.workspace = true +serde.workspace = true +serde_json.workspace = true +sha2.workspace = true +slip10_ed25519.workspace = true +tendermint-rpc = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "b7d1e5afc6f2ccb3fd1545c2174bab1cc48d7fa7", default-features = false, features = ["trait-client"], optional = true} +thiserror.workspace = true +tiny-bip39.workspace = true +tiny-hderive.workspace = true +toml.workspace = true +tracing.workspace = true +zeroize.workspace = true + +[target.'cfg(not(target_family = "wasm"))'.dependencies] +tokio = {workspace = true, features = ["full"]} + +[target.'cfg(target_family = "wasm")'.dependencies] +tokio = {workspace = true, default-features = false, features = ["sync"]} +wasmtimer = "0.2.0" + +[dev-dependencies] +assert_matches.workspace = true +namada_test_utils = {path = "../test_utils"} +tempfile.workspace = true diff --git a/sdk/src/args.rs b/sdk/src/args.rs new file mode 100644 index 0000000000..de4fd5bb98 --- /dev/null +++ b/sdk/src/args.rs @@ -0,0 +1,1970 @@ +//! Structures encapsulating SDK arguments + +use std::collections::HashMap; +use std::path::PathBuf; +use std::time::Duration as StdDuration; + +use namada_core::ledger::governance::cli::onchain::{ + DefaultProposal, PgfFundingProposal, PgfStewardProposal, +}; +use namada_core::types::address::Address; +use namada_core::types::chain::ChainId; +use namada_core::types::dec::Dec; +use namada_core::types::ethereum_events::EthAddress; +use namada_core::types::keccak::KeccakHash; +use namada_core::types::key::{common, SchemeType}; +use namada_core::types::masp::MaspValue; +use namada_core::types::storage::Epoch; +use namada_core::types::time::DateTimeUtc; +use namada_core::types::transaction::GasLimit; +use namada_core::types::{storage, token}; +use serde::{Deserialize, Serialize}; +use zeroize::Zeroizing; + +use crate::eth_bridge::bridge_pool; +use crate::ibc::core::ics24_host::identifier::{ChannelId, PortId}; +use crate::signing::SigningTxData; +use crate::{rpc, tx, Namada}; + +/// [`Duration`](StdDuration) wrapper that provides a +/// method to parse a value from a string. +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] +#[repr(transparent)] +pub struct Duration(pub StdDuration); + +impl ::std::str::FromStr for Duration { + type Err = ::parse_duration::parse::Error; + + #[inline] + fn from_str(s: &str) -> Result { + ::parse_duration::parse(s).map(Duration) + } +} + +/// Abstraction of types being used in Namada +pub trait NamadaTypes: Clone + std::fmt::Debug { + /// Represents an address on the ledger + type Address: Clone + std::fmt::Debug; + /// Represents the address of a native token + type NativeAddress: Clone + std::fmt::Debug; + /// Represents a key pair + type Keypair: Clone + std::fmt::Debug; + /// Represents the address of a Tendermint endpoint + type TendermintAddress: Clone + std::fmt::Debug; + /// Represents the address of an Ethereum endpoint + type EthereumAddress: Clone + std::fmt::Debug; + /// Represents a viewing key + type ViewingKey: Clone + std::fmt::Debug; + /// Represents the owner of a balance + type BalanceOwner: Clone + std::fmt::Debug; + /// Represents a public key + type PublicKey: Clone + std::fmt::Debug; + /// Represents the source of a Transfer + type TransferSource: Clone + std::fmt::Debug; + /// Represents the target of a Transfer + type TransferTarget: Clone + std::fmt::Debug; + /// Represents some data that is used in a transaction + type Data: Clone + std::fmt::Debug; + /// Bridge pool recommendations conversion rates table. + type BpConversionTable: Clone + std::fmt::Debug; +} + +/// The concrete types being used in Namada SDK +#[derive(Clone, Debug)] +pub struct SdkTypes; + +/// An entry in the Bridge pool recommendations conversion +/// rates table. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct BpConversionTableEntry { + /// An alias for the token, or the string representation + /// of its address if none is available. + pub alias: String, + /// Conversion rate from the given token to gwei. + pub conversion_rate: f64, +} + +impl NamadaTypes for SdkTypes { + type Address = Address; + type BalanceOwner = namada_core::types::masp::BalanceOwner; + type BpConversionTable = HashMap; + type Data = Vec; + type EthereumAddress = (); + type Keypair = namada_core::types::key::common::SecretKey; + type NativeAddress = Address; + type PublicKey = namada_core::types::key::common::PublicKey; + type TendermintAddress = (); + type TransferSource = namada_core::types::masp::TransferSource; + type TransferTarget = namada_core::types::masp::TransferTarget; + type ViewingKey = namada_core::types::masp::ExtendedViewingKey; +} + +/// Common query arguments +#[derive(Clone, Debug)] +pub struct Query { + /// The address of the ledger node as host:port + pub ledger_address: C::TendermintAddress, +} + +/// Transaction associated results arguments +#[derive(Clone, Debug)] +pub struct QueryResult { + /// Common query args + pub query: Query, + /// Hash of transaction to lookup + pub tx_hash: String, +} + +/// Custom transaction arguments +#[derive(Clone, Debug)] +pub struct TxCustom { + /// Common tx arguments + pub tx: Tx, + /// Path to the tx WASM code file + pub code_path: Option, + /// Path to the data file + pub data_path: Option, + /// Path to the serialized transaction + pub serialized_tx: Option, + /// The address that correspond to the signatures/signing-keys + pub owner: C::Address, +} + +impl TxBuilder for TxCustom { + fn tx(self, func: F) -> Self + where + F: FnOnce(Tx) -> Tx, + { + TxCustom { + tx: func(self.tx), + ..self + } + } +} + +impl TxCustom { + /// Path to the tx WASM code file + pub fn code_path(self, code_path: PathBuf) -> Self { + Self { + code_path: Some(code_path), + ..self + } + } + + /// Path to the data file + pub fn data_path(self, data_path: C::Data) -> Self { + Self { + data_path: Some(data_path), + ..self + } + } + + /// Path to the serialized transaction + pub fn serialized_tx(self, serialized_tx: C::Data) -> Self { + Self { + serialized_tx: Some(serialized_tx), + ..self + } + } + + /// The address that correspond to the signatures/signing-keys + pub fn owner(self, owner: C::Address) -> Self { + Self { owner, ..self } + } +} + +impl TxCustom { + /// Build a transaction from this builder + pub async fn build<'a>( + &self, + context: &impl Namada<'a>, + ) -> crate::error::Result<(crate::proto::Tx, SigningTxData, Option)> + { + tx::build_custom(context, self).await + } +} + +/// An amount read in by the cli +#[derive(Copy, Clone, Debug)] +pub enum InputAmount { + /// An amount whose representation has been validated + /// against the allowed representation in storage + Validated(token::DenominatedAmount), + /// The parsed amount read in from the cli. It has + /// not yet been validated against the allowed + /// representation in storage. + Unvalidated(token::DenominatedAmount), +} + +impl std::str::FromStr for InputAmount { + type Err = ::Err; + + fn from_str(s: &str) -> Result { + token::DenominatedAmount::from_str(s).map(InputAmount::Unvalidated) + } +} + +impl From for InputAmount { + fn from(amt: token::DenominatedAmount) -> Self { + InputAmount::Unvalidated(amt) + } +} + +/// Transfer transaction arguments +#[derive(Clone, Debug)] +pub struct TxTransfer { + /// Common tx arguments + pub tx: Tx, + /// Transfer source address + pub source: C::TransferSource, + /// Transfer target address + pub target: C::TransferTarget, + /// Transferred token address + pub token: C::Address, + /// Transferred token amount + pub amount: InputAmount, + /// Native token address + pub native_token: C::NativeAddress, + /// Path to the TX WASM code file + pub tx_code_path: PathBuf, +} + +impl TxBuilder for TxTransfer { + fn tx(self, func: F) -> Self + where + F: FnOnce(Tx) -> Tx, + { + TxTransfer { + tx: func(self.tx), + ..self + } + } +} + +impl TxTransfer { + /// Transfer source address + pub fn source(self, source: C::TransferSource) -> Self { + Self { source, ..self } + } + + /// Transfer target address + pub fn receiver(self, target: C::TransferTarget) -> Self { + Self { target, ..self } + } + + /// Transferred token address + pub fn token(self, token: C::Address) -> Self { + Self { token, ..self } + } + + /// Transferred token amount + pub fn amount(self, amount: InputAmount) -> Self { + Self { amount, ..self } + } + + /// Native token address + pub fn native_token(self, native_token: C::NativeAddress) -> Self { + Self { + native_token, + ..self + } + } + + /// Path to the TX WASM code file + pub fn tx_code_path(self, tx_code_path: PathBuf) -> Self { + Self { + tx_code_path, + ..self + } + } +} + +impl TxTransfer { + /// Build a transaction from this builder + pub async fn build<'a>( + &mut self, + context: &impl Namada<'a>, + ) -> crate::error::Result<(crate::proto::Tx, SigningTxData, Option)> + { + tx::build_transfer(context, self).await + } +} + +/// IBC transfer transaction arguments +#[derive(Clone, Debug)] +pub struct TxIbcTransfer { + /// Common tx arguments + pub tx: Tx, + /// Transfer source address + pub source: C::Address, + /// Transfer target address + pub receiver: String, + /// Transferred token address + pub token: C::Address, + /// Transferred token amount + pub amount: InputAmount, + /// Port ID + pub port_id: PortId, + /// Channel ID + pub channel_id: ChannelId, + /// Timeout height of the destination chain + pub timeout_height: Option, + /// Timeout timestamp offset + pub timeout_sec_offset: Option, + /// Memo + pub memo: Option, + /// Path to the TX WASM code file + pub tx_code_path: PathBuf, +} + +impl TxBuilder for TxIbcTransfer { + fn tx(self, func: F) -> Self + where + F: FnOnce(Tx) -> Tx, + { + TxIbcTransfer { + tx: func(self.tx), + ..self + } + } +} + +impl TxIbcTransfer { + /// Transfer source address + pub fn source(self, source: C::Address) -> Self { + Self { source, ..self } + } + + /// Transfer target address + pub fn receiver(self, receiver: String) -> Self { + Self { receiver, ..self } + } + + /// Transferred token address + pub fn token(self, token: C::Address) -> Self { + Self { token, ..self } + } + + /// Transferred token amount + pub fn amount(self, amount: InputAmount) -> Self { + Self { amount, ..self } + } + + /// Port ID + pub fn port_id(self, port_id: PortId) -> Self { + Self { port_id, ..self } + } + + /// Channel ID + pub fn channel_id(self, channel_id: ChannelId) -> Self { + Self { channel_id, ..self } + } + + /// Timeout height of the destination chain + pub fn timeout_height(self, timeout_height: u64) -> Self { + Self { + timeout_height: Some(timeout_height), + ..self + } + } + + /// Timeout timestamp offset + pub fn timeout_sec_offset(self, timeout_sec_offset: u64) -> Self { + Self { + timeout_sec_offset: Some(timeout_sec_offset), + ..self + } + } + + /// Memo + pub fn memo(self, memo: String) -> Self { + Self { + memo: Some(memo), + ..self + } + } + + /// Path to the TX WASM code file + pub fn tx_code_path(self, tx_code_path: PathBuf) -> Self { + Self { + tx_code_path, + ..self + } + } +} + +impl TxIbcTransfer { + /// Build a transaction from this builder + pub async fn build<'a>( + &self, + context: &impl Namada<'a>, + ) -> crate::error::Result<(crate::proto::Tx, SigningTxData, Option)> + { + tx::build_ibc_transfer(context, self).await + } +} + +/// Transaction to initialize create a new proposal +#[derive(Clone, Debug)] +pub struct InitProposal { + /// Common tx arguments + pub tx: Tx, + /// The proposal data + pub proposal_data: C::Data, + /// Native token address + pub native_token: C::NativeAddress, + /// Flag if proposal should be run offline + pub is_offline: bool, + /// Flag if proposal is of type Pgf stewards + pub is_pgf_stewards: bool, + /// Flag if proposal is of type Pgf funding + pub is_pgf_funding: bool, + /// Path to the tx WASM file + pub tx_code_path: PathBuf, +} + +impl TxBuilder for InitProposal { + fn tx(self, func: F) -> Self + where + F: FnOnce(Tx) -> Tx, + { + InitProposal { + tx: func(self.tx), + ..self + } + } +} + +impl InitProposal { + /// The proposal data + pub fn proposal_data(self, proposal_data: C::Data) -> Self { + Self { + proposal_data, + ..self + } + } + + /// Native token address + pub fn native_token(self, native_token: C::NativeAddress) -> Self { + Self { + native_token, + ..self + } + } + + /// Flag if proposal should be run offline + pub fn is_offline(self, is_offline: bool) -> Self { + Self { is_offline, ..self } + } + + /// Flag if proposal is of type Pgf stewards + pub fn is_pgf_stewards(self, is_pgf_stewards: bool) -> Self { + Self { + is_pgf_stewards, + ..self + } + } + + /// Flag if proposal is of type Pgf funding + pub fn is_pgf_funding(self, is_pgf_funding: bool) -> Self { + Self { + is_pgf_funding, + ..self + } + } + + /// Path to the tx WASM file + pub fn tx_code_path(self, tx_code_path: PathBuf) -> Self { + Self { + tx_code_path, + ..self + } + } +} + +impl InitProposal { + /// Build a transaction from this builder + pub async fn build<'a>( + &self, + context: &impl Namada<'a>, + ) -> crate::error::Result<(crate::proto::Tx, SigningTxData, Option)> + { + let current_epoch = rpc::query_epoch(context.client()).await?; + let governance_parameters = + rpc::query_governance_parameters(context.client()).await; + + if self.is_pgf_funding { + let proposal = PgfFundingProposal::try_from( + self.proposal_data.as_ref(), + ) + .map_err(|e| { + crate::error::TxError::FailedGovernaneProposalDeserialize( + e.to_string(), + ) + })? + .validate(&governance_parameters, current_epoch, self.tx.force) + .map_err(|e| { + crate::error::TxError::InvalidProposal(e.to_string()) + })?; + + tx::build_pgf_funding_proposal(context, self, proposal).await + } else if self.is_pgf_stewards { + let proposal = PgfStewardProposal::try_from( + self.proposal_data.as_ref(), + ) + .map_err(|e| { + crate::error::TxError::FailedGovernaneProposalDeserialize( + e.to_string(), + ) + })?; + let nam_address = context.native_token(); + let author_balance = rpc::get_token_balance( + context.client(), + &nam_address, + &proposal.proposal.author, + ) + .await?; + let proposal = proposal + .validate( + &governance_parameters, + current_epoch, + author_balance, + self.tx.force, + ) + .map_err(|e| { + crate::error::TxError::InvalidProposal(e.to_string()) + })?; + + tx::build_pgf_stewards_proposal(context, self, proposal).await + } else { + let proposal = DefaultProposal::try_from( + self.proposal_data.as_ref(), + ) + .map_err(|e| { + crate::error::TxError::FailedGovernaneProposalDeserialize( + e.to_string(), + ) + })?; + let nam_address = context.native_token(); + let author_balance = rpc::get_token_balance( + context.client(), + &nam_address, + &proposal.proposal.author, + ) + .await?; + let proposal = proposal + .validate( + &governance_parameters, + current_epoch, + author_balance, + self.tx.force, + ) + .map_err(|e| { + crate::error::TxError::InvalidProposal(e.to_string()) + })?; + tx::build_default_proposal(context, self, proposal).await + } + } +} + +/// Transaction to vote on a proposal +#[derive(Clone, Debug)] +pub struct VoteProposal { + /// Common tx arguments + pub tx: Tx, + /// Proposal id + pub proposal_id: Option, + /// The vote + pub vote: String, + /// The address of the voter + pub voter: C::Address, + /// Flag if proposal vote should be run offline + pub is_offline: bool, + /// The proposal file path + pub proposal_data: Option, + /// Path to the TX WASM code file + pub tx_code_path: PathBuf, +} + +impl TxBuilder for VoteProposal { + fn tx(self, func: F) -> Self + where + F: FnOnce(Tx) -> Tx, + { + VoteProposal { + tx: func(self.tx), + ..self + } + } +} + +impl VoteProposal { + /// Proposal id + pub fn proposal_id(self, proposal_id: u64) -> Self { + Self { + proposal_id: Some(proposal_id), + ..self + } + } + + /// The vote + pub fn vote(self, vote: String) -> Self { + Self { vote, ..self } + } + + /// The address of the voter + pub fn voter(self, voter: C::Address) -> Self { + Self { voter, ..self } + } + + /// Flag if proposal vote should be run offline + pub fn is_offline(self, is_offline: bool) -> Self { + Self { is_offline, ..self } + } + + /// The proposal file path + pub fn proposal_data(self, proposal_data: C::Data) -> Self { + Self { + proposal_data: Some(proposal_data), + ..self + } + } + + /// Path to the TX WASM code file + pub fn tx_code_path(self, tx_code_path: PathBuf) -> Self { + Self { + tx_code_path, + ..self + } + } +} + +impl VoteProposal { + /// Build a transaction from this builder + pub async fn build<'a>( + &self, + context: &impl Namada<'a>, + ) -> crate::error::Result<(crate::proto::Tx, SigningTxData, Option)> + { + let current_epoch = rpc::query_epoch(context.client()).await?; + tx::build_vote_proposal(context, self, current_epoch).await + } +} + +/// Transaction to initialize a new account +#[derive(Clone, Debug)] +pub struct TxInitAccount { + /// Common tx arguments + pub tx: Tx, + /// Path to the VP WASM code file for the new account + pub vp_code_path: PathBuf, + /// Path to the TX WASM code file + pub tx_code_path: PathBuf, + /// Public key for the new account + pub public_keys: Vec, + /// The account multisignature threshold + pub threshold: Option, +} + +/// Transaction to initialize a new account +#[derive(Clone, Debug)] +pub struct TxInitValidator { + /// Common tx arguments + pub tx: Tx, + /// Signature scheme + pub scheme: SchemeType, + /// Account keys + pub account_keys: Vec, + /// The account multisignature threshold + pub threshold: Option, + /// Consensus key + pub consensus_key: Option, + /// Ethereum cold key + pub eth_cold_key: Option, + /// Ethereum hot key + pub eth_hot_key: Option, + /// Protocol key + pub protocol_key: Option, + /// Commission rate + pub commission_rate: Dec, + /// Maximum commission rate change + pub max_commission_rate_change: Dec, + /// Path to the VP WASM code file + pub validator_vp_code_path: PathBuf, + /// Path to the TX WASM code file + pub tx_code_path: PathBuf, + /// Don't encrypt the keypair + pub unsafe_dont_encrypt: bool, +} + +/// Transaction to update a VP arguments +#[derive(Clone, Debug)] +pub struct TxUpdateAccount { + /// Common tx arguments + pub tx: Tx, + /// Path to the VP WASM code file + pub vp_code_path: Option, + /// Path to the TX WASM code file + pub tx_code_path: PathBuf, + /// Address of the account whose VP is to be updated + pub addr: C::Address, + /// Public keys + pub public_keys: Vec, + /// The account threshold + pub threshold: Option, +} + +impl TxBuilder for TxUpdateAccount { + fn tx(self, func: F) -> Self + where + F: FnOnce(Tx) -> Tx, + { + TxUpdateAccount { + tx: func(self.tx), + ..self + } + } +} + +impl TxUpdateAccount { + /// Path to the VP WASM code file + pub fn vp_code_path(self, vp_code_path: PathBuf) -> Self { + Self { + vp_code_path: Some(vp_code_path), + ..self + } + } + + /// Path to the TX WASM code file + pub fn tx_code_path(self, tx_code_path: PathBuf) -> Self { + Self { + tx_code_path, + ..self + } + } + + /// Address of the account whose VP is to be updated + pub fn addr(self, addr: C::Address) -> Self { + Self { addr, ..self } + } + + /// Public keys + pub fn public_keys(self, public_keys: Vec) -> Self { + Self { + public_keys, + ..self + } + } + + /// The account threshold + pub fn threshold(self, threshold: u8) -> Self { + Self { + threshold: Some(threshold), + ..self + } + } +} + +impl TxUpdateAccount { + /// Build a transaction from this builder + pub async fn build<'a>( + &self, + context: &impl Namada<'a>, + ) -> crate::error::Result<(crate::proto::Tx, SigningTxData, Option)> + { + tx::build_update_account(context, self).await + } +} + +/// Bond arguments +#[derive(Clone, Debug)] +pub struct Bond { + /// Common tx arguments + pub tx: Tx, + /// Validator address + pub validator: C::Address, + /// Amount of tokens to stake in a bond + pub amount: token::Amount, + /// Source address for delegations. For self-bonds, the validator is + /// also the source. + pub source: Option, + /// Native token address + pub native_token: C::NativeAddress, + /// Path to the TX WASM code file + pub tx_code_path: PathBuf, +} + +impl TxBuilder for Bond { + fn tx(self, func: F) -> Self + where + F: FnOnce(Tx) -> Tx, + { + Bond { + tx: func(self.tx), + ..self + } + } +} + +impl Bond { + /// Validator address + pub fn validator(self, validator: C::Address) -> Self { + Self { validator, ..self } + } + + /// Amount of tokens to stake in a bond + pub fn amount(self, amount: token::Amount) -> Self { + Self { amount, ..self } + } + + /// Source address for delegations. For self-bonds, the validator is + /// also the source. + pub fn source(self, source: C::Address) -> Self { + Self { + source: Some(source), + ..self + } + } + + /// Native token address + pub fn native_token(self, native_token: C::NativeAddress) -> Self { + Self { + native_token, + ..self + } + } + + /// Path to the TX WASM code file + pub fn tx_code_path(self, tx_code_path: PathBuf) -> Self { + Self { + tx_code_path, + ..self + } + } +} + +impl Bond { + /// Build a transaction from this builder + pub async fn build<'a>( + &self, + context: &impl Namada<'a>, + ) -> crate::error::Result<(crate::proto::Tx, SigningTxData, Option)> + { + tx::build_bond(context, self).await + } +} + +/// Unbond arguments +#[derive(Clone, Debug)] +pub struct Unbond { + /// Common tx arguments + pub tx: Tx, + /// Validator address + pub validator: C::Address, + /// Amount of tokens to unbond from a bond + pub amount: token::Amount, + /// Source address for unbonding from delegations. For unbonding from + /// self-bonds, the validator is also the source + pub source: Option, + /// Path to the TX WASM code file + pub tx_code_path: PathBuf, +} + +impl Unbond { + /// Build a transaction from this builder + pub async fn build<'a>( + &self, + context: &impl Namada<'a>, + ) -> crate::error::Result<( + crate::proto::Tx, + SigningTxData, + Option, + Option<(Epoch, token::Amount)>, + )> { + tx::build_unbond(context, self).await + } +} + +impl TxBuilder for Unbond { + fn tx(self, func: F) -> Self + where + F: FnOnce(Tx) -> Tx, + { + Unbond { + tx: func(self.tx), + ..self + } + } +} + +impl Unbond { + /// Validator address + pub fn validator(self, validator: C::Address) -> Self { + Self { validator, ..self } + } + + /// Amount of tokens to unbond from a bond + pub fn amount(self, amount: token::Amount) -> Self { + Self { amount, ..self } + } + + /// Source address for unbonding from delegations. For unbonding from + /// self-bonds, the validator is also the source + pub fn source(self, source: C::Address) -> Self { + Self { + source: Some(source), + ..self + } + } + + /// Path to the TX WASM code file + pub fn tx_code_path(self, tx_code_path: PathBuf) -> Self { + Self { + tx_code_path, + ..self + } + } +} + +/// Reveal public key +#[derive(Clone, Debug)] +pub struct RevealPk { + /// Common tx arguments + pub tx: Tx, + /// A public key to be revealed on-chain + pub public_key: C::PublicKey, +} + +impl TxBuilder for RevealPk { + fn tx(self, func: F) -> Self + where + F: FnOnce(Tx) -> Tx, + { + RevealPk { + tx: func(self.tx), + ..self + } + } +} + +impl RevealPk { + /// A public key to be revealed on-chain + pub fn public_key(self, public_key: C::PublicKey) -> Self { + Self { public_key, ..self } + } +} + +impl RevealPk { + /// Build a transaction from this builder + pub async fn build<'a>( + &self, + context: &impl Namada<'a>, + ) -> crate::error::Result<(crate::proto::Tx, SigningTxData, Option)> + { + tx::build_reveal_pk(context, &self.tx, &self.public_key).await + } +} + +/// Query proposal +#[derive(Clone, Debug)] +pub struct QueryProposal { + /// Common query args + pub query: Query, + /// Proposal id + pub proposal_id: Option, +} + +/// Query protocol parameters +#[derive(Clone, Debug)] +pub struct QueryProtocolParameters { + /// Common query args + pub query: Query, +} + +/// Query pgf data +#[derive(Clone, Debug)] +pub struct QueryPgf { + /// Common query args + pub query: Query, +} + +/// Withdraw arguments +#[derive(Clone, Debug)] +pub struct Withdraw { + /// Common tx arguments + pub tx: Tx, + /// Validator address + pub validator: C::Address, + /// Source address for withdrawing from delegations. For withdrawing + /// from self-bonds, the validator is also the source + pub source: Option, + /// Path to the TX WASM code file + pub tx_code_path: PathBuf, +} + +impl TxBuilder for Withdraw { + fn tx(self, func: F) -> Self + where + F: FnOnce(Tx) -> Tx, + { + Withdraw { + tx: func(self.tx), + ..self + } + } +} + +impl Withdraw { + /// Validator address + pub fn validator(self, validator: C::Address) -> Self { + Self { validator, ..self } + } + + /// Source address for withdrawing from delegations. For withdrawing + /// from self-bonds, the validator is also the source + pub fn source(self, source: C::Address) -> Self { + Self { + source: Some(source), + ..self + } + } + + /// Path to the TX WASM code file + pub fn tx_code_path(self, tx_code_path: PathBuf) -> Self { + Self { + tx_code_path, + ..self + } + } +} + +impl Withdraw { + /// Build a transaction from this builder + pub async fn build<'a>( + &self, + context: &impl Namada<'a>, + ) -> crate::error::Result<(crate::proto::Tx, SigningTxData, Option)> + { + tx::build_withdraw(context, self).await + } +} + +/// Query asset conversions +#[derive(Clone, Debug)] +pub struct QueryConversions { + /// Common query args + pub query: Query, + /// Address of a token + pub token: Option, + /// Epoch of the asset + pub epoch: Option, +} + +/// Query token balance(s) +#[derive(Clone, Debug)] +pub struct QueryAccount { + /// Common query args + pub query: Query, + /// Address of an owner + pub owner: C::Address, +} + +/// Query token balance(s) +#[derive(Clone, Debug)] +pub struct QueryBalance { + /// Common query args + pub query: Query, + /// Address of an owner + pub owner: Option, + /// Address of a token + pub token: Option, + /// Whether not to convert balances + pub no_conversions: bool, +} + +/// Query historical transfer(s) +#[derive(Clone, Debug)] +pub struct QueryTransfers { + /// Common query args + pub query: Query, + /// Address of an owner + pub owner: Option, + /// Address of a token + pub token: Option, +} + +/// Query PoS bond(s) +#[derive(Clone, Debug)] +pub struct QueryBonds { + /// Common query args + pub query: Query, + /// Address of an owner + pub owner: Option, + /// Address of a validator + pub validator: Option, +} + +/// Query PoS bonded stake +#[derive(Clone, Debug)] +pub struct QueryBondedStake { + /// Common query args + pub query: Query, + /// Address of a validator + pub validator: Option, + /// Epoch in which to find bonded stake + pub epoch: Option, +} + +/// Query the state of a validator (its validator set or if it is jailed) +#[derive(Clone, Debug)] +pub struct QueryValidatorState { + /// Common query args + pub query: Query, + /// Address of a validator + pub validator: C::Address, + /// Epoch in which to find the validator state + pub epoch: Option, +} + +#[derive(Clone, Debug)] +/// Commission rate change args +pub struct CommissionRateChange { + /// Common tx arguments + pub tx: Tx, + /// Validator address (should be self) + pub validator: C::Address, + /// Value to which the tx changes the commission rate + pub rate: Dec, + /// Path to the TX WASM code file + pub tx_code_path: PathBuf, +} + +impl TxBuilder for CommissionRateChange { + fn tx(self, func: F) -> Self + where + F: FnOnce(Tx) -> Tx, + { + CommissionRateChange { + tx: func(self.tx), + ..self + } + } +} + +impl CommissionRateChange { + /// Validator address (should be self) + pub fn validator(self, validator: C::Address) -> Self { + Self { validator, ..self } + } + + /// Value to which the tx changes the commission rate + pub fn rate(self, rate: Dec) -> Self { + Self { rate, ..self } + } + + /// Path to the TX WASM code file + pub fn tx_code_path(self, tx_code_path: PathBuf) -> Self { + Self { + tx_code_path, + ..self + } + } +} + +impl CommissionRateChange { + /// Build a transaction from this builder + pub async fn build<'a>( + &self, + context: &impl Namada<'a>, + ) -> crate::error::Result<(crate::proto::Tx, SigningTxData, Option)> + { + tx::build_validator_commission_change(context, self).await + } +} + +#[derive(Clone, Debug)] +/// Commission rate change args +pub struct UpdateStewardCommission { + /// Common tx arguments + pub tx: Tx, + /// Steward address + pub steward: C::Address, + /// Value to which the tx changes the commission rate + pub commission: C::Data, + /// Path to the TX WASM code file + pub tx_code_path: PathBuf, +} + +impl TxBuilder for UpdateStewardCommission { + fn tx(self, func: F) -> Self + where + F: FnOnce(Tx) -> Tx, + { + UpdateStewardCommission { + tx: func(self.tx), + ..self + } + } +} + +impl UpdateStewardCommission { + /// Steward address + pub fn steward(self, steward: C::Address) -> Self { + Self { steward, ..self } + } + + /// Value to which the tx changes the commission rate + pub fn commission(self, commission: C::Data) -> Self { + Self { commission, ..self } + } + + /// Path to the TX WASM code file + pub fn tx_code_path(self, tx_code_path: PathBuf) -> Self { + Self { + tx_code_path, + ..self + } + } +} + +impl UpdateStewardCommission { + /// Build a transaction from this builder + pub async fn build<'a>( + &self, + context: &impl Namada<'a>, + ) -> crate::error::Result<(crate::proto::Tx, SigningTxData, Option)> + { + tx::build_update_steward_commission(context, self).await + } +} + +#[derive(Clone, Debug)] +/// Commission rate change args +pub struct ResignSteward { + /// Common tx arguments + pub tx: Tx, + /// Validator address + pub steward: C::Address, + /// Path to the TX WASM code file + pub tx_code_path: PathBuf, +} + +impl TxBuilder for ResignSteward { + fn tx(self, func: F) -> Self + where + F: FnOnce(Tx) -> Tx, + { + ResignSteward { + tx: func(self.tx), + ..self + } + } +} + +impl ResignSteward { + /// Validator address + pub fn steward(self, steward: C::Address) -> Self { + Self { steward, ..self } + } + + /// Path to the TX WASM code file + pub fn tx_code_path(self, tx_code_path: PathBuf) -> Self { + Self { + tx_code_path, + ..self + } + } +} + +impl ResignSteward { + /// Build a transaction from this builder + pub async fn build<'a>( + &self, + context: &impl Namada<'a>, + ) -> crate::error::Result<(crate::proto::Tx, SigningTxData, Option)> + { + tx::build_resign_steward(context, self).await + } +} + +#[derive(Clone, Debug)] +/// Re-activate a jailed validator args +pub struct TxUnjailValidator { + /// Common tx arguments + pub tx: Tx, + /// Validator address (should be self) + pub validator: C::Address, + /// Path to the TX WASM code file + pub tx_code_path: PathBuf, +} + +impl TxBuilder for TxUnjailValidator { + fn tx(self, func: F) -> Self + where + F: FnOnce(Tx) -> Tx, + { + TxUnjailValidator { + tx: func(self.tx), + ..self + } + } +} + +impl TxUnjailValidator { + /// Validator address (should be self) + pub fn validator(self, validator: C::Address) -> Self { + Self { validator, ..self } + } + + /// Path to the TX WASM code file + pub fn tx_code_path(self, tx_code_path: PathBuf) -> Self { + Self { + tx_code_path, + ..self + } + } +} + +impl TxUnjailValidator { + /// Build a transaction from this builder + pub async fn build<'a>( + &self, + context: &impl Namada<'a>, + ) -> crate::error::Result<(crate::proto::Tx, SigningTxData, Option)> + { + tx::build_unjail_validator(context, self).await + } +} + +#[derive(Clone, Debug)] +/// Sign a transaction offline +pub struct SignTx { + /// Common tx arguments + pub tx: Tx, + /// Transaction data + pub tx_data: C::Data, + /// The account address + pub owner: C::Address, +} + +/// Query PoS commission rate +#[derive(Clone, Debug)] +pub struct QueryCommissionRate { + /// Common query args + pub query: Query, + /// Address of a validator + pub validator: C::Address, + /// Epoch in which to find commission rate + pub epoch: Option, +} + +/// Query PoS slashes +#[derive(Clone, Debug)] +pub struct QuerySlashes { + /// Common query args + pub query: Query, + /// Address of a validator + pub validator: Option, +} + +/// Query PoS delegations +#[derive(Clone, Debug)] +pub struct QueryDelegations { + /// Common query args + pub query: Query, + /// Address of an owner + pub owner: C::Address, +} + +/// Query PoS to find a validator +#[derive(Clone, Debug)] +pub struct QueryFindValidator { + /// Common query args + pub query: Query, + /// Tendermint address + pub tm_addr: String, +} + +/// Query the raw bytes of given storage key +#[derive(Clone, Debug)] +pub struct QueryRawBytes { + /// The storage key to query + pub storage_key: storage::Key, + /// Common query args + pub query: Query, +} + +/// Common transaction arguments +#[derive(Clone, Debug)] +pub struct Tx { + /// Simulate applying the transaction + pub dry_run: bool, + /// Simulate applying both the wrapper and inner transactions + pub dry_run_wrapper: bool, + /// Dump the transaction bytes to file + pub dump_tx: bool, + /// The output directory path to where serialize the data + pub output_folder: Option, + /// Submit the transaction even if it doesn't pass client checks + pub force: bool, + /// Do not wait for the transaction to be added to the blockchain + pub broadcast_only: bool, + /// The address of the ledger node as host:port + pub ledger_address: C::TendermintAddress, + /// If any new account is initialized by the tx, use the given alias to + /// save it in the wallet. + pub initialized_account_alias: Option, + /// Whether to force overwrite the above alias, if it is provided, in the + /// wallet. + pub wallet_alias_force: bool, + /// The amount being payed (for gas unit) to include the transaction + pub fee_amount: Option, + /// The fee payer signing key + pub wrapper_fee_payer: Option, + /// The token in which the fee is being paid + pub fee_token: C::Address, + /// The optional spending key for fee unshielding + pub fee_unshield: Option, + /// The max amount of gas used to process tx + pub gas_limit: GasLimit, + /// The optional expiration of the transaction + pub expiration: Option, + /// Generate an ephimeral signing key to be used only once to sign a + /// wrapper tx + pub disposable_signing_key: bool, + /// The chain id for which the transaction is intended + pub chain_id: Option, + /// Sign the tx with the key for the given alias from your wallet + pub signing_keys: Vec, + /// List of signatures to attach to the transaction + pub signatures: Vec, + /// Path to the TX WASM code file to reveal PK + pub tx_reveal_code_path: PathBuf, + /// Sign the tx with the public key for the given alias from your wallet + pub verification_key: Option, + /// Password to decrypt key + pub password: Option>, +} + +/// Builder functions for Tx +pub trait TxBuilder: Sized { + /// Apply the given function to the Tx inside self + fn tx(self, func: F) -> Self + where + F: FnOnce(Tx) -> Tx; + /// Simulate applying the transaction + fn dry_run(self, dry_run: bool) -> Self { + self.tx(|x| Tx { dry_run, ..x }) + } + /// Simulate applying both the wrapper and inner transactions + fn dry_run_wrapper(self, dry_run_wrapper: bool) -> Self { + self.tx(|x| Tx { + dry_run_wrapper, + ..x + }) + } + /// Dump the transaction bytes to file + fn dump_tx(self, dump_tx: bool) -> Self { + self.tx(|x| Tx { dump_tx, ..x }) + } + /// The output directory path to where serialize the data + fn output_folder(self, output_folder: PathBuf) -> Self { + self.tx(|x| Tx { + output_folder: Some(output_folder), + ..x + }) + } + /// Submit the transaction even if it doesn't pass client checks + fn force(self, force: bool) -> Self { + self.tx(|x| Tx { force, ..x }) + } + /// Do not wait for the transaction to be added to the blockchain + fn broadcast_only(self, broadcast_only: bool) -> Self { + self.tx(|x| Tx { + broadcast_only, + ..x + }) + } + /// The address of the ledger node as host:port + fn ledger_address(self, ledger_address: C::TendermintAddress) -> Self { + self.tx(|x| Tx { + ledger_address, + ..x + }) + } + /// If any new account is initialized by the tx, use the given alias to + /// save it in the wallet. + fn initialized_account_alias( + self, + initialized_account_alias: String, + ) -> Self { + self.tx(|x| Tx { + initialized_account_alias: Some(initialized_account_alias), + ..x + }) + } + /// Whether to force overwrite the above alias, if it is provided, in the + /// wallet. + fn wallet_alias_force(self, wallet_alias_force: bool) -> Self { + self.tx(|x| Tx { + wallet_alias_force, + ..x + }) + } + /// The amount being payed (for gas unit) to include the transaction + fn fee_amount(self, fee_amount: InputAmount) -> Self { + self.tx(|x| Tx { + fee_amount: Some(fee_amount), + ..x + }) + } + /// The fee payer signing key + fn wrapper_fee_payer(self, wrapper_fee_payer: C::Keypair) -> Self { + self.tx(|x| Tx { + wrapper_fee_payer: Some(wrapper_fee_payer), + ..x + }) + } + /// The token in which the fee is being paid + fn fee_token(self, fee_token: C::Address) -> Self { + self.tx(|x| Tx { fee_token, ..x }) + } + /// The optional spending key for fee unshielding + fn fee_unshield(self, fee_unshield: C::TransferSource) -> Self { + self.tx(|x| Tx { + fee_unshield: Some(fee_unshield), + ..x + }) + } + /// The max amount of gas used to process tx + fn gas_limit(self, gas_limit: GasLimit) -> Self { + self.tx(|x| Tx { gas_limit, ..x }) + } + /// The optional expiration of the transaction + fn expiration(self, expiration: DateTimeUtc) -> Self { + self.tx(|x| Tx { + expiration: Some(expiration), + ..x + }) + } + /// Generate an ephimeral signing key to be used only once to sign a + /// wrapper tx + fn disposable_signing_key(self, disposable_signing_key: bool) -> Self { + self.tx(|x| Tx { + disposable_signing_key, + ..x + }) + } + /// The chain id for which the transaction is intended + fn chain_id(self, chain_id: ChainId) -> Self { + self.tx(|x| Tx { + chain_id: Some(chain_id), + ..x + }) + } + /// Sign the tx with the key for the given alias from your wallet + fn signing_keys(self, signing_keys: Vec) -> Self { + self.tx(|x| Tx { signing_keys, ..x }) + } + /// List of signatures to attach to the transaction + fn signatures(self, signatures: Vec) -> Self { + self.tx(|x| Tx { signatures, ..x }) + } + /// Path to the TX WASM code file to reveal PK + fn tx_reveal_code_path(self, tx_reveal_code_path: PathBuf) -> Self { + self.tx(|x| Tx { + tx_reveal_code_path, + ..x + }) + } + /// Sign the tx with the public key for the given alias from your wallet + fn verification_key(self, verification_key: C::PublicKey) -> Self { + self.tx(|x| Tx { + verification_key: Some(verification_key), + ..x + }) + } + /// Password to decrypt key + fn password(self, password: Zeroizing) -> Self { + self.tx(|x| Tx { + password: Some(password), + ..x + }) + } +} + +impl TxBuilder for Tx { + fn tx(self, func: F) -> Self + where + F: FnOnce(Tx) -> Tx, + { + func(self) + } +} + +/// MASP add key or address arguments +#[derive(Clone, Debug)] +pub struct MaspAddrKeyAdd { + /// Key alias + pub alias: String, + /// Whether to force overwrite the alias + pub alias_force: bool, + /// Any MASP value + pub value: MaspValue, + /// Don't encrypt the keypair + pub unsafe_dont_encrypt: bool, +} + +/// MASP generate spending key arguments +#[derive(Clone, Debug)] +pub struct MaspSpendKeyGen { + /// Key alias + pub alias: String, + /// Whether to force overwrite the alias + pub alias_force: bool, + /// Don't encrypt the keypair + pub unsafe_dont_encrypt: bool, +} + +/// MASP generate payment address arguments +#[derive(Clone, Debug)] +pub struct MaspPayAddrGen { + /// Key alias + pub alias: String, + /// Whether to force overwrite the alias + pub alias_force: bool, + /// Viewing key + pub viewing_key: C::ViewingKey, + /// Pin + pub pin: bool, +} + +/// Wallet generate key and implicit address arguments +#[derive(Clone, Debug)] +pub struct KeyAndAddressGen { + /// Scheme type + pub scheme: SchemeType, + /// Key alias + pub alias: Option, + /// Whether to force overwrite the alias, if provided + pub alias_force: bool, + /// Don't encrypt the keypair + pub unsafe_dont_encrypt: bool, + /// BIP44 derivation path + pub derivation_path: Option, +} + +/// Wallet restore key and implicit address arguments +#[derive(Clone, Debug)] +pub struct KeyAndAddressRestore { + /// Scheme type + pub scheme: SchemeType, + /// Key alias + pub alias: Option, + /// Whether to force overwrite the alias, if provided + pub alias_force: bool, + /// Don't encrypt the keypair + pub unsafe_dont_encrypt: bool, + /// BIP44 derivation path + pub derivation_path: Option, +} + +/// Wallet key lookup arguments +#[derive(Clone, Debug)] +pub struct KeyFind { + /// Public key to lookup keypair with + pub public_key: Option, + /// Key alias to lookup keypair with + pub alias: Option, + /// Public key hash to lookup keypair with + pub value: Option, + /// Show secret keys to user + pub unsafe_show_secret: bool, +} + +/// Wallet find shielded address or key arguments +#[derive(Clone, Debug)] +pub struct AddrKeyFind { + /// Address/key alias + pub alias: String, + /// Show secret keys to user + pub unsafe_show_secret: bool, +} + +/// Wallet list shielded keys arguments +#[derive(Clone, Debug)] +pub struct MaspKeysList { + /// Don't decrypt spending keys + pub decrypt: bool, + /// Show secret keys to user + pub unsafe_show_secret: bool, +} + +/// Wallet list keys arguments +#[derive(Clone, Debug)] +pub struct KeyList { + /// Don't decrypt keypairs + pub decrypt: bool, + /// Show secret keys to user + pub unsafe_show_secret: bool, +} + +/// Wallet key export arguments +#[derive(Clone, Debug)] +pub struct KeyExport { + /// Key alias + pub alias: String, +} + +/// Wallet address lookup arguments +#[derive(Clone, Debug)] +pub struct AddressOrAliasFind { + /// Alias to find + pub alias: Option, + /// Address to find + pub address: Option
, +} + +/// Wallet address add arguments +#[derive(Clone, Debug)] +pub struct AddressAdd { + /// Address alias + pub alias: String, + /// Whether to force overwrite the alias + pub alias_force: bool, + /// Address to add + pub address: Address, +} + +/// Bridge pool batch recommendation. +#[derive(Clone, Debug)] +pub struct RecommendBatch { + /// The query parameters. + pub query: Query, + /// The maximum amount of gas to spend. + pub max_gas: Option, + /// An optional parameter indicating how much net + /// gas the relayer is willing to pay. + pub gas: Option, + /// Bridge pool recommendations conversion rates table. + pub conversion_table: C::BpConversionTable, +} + +/// A transfer to be added to the Ethereum bridge pool. +#[derive(Clone, Debug)] +pub struct EthereumBridgePool { + /// Whether the transfer is for a NUT. + /// + /// By default, we add wrapped ERC20s onto the + /// Bridge pool. + pub nut: bool, + /// The args for building a tx to the bridge pool + pub tx: Tx, + /// The type of token + pub asset: EthAddress, + /// The recipient address + pub recipient: EthAddress, + /// The sender of the transfer + pub sender: C::Address, + /// The amount to be transferred + pub amount: InputAmount, + /// The amount of gas fees + pub fee_amount: InputAmount, + /// The account of fee payer. + /// + /// If unset, it is the same as the sender. + pub fee_payer: Option, + /// The token in which the gas is being paid + pub fee_token: C::Address, + /// Path to the tx WASM code file + pub code_path: PathBuf, +} + +impl TxBuilder for EthereumBridgePool { + fn tx(self, func: F) -> Self + where + F: FnOnce(Tx) -> Tx, + { + EthereumBridgePool { + tx: func(self.tx), + ..self + } + } +} + +impl EthereumBridgePool { + /// Whether the transfer is for a NUT. + /// + /// By default, we add wrapped ERC20s onto the + /// Bridge pool. + pub fn nut(self, nut: bool) -> Self { + Self { nut, ..self } + } + + /// The type of token + pub fn asset(self, asset: EthAddress) -> Self { + Self { asset, ..self } + } + + /// The recipient address + pub fn recipient(self, recipient: EthAddress) -> Self { + Self { recipient, ..self } + } + + /// The sender of the transfer + pub fn sender(self, sender: C::Address) -> Self { + Self { sender, ..self } + } + + /// The amount to be transferred + pub fn amount(self, amount: InputAmount) -> Self { + Self { amount, ..self } + } + + /// The amount of gas fees + pub fn fee_amount(self, fee_amount: InputAmount) -> Self { + Self { fee_amount, ..self } + } + + /// The account of fee payer. + /// + /// If unset, it is the same as the sender. + pub fn fee_payer(self, fee_payer: C::Address) -> Self { + Self { + fee_payer: Some(fee_payer), + ..self + } + } + + /// The token in which the gas is being paid + pub fn fee_token(self, fee_token: C::Address) -> Self { + Self { fee_token, ..self } + } + + /// Path to the tx WASM code file + pub fn code_path(self, code_path: PathBuf) -> Self { + Self { code_path, ..self } + } +} + +impl EthereumBridgePool { + /// Build a transaction from this builder + pub async fn build<'a>( + self, + context: &impl Namada<'a>, + ) -> crate::error::Result<(crate::proto::Tx, SigningTxData, Option)> + { + bridge_pool::build_bridge_pool_tx(context, self).await + } +} + +/// Bridge pool proof arguments. +#[derive(Debug, Clone)] +pub struct BridgePoolProof { + /// The query parameters. + pub query: Query, + /// The keccak hashes of transfers to + /// acquire a proof of. + pub transfers: Vec, + /// The address of the node responsible for relaying + /// the transfers. + /// + /// This node will receive the gas fees escrowed in + /// the Bridge pool, to compensate the Ethereum relay + /// procedure. + pub relayer: Address, +} + +/// Arguments to an Ethereum Bridge pool relay operation. +#[derive(Debug, Clone)] +pub struct RelayBridgePoolProof { + /// The query parameters. + pub query: Query, + /// The hashes of the transfers to be relayed + pub transfers: Vec, + /// The Namada address for receiving fees for relaying + pub relayer: Address, + /// The number of confirmations to wait for on Ethereum + pub confirmations: u64, + /// The Ethereum RPC endpoint. + pub eth_rpc_endpoint: C::EthereumAddress, + /// The Ethereum gas that can be spent during + /// the relay call. + pub gas: Option, + /// The price of Ethereum gas, during the + /// relay call. + pub gas_price: Option, + /// The address of the Ethereum wallet to pay the gas fees. + /// If unset, the default wallet is used. + pub eth_addr: Option, + /// Synchronize with the network, or exit immediately, + /// if the Ethereum node has fallen behind. + pub sync: bool, + /// Safe mode overrides keyboard interrupt signals, to ensure + /// Ethereum transfers aren't canceled midway through. + pub safe_mode: bool, +} + +/// Bridge validator set arguments. +#[derive(Debug, Clone)] +pub struct BridgeValidatorSet { + /// The query parameters. + pub query: Query, + /// The epoch to query. + pub epoch: Option, +} + +/// Governance validator set arguments. +#[derive(Debug, Clone)] +pub struct GovernanceValidatorSet { + /// The query parameters. + pub query: Query, + /// The epoch to query. + pub epoch: Option, +} + +/// Validator set proof arguments. +#[derive(Debug, Clone)] +pub struct ValidatorSetProof { + /// The query parameters. + pub query: Query, + /// The epoch to query. + pub epoch: Option, +} + +/// Validator set update relayer arguments. +#[derive(Debug, Clone)] +pub struct ValidatorSetUpdateRelay { + /// Run in daemon mode, which will continuously + /// perform validator set updates. + pub daemon: bool, + /// The query parameters. + pub query: Query, + /// The number of block confirmations on Ethereum. + pub confirmations: u64, + /// The Ethereum RPC endpoint. + pub eth_rpc_endpoint: C::EthereumAddress, + /// The epoch of the validator set to relay. + pub epoch: Option, + /// The Ethereum gas that can be spent during + /// the relay call. + pub gas: Option, + /// The price of Ethereum gas, during the + /// relay call. + pub gas_price: Option, + /// The address of the Ethereum wallet to pay the gas fees. + /// If unset, the default wallet is used. + pub eth_addr: Option, + /// Synchronize with the network, or exit immediately, + /// if the Ethereum node has fallen behind. + pub sync: bool, + /// The amount of time to sleep between failed + /// daemon mode relays. + pub retry_dur: Option, + /// The amount of time to sleep between successful + /// daemon mode relays. + pub success_dur: Option, + /// Safe mode overrides keyboard interrupt signals, to ensure + /// Ethereum transfers aren't canceled midway through. + pub safe_mode: bool, +} diff --git a/shared/src/types/control_flow.rs b/sdk/src/control_flow/mod.rs similarity index 100% rename from shared/src/types/control_flow.rs rename to sdk/src/control_flow/mod.rs diff --git a/shared/src/types/control_flow/time.rs b/sdk/src/control_flow/time.rs similarity index 100% rename from shared/src/types/control_flow/time.rs rename to sdk/src/control_flow/time.rs diff --git a/shared/src/sdk/error.rs b/sdk/src/error.rs similarity index 97% rename from shared/src/sdk/error.rs rename to sdk/src/error.rs index b103a9523f..a3091a3d7c 100644 --- a/shared/src/sdk/error.rs +++ b/sdk/src/error.rs @@ -9,8 +9,7 @@ use prost::EncodeError; use tendermint_rpc::Error as RpcError; use thiserror::Error; -use crate::sdk::error::Error::Pinned; -use crate::vm::WasmValidationError; +use crate::error::Error::Pinned; /// The standard Result type that most code ought to return pub type Result = std::result::Result; @@ -222,9 +221,6 @@ pub enum TxError { /// Error in the fee unshielding transaction #[error("Error in fee unshielding: {0}")] FeeUnshieldingError(String), - /// Wasm validation failed - #[error("Validity predicate code validation failed with {0}")] - WasmValidationFailure(WasmValidationError), /// Encoding transaction failure #[error("Encoding tx data, {0}, shouldn't fail")] EncodeTxFailure(String), diff --git a/shared/src/ledger/eth_bridge/bridge_pool.rs b/sdk/src/eth_bridge/bridge_pool.rs similarity index 86% rename from shared/src/ledger/eth_bridge/bridge_pool.rs rename to sdk/src/eth_bridge/bridge_pool.rs index b9573cab97..da80bdf41f 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool.rs +++ b/sdk/src/eth_bridge/bridge_pool.rs @@ -9,49 +9,37 @@ use borsh::BorshSerialize; use ethbridge_bridge_contract::Bridge; use ethers::providers::Middleware; use namada_core::ledger::eth_bridge::storage::wrapped_erc20s; -use namada_core::types::key::common; +use namada_core::types::address::Address; +use namada_core::types::eth_abi::Encode; +use namada_core::types::eth_bridge_pool::{ + GasFee, PendingTransfer, TransferToEthereum, TransferToEthereumKind, +}; +use namada_core::types::keccak::KeccakHash; use namada_core::types::storage::Epoch; +use namada_core::types::token::{Amount, DenominatedAmount}; +use namada_core::types::voting_power::FractionalVotingPower; use owo_colors::OwoColorize; use serde::{Deserialize, Serialize}; use super::{block_on_eth_sync, eth_sync_or_exit, BlockOnEthSync}; +use crate::control_flow::time::{Duration, Instant}; +use crate::control_flow::{self, install_shutdown_signal, Halt, TryHalt}; +use crate::error::Error; use crate::eth_bridge::ethers::abi::AbiDecode; -use crate::ledger::queries::{ +use crate::io::Io; +use crate::proto::Tx; +use crate::queries::{ Client, GenBridgePoolProofReq, GenBridgePoolProofRsp, TransferToErcArgs, RPC, }; -use crate::proto::Tx; -use crate::sdk::args; -use crate::sdk::error::Error; -use crate::sdk::masp::{ShieldedContext, ShieldedUtils}; -use crate::sdk::rpc::{query_wasm_code_hash, validate_amount}; -use crate::sdk::tx::prepare_tx; -use crate::sdk::wallet::{Wallet, WalletUtils}; -use crate::types::address::Address; -use crate::types::control_flow::time::{Duration, Instant}; -use crate::types::control_flow::{ - self, install_shutdown_signal, Halt, TryHalt, -}; -use crate::types::eth_abi::Encode; -use crate::types::eth_bridge_pool::{ - GasFee, PendingTransfer, TransferToEthereum, TransferToEthereumKind, -}; -use crate::types::io::Io; -use crate::types::keccak::KeccakHash; -use crate::types::token::{Amount, DenominatedAmount}; -use crate::types::voting_power::FractionalVotingPower; -use crate::{display, display_line}; +use crate::rpc::{query_wasm_code_hash, validate_amount}; +use crate::signing::aux_signing_data; +use crate::tx::prepare_tx; +use crate::{args, display, display_line, Namada, SigningTxData}; /// Craft a transaction that adds a transfer to the Ethereum bridge pool. -pub async fn build_bridge_pool_tx< - C: crate::ledger::queries::Client + Sync, - U: WalletUtils, - V: ShieldedUtils, - IO: Io, ->( - client: &C, - wallet: &mut Wallet, - shielded: &mut ShieldedContext, +pub async fn build_bridge_pool_tx<'a>( + context: &impl Namada<'a>, args::EthereumBridgePool { tx: tx_args, nut, @@ -64,11 +52,18 @@ pub async fn build_bridge_pool_tx< fee_token, code_path, }: args::EthereumBridgePool, - wrapper_fee_payer: common::PublicKey, -) -> Result<(Tx, Option), Error> { +) -> Result<(Tx, SigningTxData, Option), Error> { + let default_signer = Some(sender.clone()); + let signing_data = aux_signing_data( + context, + &tx_args, + Some(sender.clone()), + default_signer, + ) + .await?; let fee_payer = fee_payer.unwrap_or_else(|| sender.clone()); - let DenominatedAmount { amount, .. } = validate_amount::<_, IO>( - client, + let DenominatedAmount { amount, .. } = validate_amount( + context, amount, &wrapped_erc20s::token(&asset), tx_args.force, @@ -77,7 +72,7 @@ pub async fn build_bridge_pool_tx< .map_err(|e| Error::Other(format!("Failed to validate amount. {}", e)))?; let DenominatedAmount { amount: fee_amount, .. - } = validate_amount::<_, IO>(client, fee_amount, &fee_token, tx_args.force) + } = validate_amount(context, fee_amount, &fee_token, tx_args.force) .await .map_err(|e| { Error::Other(format!( @@ -105,7 +100,7 @@ pub async fn build_bridge_pool_tx< }; let tx_code_hash = - query_wasm_code_hash::<_, IO>(client, code_path.to_str().unwrap()) + query_wasm_code_hash(context, code_path.to_str().unwrap()) .await .unwrap(); @@ -115,18 +110,16 @@ pub async fn build_bridge_pool_tx< // TODO(namada#1800): validate the tx on the client side - let epoch = prepare_tx::( - client, - wallet, - shielded, + let epoch = prepare_tx( + context, &tx_args, &mut tx, - wrapper_fee_payer, + signing_data.fee_payer.clone(), None, ) .await?; - Ok((tx, epoch)) + Ok((tx, signing_data, epoch)) } /// A json serializable representation of the Ethereum @@ -138,10 +131,10 @@ struct BridgePoolResponse { /// Query the contents of the Ethereum bridge pool. /// Prints out a json payload. -pub async fn query_bridge_pool(client: &C) -where - C: Client + Sync, -{ +pub async fn query_bridge_pool<'a>( + client: &(impl Client + Sync), + io: &impl Io, +) { let response: Vec = RPC .shell() .eth_bridge() @@ -153,24 +146,22 @@ where .map(|transfer| (transfer.keccak256().to_string(), transfer)) .collect(); if pool_contents.is_empty() { - display_line!(IO, "Bridge pool is empty."); + display_line!(io, "Bridge pool is empty."); return; } let contents = BridgePoolResponse { bridge_pool_contents: pool_contents, }; - display_line!(IO, "{}", serde_json::to_string_pretty(&contents).unwrap()); + display_line!(io, "{}", serde_json::to_string_pretty(&contents).unwrap()); } /// Query the contents of the Ethereum bridge pool that /// is covered by the latest signed root. /// Prints out a json payload. -pub async fn query_signed_bridge_pool( - client: &C, -) -> Halt> -where - C: Client + Sync, -{ +pub async fn query_signed_bridge_pool<'a>( + client: &(impl Client + Sync), + io: &impl Io, +) -> Halt> { let response: Vec = RPC .shell() .eth_bridge() @@ -182,13 +173,13 @@ where .map(|transfer| (transfer.keccak256().to_string(), transfer)) .collect(); if pool_contents.is_empty() { - display_line!(IO, "Bridge pool is empty."); + display_line!(io, "Bridge pool is empty."); return control_flow::halt(); } let contents = BridgePoolResponse { bridge_pool_contents: pool_contents.clone(), }; - display_line!(IO, "{}", serde_json::to_string_pretty(&contents).unwrap()); + display_line!(io, "{}", serde_json::to_string_pretty(&contents).unwrap()); control_flow::proceed(pool_contents) } @@ -197,28 +188,26 @@ where /// backing each `TransferToEthereum` event. /// /// Prints a json payload. -pub async fn query_relay_progress(client: &C) -where - C: Client + Sync, -{ +pub async fn query_relay_progress<'a>( + client: &(impl Client + Sync), + io: &impl Io, +) { let resp = RPC .shell() .eth_bridge() .transfer_to_ethereum_progress(client) .await .unwrap(); - display_line!(IO, "{}", serde_json::to_string_pretty(&resp).unwrap()); + display_line!(io, "{}", serde_json::to_string_pretty(&resp).unwrap()); } /// Internal methdod to construct a proof that a set of transfers are in the /// bridge pool. -async fn construct_bridge_pool_proof( - client: &C, +async fn construct_bridge_pool_proof<'a>( + client: &(impl Client + Sync), + io: &impl Io, args: GenBridgePoolProofReq<'_, '_>, -) -> Halt -where - C: Client + Sync, -{ +) -> Halt { let in_progress = RPC .shell() .eth_bridge() @@ -243,19 +232,19 @@ where let warning = warning.bold(); let warning = warning.blink(); display_line!( - IO, + io, "{warning}: The following hashes correspond to transfers that \ have surpassed the security threshold in Namada, therefore have \ likely been relayed to Ethereum, but do not yet have a quorum of \ validator signatures behind them in Namada; thus they are still \ in the Bridge pool:\n{warnings:?}", ); - display!(IO, "\nDo you wish to proceed? (y/n): "); - IO::flush(); + display!(io, "\nDo you wish to proceed? (y/n): "); + io.flush(); loop { - let resp = IO::read().await.try_halt(|e| { + let resp = io.read().await.try_halt(|e| { display_line!( - IO, + io, "Encountered error reading from STDIN: {e:?}" ); })?; @@ -263,8 +252,8 @@ where "y" => break, "n" => return control_flow::halt(), _ => { - display!(IO, "Expected 'y' or 'n'. Please try again: "); - IO::flush(); + display!(io, "Expected 'y' or 'n'. Please try again: "); + io.flush(); } } } @@ -278,7 +267,7 @@ where .await; response.map(|response| response.data).try_halt(|e| { - display_line!(IO, "Encountered error constructing proof:\n{:?}", e); + display_line!(io, "Encountered error constructing proof:\n{:?}", e); }) } @@ -294,18 +283,17 @@ struct BridgePoolProofResponse { /// Construct a merkle proof of a batch of transfers in /// the bridge pool and return it to the user (as opposed /// to relaying it to ethereum). -pub async fn construct_proof( - client: &C, +pub async fn construct_proof<'a>( + client: &(impl Client + Sync), + io: &impl Io, args: args::BridgePoolProof, -) -> Halt<()> -where - C: Client + Sync, -{ +) -> Halt<()> { let GenBridgePoolProofRsp { abi_encoded_args, appendices, - } = construct_bridge_pool_proof::<_, IO>( + } = construct_bridge_pool_proof( client, + io, GenBridgePoolProofReq { transfers: args.transfers.as_slice().into(), relayer: Cow::Borrowed(&args.relayer), @@ -334,26 +322,27 @@ where .unwrap_or_default(), abi_encoded_args, }; - display_line!(IO, "{}", serde_json::to_string(&resp).unwrap()); + display_line!(io, "{}", serde_json::to_string(&resp).unwrap()); control_flow::proceed(()) } /// Relay a validator set update, signed off for a given epoch. -pub async fn relay_bridge_pool_proof( +pub async fn relay_bridge_pool_proof<'a, E>( eth_client: Arc, - nam_client: &C, + client: &(impl Client + Sync), + io: &impl Io, args: args::RelayBridgePoolProof, ) -> Halt<()> where - C: Client + Sync, E: Middleware, E::Error: std::fmt::Debug + std::fmt::Display, { let _signal_receiver = args.safe_mode.then(install_shutdown_signal); if args.sync { - block_on_eth_sync::<_, IO>( + block_on_eth_sync( &*eth_client, + io, BlockOnEthSync { deadline: Instant::now() + Duration::from_secs(60), delta_sleep: Duration::from_secs(1), @@ -361,13 +350,14 @@ where ) .await?; } else { - eth_sync_or_exit::<_, IO>(&*eth_client).await?; + eth_sync_or_exit(&*eth_client, io).await?; } let GenBridgePoolProofRsp { abi_encoded_args, .. - } = construct_bridge_pool_proof::<_, IO>( - nam_client, + } = construct_bridge_pool_proof( + client, + io, GenBridgePoolProofReq { transfers: Cow::Owned(args.transfers), relayer: Cow::Owned(args.relayer), @@ -375,32 +365,28 @@ where }, ) .await?; - let bridge = match RPC - .shell() - .eth_bridge() - .read_bridge_contract(nam_client) - .await - { - Ok(address) => Bridge::new(address.address, eth_client), - Err(err_msg) => { - let error = "Error".on_red(); - let error = error.bold(); - let error = error.blink(); - display_line!( - IO, - "{error}: Failed to retrieve the Ethereum Bridge smart \ - contract address from storage with \ - reason:\n{err_msg}\n\nPerhaps the Ethereum bridge is not \ - active.", - ); - return control_flow::halt(); - } - }; + let bridge = + match RPC.shell().eth_bridge().read_bridge_contract(client).await { + Ok(address) => Bridge::new(address.address, eth_client), + Err(err_msg) => { + let error = "Error".on_red(); + let error = error.bold(); + let error = error.blink(); + display_line!( + io, + "{error}: Failed to retrieve the Ethereum Bridge smart \ + contract address from storage with \ + reason:\n{err_msg}\n\nPerhaps the Ethereum bridge is not \ + active.", + ); + return control_flow::halt(); + } + }; let (validator_set, signatures, bp_proof): TransferToErcArgs = AbiDecode::decode(&abi_encoded_args).try_halt(|error| { display_line!( - IO, + io, "Unable to decode the generated proof: {:?}", error ); @@ -417,7 +403,7 @@ where let error = error.bold(); let error = error.blink(); display_line!( - IO, + io, "{error}: The Bridge pool nonce in the smart contract is \ {contract_nonce}, while the nonce in Namada is still {}. A \ relay of the former one has already happened, but a proof \ @@ -431,7 +417,7 @@ where let error = error.bold(); let error = error.blink(); display_line!( - IO, + io, "{error}: The Bridge pool nonce in the smart contract is \ {contract_nonce}, while the nonce in Namada is still {}. \ Somehow, Namada's nonce is ahead of the contract's nonce!", @@ -459,7 +445,7 @@ where .await .unwrap(); - display_line!(IO, "{transf_result:?}"); + display_line!(io, "{transf_result:?}"); control_flow::proceed(()) } @@ -467,7 +453,12 @@ mod recommendations { use std::collections::BTreeSet; use borsh::BorshDeserialize; + use namada_core::types::ethereum_events::Uint as EthUint; + use namada_core::types::storage::BlockHeight; use namada_core::types::uint::{self, Uint, I256}; + use namada_core::types::vote_extensions::validator_set_update::{ + EthAddrBook, VotingPowersMap, VotingPowersMapExt, + }; use super::*; use crate::edisplay_line; @@ -475,12 +466,7 @@ mod recommendations { get_nonce_key, get_signed_root_key, }; use crate::eth_bridge::storage::proof::BridgePoolRootProof; - use crate::types::ethereum_events::Uint as EthUint; - use crate::types::io::Io; - use crate::types::storage::BlockHeight; - use crate::types::vote_extensions::validator_set_update::{ - EthAddrBook, VotingPowersMap, VotingPowersMapExt, - }; + use crate::io::Io; const fn unsigned_transfer_fee() -> Uint { Uint::from_u64(37_500_u64) @@ -558,19 +544,16 @@ mod recommendations { /// Recommend the most economical batch of transfers to relay based /// on a conversion rate estimates from NAM to ETH and gas usage /// heuristics. - pub async fn recommend_batch( - client: &C, + pub async fn recommend_batch<'a>( + context: &impl Namada<'a>, args: args::RecommendBatch, - ) -> Halt<()> - where - C: Client + Sync, - { + ) -> Halt<()> { // get transfers that can already been relayed but are awaiting a quorum // of backing votes. let in_progress = RPC .shell() .eth_bridge() - .transfer_to_ethereum_progress(client) + .transfer_to_ethereum_progress(context.client()) .await .unwrap() .into_keys() @@ -583,7 +566,7 @@ mod recommendations { <(BridgePoolRootProof, BlockHeight)>::try_from_slice( &RPC.shell() .storage_value( - client, + context.client(), None, None, false, @@ -592,36 +575,48 @@ mod recommendations { .await .try_halt(|err| { edisplay_line!( - IO, + context.io(), "Failed to query Bridge pool proof: {err}" ); })? .data, ) .try_halt(|err| { - edisplay_line!(IO, "Failed to decode Bridge pool proof: {err}"); + edisplay_line!( + context.io(), + "Failed to decode Bridge pool proof: {err}" + ); })?; // get the latest bridge pool nonce let latest_bp_nonce = EthUint::try_from_slice( &RPC.shell() - .storage_value(client, None, None, false, &get_nonce_key()) + .storage_value( + context.client(), + None, + None, + false, + &get_nonce_key(), + ) .await .try_halt(|err| { edisplay_line!( - IO, + context.io(), "Failed to query Bridge pool nonce: {err}" ); })? .data, ) .try_halt(|err| { - edisplay_line!(IO, "Failed to decode Bridge pool nonce: {err}"); + edisplay_line!( + context.io(), + "Failed to decode Bridge pool nonce: {err}" + ); })?; if latest_bp_nonce != bp_root.data.1 { edisplay_line!( - IO, + context.io(), "The signed Bridge pool nonce is not up to date, repeat this \ query at a later time" ); @@ -633,7 +628,7 @@ mod recommendations { let voting_powers = RPC .shell() .eth_bridge() - .voting_powers_at_height(client, &height) + .voting_powers_at_height(context.client(), &height) .await .unwrap(); let valset_size = Uint::from_u64(voting_powers.len() as u64); @@ -645,17 +640,19 @@ mod recommendations { + valset_fee() * valset_size; // we don't recommend transfers that have already been relayed - let eligible = generate_eligible::( + let eligible = generate_eligible( + context.io(), &args.conversion_table, &in_progress, - query_signed_bridge_pool::<_, IO>(client).await?, + query_signed_bridge_pool(context.client(), context.io()).await?, )?; let max_gas = args.max_gas.map(Uint::from_u64).unwrap_or(uint::MAX_VALUE); let max_cost = args.gas.map(I256::from).unwrap_or_default(); - generate_recommendations::( + generate_recommendations( + context.io(), eligible, &args.conversion_table, validator_gas, @@ -669,22 +666,28 @@ mod recommendations { net_profit, bridge_pool_gas_fees, }| { - display_line!(IO, "Recommended batch: {transfer_hashes:#?}"); display_line!( - IO, + context.io(), + "Recommended batch: {transfer_hashes:#?}" + ); + display_line!( + context.io(), "Estimated Ethereum transaction gas (in gwei): \ {ethereum_gas_fees}", ); display_line!( - IO, + context.io(), "Estimated net profit (in gwei): {net_profit}" ); - display_line!(IO, "Total fees: {bridge_pool_gas_fees:#?}"); + display_line!( + context.io(), + "Total fees: {bridge_pool_gas_fees:#?}" + ); }, ) .unwrap_or_else(|| { display_line!( - IO, + context.io(), "Unable to find a recommendation satisfying the input \ parameters." ); @@ -729,6 +732,7 @@ mod recommendations { /// Generate eligible recommendations. fn generate_eligible( + io: &IO, conversion_table: &HashMap, in_progress: &BTreeSet, signed_pool: HashMap, @@ -745,7 +749,7 @@ mod recommendations { .and_then(|entry| match entry.conversion_rate { r if r == 0.0f64 => { edisplay_line!( - IO, + io, "{}: Ignoring null conversion rate", pending.gas_fee.token, ); @@ -753,7 +757,7 @@ mod recommendations { } r if r < 0.0f64 => { edisplay_line!( - IO, + io, "{}: Ignoring negative conversion rate: {r:.1}", pending.gas_fee.token, ); @@ -761,7 +765,7 @@ mod recommendations { } r if r > 1e9 => { edisplay_line!( - IO, + io, "{}: Ignoring high conversion rate: {r:.1} > \ 10^9", pending.gas_fee.token, @@ -812,6 +816,7 @@ mod recommendations { /// Generates the actual recommendation from restrictions given by the /// input parameters. fn generate_recommendations( + io: &IO, contents: Vec, conversion_table: &HashMap, validator_gas: Uint, @@ -880,7 +885,7 @@ mod recommendations { }) } else { display_line!( - IO, + io, "Unable to find a recommendation satisfying the input \ parameters." ); @@ -912,8 +917,8 @@ mod recommendations { use namada_core::types::ethereum_events::EthAddress; use super::*; - use crate::types::control_flow::ProceedOrElse; - use crate::types::io::DefaultIo; + use crate::control_flow::ProceedOrElse; + use crate::io::StdIo; /// An established user address for testing & development pub fn bertha_address() -> Address { @@ -1019,12 +1024,9 @@ mod recommendations { signed_pool: &mut signed_pool, expected_eligible: &mut expected, }); - let eligible = generate_eligible::( - &table, - &in_progress, - signed_pool, - ) - .proceed(); + let eligible = + generate_eligible(&StdIo, &table, &in_progress, signed_pool) + .proceed(); assert_eq!(eligible, expected); eligible } @@ -1114,7 +1116,8 @@ mod recommendations { let profitable = vec![transfer(100_000); 17]; let hash = profitable[0].keccak256().to_string(); let expected = vec![hash; 17]; - let recommendation = generate_recommendations::( + let recommendation = generate_recommendations( + &StdIo, process_transfers(profitable), &Default::default(), Uint::from_u64(800_000), @@ -1133,7 +1136,8 @@ mod recommendations { let hash = transfers[0].keccak256().to_string(); transfers.push(transfer(0)); let expected: Vec<_> = vec![hash; 17]; - let recommendation = generate_recommendations::( + let recommendation = generate_recommendations( + &StdIo, process_transfers(transfers), &Default::default(), Uint::from_u64(800_000), @@ -1151,7 +1155,8 @@ mod recommendations { let transfers = vec![transfer(75_000); 4]; let hash = transfers[0].keccak256().to_string(); let expected = vec![hash; 2]; - let recommendation = generate_recommendations::( + let recommendation = generate_recommendations( + &StdIo, process_transfers(transfers), &Default::default(), Uint::from_u64(50_000), @@ -1173,7 +1178,8 @@ mod recommendations { .map(|t| t.keccak256().to_string()) .take(5) .collect(); - let recommendation = generate_recommendations::( + let recommendation = generate_recommendations( + &StdIo, process_transfers(transfers), &Default::default(), Uint::from_u64(150_000), @@ -1192,7 +1198,8 @@ mod recommendations { let hash = transfers[0].keccak256().to_string(); let expected = vec![hash; 4]; transfers.extend([transfer(17_500), transfer(17_500)]); - let recommendation = generate_recommendations::( + let recommendation = generate_recommendations( + &StdIo, process_transfers(transfers), &Default::default(), Uint::from_u64(150_000), @@ -1208,7 +1215,8 @@ mod recommendations { #[test] fn test_wholly_infeasible() { let transfers = vec![transfer(75_000); 4]; - let recommendation = generate_recommendations::( + let recommendation = generate_recommendations( + &StdIo, process_transfers(transfers), &Default::default(), Uint::from_u64(300_000), @@ -1289,7 +1297,8 @@ mod recommendations { const VALIDATOR_GAS_FEE: Uint = Uint::from_u64(100_000); - let recommended_batch = generate_recommendations::( + let recommended_batch = generate_recommendations( + &StdIo, eligible, &conversion_table, // gas spent by validator signature checks diff --git a/shared/src/ledger/eth_bridge.rs b/sdk/src/eth_bridge/mod.rs similarity index 90% rename from shared/src/ledger/eth_bridge.rs rename to sdk/src/eth_bridge/mod.rs index a73f5efd77..49b77705a3 100644 --- a/shared/src/ledger/eth_bridge.rs +++ b/sdk/src/eth_bridge/mod.rs @@ -5,19 +5,22 @@ pub mod validator_set; use std::ops::ControlFlow; +pub use ethers; use ethers::providers::Middleware; use itertools::Either; pub use namada_core::ledger::eth_bridge::storage::wrapped_erc20s; pub use namada_core::ledger::eth_bridge::{ADDRESS, INTERNAL_ADDRESS}; +pub use namada_core::types::ethereum_structs as structs; pub use namada_ethereum_bridge::parameters::*; pub use namada_ethereum_bridge::storage::eth_bridge_queries::*; +pub use namada_ethereum_bridge::*; use num256::Uint256; -use crate::types::control_flow::time::{ +use crate::control_flow::time::{ Constant, Duration, Error as TimeoutError, Instant, LinearBackoff, Sleep, }; -use crate::types::control_flow::{self, Halt, TryHalt}; -use crate::types::io::Io; +use crate::control_flow::{self, Halt, TryHalt}; +use crate::io::Io; use crate::{display_line, edisplay_line}; const DEFAULT_BACKOFF: Duration = std::time::Duration::from_millis(500); @@ -102,6 +105,7 @@ pub struct BlockOnEthSync { /// Block until Ethereum finishes synchronizing. pub async fn block_on_eth_sync( client: &C, + io: &IO, args: BlockOnEthSync, ) -> Halt<()> where @@ -111,7 +115,7 @@ where deadline, delta_sleep, } = args; - display_line!(IO, "Attempting to synchronize with the Ethereum network"); + display_line!(io, "Attempting to synchronize with the Ethereum network"); Sleep { strategy: LinearBackoff { delta: delta_sleep }, } @@ -128,11 +132,11 @@ where .await .try_halt(|_| { edisplay_line!( - IO, + io, "Timed out while waiting for Ethereum to synchronize" ); })?; - display_line!(IO, "The Ethereum node is up to date"); + display_line!(io, "The Ethereum node is up to date"); control_flow::proceed(()) } @@ -140,6 +144,7 @@ where /// not, perform `action`. pub async fn eth_sync_or( client: &C, + io: &IO, mut action: F, ) -> Halt> where @@ -151,7 +156,7 @@ where .map(|status| status.is_synchronized()) .try_halt(|err| { edisplay_line!( - IO, + io, "An error occurred while fetching the Ethereum \ synchronization status: {err}" ); @@ -165,11 +170,11 @@ where /// Check if Ethereum has finished synchronizing. In case it has /// not, end execution. -pub async fn eth_sync_or_exit(client: &C) -> Halt<()> +pub async fn eth_sync_or_exit(client: &C, io: &IO) -> Halt<()> where C: Middleware, { - eth_sync_or::<_, _, _, IO>(client, || { + eth_sync_or(client, io, || { tracing::error!("The Ethereum node has not finished synchronizing"); }) .await? diff --git a/shared/src/ledger/eth_bridge/validator_set.rs b/sdk/src/eth_bridge/validator_set.rs similarity index 92% rename from shared/src/ledger/eth_bridge/validator_set.rs rename to sdk/src/eth_bridge/validator_set.rs index 4ae08dd598..5c98b39ae0 100644 --- a/shared/src/ledger/eth_bridge/validator_set.rs +++ b/sdk/src/eth_bridge/validator_set.rs @@ -12,23 +12,19 @@ use ethbridge_bridge_contract::Bridge; use ethers::providers::Middleware; use futures::future::{self, FutureExt}; use namada_core::hints; +use namada_core::types::ethereum_events::EthAddress; use namada_core::types::storage::Epoch; +use namada_core::types::vote_extensions::validator_set_update::ValidatorSetArgs; use super::{block_on_eth_sync, eth_sync_or, eth_sync_or_exit, BlockOnEthSync}; +use crate::control_flow::time::{self, Duration, Instant}; +use crate::control_flow::{self, install_shutdown_signal, Halt, TryHalt}; use crate::eth_bridge::ethers::abi::{AbiDecode, AbiType, Tokenizable}; use crate::eth_bridge::ethers::core::types::TransactionReceipt; use crate::eth_bridge::structs::Signature; -use crate::ledger::queries::RPC; -use crate::sdk::args; -use crate::sdk::queries::Client; -use crate::types::control_flow::time::{self, Duration, Instant}; -use crate::types::control_flow::{ - self, install_shutdown_signal, Halt, TryHalt, -}; -use crate::types::ethereum_events::EthAddress; -use crate::types::io::{DefaultIo, Io}; -use crate::types::vote_extensions::validator_set_update::ValidatorSetArgs; -use crate::{display_line, edisplay_line}; +use crate::io::Io; +use crate::queries::{Client, RPC}; +use crate::{args, display_line, edisplay_line}; /// Relayer related errors. #[derive(Debug, Default)] @@ -268,12 +264,11 @@ impl From> for RelayResult { /// Query an ABI encoding of the validator set to be installed /// at the given epoch, and its associated proof. -pub async fn query_validator_set_update_proof( - client: &C, +pub async fn query_validator_set_update_proof<'a>( + client: &(impl Client + Sync), + io: &impl Io, args: args::ValidatorSetProof, -) where - C: Client + Sync, -{ +) { let epoch = if let Some(epoch) = args.epoch { epoch } else { @@ -287,17 +282,15 @@ pub async fn query_validator_set_update_proof( .await .unwrap(); - display_line!(IO, "0x{}", HEXLOWER.encode(encoded_proof.as_ref())); + display_line!(io, "0x{}", HEXLOWER.encode(encoded_proof.as_ref())); } /// Query an ABI encoding of the Bridge validator set at a given epoch. -pub async fn query_bridge_validator_set( - client: &C, +pub async fn query_bridge_validator_set<'a>( + client: &(impl Client + Sync), + io: &impl Io, args: args::BridgeValidatorSet, -) -> Halt<()> -where - C: Client + Sync, -{ +) -> Halt<()> { let epoch = if let Some(epoch) = args.epoch { epoch } else { @@ -313,18 +306,16 @@ where tracing::error!(%err, "Failed to fetch Bridge validator set"); })?; - display_validator_set::(args); + display_validator_set(io, args); control_flow::proceed(()) } /// Query an ABI encoding of the Governance validator set at a given epoch. -pub async fn query_governnace_validator_set( - client: &C, +pub async fn query_governnace_validator_set<'a>( + client: &(impl Client + Sync), + io: &impl Io, args: args::GovernanceValidatorSet, -) -> Halt<()> -where - C: Client + Sync, -{ +) -> Halt<()> { let epoch = if let Some(epoch) = args.epoch { epoch } else { @@ -340,12 +331,12 @@ where tracing::error!(%err, "Failed to fetch Governance validator set"); })?; - display_validator_set::(args); + display_validator_set(io, args); control_flow::proceed(()) } /// Display the given [`ValidatorSetArgs`]. -fn display_validator_set(args: ValidatorSetArgs) { +fn display_validator_set(io: &IO, args: ValidatorSetArgs) { use serde::Serialize; #[derive(Serialize)] @@ -373,28 +364,29 @@ fn display_validator_set(args: ValidatorSetArgs) { }; display_line!( - IO, + io, "{}", serde_json::to_string_pretty(&validator_set).unwrap() ); } /// Relay a validator set update, signed off for a given epoch. -pub async fn relay_validator_set_update( +pub async fn relay_validator_set_update<'a, E>( eth_client: Arc, - nam_client: &C, + client: &(impl Client + Sync), + io: &impl Io, args: args::ValidatorSetUpdateRelay, ) -> Halt<()> where - C: Client + Sync, E: Middleware, E::Error: std::fmt::Debug + std::fmt::Display, { let mut signal_receiver = args.safe_mode.then(install_shutdown_signal); if args.sync { - block_on_eth_sync::<_, IO>( + block_on_eth_sync( &*eth_client, + io, BlockOnEthSync { deadline: Instant::now() + Duration::from_secs(60), delta_sleep: Duration::from_secs(1), @@ -402,14 +394,15 @@ where ) .await?; } else { - eth_sync_or_exit::<_, IO>(&*eth_client).await?; + eth_sync_or_exit(&*eth_client, io).await?; } if args.daemon { relay_validator_set_update_daemon( args, eth_client, - nam_client, + client, + io, &mut signal_receiver, ) .await @@ -417,11 +410,11 @@ where relay_validator_set_update_once::( &args, eth_client, - nam_client, + client, |relay_result| match relay_result { RelayResult::BridgeCallError(reason) => { edisplay_line!( - IO, + io, "Calling Bridge failed due to: {reason}" ); } @@ -432,27 +425,27 @@ where Ordering::Greater => "too far ahead of", }; edisplay_line!( - IO, + io, "Argument nonce <{argument}> is {whence} contract \ nonce <{contract}>" ); } RelayResult::NoReceipt => { edisplay_line!( - IO, + io, "No transfer receipt received from the Ethereum node" ); } RelayResult::Receipt { receipt } => { if receipt.is_successful() { display_line!( - IO, + io, "Ethereum transfer succeeded: {:?}", receipt ); } else { display_line!( - IO, + io, "Ethereum transfer failed: {:?}", receipt ); @@ -465,14 +458,14 @@ where } } -async fn relay_validator_set_update_daemon( +async fn relay_validator_set_update_daemon<'a, E, F>( mut args: args::ValidatorSetUpdateRelay, eth_client: Arc, - nam_client: &C, + client: &(impl Client + Sync), + io: &impl Io, shutdown_receiver: &mut Option, ) -> Halt<()> where - C: Client + Sync, E: Middleware, E::Error: std::fmt::Debug + std::fmt::Display, F: Future + Unpin, @@ -513,9 +506,7 @@ where time::sleep(sleep_for).await; let is_synchronizing = - eth_sync_or::<_, _, _, DefaultIo>(&*eth_client, || ()) - .await - .is_break(); + eth_sync_or(&*eth_client, io, || ()).await.is_break(); if is_synchronizing { tracing::debug!("The Ethereum node is synchronizing"); last_call_succeeded = false; @@ -525,7 +516,7 @@ where // we could be racing against governance updates, // so it is best to always fetch the latest Bridge // contract address - let bridge = get_bridge_contract(nam_client, Arc::clone(ð_client)) + let bridge = get_bridge_contract(client, Arc::clone(ð_client)) .await .try_halt(|err| { // only care about displaying errors, @@ -544,7 +535,7 @@ where }); let shell = RPC.shell(); - let nam_current_epoch_fut = shell.epoch(nam_client).map(|result| { + let nam_current_epoch_fut = shell.epoch(client).map(|result| { result .map_err(|err| { tracing::error!( @@ -596,7 +587,7 @@ where let result = relay_validator_set_update_once::( &args, Arc::clone(ð_client), - nam_client, + client, |transf_result| { let Some(receipt) = transf_result else { tracing::warn!("No transfer receipt received from the Ethereum node"); diff --git a/shared/src/ledger/events/log.rs b/sdk/src/events/log.rs similarity index 97% rename from shared/src/ledger/events/log.rs rename to sdk/src/events/log.rs index a2dc3978d0..596c23bdc9 100644 --- a/shared/src/ledger/events/log.rs +++ b/sdk/src/events/log.rs @@ -8,7 +8,7 @@ use std::default::Default; use circular_queue::CircularQueue; -use crate::ledger::events::Event; +use crate::events::Event; pub mod dumb_queries; @@ -85,9 +85,10 @@ impl EventLog { #[cfg(test)] mod tests { + use namada_core::types::hash::Hash; + use super::*; - use crate::ledger::events::{EventLevel, EventType}; - use crate::types::hash::Hash; + use crate::events::{EventLevel, EventType}; const HASH: &str = "DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF"; diff --git a/shared/src/ledger/events/log/dumb_queries.rs b/sdk/src/events/log/dumb_queries.rs similarity index 96% rename from shared/src/ledger/events/log/dumb_queries.rs rename to sdk/src/events/log/dumb_queries.rs index 5ff7c8d54f..44988fb0dc 100644 --- a/shared/src/ledger/events/log/dumb_queries.rs +++ b/sdk/src/events/log/dumb_queries.rs @@ -8,12 +8,13 @@ use std::collections::HashMap; +use namada_core::types::hash::Hash; +use namada_core::types::storage::BlockHeight; + +use crate::events::{Event, EventType}; use crate::ibc::core::ics04_channel::packet::Sequence; use crate::ibc::core::ics24_host::identifier::{ChannelId, ClientId, PortId}; use crate::ibc::Height as IbcHeight; -use crate::ledger::events::{Event, EventType}; -use crate::types::hash::Hash; -use crate::types::storage::BlockHeight; /// A [`QueryMatcher`] verifies if a Namada event matches a /// given Tendermint query. @@ -118,7 +119,7 @@ impl QueryMatcher { #[cfg(test)] mod tests { use super::*; - use crate::ledger::events::EventLevel; + use crate::events::EventLevel; /// Test if query matching is working as expected. #[test] diff --git a/shared/src/ledger/events.rs b/sdk/src/events/mod.rs similarity index 94% rename from shared/src/ledger/events.rs rename to sdk/src/events/mod.rs index ff5b9f108d..141867c63d 100644 --- a/shared/src/ledger/events.rs +++ b/sdk/src/events/mod.rs @@ -8,14 +8,14 @@ use std::ops::{Index, IndexMut}; use std::str::FromStr; use borsh::{BorshDeserialize, BorshSerialize}; +use namada_core::types::ibc::IbcEvent; +#[cfg(feature = "ferveo-tpke")] +use namada_core::types::transaction::TxType; use serde_json::Value; -use crate::ledger::governance::utils::ProposalEvent; -use crate::sdk::error::{EncodingError, Error, EventError}; +// use crate::ledger::governance::utils::ProposalEvent; +use crate::error::{EncodingError, Error, EventError}; use crate::tendermint_proto::abci::EventAttribute; -use crate::types::ibc::IbcEvent; -#[cfg(feature = "ferveo-tpke")] -use crate::types::transaction::TxType; /// Indicates if an event is emitted do to /// an individual Tx or the nature of a finalized block @@ -171,16 +171,6 @@ impl From for Event { } } -impl From for Event { - fn from(proposal_event: ProposalEvent) -> Self { - Self { - event_type: EventType::Proposal, - level: EventLevel::Block, - attributes: proposal_event.attributes, - } - } -} - /// Convert our custom event into the necessary tendermint proto type impl From for crate::tendermint_proto::abci::Event { fn from(event: Event) -> Self { diff --git a/shared/src/types/io.rs b/sdk/src/io.rs similarity index 56% rename from shared/src/types/io.rs rename to sdk/src/io.rs index 462dbef95f..248f6f91d9 100644 --- a/shared/src/types/io.rs +++ b/sdk/src/io.rs @@ -2,47 +2,50 @@ //! generic IO. The defaults are the obvious Rust native //! functions. -/// Rust native I/O handling. -pub struct DefaultIo; - -#[async_trait::async_trait(?Send)] -impl Io for DefaultIo {} - +/// A trait that abstracts out I/O operations #[async_trait::async_trait(?Send)] -#[allow(missing_docs)] pub trait Io { - fn print(output: impl AsRef) { + /// Print the given string + fn print(&self, output: impl AsRef) { print!("{}", output.as_ref()); } - fn flush() { + /// Flush the output + fn flush(&self) { use std::io::Write; std::io::stdout().flush().unwrap(); } - fn println(output: impl AsRef) { + /// Print the given string with a newline + fn println(&self, output: impl AsRef) { println!("{}", output.as_ref()); } + /// Print the given string into the given Writer fn write( + &self, mut writer: W, output: impl AsRef, ) -> std::io::Result<()> { write!(writer, "{}", output.as_ref()) } + /// Print the given string into the given Writer and terminate with newline fn writeln( + &self, mut writer: W, output: impl AsRef, ) -> std::io::Result<()> { writeln!(writer, "{}", output.as_ref()) } - fn eprintln(output: impl AsRef) { + /// Print the given error string + fn eprintln(&self, output: impl AsRef) { eprintln!("{}", output.as_ref()); } - async fn read() -> std::io::Result { + /// Read a string from input + async fn read(&self) -> std::io::Result { #[cfg(not(target_family = "wasm"))] { read_aux(tokio::io::stdin()).await @@ -53,7 +56,8 @@ pub trait Io { } } - async fn prompt(question: impl AsRef) -> String { + /// Display the given prompt and return the string input + async fn prompt(&self, question: impl AsRef) -> String { #[cfg(not(target_family = "wasm"))] { prompt_aux( @@ -74,6 +78,50 @@ pub trait Io { } } +/// Rust native I/O handling. +pub struct StdIo; + +#[async_trait::async_trait(?Send)] +impl Io for StdIo {} + +/// Ignores all I/O operations. +pub struct NullIo; + +#[async_trait::async_trait(?Send)] +impl Io for NullIo { + fn print(&self, _output: impl AsRef) {} + + fn flush(&self) {} + + fn println(&self, _output: impl AsRef) {} + + fn write( + &self, + mut _writer: W, + _output: impl AsRef, + ) -> std::io::Result<()> { + Ok(()) + } + + fn writeln( + &self, + mut _writer: W, + _output: impl AsRef, + ) -> std::io::Result<()> { + Ok(()) + } + + fn eprintln(&self, _output: impl AsRef) {} + + async fn read(&self) -> std::io::Result { + panic!("Unsupported operation") + } + + async fn prompt(&self, _question: impl AsRef) -> String { + panic!("Unsupported operation") + } +} + /// A generic function for displaying a prompt to users and reading /// in their response. #[cfg(not(target_family = "wasm"))] @@ -111,14 +159,14 @@ where /// [`Io::print`] #[macro_export] macro_rules! display { - ($io:ty) => { - <$io>::print("") + ($io:expr) => { + $io.print("") }; - ($io:ty, $w:expr; $($args:tt)*) => { - <$io>::write($w, format_args!($($args)*).to_string()) + ($io:expr, $w:expr; $($args:tt)*) => { + $io.write($w, format_args!($($args)*).to_string()) }; - ($io:ty,$($args:tt)*) => { - <$io>::print(format_args!($($args)*).to_string()) + ($io:expr,$($args:tt)*) => { + $io.print(format_args!($($args)*).to_string()) }; } @@ -126,14 +174,14 @@ macro_rules! display { /// [`Io::println`] and [`Io::writeln`] #[macro_export] macro_rules! display_line { - ($io:ty) => { - <$io>::println("") + ($io:expr) => { + $io.println("") }; - ($io:ty, $w:expr; $($args:tt)*) => { - <$io>::writeln($w, format_args!($($args)*).to_string()) + ($io:expr, $w:expr; $($args:tt)*) => { + $io.writeln($w, format_args!($($args)*).to_string()) }; - ($io:ty,$($args:tt)*) => { - <$io>::println(format_args!($($args)*).to_string()) + ($io:expr,$($args:tt)*) => { + $io.println(format_args!($($args)*).to_string()) }; } @@ -141,8 +189,8 @@ macro_rules! display_line { /// [`Io::eprintln`] #[macro_export] macro_rules! edisplay_line { - ($io:ty,$($args:tt)*) => { - <$io>::eprintln(format_args!($($args)*).to_string()) + ($io:expr,$($args:tt)*) => { + $io.eprintln(format_args!($($args)*).to_string()) }; } @@ -150,7 +198,7 @@ macro_rules! edisplay_line { /// A convenience macro for formatting the user prompt before /// forwarding it to the [`Io::prompt`] method. macro_rules! prompt { - ($io:ty,$($arg:tt)*) => {{ - <$io>::prompt(format!("{}", format_args!($($arg)*))) + ($io:expr,$($arg:tt)*) => {{ + $io.prompt(format!("{}", format_args!($($arg)*))) }} } diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs new file mode 100644 index 0000000000..622a63a1d1 --- /dev/null +++ b/sdk/src/lib.rs @@ -0,0 +1,583 @@ +pub use namada_core::proto; +#[cfg(feature = "tendermint-rpc")] +pub use tendermint_rpc; +#[cfg(feature = "tendermint-rpc-abcipp")] +pub use tendermint_rpc_abcipp as tendermint_rpc; +pub use {bip39, namada_core as core, namada_proof_of_stake as proof_of_stake}; +#[cfg(feature = "abcipp")] +pub use { + ibc_abcipp as ibc, ibc_proto_abcipp as ibc_proto, + tendermint_abcipp as tendermint, + tendermint_proto_abcipp as tendermint_proto, +}; +#[cfg(feature = "abciplus")] +pub use { + namada_core::ibc, namada_core::ibc_proto, namada_core::tendermint, + namada_core::tendermint_proto, +}; + +pub mod eth_bridge; + +pub mod rpc; + +pub mod args; +pub mod masp; +pub mod signing; +#[allow(clippy::result_large_err)] +pub mod tx; + +pub mod control_flow; +pub mod error; +pub mod events; +pub mod io; +pub mod queries; +pub mod wallet; + +use std::path::PathBuf; +use std::str::FromStr; + +use args::{InputAmount, SdkTypes}; +use namada_core::types::address::Address; +use namada_core::types::dec::Dec; +use namada_core::types::ethereum_events::EthAddress; +use namada_core::types::key::*; +use namada_core::types::masp::{TransferSource, TransferTarget}; +use namada_core::types::token; +use namada_core::types::token::NATIVE_MAX_DECIMAL_PLACES; +use namada_core::types::transaction::GasLimit; +use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; + +use crate::ibc::core::ics24_host::identifier::{ChannelId, PortId}; +use crate::io::Io; +use crate::masp::{ShieldedContext, ShieldedUtils}; +use crate::proto::Tx; +use crate::rpc::{ + denominate_amount, format_denominated_amount, query_native_token, +}; +use crate::signing::SigningTxData; +use crate::token::DenominatedAmount; +use crate::tx::{ + ProcessTxResponse, TX_BOND_WASM, TX_BRIDGE_POOL_WASM, + TX_CHANGE_COMMISSION_WASM, TX_IBC_WASM, TX_INIT_PROPOSAL, + TX_INIT_VALIDATOR_WASM, TX_RESIGN_STEWARD, TX_REVEAL_PK, TX_TRANSFER_WASM, + TX_UNBOND_WASM, TX_UNJAIL_VALIDATOR_WASM, TX_UPDATE_ACCOUNT_WASM, + TX_UPDATE_STEWARD_COMMISSION, TX_VOTE_PROPOSAL, TX_WITHDRAW_WASM, + VP_USER_WASM, +}; +use crate::wallet::{Wallet, WalletIo, WalletStorage}; + +#[async_trait::async_trait(?Send)] +/// An interface for high-level interaction with the Namada SDK +pub trait Namada<'a>: Sized { + /// A client with async request dispatcher method + type Client: 'a + queries::Client + Sync; + /// Captures the interactive parts of the wallet's functioning + type WalletUtils: 'a + WalletIo + WalletStorage; + /// Abstracts platform specific details away from the logic of shielded pool + /// operations. + type ShieldedUtils: 'a + ShieldedUtils; + /// Captures the input/output streams used by this object + type Io: 'a + Io; + + /// Obtain the client for communicating with the ledger + fn client(&self) -> &'a Self::Client; + + /// Obtain the input/output handle for this context + fn io(&self) -> &'a Self::Io; + + /// Obtain read guard on the wallet + async fn wallet( + &self, + ) -> RwLockReadGuard<&'a mut Wallet>; + + /// Obtain write guard on the wallet + async fn wallet_mut( + &self, + ) -> RwLockWriteGuard<&'a mut Wallet>; + + /// Obtain read guard on the shielded context + async fn shielded( + &self, + ) -> RwLockReadGuard<&'a mut ShieldedContext>; + + /// Obtain write guard on the shielded context + async fn shielded_mut( + &self, + ) -> RwLockWriteGuard<&'a mut ShieldedContext>; + + /// Return the native token + fn native_token(&self) -> Address; + + /// Make a tx builder using no arguments + fn tx_builder(&self) -> args::Tx { + args::Tx { + dry_run: false, + dry_run_wrapper: false, + dump_tx: false, + output_folder: None, + force: false, + broadcast_only: false, + ledger_address: (), + initialized_account_alias: None, + wallet_alias_force: false, + fee_amount: None, + wrapper_fee_payer: None, + fee_token: self.native_token(), + fee_unshield: None, + gas_limit: GasLimit::from(20_000), + expiration: None, + disposable_signing_key: false, + chain_id: None, + signing_keys: vec![], + signatures: vec![], + tx_reveal_code_path: PathBuf::from(TX_REVEAL_PK), + verification_key: None, + password: None, + } + } + + /// Make a TxTransfer builder from the given minimum set of arguments + fn new_transfer( + &self, + source: TransferSource, + target: TransferTarget, + token: Address, + amount: InputAmount, + ) -> args::TxTransfer { + args::TxTransfer { + source, + target, + token, + amount, + tx_code_path: PathBuf::from(TX_TRANSFER_WASM), + tx: self.tx_builder(), + native_token: self.native_token(), + } + } + + /// Make a RevealPK builder from the given minimum set of arguments + fn new_reveal_pk(&self, public_key: common::PublicKey) -> args::RevealPk { + args::RevealPk { + public_key, + tx: self.tx_builder(), + } + } + + /// Make a Bond builder from the given minimum set of arguments + fn new_bond( + &self, + validator: Address, + amount: token::Amount, + ) -> args::Bond { + args::Bond { + validator, + amount, + source: None, + tx: self.tx_builder(), + native_token: self.native_token(), + tx_code_path: PathBuf::from(TX_BOND_WASM), + } + } + + /// Make a Unbond builder from the given minimum set of arguments + fn new_unbond( + &self, + validator: Address, + amount: token::Amount, + ) -> args::Unbond { + args::Unbond { + validator, + amount, + source: None, + tx: self.tx_builder(), + tx_code_path: PathBuf::from(TX_UNBOND_WASM), + } + } + + /// Make a TxIbcTransfer builder from the given minimum set of arguments + fn new_ibc_transfer( + &self, + source: Address, + receiver: String, + token: Address, + amount: InputAmount, + channel_id: ChannelId, + ) -> args::TxIbcTransfer { + args::TxIbcTransfer { + source, + receiver, + token, + amount, + channel_id, + port_id: PortId::from_str("transfer").unwrap(), + timeout_height: None, + timeout_sec_offset: None, + memo: None, + tx: self.tx_builder(), + tx_code_path: PathBuf::from(TX_IBC_WASM), + } + } + + /// Make a InitProposal builder from the given minimum set of arguments + fn new_init_proposal(&self, proposal_data: Vec) -> args::InitProposal { + args::InitProposal { + proposal_data, + native_token: self.native_token(), + is_offline: false, + is_pgf_stewards: false, + is_pgf_funding: false, + tx_code_path: PathBuf::from(TX_INIT_PROPOSAL), + tx: self.tx_builder(), + } + } + + /// Make a TxUpdateAccount builder from the given minimum set of arguments + fn new_update_account(&self, addr: Address) -> args::TxUpdateAccount { + args::TxUpdateAccount { + addr, + vp_code_path: None, + public_keys: vec![], + threshold: None, + tx_code_path: PathBuf::from(TX_UPDATE_ACCOUNT_WASM), + tx: self.tx_builder(), + } + } + + /// Make a VoteProposal builder from the given minimum set of arguments + fn new_vote_prposal( + &self, + vote: String, + voter: Address, + ) -> args::VoteProposal { + args::VoteProposal { + vote, + voter, + proposal_id: None, + is_offline: false, + proposal_data: None, + tx_code_path: PathBuf::from(TX_VOTE_PROPOSAL), + tx: self.tx_builder(), + } + } + + /// Make a CommissionRateChange builder from the given minimum set of + /// arguments + fn new_change_commission_rate( + &self, + rate: Dec, + validator: Address, + ) -> args::CommissionRateChange { + args::CommissionRateChange { + rate, + validator, + tx_code_path: PathBuf::from(TX_CHANGE_COMMISSION_WASM), + tx: self.tx_builder(), + } + } + + /// Make a TxInitValidator builder from the given minimum set of arguments + fn new_init_validator( + &self, + commission_rate: Dec, + max_commission_rate_change: Dec, + ) -> args::TxInitValidator { + args::TxInitValidator { + commission_rate, + max_commission_rate_change, + scheme: SchemeType::Ed25519, + account_keys: vec![], + threshold: None, + consensus_key: None, + eth_cold_key: None, + eth_hot_key: None, + protocol_key: None, + validator_vp_code_path: PathBuf::from(VP_USER_WASM), + unsafe_dont_encrypt: false, + tx_code_path: PathBuf::from(TX_INIT_VALIDATOR_WASM), + tx: self.tx_builder(), + } + } + + /// Make a TxUnjailValidator builder from the given minimum set of arguments + fn new_unjail_validator( + &self, + validator: Address, + ) -> args::TxUnjailValidator { + args::TxUnjailValidator { + validator, + tx_code_path: PathBuf::from(TX_UNJAIL_VALIDATOR_WASM), + tx: self.tx_builder(), + } + } + + /// Make a Withdraw builder from the given minimum set of arguments + fn new_withdraw(&self, validator: Address) -> args::Withdraw { + args::Withdraw { + validator, + source: None, + tx_code_path: PathBuf::from(TX_WITHDRAW_WASM), + tx: self.tx_builder(), + } + } + + /// Make a Withdraw builder from the given minimum set of arguments + fn new_add_erc20_transfer( + &self, + sender: Address, + recipient: EthAddress, + asset: EthAddress, + amount: InputAmount, + ) -> args::EthereumBridgePool { + args::EthereumBridgePool { + sender, + recipient, + asset, + amount, + fee_amount: InputAmount::Unvalidated(token::DenominatedAmount { + amount: token::Amount::default(), + denom: NATIVE_MAX_DECIMAL_PLACES.into(), + }), + fee_payer: None, + fee_token: self.native_token(), + nut: false, + code_path: PathBuf::from(TX_BRIDGE_POOL_WASM), + tx: self.tx_builder(), + } + } + + /// Make a ResignSteward builder from the given minimum set of arguments + fn new_resign_steward(&self, steward: Address) -> args::ResignSteward { + args::ResignSteward { + steward, + tx: self.tx_builder(), + tx_code_path: PathBuf::from(TX_RESIGN_STEWARD), + } + } + + /// Make a UpdateStewardCommission builder from the given minimum set of + /// arguments + fn new_update_steward_rewards( + &self, + steward: Address, + commission: Vec, + ) -> args::UpdateStewardCommission { + args::UpdateStewardCommission { + steward, + commission, + tx: self.tx_builder(), + tx_code_path: PathBuf::from(TX_UPDATE_STEWARD_COMMISSION), + } + } + + /// Make a TxCustom builder from the given minimum set of arguments + fn new_custom(&self, owner: Address) -> args::TxCustom { + args::TxCustom { + owner, + tx: self.tx_builder(), + code_path: None, + data_path: None, + serialized_tx: None, + } + } + + /// Sign the given transaction using the given signing data + async fn sign( + &self, + tx: &mut Tx, + args: &args::Tx, + signing_data: SigningTxData, + ) -> crate::error::Result<()> { + signing::sign_tx(*self.wallet_mut().await, args, tx, signing_data) + } + + /// Process the given transaction using the given flags + async fn submit( + &self, + tx: Tx, + args: &args::Tx, + ) -> crate::error::Result { + tx::process_tx(self, args, tx).await + } + + /// Look up the denomination of a token in order to make a correctly + /// denominated amount. + async fn denominate_amount( + &self, + token: &Address, + amount: token::Amount, + ) -> DenominatedAmount { + denominate_amount(self.client(), self.io(), token, amount).await + } + + /// Look up the denomination of a token in order to format it correctly as a + /// string. + async fn format_amount( + &self, + token: &Address, + amount: token::Amount, + ) -> String { + format_denominated_amount(self.client(), self.io(), token, amount).await + } +} + +/// Provides convenience methods for common Namada interactions +pub struct NamadaImpl<'a, C, U, V, I> +where + C: queries::Client + Sync, + U: WalletIo, + V: ShieldedUtils, + I: Io, +{ + /// Used to send and receive messages from the ledger + pub client: &'a C, + /// Stores the addresses and keys required for ledger interactions + pub wallet: RwLock<&'a mut Wallet>, + /// Stores the current state of the shielded pool + pub shielded: RwLock<&'a mut ShieldedContext>, + /// Captures the input/output streams used by this object + pub io: &'a I, + /// The address of the native token + native_token: Address, + /// The default builder for a Tx + prototype: args::Tx, +} + +impl<'a, C, U, V, I> NamadaImpl<'a, C, U, V, I> +where + C: queries::Client + Sync, + U: WalletIo, + V: ShieldedUtils, + I: Io, +{ + /// Construct a new Namada context with the given native token address + pub fn native_new( + client: &'a C, + wallet: &'a mut Wallet, + shielded: &'a mut ShieldedContext, + io: &'a I, + native_token: Address, + ) -> Self { + NamadaImpl { + client, + wallet: RwLock::new(wallet), + shielded: RwLock::new(shielded), + io, + native_token: native_token.clone(), + prototype: args::Tx { + dry_run: false, + dry_run_wrapper: false, + dump_tx: false, + output_folder: None, + force: false, + broadcast_only: false, + ledger_address: (), + initialized_account_alias: None, + wallet_alias_force: false, + fee_amount: None, + wrapper_fee_payer: None, + fee_token: native_token, + fee_unshield: None, + gas_limit: GasLimit::from(20_000), + expiration: None, + disposable_signing_key: false, + chain_id: None, + signing_keys: vec![], + signatures: vec![], + tx_reveal_code_path: PathBuf::from(TX_REVEAL_PK), + verification_key: None, + password: None, + }, + } + } + + /// Construct a new Namada context looking up the native token address + pub async fn new( + client: &'a C, + wallet: &'a mut Wallet, + shielded: &'a mut ShieldedContext, + io: &'a I, + ) -> crate::error::Result> { + let native_token = query_native_token(client).await?; + Ok(NamadaImpl::native_new( + client, + wallet, + shielded, + io, + native_token, + )) + } +} + +#[async_trait::async_trait(?Send)] +impl<'a, C, U, V, I> Namada<'a> for NamadaImpl<'a, C, U, V, I> +where + C: queries::Client + Sync, + U: WalletIo + WalletStorage, + V: ShieldedUtils, + I: Io, +{ + type Client = C; + type Io = I; + type ShieldedUtils = V; + type WalletUtils = U; + + /// Obtain the prototypical Tx builder + fn tx_builder(&self) -> args::Tx { + self.prototype.clone() + } + + fn native_token(&self) -> Address { + self.native_token.clone() + } + + fn io(&self) -> &'a Self::Io { + self.io + } + + fn client(&self) -> &'a Self::Client { + self.client + } + + async fn wallet( + &self, + ) -> RwLockReadGuard<&'a mut Wallet> { + self.wallet.read().await + } + + async fn wallet_mut( + &self, + ) -> RwLockWriteGuard<&'a mut Wallet> { + self.wallet.write().await + } + + async fn shielded( + &self, + ) -> RwLockReadGuard<&'a mut ShieldedContext> { + self.shielded.read().await + } + + async fn shielded_mut( + &self, + ) -> RwLockWriteGuard<&'a mut ShieldedContext> { + self.shielded.write().await + } +} + +/// Allow the prototypical Tx builder to be modified +impl<'a, C, U, V, I> args::TxBuilder for NamadaImpl<'a, C, U, V, I> +where + C: queries::Client + Sync, + U: WalletIo, + V: ShieldedUtils, + I: Io, +{ + fn tx(self, func: F) -> Self + where + F: FnOnce(args::Tx) -> args::Tx, + { + Self { + prototype: func(self.prototype), + ..self + } + } +} diff --git a/shared/src/sdk/masp.rs b/sdk/src/masp.rs similarity index 89% rename from shared/src/sdk/masp.rs rename to sdk/src/masp.rs index 739f941b9a..a860839e3a 100644 --- a/shared/src/sdk/masp.rs +++ b/sdk/src/masp.rs @@ -49,8 +49,18 @@ use masp_proofs::bellman::groth16::PreparedVerifyingKey; use masp_proofs::bls12_381::Bls12; use masp_proofs::prover::LocalTxProver; use masp_proofs::sapling::SaplingVerificationContext; -use namada_core::types::token::{Change, MaspDenom}; -use namada_core::types::transaction::AffineCurve; +use namada_core::types::address::{masp, Address}; +use namada_core::types::masp::{ + BalanceOwner, ExtendedViewingKey, PaymentAddress, +}; +use namada_core::types::storage::{BlockHeight, Epoch, Key, KeySeg, TxIndex}; +use namada_core::types::token; +use namada_core::types::token::{ + Change, MaspDenom, Transfer, HEAD_TX_KEY, PIN_KEY_PREFIX, TX_KEY_PREFIX, +}; +use namada_core::types::transaction::{ + AffineCurve, EllipticCurve, PairingEngine, WrapperTx, +}; #[cfg(feature = "masp-tx-gen")] use rand_core::{CryptoRng, OsRng, RngCore}; use ripemd::Digest as RipemdDigest; @@ -58,25 +68,16 @@ use ripemd::Digest as RipemdDigest; use sha2::Digest; use thiserror::Error; +use crate::args::InputAmount; +use crate::error::{EncodingError, Error, PinnedBalanceError, QueryError}; +use crate::io::Io; use crate::proto::Tx; -use crate::sdk::args::InputAmount; -use crate::sdk::error::{EncodingError, Error, PinnedBalanceError, QueryError}; -use crate::sdk::queries::Client; -use crate::sdk::rpc::{query_conversion, query_storage_value}; -use crate::sdk::tx::decode_component; -use crate::sdk::{args, rpc}; +use crate::queries::Client; +use crate::rpc::{query_conversion, query_storage_value}; use crate::tendermint_rpc::query::Query; use crate::tendermint_rpc::Order; -use crate::types::address::{masp, Address}; -use crate::types::io::Io; -use crate::types::masp::{BalanceOwner, ExtendedViewingKey, PaymentAddress}; -use crate::types::storage::{BlockHeight, Epoch, Key, KeySeg, TxIndex}; -use crate::types::token; -use crate::types::token::{ - Transfer, HEAD_TX_KEY, PIN_KEY_PREFIX, TX_KEY_PREFIX, -}; -use crate::types::transaction::{EllipticCurve, PairingEngine, WrapperTx}; -use crate::{display_line, edisplay_line}; +use crate::tx::decode_component; +use crate::{args, display_line, edisplay_line, rpc, Namada}; /// Env var to point to a dir with MASP parameters. When not specified, /// the default OS specific path is used. @@ -396,10 +397,16 @@ pub trait ShieldedUtils: fn local_tx_prover(&self) -> LocalTxProver; /// Load up the currently saved ShieldedContext - async fn load(self) -> std::io::Result>; + async fn load( + &self, + ctx: &mut ShieldedContext, + ) -> std::io::Result<()>; - /// Sace the given ShieldedContext for future loads - async fn save(&self, ctx: &ShieldedContext) -> std::io::Result<()>; + /// Save the given ShieldedContext for future loads + async fn save( + &self, + ctx: &ShieldedContext, + ) -> std::io::Result<()>; } /// Make a ViewingKey that can view notes encrypted by given ExtendedSpendingKey @@ -620,9 +627,7 @@ impl ShieldedContext { /// Try to load the last saved shielded context from the given context /// directory. If this fails, then leave the current context unchanged. pub async fn load(&mut self) -> std::io::Result<()> { - let new_ctx = self.utils.clone().load().await?; - *self = new_ctx; - Ok(()) + self.utils.clone().load(self).await } /// Save this shielded context into its associated context directory @@ -1025,9 +1030,10 @@ impl ShieldedContext { /// context and express that value in terms of the currently timestamped /// asset types. If the key is not in the context, then we do not know the /// balance and hence we return None. - pub async fn compute_exchanged_balance( + pub async fn compute_exchanged_balance( &mut self, - client: &C, + client: &(impl Client + Sync), + io: &impl Io, vk: &ViewingKey, target_epoch: Epoch, ) -> Result, Error> { @@ -1035,8 +1041,9 @@ impl ShieldedContext { if let Some(balance) = self.compute_shielded_balance(client, vk).await? { let exchanged_amount = self - .compute_exchanged_amount::<_, IO>( + .compute_exchanged_amount( client, + io, balance, target_epoch, BTreeMap::new(), @@ -1058,9 +1065,10 @@ impl ShieldedContext { /// the trace amount that could not be converted is moved from input to /// output. #[allow(clippy::too_many_arguments)] - async fn apply_conversion( + async fn apply_conversion( &mut self, - client: &C, + client: &(impl Client + Sync), + io: &impl Io, conv: AllowedConversion, asset_type: (Epoch, Address, MaspDenom), value: i128, @@ -1080,7 +1088,7 @@ impl ShieldedContext { let threshold = -conv[&masp_asset]; if threshold == 0 { edisplay_line!( - IO, + io, "Asset threshold of selected conversion for asset type {} is \ 0, this is a bug, please report it.", masp_asset @@ -1110,9 +1118,10 @@ impl ShieldedContext { /// note of the conversions that were used. Note that this function does /// not assume that allowed conversions from the ledger are expressed in /// terms of the latest asset types. - pub async fn compute_exchanged_amount( + pub async fn compute_exchanged_amount( &mut self, - client: &C, + client: &(impl Client + Sync), + io: &impl Io, mut input: MaspAmount, target_epoch: Epoch, mut conversions: Conversions, @@ -1149,15 +1158,16 @@ impl ShieldedContext { (conversions.get_mut(&asset_type), at_target_asset_type) { display_line!( - IO, + io, "converting current asset type to latest asset type..." ); // Not at the target asset type, not at the latest asset // type. Apply conversion to get from // current asset type to the latest // asset type. - self.apply_conversion::<_, IO>( + self.apply_conversion( client, + io, conv.clone(), (asset_epoch, token_addr.clone(), denom), denom_value, @@ -1171,15 +1181,16 @@ impl ShieldedContext { at_target_asset_type, ) { display_line!( - IO, + io, "converting latest asset type to target asset type..." ); // Not at the target asset type, yet at the latest asset // type. Apply inverse conversion to get // from latest asset type to the target // asset type. - self.apply_conversion::<_, IO>( + self.apply_conversion( client, + io, conv.clone(), (asset_epoch, token_addr.clone(), denom), denom_value, @@ -1213,9 +1224,9 @@ impl ShieldedContext { /// of the specified asset type. Return the total value accumulated plus /// notes and the corresponding diversifiers/merkle paths that were used to /// achieve the total value. - pub async fn collect_unspent_notes( + pub async fn collect_unspent_notes<'a>( &mut self, - client: &C, + context: &impl Namada<'a>, vk: &ViewingKey, target: I128Sum, target_epoch: Epoch, @@ -1257,10 +1268,12 @@ impl ShieldedContext { .to_string(), ) })?; - let input = self.decode_all_amounts(client, pre_contr).await; + let input = + self.decode_all_amounts(context.client(), pre_contr).await; let (contr, proposed_convs) = self - .compute_exchanged_amount::<_, IO>( - client, + .compute_exchanged_amount( + context.client(), + context.io(), input, target_epoch, conversions.clone(), @@ -1398,31 +1411,37 @@ impl ShieldedContext { /// the epoch of the transaction or even before, so exchange all these /// amounts to the epoch of the transaction in order to get the value that /// would have been displayed in the epoch of the transaction. - pub async fn compute_exchanged_pinned_balance( + pub async fn compute_exchanged_pinned_balance<'a>( &mut self, - client: &C, + context: &impl Namada<'a>, owner: PaymentAddress, viewing_key: &ViewingKey, ) -> Result<(MaspAmount, Epoch), Error> { // Obtain the balance that will be exchanged let (amt, ep) = - Self::compute_pinned_balance(client, owner, viewing_key).await?; - display_line!(IO, "Pinned balance: {:?}", amt); + Self::compute_pinned_balance(context.client(), owner, viewing_key) + .await?; + display_line!(context.io(), "Pinned balance: {:?}", amt); // Establish connection with which to do exchange rate queries - let amount = self.decode_all_amounts(client, amt).await; - display_line!(IO, "Decoded pinned balance: {:?}", amount); + let amount = self.decode_all_amounts(context.client(), amt).await; + display_line!(context.io(), "Decoded pinned balance: {:?}", amount); // Finally, exchange the balance to the transaction's epoch let computed_amount = self - .compute_exchanged_amount::<_, IO>( - client, + .compute_exchanged_amount( + context.client(), + context.io(), amount, ep, BTreeMap::new(), ) .await? .0; - display_line!(IO, "Exchanged amount: {:?}", computed_amount); - Ok((self.decode_all_amounts(client, computed_amount).await, ep)) + display_line!(context.io(), "Exchanged amount: {:?}", computed_amount); + Ok(( + self.decode_all_amounts(context.client(), computed_amount) + .await, + ep, + )) } /// Convert an amount whose units are AssetTypes to one whose units are @@ -1483,10 +1502,9 @@ impl ShieldedContext { /// understood that transparent account changes are effected only by the /// amounts and signatures specified by the containing Transfer object. #[cfg(feature = "masp-tx-gen")] - pub async fn gen_shielded_transfer( - &mut self, - client: &C, - args: args::TxTransfer, + pub async fn gen_shielded_transfer<'a>( + context: &impl Namada<'a>, + args: &args::TxTransfer, ) -> Result, TransferErr> { // No shielded components are needed when neither source nor destination // are shielded @@ -1506,13 +1524,20 @@ impl ShieldedContext { // We want to fund our transaction solely from supplied spending key let spending_key = spending_key.map(|x| x.into()); let spending_keys: Vec<_> = spending_key.into_iter().collect(); - // Load the current shielded context given the spending key we possess - let _ = self.load().await; - self.fetch(client, &spending_keys, &[]).await?; - // Save the update state so that future fetches can be short-circuited - let _ = self.save().await; + { + // Load the current shielded context given the spending key we + // possess + let mut shielded = context.shielded_mut().await; + let _ = shielded.load().await; + shielded + .fetch(context.client(), &spending_keys, &[]) + .await?; + // Save the update state so that future fetches can be + // short-circuited + let _ = shielded.save().await; + } // Determine epoch in which to submit potential shielded transaction - let epoch = rpc::query_epoch(client).await?; + let epoch = rpc::query_epoch(context.client()).await?; // Context required for storing which notes are in the source's // possesion let memo = MemoBytes::empty(); @@ -1552,9 +1577,11 @@ impl ShieldedContext { // If there are shielded inputs if let Some(sk) = spending_key { // Locate unspent notes that can help us meet the transaction amount - let (_, unspent_notes, used_convs) = self - .collect_unspent_notes::<_, IO>( - client, + let (_, unspent_notes, used_convs) = context + .shielded_mut() + .await + .collect_unspent_notes( + context, &to_viewing_key(&sk).vk, I128Sum::from_sum(amount), epoch, @@ -1740,17 +1767,17 @@ impl ShieldedContext { Error::from(EncodingError::Conversion(e.to_string())) })?; - let build_transfer = - || -> Result> { - let (masp_tx, metadata) = builder.build( - &self.utils.local_tx_prover(), - &FeeRule::non_standard(U64Sum::zero()), - )?; - Ok(ShieldedTransfer { - builder: builder_clone, - masp_tx, - metadata, - epoch, + let build_transfer = |prover: LocalTxProver| -> Result< + ShieldedTransfer, + builder::Error, + > { + let (masp_tx, metadata) = builder + .build(&prover, &FeeRule::non_standard(U64Sum::zero()))?; + Ok(ShieldedTransfer { + builder: builder_clone, + masp_tx, + metadata, + epoch, }) }; @@ -1796,7 +1823,9 @@ impl ShieldedContext { Ok(Some(loaded)) } else { // Build and return the constructed transaction - let built = build_transfer()?; + let built = build_transfer( + context.shielded().await.utils.local_tx_prover(), + )?; if let LoadOrSaveProofs::Save = load_or_save { let built_bytes = BorshSerialize::try_to_vec(&built) .map_err(|e| { @@ -1815,7 +1844,9 @@ impl ShieldedContext { #[cfg(not(feature = "testing"))] { // Build and return the constructed transaction - let built = build_transfer()?; + let built = build_transfer( + context.shielded().await.utils.local_tx_prover(), + )?; Ok(Some(built)) } } @@ -2108,3 +2139,133 @@ mod tests { super::load_pvks(); } } + +#[cfg(feature = "std")] +/// Implementation of MASP functionality depending on a standard filesystem +pub mod fs { + use std::fs::{File, OpenOptions}; + use std::io::{Read, Write}; + + use async_trait::async_trait; + + use super::*; + + /// Shielded context file name + const FILE_NAME: &str = "shielded.dat"; + const TMP_FILE_NAME: &str = "shielded.tmp"; + + #[derive(Debug, BorshSerialize, BorshDeserialize, Clone)] + /// An implementation of ShieldedUtils for standard filesystems + pub struct FsShieldedUtils { + #[borsh_skip] + context_dir: PathBuf, + } + + impl FsShieldedUtils { + /// Initialize a shielded transaction context that identifies notes + /// decryptable by any viewing key in the given set + pub fn new(context_dir: PathBuf) -> ShieldedContext { + // Make sure that MASP parameters are downloaded to enable MASP + // transaction building and verification later on + let params_dir = get_params_dir(); + let spend_path = params_dir.join(SPEND_NAME); + let convert_path = params_dir.join(CONVERT_NAME); + let output_path = params_dir.join(OUTPUT_NAME); + if !(spend_path.exists() + && convert_path.exists() + && output_path.exists()) + { + println!("MASP parameters not present, downloading..."); + masp_proofs::download_masp_parameters(None) + .expect("MASP parameters not present or downloadable"); + println!( + "MASP parameter download complete, resuming execution..." + ); + } + // Finally initialize a shielded context with the supplied directory + let utils = Self { context_dir }; + ShieldedContext { + utils, + ..Default::default() + } + } + } + + impl Default for FsShieldedUtils { + fn default() -> Self { + Self { + context_dir: PathBuf::from(FILE_NAME), + } + } + } + + #[async_trait(?Send)] + impl ShieldedUtils for FsShieldedUtils { + fn local_tx_prover(&self) -> LocalTxProver { + if let Ok(params_dir) = env::var(ENV_VAR_MASP_PARAMS_DIR) { + let params_dir = PathBuf::from(params_dir); + let spend_path = params_dir.join(SPEND_NAME); + let convert_path = params_dir.join(CONVERT_NAME); + let output_path = params_dir.join(OUTPUT_NAME); + LocalTxProver::new(&spend_path, &output_path, &convert_path) + } else { + LocalTxProver::with_default_location() + .expect("unable to load MASP Parameters") + } + } + + /// Try to load the last saved shielded context from the given context + /// directory. If this fails, then leave the current context unchanged. + async fn load( + &self, + ctx: &mut ShieldedContext, + ) -> std::io::Result<()> { + // Try to load shielded context from file + let mut ctx_file = File::open(self.context_dir.join(FILE_NAME))?; + let mut bytes = Vec::new(); + ctx_file.read_to_end(&mut bytes)?; + // Fill the supplied context with the deserialized object + *ctx = ShieldedContext { + utils: ctx.utils.clone(), + ..ShieldedContext::::deserialize(&mut &bytes[..])? + }; + Ok(()) + } + + /// Save this shielded context into its associated context directory + async fn save( + &self, + ctx: &ShieldedContext, + ) -> std::io::Result<()> { + // TODO: use mktemp crate? + let tmp_path = self.context_dir.join(TMP_FILE_NAME); + { + // First serialize the shielded context into a temporary file. + // Inability to create this file implies a simultaneuous write + // is in progress. In this case, immediately + // fail. This is unproblematic because the data + // intended to be stored can always be re-fetched + // from the blockchain. + let mut ctx_file = OpenOptions::new() + .write(true) + .create_new(true) + .open(tmp_path.clone())?; + let mut bytes = Vec::new(); + ctx.serialize(&mut bytes) + .expect("cannot serialize shielded context"); + ctx_file.write_all(&bytes[..])?; + } + // Atomically update the old shielded context file with new data. + // Atomicity is required to prevent other client instances from + // reading corrupt data. + std::fs::rename( + tmp_path.clone(), + self.context_dir.join(FILE_NAME), + )?; + // Finally, remove our temporary file to allow future saving of + // shielded contexts. + std::fs::remove_file(tmp_path)?; + Ok(()) + } + } +} diff --git a/shared/src/sdk/queries.rs b/sdk/src/queries/mod.rs similarity index 59% rename from shared/src/sdk/queries.rs rename to sdk/src/queries/mod.rs index a7cb9badb1..fdd5b042a8 100644 --- a/shared/src/sdk/queries.rs +++ b/sdk/src/queries/mod.rs @@ -1,7 +1,207 @@ -//! Query functionality related to the SDK -use std::fmt::{Debug, Display}; +//! Ledger read-only queries can be handled and dispatched via the [`RPC`] +//! defined via `router!` macro. +// Re-export to show in rustdoc! +use namada_core::ledger::storage::traits::StorageHasher; +use namada_core::ledger::storage::{DBIter, DB}; +use namada_core::ledger::storage_api; use namada_core::types::storage::BlockHeight; +pub use shell::Shell; +use shell::SHELL; +pub use types::{ + EncodedResponseQuery, Error, RequestCtx, RequestQuery, ResponseQuery, + Router, +}; +use vp::{Vp, VP}; + +pub use self::shell::eth_bridge::{ + Erc20FlowControl, GenBridgePoolProofReq, GenBridgePoolProofRsp, + TransferToErcArgs, +}; + +#[macro_use] +mod router; +mod shell; +mod types; +pub mod vp; + +// Most commonly expected patterns should be declared first +router! {RPC, + // Shell provides storage read access, block metadata and can dry-run a tx + ( "shell" ) = (sub SHELL), + + // Validity-predicate's specific storage queries + ( "vp" ) = (sub VP), +} + +/// Handle RPC query request in the ledger. On success, returns response with +/// borsh-encoded data. +pub fn handle_path( + ctx: RequestCtx<'_, D, H, V, T>, + request: &RequestQuery, +) -> storage_api::Result +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ + RPC.handle(ctx, request) +} + +// Handler helpers: + +/// For queries that only support latest height, check that the given height is +/// not different from latest height, otherwise return an error. +pub fn require_latest_height( + ctx: &RequestCtx<'_, D, H, V, T>, + request: &RequestQuery, +) -> storage_api::Result<()> +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ + if request.height != BlockHeight(0) + && request.height != ctx.wl_storage.storage.get_last_block_height() + { + return Err(storage_api::Error::new_const( + "This query doesn't support arbitrary block heights, only the \ + latest committed block height ('0' can be used as a special \ + value that means the latest block height)", + )); + } + Ok(()) +} + +/// For queries that do not support proofs, check that proof is not requested, +/// otherwise return an error. +pub fn require_no_proof(request: &RequestQuery) -> storage_api::Result<()> { + if request.prove { + return Err(storage_api::Error::new_const( + "This query doesn't support proofs", + )); + } + Ok(()) +} + +/// For queries that don't use request data, require that there are no data +/// attached. +pub fn require_no_data(request: &RequestQuery) -> storage_api::Result<()> { + if !request.data.is_empty() { + return Err(storage_api::Error::new_const( + "This query doesn't accept request data", + )); + } + Ok(()) +} + +/// Queries testing helpers +#[cfg(any(test, feature = "testing"))] +mod testing { + + use namada_core::ledger::storage::testing::TestWlStorage; + use namada_core::types::storage::BlockHeight; + use tendermint_rpc::Response; + + use super::*; + use crate::events::log::EventLog; + use crate::tendermint_rpc::error::Error as RpcError; + + /// A test client that has direct access to the storage + pub struct TestClient + where + RPC: Router, + { + /// RPC router + pub rpc: RPC, + /// storage + pub wl_storage: TestWlStorage, + /// event log + pub event_log: EventLog, + } + + impl TestClient + where + RPC: Router, + { + #[allow(dead_code)] + /// Initialize a test client for the given root RPC router + pub fn new(rpc: RPC) -> Self { + // Initialize the `TestClient` + let mut wl_storage = TestWlStorage::default(); + + // Initialize mock gas limit + let max_block_gas_key = + namada_core::ledger::parameters::storage::get_max_block_gas_key( + ); + wl_storage + .storage + .write( + &max_block_gas_key, + namada_core::ledger::storage::types::encode( + &20_000_000_u64, + ), + ) + .expect( + "Max block gas parameter must be initialized in storage", + ); + let event_log = EventLog::default(); + Self { + rpc, + wl_storage, + event_log, + } + } + } + + #[cfg_attr(feature = "async-send", async_trait::async_trait)] + #[cfg_attr(not(feature = "async-send"), async_trait::async_trait(?Send))] + impl Client for TestClient + where + RPC: Router + Sync, + { + type Error = std::io::Error; + + async fn request( + &self, + path: String, + data: Option>, + height: Option, + prove: bool, + ) -> Result { + let data = data.unwrap_or_default(); + let height = height.unwrap_or_default(); + // Handle a path by invoking the `RPC.handle` directly with the + // borrowed storage + let request = RequestQuery { + data, + path, + height, + prove, + }; + let ctx = RequestCtx { + wl_storage: &self.wl_storage, + event_log: &self.event_log, + vp_wasm_cache: (), + tx_wasm_cache: (), + storage_read_past_height_limit: None, + }; + // TODO: this is a hack to propagate errors to the caller, we should + // really permit error types other than [`std::io::Error`] + self.rpc.handle(ctx, &request).map_err(|err| { + std::io::Error::new(std::io::ErrorKind::Other, err.to_string()) + }) + } + + async fn perform(&self, _request: R) -> Result + where + R: tendermint_rpc::SimpleRequest, + { + Response::from_string("TODO") + } + } +} + +use std::fmt::{Debug, Display}; + use tendermint_rpc::endpoint::{ abci_info, block, block_results, blockchain, commit, consensus_params, consensus_state, health, net_info, status, @@ -9,7 +209,6 @@ use tendermint_rpc::endpoint::{ use tendermint_rpc::query::Query; use tendermint_rpc::{Error as RpcError, Order}; -use crate::ledger::queries::{EncodedResponseQuery, Error}; use crate::tendermint::block::Height; /// A client with async request dispatcher method, which can be used to invoke diff --git a/shared/src/ledger/queries/router.rs b/sdk/src/queries/router.rs similarity index 92% rename from shared/src/ledger/queries/router.rs rename to sdk/src/queries/router.rs index 799a34e5bd..9783d21309 100644 --- a/shared/src/ledger/queries/router.rs +++ b/sdk/src/queries/router.rs @@ -82,16 +82,16 @@ macro_rules! handle_match { break } // Check that the request is not sent with unsupported non-default - $crate::ledger::queries::require_latest_height(&$ctx, $request)?; - $crate::ledger::queries::require_no_proof($request)?; - $crate::ledger::queries::require_no_data($request)?; + $crate::queries::require_latest_height(&$ctx, $request)?; + $crate::queries::require_no_proof($request)?; + $crate::queries::require_no_data($request)?; // If you get a compile error from here with `expected function, found // queries::Storage`, you're probably missing the marker `(sub _)` let data = $handle($ctx, $( $matched_args ),* )?; // Encode the returned data with borsh let data = borsh::BorshSerialize::try_to_vec(&data).into_storage_result()?; - return Ok($crate::ledger::queries::EncodedResponseQuery { + return Ok($crate::queries::EncodedResponseQuery { data, info: Default::default(), proof: None, @@ -401,22 +401,22 @@ macro_rules! pattern_and_handler_to_method { `storage_value` and `storage_prefix`) from `storage_value`."] pub async fn storage_value(&self, client: &CLIENT, data: Option>, - height: Option<$crate::types::storage::BlockHeight>, + height: Option, prove: bool, $( $param: &$param_ty ),* ) -> std::result::Result< - $crate::ledger::queries::ResponseQuery>, - ::Error + $crate::queries::ResponseQuery>, + ::Error > - where CLIENT: $crate::ledger::queries::Client + std::marker::Sync { + where CLIENT: $crate::queries::Client + std::marker::Sync { let path = self.storage_value_path( $( $param ),* ); - let $crate::ledger::queries::ResponseQuery { + let $crate::queries::ResponseQuery { data, info, proof } = client.request(path, data, height, prove).await?; - Ok($crate::ledger::queries::ResponseQuery { + Ok($crate::queries::ResponseQuery { data, info, proof, @@ -453,25 +453,25 @@ macro_rules! pattern_and_handler_to_method { `storage_value` and `storage_prefix`) from `" $handle "`."] pub async fn $handle(&self, client: &CLIENT, data: Option>, - height: Option<$crate::types::storage::BlockHeight>, + height: Option, prove: bool, $( $param: &$param_ty ),* ) -> std::result::Result< - $crate::ledger::queries::ResponseQuery<$return_type>, - ::Error + $crate::queries::ResponseQuery<$return_type>, + ::Error > - where CLIENT: $crate::ledger::queries::Client + std::marker::Sync { + where CLIENT: $crate::queries::Client + std::marker::Sync { let path = self.[<$handle _path>]( $( $param ),* ); - let $crate::ledger::queries::ResponseQuery { + let $crate::queries::ResponseQuery { data, info, proof } = client.request(path, data, height, prove).await?; let decoded: $return_type = borsh::BorshDeserialize::try_from_slice(&data[..])?; - Ok($crate::ledger::queries::ResponseQuery { + Ok($crate::queries::ResponseQuery { data: decoded, info, proof, @@ -510,9 +510,9 @@ macro_rules! pattern_and_handler_to_method { ) -> std::result::Result< $return_type, - ::Error + ::Error > - where CLIENT: $crate::ledger::queries::Client + std::marker::Sync { + where CLIENT: $crate::queries::Client + std::marker::Sync { let path = self.[<$handle _path>]( $( $param ),* ); let data = client.simple_request(path).await?; @@ -783,25 +783,25 @@ macro_rules! router { router_type!{[<$name:camel>] {}, $( $pattern $( -> $return_type )? = $handle ),* } - impl $crate::ledger::queries::Router for [<$name:camel>] { + impl $crate::queries::Router for [<$name:camel>] { // TODO: for some patterns, there's unused assignment of `$end` #[allow(unused_assignments)] - fn internal_handle( + fn internal_handle( &self, - ctx: $crate::ledger::queries::RequestCtx<'_, D, H>, - request: &$crate::ledger::queries::RequestQuery, + ctx: $crate::queries::RequestCtx<'_, D, H, V, T>, + request: &$crate::queries::RequestQuery, start: usize - ) -> $crate::ledger::storage_api::Result<$crate::ledger::queries::EncodedResponseQuery> + ) -> namada_core::ledger::storage_api::Result<$crate::queries::EncodedResponseQuery> where - D: 'static + $crate::ledger::storage::DB + for<'iter> $crate::ledger::storage::DBIter<'iter> + Sync, - H: 'static + $crate::ledger::storage::StorageHasher + Sync, + D: 'static + namada_core::ledger::storage::DB + for<'iter> namada_core::ledger::storage::DBIter<'iter> + Sync, + H: 'static + namada_core::ledger::storage::StorageHasher + Sync, { // Import for `.into_storage_result()` - use $crate::ledger::storage_api::ResultExt; + use namada_core::ledger::storage_api::ResultExt; // Import helper from this crate used inside the macros - use $crate::ledger::queries::router::find_next_slash_index; + use $crate::queries::router::find_next_slash_index; $( // This loop never repeats, it's only used for a breaking @@ -816,7 +816,7 @@ macro_rules! router { )* return Err( - $crate::ledger::queries::router::Error::WrongPath(request.path.clone())) + $crate::queries::router::Error::WrongPath(request.path.clone())) .into_storage_result(); } } @@ -835,14 +835,14 @@ macro_rules! router { #[cfg(test)] mod test_rpc_handlers { use borsh::BorshSerialize; + use namada_core::ledger::storage::{DBIter, StorageHasher, DB}; + use namada_core::ledger::storage_api::{self, ResultExt}; + use namada_core::types::storage::Epoch; + use namada_core::types::token; - use crate::ledger::queries::{ + use crate::queries::{ EncodedResponseQuery, RequestCtx, RequestQuery, ResponseQuery, }; - use crate::ledger::storage::{DBIter, StorageHasher, DB}; - use crate::ledger::storage_api::{self, ResultExt}; - use crate::types::storage::Epoch; - use crate::types::token; /// A little macro to generate boilerplate for RPC handler functions. /// These are implemented to return their name as a String, joined by @@ -854,8 +854,8 @@ mod test_rpc_handlers { // optional trailing comma $(,)? ) => { $( - pub fn $name( - _ctx: RequestCtx<'_, D, H>, + pub fn $name( + _ctx: RequestCtx<'_, D, H, V, T>, $( $( $param: $param_ty ),* )? ) -> storage_api::Result where @@ -901,8 +901,8 @@ mod test_rpc_handlers { /// This handler is hand-written, because the test helper macro doesn't /// support optional args. - pub fn b3iii( - _ctx: RequestCtx<'_, D, H>, + pub fn b3iii( + _ctx: RequestCtx<'_, D, H, V, T>, a1: token::DenominatedAmount, a2: token::DenominatedAmount, a3: Option, @@ -920,8 +920,8 @@ mod test_rpc_handlers { /// This handler is hand-written, because the test helper macro doesn't /// support optional args. - pub fn b3iiii( - _ctx: RequestCtx<'_, D, H>, + pub fn b3iiii( + _ctx: RequestCtx<'_, D, H, V, T>, a1: token::DenominatedAmount, a2: token::DenominatedAmount, a3: Option, @@ -941,8 +941,8 @@ mod test_rpc_handlers { /// This handler is hand-written, because the test helper macro doesn't /// support handlers with `with_options`. - pub fn c( - _ctx: RequestCtx<'_, D, H>, + pub fn c( + _ctx: RequestCtx<'_, D, H, V, T>, _request: &RequestQuery, ) -> storage_api::Result where @@ -963,9 +963,10 @@ mod test_rpc_handlers { /// ``` #[cfg(test)] mod test_rpc { + use namada_core::types::storage::Epoch; + use namada_core::types::token; + use super::test_rpc_handlers::*; - use crate::types::storage::Epoch; - use crate::types::token; // Setup an RPC router for testing router! {TEST_RPC, @@ -1000,14 +1001,14 @@ mod test_rpc { #[cfg(test)] mod test { + use namada_core::ledger::storage_api; + use namada_core::types::storage::Epoch; + use namada_core::types::token; use namada_core::types::token::NATIVE_MAX_DECIMAL_PLACES; use super::test_rpc::TEST_RPC; - use crate::ledger::queries::testing::TestClient; - use crate::ledger::queries::{RequestCtx, RequestQuery, Router}; - use crate::ledger::storage_api; - use crate::types::storage::Epoch; - use crate::types::token; + use crate::queries::testing::TestClient; + use crate::queries::{RequestCtx, RequestQuery, Router}; /// Test all the possible paths in `TEST_RPC` router. #[tokio::test] @@ -1022,8 +1023,8 @@ mod test { let ctx = RequestCtx { event_log: &client.event_log, wl_storage: &client.wl_storage, - vp_wasm_cache: client.vp_wasm_cache.clone(), - tx_wasm_cache: client.tx_wasm_cache.clone(), + vp_wasm_cache: (), + tx_wasm_cache: (), storage_read_past_height_limit: None, }; let result = TEST_RPC.handle(ctx, &request); diff --git a/shared/src/ledger/queries/shell.rs b/sdk/src/queries/shell.rs similarity index 58% rename from shared/src/ledger/queries/shell.rs rename to sdk/src/queries/shell.rs index a766846916..22ba39feda 100644 --- a/shared/src/ledger/queries/shell.rs +++ b/sdk/src/queries/shell.rs @@ -4,27 +4,27 @@ use borsh::{BorshDeserialize, BorshSerialize}; use masp_primitives::asset_type::AssetType; use masp_primitives::merkle_tree::MerklePath; use masp_primitives::sapling::Node; -use namada_core::ledger::storage::LastBlock; +use namada_core::ledger::storage::traits::StorageHasher; +use namada_core::ledger::storage::{DBIter, LastBlock, DB}; +use namada_core::ledger::storage_api::{self, ResultExt, StorageRead}; use namada_core::types::account::{Account, AccountPublicKeysMap}; use namada_core::types::address::Address; use namada_core::types::hash::Hash; -use namada_core::types::storage::{BlockHeight, BlockResults, KeySeg}; +use namada_core::types::storage::{ + self, BlockHeight, BlockResults, Epoch, KeySeg, PrefixValue, +}; use namada_core::types::token::MaspDenom; +#[cfg(any(test, feature = "async-client"))] +use namada_core::types::transaction::TxResult; use self::eth_bridge::{EthBridge, ETH_BRIDGE}; +use crate::events::log::dumb_queries; +use crate::events::{Event, EventType}; use crate::ibc::core::ics04_channel::packet::Sequence; use crate::ibc::core::ics24_host::identifier::{ChannelId, ClientId, PortId}; -use crate::ledger::events::log::dumb_queries; -use crate::ledger::events::{Event, EventType}; -use crate::ledger::queries::types::{RequestCtx, RequestQuery}; -use crate::ledger::queries::{require_latest_height, EncodedResponseQuery}; -use crate::ledger::storage::traits::StorageHasher; -use crate::ledger::storage::{DBIter, DB}; -use crate::ledger::storage_api::{self, ResultExt, StorageRead}; +use crate::queries::types::{RequestCtx, RequestQuery}; +use crate::queries::{require_latest_height, EncodedResponseQuery}; use crate::tendermint::merkle::proof::Proof; -use crate::types::storage::{self, Epoch, PrefixValue}; -#[cfg(any(test, feature = "async-client"))] -use crate::types::transaction::TxResult; type Conversion = ( Address, @@ -43,6 +43,9 @@ router! {SHELL, // Epoch of the last committed block ( "epoch" ) -> Epoch = epoch, + // The address of the native token + ( "native_token" ) -> Address = native_token, + // Epoch of the input block height ( "epoch_at_height" / [height: BlockHeight]) -> Option = epoch_at_height, @@ -91,114 +94,20 @@ router! {SHELL, // Handlers: -#[cfg(all(feature = "wasm-runtime", feature = "ferveo-tpke"))] -fn dry_run_tx( - mut ctx: RequestCtx<'_, D, H>, - request: &RequestQuery, +fn dry_run_tx( + _ctx: RequestCtx<'_, D, H, V, T>, + _request: &RequestQuery, ) -> storage_api::Result where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, { - use namada_core::ledger::gas::{Gas, GasMetering, TxGasMeter}; - use namada_core::ledger::storage::TempWlStorage; - use namada_core::types::transaction::DecryptedTx; - - use crate::ledger::protocol::{self, ShellParams}; - use crate::proto::Tx; - use crate::types::storage::TxIndex; - use crate::types::transaction::wrapper::wrapper_tx::PairingEngine; - use crate::types::transaction::{AffineCurve, EllipticCurve, TxType}; - - let mut tx = Tx::try_from(&request.data[..]).into_storage_result()?; - tx.validate_tx().into_storage_result()?; - - let mut temp_wl_storage = TempWlStorage::new(&ctx.wl_storage.storage); - let mut cumulated_gas = Gas::default(); - - // Wrapper dry run to allow estimating the gas cost of a transaction - let mut tx_gas_meter = match tx.header().tx_type { - TxType::Wrapper(wrapper) => { - let mut tx_gas_meter = - TxGasMeter::new(wrapper.gas_limit.to_owned()); - protocol::apply_wrapper_tx( - &wrapper, - None, - &request.data, - ShellParams::new( - &mut tx_gas_meter, - &mut temp_wl_storage, - &mut ctx.vp_wasm_cache, - &mut ctx.tx_wasm_cache, - ), - None, - ) - .into_storage_result()?; - - temp_wl_storage.write_log.commit_tx(); - cumulated_gas = tx_gas_meter.get_tx_consumed_gas(); - - // NOTE: the encryption key for a dry-run should always be an - // hardcoded, dummy one - let _privkey = - ::G2Affine::prime_subgroup_generator(); - tx.update_header(TxType::Decrypted(DecryptedTx::Decrypted)); - TxGasMeter::new_from_sub_limit(tx_gas_meter.get_available_gas()) - } - TxType::Protocol(_) | TxType::Decrypted(_) => { - // If dry run only the inner tx, use the max block gas as the gas - // limit - TxGasMeter::new( - namada_core::ledger::gas::get_max_block_gas(ctx.wl_storage) - .unwrap() - .into(), - ) - } - TxType::Raw => { - // Cast tx to a decrypted for execution - tx.update_header(TxType::Decrypted(DecryptedTx::Decrypted)); - - // If dry run only the inner tx, use the max block gas as the gas - // limit - TxGasMeter::new( - namada_core::ledger::gas::get_max_block_gas(ctx.wl_storage) - .unwrap() - .into(), - ) - } - }; - - let mut data = protocol::apply_wasm_tx( - tx, - &TxIndex(0), - ShellParams::new( - &mut tx_gas_meter, - &mut temp_wl_storage, - &mut ctx.vp_wasm_cache, - &mut ctx.tx_wasm_cache, - ), - ) - .into_storage_result()?; - cumulated_gas = cumulated_gas - .checked_add(tx_gas_meter.get_tx_consumed_gas()) - .ok_or(namada_core::ledger::storage_api::Error::SimpleMessage( - "Overflow in gas", - ))?; - // Account gas for both inner and wrapper (if available) - data.gas_used = cumulated_gas; - // NOTE: the keys changed by the wrapper transaction (if any) are not - // returned from this function - let data = data.try_to_vec().into_storage_result()?; - Ok(EncodedResponseQuery { - data, - proof: None, - info: Default::default(), - }) + unimplemented!("Dry running tx requires \"wasm-runtime\" feature.") } /// Query to read block results from storage -pub fn read_results( - ctx: RequestCtx<'_, D, H>, +pub fn read_results( + ctx: RequestCtx<'_, D, H, V, T>, ) -> storage_api::Result> where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, @@ -234,8 +143,8 @@ where } /// Query to read a conversion from storage -fn read_conversion( - ctx: RequestCtx<'_, D, H>, +fn read_conversion( + ctx: RequestCtx<'_, D, H, V, T>, asset_type: AssetType, ) -> storage_api::Result where @@ -267,29 +176,30 @@ where } } -#[cfg(not(all(feature = "wasm-runtime", feature = "ferveo-tpke")))] -fn dry_run_tx( - _ctx: RequestCtx<'_, D, H>, - _request: &RequestQuery, -) -> storage_api::Result +fn epoch( + ctx: RequestCtx<'_, D, H, V, T>, +) -> storage_api::Result where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, { - unimplemented!("Dry running tx requires \"wasm-runtime\" feature.") + let data = ctx.wl_storage.storage.last_epoch; + Ok(data) } -fn epoch(ctx: RequestCtx<'_, D, H>) -> storage_api::Result +fn native_token( + ctx: RequestCtx<'_, D, H, V, T>, +) -> storage_api::Result
where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, { - let data = ctx.wl_storage.storage.last_epoch; + let data = ctx.wl_storage.storage.native_token.clone(); Ok(data) } -fn epoch_at_height( - ctx: RequestCtx<'_, D, H>, +fn epoch_at_height( + ctx: RequestCtx<'_, D, H, V, T>, height: BlockHeight, ) -> storage_api::Result> where @@ -299,8 +209,8 @@ where Ok(ctx.wl_storage.storage.block.pred_epochs.get_epoch(height)) } -fn last_block( - ctx: RequestCtx<'_, D, H>, +fn last_block( + ctx: RequestCtx<'_, D, H, V, T>, ) -> storage_api::Result> where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, @@ -313,8 +223,8 @@ where /// borsh-encoded types, it is safe to check `data.is_empty()` to see if the /// value was found, except for unit - see `fn query_storage_value` in /// `apps/src/lib/client/rpc.rs` for unit type handling via `storage_has_key`. -fn storage_value( - ctx: RequestCtx<'_, D, H>, +fn storage_value( + ctx: RequestCtx<'_, D, H, V, T>, request: &RequestQuery, storage_key: storage::Key, ) -> storage_api::Result @@ -380,8 +290,8 @@ where } } -fn storage_prefix( - ctx: RequestCtx<'_, D, H>, +fn storage_prefix( + ctx: RequestCtx<'_, D, H, V, T>, request: &RequestQuery, storage_key: storage::Key, ) -> storage_api::Result @@ -423,8 +333,8 @@ where }) } -fn storage_has_key( - ctx: RequestCtx<'_, D, H>, +fn storage_has_key( + ctx: RequestCtx<'_, D, H, V, T>, storage_key: storage::Key, ) -> storage_api::Result where @@ -435,8 +345,8 @@ where Ok(data) } -fn accepted( - ctx: RequestCtx<'_, D, H>, +fn accepted( + ctx: RequestCtx<'_, D, H, V, T>, tx_hash: Hash, ) -> storage_api::Result> where @@ -452,8 +362,8 @@ where .cloned()) } -fn applied( - ctx: RequestCtx<'_, D, H>, +fn applied( + ctx: RequestCtx<'_, D, H, V, T>, tx_hash: Hash, ) -> storage_api::Result> where @@ -469,8 +379,8 @@ where .cloned()) } -fn ibc_client_update( - ctx: RequestCtx<'_, D, H>, +fn ibc_client_update( + ctx: RequestCtx<'_, D, H, V, T>, client_id: ClientId, consensus_height: BlockHeight, ) -> storage_api::Result> @@ -490,8 +400,8 @@ where .cloned()) } -fn ibc_packet( - ctx: RequestCtx<'_, D, H>, +fn ibc_packet( + ctx: RequestCtx<'_, D, H, V, T>, event_type: EventType, source_port: PortId, source_channel: ChannelId, @@ -519,8 +429,8 @@ where .cloned()) } -fn account( - ctx: RequestCtx<'_, D, H>, +fn account( + ctx: RequestCtx<'_, D, H, V, T>, owner: Address, ) -> storage_api::Result> where @@ -545,8 +455,8 @@ where } } -fn revealed( - ctx: RequestCtx<'_, D, H>, +fn revealed( + ctx: RequestCtx<'_, D, H, V, T>, owner: Address, ) -> storage_api::Result where @@ -561,18 +471,9 @@ where #[cfg(test)] mod test { - use borsh::{BorshDeserialize, BorshSerialize}; - use namada_test_utils::TestWasms; - - use crate::ledger::queries::testing::TestClient; - use crate::ledger::queries::RPC; - use crate::ledger::storage_api::{self, StorageWrite}; - use crate::proto::{Code, Data, Tx}; - use crate::types::hash::Hash; - use crate::types::storage::Key; - use crate::types::transaction::decrypted::DecryptedTx; - use crate::types::transaction::TxType; - use crate::types::{address, token}; + use namada_core::types::{address, token}; + + use crate::queries::RPC; #[test] fn test_shell_queries_router_paths() { @@ -594,106 +495,4 @@ mod test { let path = RPC.shell().storage_has_key_path(&key); assert_eq!(format!("/shell/has_key/{}", key), path); } - - #[tokio::test] - async fn test_shell_queries_router_with_client() -> storage_api::Result<()> - { - // Initialize the `TestClient` - let mut client = TestClient::new(RPC); - // store the wasm code - let tx_no_op = TestWasms::TxNoOp.read_bytes(); - let tx_hash = Hash::sha256(&tx_no_op); - let key = Key::wasm_code(&tx_hash); - let len_key = Key::wasm_code_len(&tx_hash); - client.wl_storage.storage.write(&key, &tx_no_op).unwrap(); - client - .wl_storage - .storage - .write(&len_key, (tx_no_op.len() as u64).try_to_vec().unwrap()) - .unwrap(); - - // Request last committed epoch - let read_epoch = RPC.shell().epoch(&client).await.unwrap(); - let current_epoch = client.wl_storage.storage.last_epoch; - assert_eq!(current_epoch, read_epoch); - - // Request dry run tx - let mut outer_tx = - Tx::from_type(TxType::Decrypted(DecryptedTx::Decrypted)); - outer_tx.header.chain_id = client.wl_storage.storage.chain_id.clone(); - outer_tx.set_code(Code::from_hash(tx_hash)); - outer_tx.set_data(Data::new(vec![])); - let tx_bytes = outer_tx.to_bytes(); - let result = RPC - .shell() - .dry_run_tx(&client, Some(tx_bytes), None, false) - .await - .unwrap(); - assert!(result.data.is_accepted()); - - // Request storage value for a balance key ... - let token_addr = address::testing::established_address_1(); - let owner = address::testing::established_address_2(); - let balance_key = token::balance_key(&token_addr, &owner); - // ... there should be no value yet. - let read_balance = RPC - .shell() - .storage_value(&client, None, None, false, &balance_key) - .await - .unwrap(); - assert!(read_balance.data.is_empty()); - - // Request storage prefix iterator - let balance_prefix = token::balance_prefix(&token_addr); - let read_balances = RPC - .shell() - .storage_prefix(&client, None, None, false, &balance_prefix) - .await - .unwrap(); - assert!(read_balances.data.is_empty()); - - // Request storage has key - let has_balance_key = RPC - .shell() - .storage_has_key(&client, &balance_key) - .await - .unwrap(); - assert!(!has_balance_key); - - // Then write some balance ... - let balance = token::Amount::native_whole(1000); - StorageWrite::write(&mut client.wl_storage, &balance_key, balance)?; - // It has to be committed to be visible in a query - client.wl_storage.commit_tx(); - client.wl_storage.commit_block().unwrap(); - // ... there should be the same value now - let read_balance = RPC - .shell() - .storage_value(&client, None, None, false, &balance_key) - .await - .unwrap(); - assert_eq!( - balance, - token::Amount::try_from_slice(&read_balance.data).unwrap() - ); - - // Request storage prefix iterator - let balance_prefix = token::balance_prefix(&token_addr); - let read_balances = RPC - .shell() - .storage_prefix(&client, None, None, false, &balance_prefix) - .await - .unwrap(); - assert_eq!(read_balances.data.len(), 1); - - // Request storage has key - let has_balance_key = RPC - .shell() - .storage_has_key(&client, &balance_key) - .await - .unwrap(); - assert!(has_balance_key); - - Ok(()) - } } diff --git a/shared/src/ledger/queries/shell/eth_bridge.rs b/sdk/src/queries/shell/eth_bridge.rs similarity index 96% rename from shared/src/ledger/queries/shell/eth_bridge.rs rename to sdk/src/queries/shell/eth_bridge.rs index 0bbc0aa679..6baf649992 100644 --- a/shared/src/ledger/queries/shell/eth_bridge.rs +++ b/sdk/src/queries/shell/eth_bridge.rs @@ -12,12 +12,17 @@ use namada_core::ledger::storage_api::{ self, CustomError, ResultExt, StorageRead, }; use namada_core::types::address::Address; -use namada_core::types::eth_bridge_pool::PendingTransferAppendix; +use namada_core::types::eth_abi::{Encode, EncodeCell}; +use namada_core::types::eth_bridge_pool::{ + PendingTransfer, PendingTransferAppendix, +}; use namada_core::types::ethereum_events::{ EthAddress, EthereumEvent, TransferToEthereum, }; use namada_core::types::ethereum_structs; -use namada_core::types::storage::{BlockHeight, DbKeySeg, Key}; +use namada_core::types::keccak::KeccakHash; +use namada_core::types::storage::MembershipProof::BridgePool; +use namada_core::types::storage::{BlockHeight, DbKeySeg, Epoch, Key}; use namada_core::types::token::Amount; use namada_core::types::vote_extensions::validator_set_update::{ ValidatorSetArgs, VotingPowersMap, @@ -36,12 +41,7 @@ use namada_ethereum_bridge::storage::{ use namada_proof_of_stake::pos_queries::PosQueries; use crate::eth_bridge::ethers::abi::AbiDecode; -use crate::ledger::queries::{EncodedResponseQuery, RequestCtx, RequestQuery}; -use crate::types::eth_abi::{Encode, EncodeCell}; -use crate::types::eth_bridge_pool::PendingTransfer; -use crate::types::keccak::KeccakHash; -use crate::types::storage::Epoch; -use crate::types::storage::MembershipProof::BridgePool; +use crate::queries::{EncodedResponseQuery, RequestCtx, RequestQuery}; /// Contains information about the flow control of some ERC20 /// wrapped asset. @@ -167,8 +167,8 @@ router! {ETH_BRIDGE, /// Read the total supply and respective cap of some wrapped /// ERC20 token in Namada. -fn get_erc20_flow_control( - ctx: RequestCtx<'_, D, H>, +fn get_erc20_flow_control( + ctx: RequestCtx<'_, D, H, V, T>, asset: EthAddress, ) -> storage_api::Result where @@ -191,9 +191,9 @@ where } /// Helper function to read a smart contract from storage. -fn read_contract( +fn read_contract( key: &Key, - ctx: RequestCtx<'_, D, H>, + ctx: RequestCtx<'_, D, H, V, U>, ) -> storage_api::Result where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, @@ -212,8 +212,8 @@ where /// Read the address and version of the Ethereum bridge's Bridge /// smart contract. #[inline] -fn read_bridge_contract( - ctx: RequestCtx<'_, D, H>, +fn read_bridge_contract( + ctx: RequestCtx<'_, D, H, V, T>, ) -> storage_api::Result where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, @@ -225,8 +225,8 @@ where /// Read the address of the Ethereum bridge's native ERC20 /// smart contract. #[inline] -fn read_native_erc20_contract( - ctx: RequestCtx<'_, D, H>, +fn read_native_erc20_contract( + ctx: RequestCtx<'_, D, H, V, T>, ) -> storage_api::Result where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, @@ -237,8 +237,8 @@ where /// Read the current contents of the Ethereum bridge /// pool. -fn read_ethereum_bridge_pool( - ctx: RequestCtx<'_, D, H>, +fn read_ethereum_bridge_pool( + ctx: RequestCtx<'_, D, H, V, T>, ) -> storage_api::Result> where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, @@ -252,8 +252,8 @@ where /// Read the contents of the Ethereum bridge /// pool covered by the latest signed root. -fn read_signed_ethereum_bridge_pool( - ctx: RequestCtx<'_, D, H>, +fn read_signed_ethereum_bridge_pool( + ctx: RequestCtx<'_, D, H, V, T>, ) -> storage_api::Result> where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, @@ -272,9 +272,9 @@ where } /// Read the Ethereum bridge pool contents at a specified height. -fn read_ethereum_bridge_pool_at_height( +fn read_ethereum_bridge_pool_at_height( height: BlockHeight, - ctx: RequestCtx<'_, D, H>, + ctx: RequestCtx<'_, D, H, V, T>, ) -> Vec where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, @@ -311,8 +311,8 @@ where /// Generate a merkle proof for the inclusion of the /// requested transfers in the Ethereum bridge pool. -fn generate_bridge_pool_proof( - ctx: RequestCtx<'_, D, H>, +fn generate_bridge_pool_proof( + ctx: RequestCtx<'_, D, H, V, T>, request: &RequestQuery, ) -> storage_api::Result where @@ -444,8 +444,8 @@ where /// Iterates over all ethereum events /// and returns the amount of voting power /// backing each `TransferToEthereum` event. -fn transfer_to_ethereum_progress( - ctx: RequestCtx<'_, D, H>, +fn transfer_to_ethereum_progress( + ctx: RequestCtx<'_, D, H, V, T>, ) -> storage_api::Result> where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, @@ -518,8 +518,8 @@ where /// /// This method may fail if a complete proof (i.e. with more than /// 2/3 of the total voting power behind it) is not available yet. -fn read_valset_upd_proof( - ctx: RequestCtx<'_, D, H>, +fn read_valset_upd_proof( + ctx: RequestCtx<'_, D, H, V, T>, epoch: Epoch, ) -> storage_api::Result>> where @@ -568,8 +568,8 @@ where /// /// This method may fail if no set of validators exists yet, /// at that [`Epoch`]. -fn read_bridge_valset( - ctx: RequestCtx<'_, D, H>, +fn read_bridge_valset( + ctx: RequestCtx<'_, D, H, V, T>, epoch: Epoch, ) -> storage_api::Result where @@ -598,8 +598,8 @@ where /// /// This method may fail if no set of validators exists yet, /// at that [`Epoch`]. -fn read_governance_valset( - ctx: RequestCtx<'_, D, H>, +fn read_governance_valset( + ctx: RequestCtx<'_, D, H, V, T>, epoch: Epoch, ) -> storage_api::Result where @@ -626,8 +626,8 @@ where /// Retrieve the consensus validator voting powers at the /// given [`BlockHeight`]. -fn voting_powers_at_height( - ctx: RequestCtx<'_, D, H>, +fn voting_powers_at_height( + ctx: RequestCtx<'_, D, H, V, T>, height: BlockHeight, ) -> storage_api::Result where @@ -645,8 +645,8 @@ where /// Retrieve the consensus validator voting powers at the /// given [`Epoch`]. -fn voting_powers_at_epoch( - ctx: RequestCtx<'_, D, H>, +fn voting_powers_at_epoch( + ctx: RequestCtx<'_, D, H, V, T>, epoch: Epoch, ) -> storage_api::Result where @@ -678,7 +678,13 @@ mod test_ethbridge_router { use namada_core::ledger::eth_bridge::storage::whitelist; use namada_core::ledger::storage::mockdb::MockDBWriteBatch; use namada_core::ledger::storage_api::StorageWrite; + use namada_core::types::address::nam; use namada_core::types::address::testing::established_address_1; + use namada_core::types::eth_abi::Encode; + use namada_core::types::eth_bridge_pool::{ + GasFee, PendingTransfer, TransferToEthereum, TransferToEthereumKind, + }; + use namada_core::types::ethereum_events::EthAddress; use namada_core::types::storage::BlockHeight; use namada_core::types::vote_extensions::validator_set_update; use namada_core::types::vote_extensions::validator_set_update::{ @@ -693,14 +699,8 @@ mod test_ethbridge_router { use super::test_utils::bertha_address; use super::*; - use crate::ledger::queries::testing::TestClient; - use crate::ledger::queries::RPC; - use crate::types::address::nam; - use crate::types::eth_abi::Encode; - use crate::types::eth_bridge_pool::{ - GasFee, PendingTransfer, TransferToEthereum, TransferToEthereumKind, - }; - use crate::types::ethereum_events::EthAddress; + use crate::queries::testing::TestClient; + use crate::queries::RPC; /// Test that reading the bridge validator set works. #[tokio::test] diff --git a/shared/src/ledger/queries/types.rs b/sdk/src/queries/types.rs similarity index 88% rename from shared/src/ledger/queries/types.rs rename to sdk/src/queries/types.rs index 235bf76e99..7283982099 100644 --- a/shared/src/ledger/queries/types.rs +++ b/sdk/src/queries/types.rs @@ -1,21 +1,16 @@ use std::fmt::Debug; -use namada_core::ledger::storage::WlStorage; +use namada_core::ledger::storage::{DBIter, StorageHasher, WlStorage, DB}; +use namada_core::ledger::storage_api; +use namada_core::types::storage::BlockHeight; use thiserror::Error; -use crate::ledger::events::log::EventLog; -use crate::ledger::storage::{DBIter, StorageHasher, DB}; -use crate::ledger::storage_api; +use crate::events::log::EventLog; use crate::tendermint::merkle::proof::Proof; -use crate::types::storage::BlockHeight; -#[cfg(feature = "wasm-runtime")] -use crate::vm::wasm::{TxCache, VpCache}; -#[cfg(feature = "wasm-runtime")] -use crate::vm::WasmCacheRoAccess; /// A request context provides read-only access to storage and WASM compilation /// caches to request handlers. #[derive(Debug, Clone)] -pub struct RequestCtx<'shell, D, H> +pub struct RequestCtx<'shell, D, H, VpCache, TxCache> where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, @@ -25,11 +20,9 @@ where /// Log of events emitted by `FinalizeBlock` ABCI calls. pub event_log: &'shell EventLog, /// Cache of VP wasm compiled artifacts. - #[cfg(feature = "wasm-runtime")] - pub vp_wasm_cache: VpCache, + pub vp_wasm_cache: VpCache, /// Cache of transaction wasm compiled artifacts. - #[cfg(feature = "wasm-runtime")] - pub tx_wasm_cache: TxCache, + pub tx_wasm_cache: TxCache, /// Taken from config `storage_read_past_height_limit`. When set, will /// limit the how many block heights in the past can the storage be /// queried for reading values. @@ -41,9 +34,9 @@ where pub trait Router { /// Handle a given request using the provided context. This must be invoked /// on the root `Router` to be able to match the `request.path` fully. - fn handle( + fn handle( &self, - ctx: RequestCtx<'_, D, H>, + ctx: RequestCtx<'_, D, H, V, T>, request: &RequestQuery, ) -> storage_api::Result where @@ -59,9 +52,9 @@ pub trait Router { /// Handle a given request using the provided context, starting to /// try to match `request.path` against the `Router`'s patterns at the /// given `start` offset. - fn internal_handle( + fn internal_handle( &self, - ctx: RequestCtx<'_, D, H>, + ctx: RequestCtx<'_, D, H, V, T>, request: &RequestQuery, start: usize, ) -> storage_api::Result diff --git a/shared/src/ledger/queries/vp/governance.rs b/sdk/src/queries/vp/governance.rs similarity index 75% rename from shared/src/ledger/queries/vp/governance.rs rename to sdk/src/queries/vp/governance.rs index 92c3495f24..1e3a5a8ece 100644 --- a/shared/src/ledger/queries/vp/governance.rs +++ b/sdk/src/queries/vp/governance.rs @@ -1,12 +1,12 @@ // cd shared && cargo expand ledger::queries::vp::governance +use namada_core::ledger::governance::parameters::GovernanceParameters; use namada_core::ledger::governance::storage::proposal::StorageProposal; use namada_core::ledger::governance::utils::Vote; +use namada_core::ledger::storage::{DBIter, StorageHasher, DB}; +use namada_core::ledger::storage_api; -use crate::core::ledger::governance::parameters::GovernanceParameters; -use crate::ledger::queries::types::RequestCtx; -use crate::ledger::storage::{DBIter, StorageHasher, DB}; -use crate::ledger::storage_api; +use crate::queries::types::RequestCtx; // Governance queries router! {GOV, @@ -16,8 +16,8 @@ router! {GOV, } /// Find if the given address belongs to a validator account. -fn proposal_id( - ctx: RequestCtx<'_, D, H>, +fn proposal_id( + ctx: RequestCtx<'_, D, H, V, T>, id: u64, ) -> storage_api::Result> where @@ -28,8 +28,8 @@ where } /// Find if the given address belongs to a validator account. -fn proposal_id_votes( - ctx: RequestCtx<'_, D, H>, +fn proposal_id_votes( + ctx: RequestCtx<'_, D, H, V, T>, id: u64, ) -> storage_api::Result> where @@ -40,8 +40,8 @@ where } /// Get the governane parameters -fn parameters( - ctx: RequestCtx<'_, D, H>, +fn parameters( + ctx: RequestCtx<'_, D, H, V, T>, ) -> storage_api::Result where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, diff --git a/shared/src/ledger/queries/vp/mod.rs b/sdk/src/queries/vp/mod.rs similarity index 100% rename from shared/src/ledger/queries/vp/mod.rs rename to sdk/src/queries/vp/mod.rs diff --git a/shared/src/ledger/queries/vp/pgf.rs b/sdk/src/queries/vp/pgf.rs similarity index 76% rename from shared/src/ledger/queries/vp/pgf.rs rename to sdk/src/queries/vp/pgf.rs index 8f5b14c91b..9e8ea2f5cc 100644 --- a/shared/src/ledger/queries/vp/pgf.rs +++ b/sdk/src/queries/vp/pgf.rs @@ -1,11 +1,11 @@ use namada_core::ledger::governance::storage::proposal::StoragePgfFunding; +use namada_core::ledger::pgf::parameters::PgfParameters; use namada_core::ledger::pgf::storage::steward::StewardDetail; +use namada_core::ledger::storage::{DBIter, StorageHasher, DB}; +use namada_core::ledger::storage_api; use namada_core::types::address::Address; -use crate::core::ledger::pgf::parameters::PgfParameters; -use crate::ledger::queries::types::RequestCtx; -use crate::ledger::storage::{DBIter, StorageHasher, DB}; -use crate::ledger::storage_api; +use crate::queries::types::RequestCtx; // PoS validity predicate queries router! {PGF, @@ -16,8 +16,8 @@ router! {PGF, } /// Query the currect pgf steward set -fn stewards( - ctx: RequestCtx<'_, D, H>, +fn stewards( + ctx: RequestCtx<'_, D, H, V, T>, ) -> storage_api::Result> where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, @@ -27,8 +27,8 @@ where } /// Check if an address is a pgf steward -fn is_steward( - ctx: RequestCtx<'_, D, H>, +fn is_steward( + ctx: RequestCtx<'_, D, H, V, T>, address: Address, ) -> storage_api::Result where @@ -39,8 +39,8 @@ where } /// Query the continous pgf fundings -fn funding( - ctx: RequestCtx<'_, D, H>, +fn funding( + ctx: RequestCtx<'_, D, H, V, T>, ) -> storage_api::Result> where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, @@ -50,8 +50,8 @@ where } /// Query the PGF parameters -fn parameters( - ctx: RequestCtx<'_, D, H>, +fn parameters( + ctx: RequestCtx<'_, D, H, V, T>, ) -> storage_api::Result where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, diff --git a/shared/src/ledger/queries/vp/pos.rs b/sdk/src/queries/vp/pos.rs similarity index 90% rename from shared/src/ledger/queries/vp/pos.rs rename to sdk/src/queries/vp/pos.rs index e78bff146b..875779447d 100644 --- a/shared/src/ledger/queries/vp/pos.rs +++ b/sdk/src/queries/vp/pos.rs @@ -3,8 +3,13 @@ use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use namada_core::ledger::storage::{DBIter, StorageHasher, DB}; +use namada_core::ledger::storage_api; use namada_core::ledger::storage_api::collections::lazy_map; use namada_core::ledger::storage_api::OptionExt; +use namada_core::types::address::Address; +use namada_core::types::storage::Epoch; +use namada_core::types::token; use namada_proof_of_stake::parameters::PosParams; use namada_proof_of_stake::types::{ BondId, BondsAndUnbondsDetail, BondsAndUnbondsDetails, CommissionPair, @@ -21,12 +26,7 @@ use namada_proof_of_stake::{ validator_slashes_handle, validator_state_handle, }; -use crate::ledger::queries::types::RequestCtx; -use crate::ledger::storage::{DBIter, StorageHasher, DB}; -use crate::ledger::storage_api; -use crate::types::address::Address; -use crate::types::storage::Epoch; -use crate::types::token; +use crate::queries::types::RequestCtx; type AmountPair = (token::Amount, token::Amount); @@ -148,7 +148,9 @@ impl Enriched { // Handlers that implement the functions via `trait StorageRead`: /// Get the PoS parameters -fn pos_params(ctx: RequestCtx<'_, D, H>) -> storage_api::Result +fn pos_params( + ctx: RequestCtx<'_, D, H, V, T>, +) -> storage_api::Result where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, @@ -157,8 +159,8 @@ where } /// Find if the given address belongs to a validator account. -fn is_validator( - ctx: RequestCtx<'_, D, H>, +fn is_validator( + ctx: RequestCtx<'_, D, H, V, T>, addr: Address, ) -> storage_api::Result where @@ -169,8 +171,8 @@ where } /// Find if the given address is a delegator -fn is_delegator( - ctx: RequestCtx<'_, D, H>, +fn is_delegator( + ctx: RequestCtx<'_, D, H, V, T>, addr: Address, epoch: Option, ) -> storage_api::Result @@ -183,8 +185,8 @@ where /// Get all the validator known addresses. These validators may be in any state, /// e.g. consensus, below-capacity, inactive or jailed. -fn validator_addresses( - ctx: RequestCtx<'_, D, H>, +fn validator_addresses( + ctx: RequestCtx<'_, D, H, V, T>, epoch: Option, ) -> storage_api::Result> where @@ -196,8 +198,8 @@ where } /// Get the validator commission rate and max commission rate change per epoch -fn validator_commission( - ctx: RequestCtx<'_, D, H>, +fn validator_commission( + ctx: RequestCtx<'_, D, H, V, T>, validator: Address, epoch: Option, ) -> storage_api::Result> @@ -227,8 +229,8 @@ where } /// Get the validator state -fn validator_state( - ctx: RequestCtx<'_, D, H>, +fn validator_state( + ctx: RequestCtx<'_, D, H, V, T>, validator: Address, epoch: Option, ) -> storage_api::Result> @@ -251,8 +253,8 @@ where /// to their address. /// Returns `None` when the given address is not a validator address. For a /// validator with `0` stake, this returns `Ok(token::Amount::zero())`. -fn validator_stake( - ctx: RequestCtx<'_, D, H>, +fn validator_stake( + ctx: RequestCtx<'_, D, H, V, T>, validator: Address, epoch: Option, ) -> storage_api::Result> @@ -266,8 +268,8 @@ where } /// Get all the validator in the consensus set with their bonded stake. -fn consensus_validator_set( - ctx: RequestCtx<'_, D, H>, +fn consensus_validator_set( + ctx: RequestCtx<'_, D, H, V, T>, epoch: Option, ) -> storage_api::Result> where @@ -279,8 +281,8 @@ where } /// Get all the validator in the below-capacity set with their bonded stake. -fn below_capacity_validator_set( - ctx: RequestCtx<'_, D, H>, +fn below_capacity_validator_set( + ctx: RequestCtx<'_, D, H, V, T>, epoch: Option, ) -> storage_api::Result> where @@ -295,8 +297,8 @@ where } /// Get the total stake in PoS system at the given epoch or current when `None`. -fn total_stake( - ctx: RequestCtx<'_, D, H>, +fn total_stake( + ctx: RequestCtx<'_, D, H, V, T>, epoch: Option, ) -> storage_api::Result where @@ -308,8 +310,8 @@ where read_total_stake(ctx.wl_storage, ¶ms, epoch) } -fn bond_deltas( - ctx: RequestCtx<'_, D, H>, +fn bond_deltas( + ctx: RequestCtx<'_, D, H, V, T>, source: Address, validator: Address, ) -> storage_api::Result> @@ -322,8 +324,8 @@ where /// Find the sum of bond amount up the given epoch when `Some`, or up to the /// pipeline length parameter offset otherwise -fn bond( - ctx: RequestCtx<'_, D, H>, +fn bond( + ctx: RequestCtx<'_, D, H, V, T>, source: Address, validator: Address, epoch: Option, @@ -343,8 +345,8 @@ where .ok_or_err_msg("Cannot find bond") } -fn bond_with_slashing( - ctx: RequestCtx<'_, D, H>, +fn bond_with_slashing( + ctx: RequestCtx<'_, D, H, V, T>, source: Address, validator: Address, epoch: Option, @@ -359,8 +361,8 @@ where bond_amount(ctx.wl_storage, &bond_id, epoch) } -fn unbond( - ctx: RequestCtx<'_, D, H>, +fn unbond( + ctx: RequestCtx<'_, D, H, V, T>, source: Address, validator: Address, ) -> storage_api::Result> @@ -384,8 +386,8 @@ where .collect() } -fn unbond_with_slashing( - ctx: RequestCtx<'_, D, H>, +fn unbond_with_slashing( + ctx: RequestCtx<'_, D, H, V, T>, source: Address, validator: Address, ) -> storage_api::Result> @@ -410,8 +412,8 @@ where .collect() } -fn withdrawable_tokens( - ctx: RequestCtx<'_, D, H>, +fn withdrawable_tokens( + ctx: RequestCtx<'_, D, H, V, T>, source: Address, validator: Address, epoch: Option, @@ -439,8 +441,8 @@ where Ok(total) } -fn bonds_and_unbonds( - ctx: RequestCtx<'_, D, H>, +fn bonds_and_unbonds( + ctx: RequestCtx<'_, D, H, V, T>, source: Option
, validator: Option
, ) -> storage_api::Result @@ -453,8 +455,8 @@ where /// Find all the validator addresses to whom the given `owner` address has /// some delegation in any epoch -fn delegation_validators( - ctx: RequestCtx<'_, D, H>, +fn delegation_validators( + ctx: RequestCtx<'_, D, H, V, T>, owner: Address, ) -> storage_api::Result> where @@ -466,8 +468,8 @@ where /// Find all the validator addresses to whom the given `owner` address has /// some delegation in any epoch -fn delegations( - ctx: RequestCtx<'_, D, H>, +fn delegations( + ctx: RequestCtx<'_, D, H, V, T>, owner: Address, epoch: Option, ) -> storage_api::Result> @@ -480,8 +482,8 @@ where } /// Validator slashes -fn validator_slashes( - ctx: RequestCtx<'_, D, H>, +fn validator_slashes( + ctx: RequestCtx<'_, D, H, V, T>, validator: Address, ) -> storage_api::Result> where @@ -493,8 +495,8 @@ where } /// All slashes -fn slashes( - ctx: RequestCtx<'_, D, H>, +fn slashes( + ctx: RequestCtx<'_, D, H, V, T>, ) -> storage_api::Result>> where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, @@ -504,8 +506,8 @@ where } /// Enqueued slashes -fn enqueued_slashes( - ctx: RequestCtx<'_, D, H>, +fn enqueued_slashes( + ctx: RequestCtx<'_, D, H, V, T>, ) -> storage_api::Result>>> where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, @@ -516,8 +518,8 @@ where } /// Native validator address by looking up the Tendermint address -fn validator_by_tm_addr( - ctx: RequestCtx<'_, D, H>, +fn validator_by_tm_addr( + ctx: RequestCtx<'_, D, H, V, T>, tm_addr: String, ) -> storage_api::Result> where @@ -531,8 +533,7 @@ where #[cfg(any(test, feature = "async-client"))] pub mod client_only_methods { use super::*; - use crate::ledger::queries::RPC; - use crate::sdk::queries::Client; + use crate::queries::{Client, RPC}; impl Pos { /// Get bonds and unbonds with all details (slashes and rewards, if any) diff --git a/shared/src/ledger/queries/vp/token.rs b/sdk/src/queries/vp/token.rs similarity index 87% rename from shared/src/ledger/queries/vp/token.rs rename to sdk/src/queries/vp/token.rs index 3b99cb0fda..0a2a5df509 100644 --- a/shared/src/ledger/queries/vp/token.rs +++ b/sdk/src/queries/vp/token.rs @@ -6,7 +6,7 @@ use namada_core::ledger::storage_api::token::read_denom; use namada_core::types::address::Address; use namada_core::types::token; -use crate::ledger::queries::RequestCtx; +use crate::queries::RequestCtx; router! {TOKEN, ( "denomination" / [addr: Address] ) -> Option = denomination, @@ -14,8 +14,8 @@ router! {TOKEN, /// Get the number of decimal places (in base 10) for a /// token specified by `addr`. -fn denomination( - ctx: RequestCtx<'_, D, H>, +fn denomination( + ctx: RequestCtx<'_, D, H, V, T>, addr: Address, ) -> storage_api::Result> where @@ -28,12 +28,11 @@ where #[cfg(any(test, feature = "async-client"))] pub mod client_only_methods { use borsh::BorshDeserialize; + use namada_core::types::address::Address; + use namada_core::types::token; use super::Token; - use crate::ledger::queries::RPC; - use crate::sdk::queries::Client; - use crate::types::address::Address; - use crate::types::token; + use crate::queries::{Client, RPC}; impl Token { /// Get the balance of the given `token` belonging to the given `owner`. diff --git a/shared/src/sdk/rpc.rs b/sdk/src/rpc.rs similarity index 83% rename from shared/src/sdk/rpc.rs rename to sdk/src/rpc.rs index 58609bed42..f1267fe8a8 100644 --- a/shared/src/sdk/rpc.rs +++ b/sdk/src/rpc.rs @@ -14,48 +14,45 @@ use namada_core::ledger::governance::utils::Vote; use namada_core::ledger::storage::LastBlock; use namada_core::types::account::Account; use namada_core::types::address::Address; -use namada_core::types::storage::Key; +use namada_core::types::hash::Hash; +use namada_core::types::key::common; +use namada_core::types::storage::{ + BlockHeight, BlockResults, Epoch, Key, PrefixValue, +}; use namada_core::types::token::{ Amount, DenominatedAmount, Denomination, MaspDenom, }; +use namada_core::types::{storage, token}; use namada_proof_of_stake::parameters::PosParams; use namada_proof_of_stake::types::{ BondsAndUnbondsDetails, CommissionPair, ValidatorState, }; use serde::Serialize; -use crate::ledger::events::Event; -use crate::ledger::queries::vp::pos::EnrichedBondsAndUnbondsDetails; -use crate::ledger::queries::RPC; +use crate::args::InputAmount; +use crate::control_flow::{time, Halt, TryHalt}; +use crate::error::{EncodingError, Error, QueryError}; +use crate::events::Event; +use crate::io::Io; use crate::proto::Tx; -use crate::sdk::args::InputAmount; -use crate::sdk::error; -use crate::sdk::error::{EncodingError, Error, QueryError}; +use crate::queries::vp::pos::EnrichedBondsAndUnbondsDetails; +use crate::queries::{Client, RPC}; use crate::tendermint::block::Height; use crate::tendermint::merkle::proof::Proof; use crate::tendermint_rpc::error::Error as TError; use crate::tendermint_rpc::query::Query; use crate::tendermint_rpc::Order; -use crate::types::control_flow::{time, Halt, TryHalt}; -use crate::types::hash::Hash; -use crate::types::io::Io; -use crate::types::key::common; -use crate::types::storage::{BlockHeight, BlockResults, Epoch, PrefixValue}; -use crate::types::{storage, token}; -use crate::{display_line, edisplay_line}; +use crate::{display_line, edisplay_line, error, Namada}; /// Query the status of a given transaction. /// /// If a response is not delivered until `deadline`, we exit the cli with an /// error. -pub async fn query_tx_status( - client: &C, +pub async fn query_tx_status<'a>( + context: &impl Namada<'a>, status: TxEventQuery<'_>, deadline: time::Instant, -) -> Halt -where - C: crate::ledger::queries::Client + Sync, -{ +) -> Halt { time::Sleep { strategy: time::LinearBackoff { delta: time::Duration::from_secs(1), @@ -63,7 +60,8 @@ where } .timeout(deadline, || async { tracing::debug!(query = ?status, "Querying tx status"); - let maybe_event = match query_tx_events(client, status).await { + let maybe_event = match query_tx_events(context.client(), status).await + { Ok(response) => response, Err(err) => { tracing::debug!( @@ -90,23 +88,30 @@ where .await .try_halt(|_| { edisplay_line!( - IO, + context.io(), "Transaction status query deadline of {deadline:?} exceeded" ); }) } /// Query the epoch of the last committed block -pub async fn query_epoch( +pub async fn query_epoch( client: &C, ) -> Result { convert_response::(RPC.shell().epoch(client).await) } +/// Query the address of the native token +pub async fn query_native_token( + client: &C, +) -> Result { + convert_response::(RPC.shell().native_token(client).await) +} + /// Query the epoch of the given block height, if it exists. /// Will return none if the input block height is greater than /// the latest committed block height. -pub async fn query_epoch_at_height( +pub async fn query_epoch_at_height( client: &C, height: BlockHeight, ) -> Result, error::Error> { @@ -114,7 +119,7 @@ pub async fn query_epoch_at_height( } /// Query the last committed block, if any. -pub async fn query_block( +pub async fn query_block( client: &C, ) -> Result, error::Error> { // NOTE: We're not using `client.latest_block()` because it may return an @@ -123,7 +128,7 @@ pub async fn query_block( } /// A helper to unwrap client's response. Will shut down process on error. -fn unwrap_client_response( +fn unwrap_client_response( response: Result, ) -> T { response.unwrap_or_else(|err| { @@ -134,21 +139,21 @@ fn unwrap_client_response( /// A helper to turn client's response into an error type that can be used with /// ? The exact error type is a `QueryError::NoResponse`, and thus should be /// seen as getting no response back from a query. -fn convert_response( +fn convert_response( response: Result, ) -> Result { response.map_err(|err| Error::from(QueryError::NoResponse(err.to_string()))) } /// Query the results of the last committed block -pub async fn query_results( +pub async fn query_results( client: &C, ) -> Result, Error> { convert_response::(RPC.shell().read_results(client).await) } /// Query token amount of owner. -pub async fn get_token_balance( +pub async fn get_token_balance( client: &C, token: &Address, owner: &Address, @@ -159,7 +164,7 @@ pub async fn get_token_balance( } /// Check if the given address is a known validator. -pub async fn is_validator( +pub async fn is_validator( client: &C, address: &Address, ) -> Result { @@ -167,7 +172,7 @@ pub async fn is_validator( } /// Check if the given address is a pgf steward. -pub async fn is_steward( +pub async fn is_steward( client: &C, address: &Address, ) -> bool { @@ -177,7 +182,7 @@ pub async fn is_steward( } /// Check if a given address is a known delegator -pub async fn is_delegator( +pub async fn is_delegator( client: &C, address: &Address, ) -> Result { @@ -187,7 +192,7 @@ pub async fn is_delegator( } /// Check if a given address is a known delegator at the given epoch -pub async fn is_delegator_at( +pub async fn is_delegator_at( client: &C, address: &Address, epoch: Epoch, @@ -203,7 +208,7 @@ pub async fn is_delegator_at( /// Check if the address exists on chain. Established address exists if it has a /// stored validity predicate. Implicit and internal addresses always return /// true. -pub async fn known_address( +pub async fn known_address( client: &C, address: &Address, ) -> Result { @@ -221,7 +226,7 @@ pub async fn known_address( // often ignore the optional value and do not have any error type surrounding // it. /// Query a conversion. -pub async fn query_conversion( +pub async fn query_conversion( client: &C, asset_type: AssetType, ) -> Option<( @@ -237,22 +242,19 @@ pub async fn query_conversion( } /// Query a wasm code hash -pub async fn query_wasm_code_hash< - C: crate::ledger::queries::Client + Sync, - IO: Io, ->( - client: &C, +pub async fn query_wasm_code_hash<'a>( + context: &impl Namada<'a>, code_path: impl AsRef, ) -> Result { let hash_key = Key::wasm_hash(code_path.as_ref()); - match query_storage_value_bytes(client, &hash_key, None, false) + match query_storage_value_bytes(context.client(), &hash_key, None, false) .await? .0 { Some(hash) => Ok(Hash::try_from(&hash[..]).expect("Invalid code hash")), None => { edisplay_line!( - IO, + context.io(), "The corresponding wasm code of the code path {} doesn't \ exist on chain.", code_path.as_ref(), @@ -271,7 +273,7 @@ pub async fn query_storage_value( ) -> Result where T: BorshDeserialize, - C: crate::ledger::queries::Client + Sync, + C: crate::queries::Client + Sync, { // In case `T` is a unit (only thing that encodes to 0 bytes), we have to // use `storage_has_key` instead of `storage_value`, because `storage_value` @@ -302,9 +304,7 @@ where } /// Query a storage value and the proof without decoding. -pub async fn query_storage_value_bytes< - C: crate::ledger::queries::Client + Sync, ->( +pub async fn query_storage_value_bytes( client: &C, key: &storage::Key, height: Option, @@ -326,20 +326,16 @@ pub async fn query_storage_value_bytes< /// Query a range of storage values with a matching prefix and decode them with /// [`BorshDeserialize`]. Returns an iterator of the storage keys paired with /// their associated values. -pub async fn query_storage_prefix< - C: crate::ledger::queries::Client + Sync, - IO: Io, - T, ->( - client: &C, +pub async fn query_storage_prefix<'a, 'b, N: Namada<'a>, T>( + context: &'b N, key: &storage::Key, -) -> Result>, error::Error> +) -> Result>, error::Error> where T: BorshDeserialize, { - let values = convert_response::( + let values = convert_response::( RPC.shell() - .storage_prefix(client, None, None, false, key) + .storage_prefix(context.client(), None, None, false, key) .await, )?; let decode = @@ -348,7 +344,7 @@ where ) { Err(err) => { edisplay_line!( - IO, + context.io(), "Skipping a value for key {}. Error in decoding: {}", key, err @@ -365,7 +361,7 @@ where } /// Query to check if the given storage key exists. -pub async fn query_has_storage_key( +pub async fn query_has_storage_key( client: &C, key: &storage::Key, ) -> Result { @@ -415,13 +411,10 @@ impl<'a> From> for Query { /// Call the corresponding `tx_event_query` RPC method, to fetch /// the current status of a transation. -pub async fn query_tx_events( +pub async fn query_tx_events( client: &C, tx_event_query: TxEventQuery<'_>, -) -> std::result::Result< - Option, - ::Error, -> { +) -> std::result::Result, ::Error> { let tx_hash: Hash = tx_event_query.tx_hash().try_into().unwrap(); match tx_event_query { TxEventQuery::Accepted(_) => { @@ -437,16 +430,18 @@ pub async fn query_tx_events( } /// Dry run a transaction -pub async fn dry_run_tx( - client: &C, +pub async fn dry_run_tx<'a, N: Namada<'a>>( + context: &N, tx_bytes: Vec, ) -> Result { let (data, height, prove) = (Some(tx_bytes), None, false); - let result = convert_response::( - RPC.shell().dry_run_tx(client, data, height, prove).await, + let result = convert_response::( + RPC.shell() + .dry_run_tx(context.client(), data, height, prove) + .await, )? .data; - display_line!(IO, "Dry-run result: {}", result); + display_line!(context.io(), "Dry-run result: {}", result); Ok(result) } @@ -555,7 +550,7 @@ impl TxResponse { /// Lookup the full response accompanying the specified transaction event // TODO: maybe remove this in favor of `query_tx_status` -pub async fn query_tx_response( +pub async fn query_tx_response( client: &C, tx_query: TxEventQuery<'_>, ) -> Result { @@ -625,14 +620,14 @@ pub async fn query_tx_response( } /// Get the PoS parameters -pub async fn get_pos_params( +pub async fn get_pos_params( client: &C, ) -> Result { convert_response::(RPC.vp().pos().pos_params(client).await) } /// Get all validators in the given epoch -pub async fn get_all_validators( +pub async fn get_all_validators( client: &C, epoch: Epoch, ) -> Result, error::Error> { @@ -645,9 +640,7 @@ pub async fn get_all_validators( } /// Get the total staked tokens in the given epoch -pub async fn get_total_staked_tokens< - C: crate::ledger::queries::Client + Sync, ->( +pub async fn get_total_staked_tokens( client: &C, epoch: Epoch, ) -> Result { @@ -657,7 +650,7 @@ pub async fn get_total_staked_tokens< } /// Get the given validator's stake at the given epoch -pub async fn get_validator_stake( +pub async fn get_validator_stake( client: &C, epoch: Epoch, validator: &Address, @@ -672,7 +665,7 @@ pub async fn get_validator_stake( } /// Query and return a validator's state -pub async fn get_validator_state( +pub async fn get_validator_state( client: &C, validator: &Address, epoch: Option, @@ -686,9 +679,7 @@ pub async fn get_validator_state( } /// Get the delegator's delegation -pub async fn get_delegators_delegation< - C: crate::ledger::queries::Client + Sync, ->( +pub async fn get_delegators_delegation( client: &C, address: &Address, ) -> Result, error::Error> { @@ -698,9 +689,7 @@ pub async fn get_delegators_delegation< } /// Get the delegator's delegation at some epoh -pub async fn get_delegators_delegation_at< - C: crate::ledger::queries::Client + Sync, ->( +pub async fn get_delegators_delegation_at( client: &C, address: &Address, epoch: Epoch, @@ -714,7 +703,7 @@ pub async fn get_delegators_delegation_at< } /// Query proposal by Id -pub async fn query_proposal_by_id( +pub async fn query_proposal_by_id( client: &C, proposal_id: u64, ) -> Result, Error> { @@ -725,7 +714,7 @@ pub async fn query_proposal_by_id( /// Query and return validator's commission rate and max commission rate change /// per epoch -pub async fn query_commission_rate( +pub async fn query_commission_rate( client: &C, validator: &Address, epoch: Option, @@ -739,7 +728,7 @@ pub async fn query_commission_rate( } /// Query a validator's bonds for a given epoch -pub async fn query_bond( +pub async fn query_bond( client: &C, source: &Address, validator: &Address, @@ -751,7 +740,7 @@ pub async fn query_bond( } /// Query the accunt substorage space of an address -pub async fn get_account_info( +pub async fn get_account_info( client: &C, owner: &Address, ) -> Result, error::Error> { @@ -761,9 +750,7 @@ pub async fn get_account_info( } /// Query if the public_key is revealed -pub async fn is_public_key_revealed< - C: crate::ledger::queries::Client + Sync, ->( +pub async fn is_public_key_revealed( client: &C, owner: &Address, ) -> Result { @@ -771,7 +758,7 @@ pub async fn is_public_key_revealed< } /// Query an account substorage at a specific index -pub async fn get_public_key_at( +pub async fn get_public_key_at( client: &C, owner: &Address, index: u8, @@ -787,16 +774,14 @@ pub async fn get_public_key_at( } /// Query a validator's unbonds for a given epoch -pub async fn query_and_print_unbonds< - C: crate::ledger::queries::Client + Sync, - IO: Io, ->( - client: &C, +pub async fn query_and_print_unbonds<'a>( + context: &impl Namada<'a>, source: &Address, validator: &Address, ) -> Result<(), error::Error> { - let unbonds = query_unbond_with_slashing(client, source, validator).await?; - let current_epoch = query_epoch(client).await?; + let unbonds = + query_unbond_with_slashing(context.client(), source, validator).await?; + let current_epoch = query_epoch(context.client()).await?; let mut total_withdrawable = token::Amount::default(); let mut not_yet_withdrawable = HashMap::::new(); @@ -811,17 +796,17 @@ pub async fn query_and_print_unbonds< } if total_withdrawable != token::Amount::default() { display_line!( - IO, + context.io(), "Total withdrawable now: {}.", total_withdrawable.to_string_native() ); } if !not_yet_withdrawable.is_empty() { - display_line!(IO, "Current epoch: {current_epoch}.") + display_line!(context.io(), "Current epoch: {current_epoch}.") } for (withdraw_epoch, amount) in not_yet_withdrawable { display_line!( - IO, + context.io(), "Amount {} withdrawable starting from epoch {withdraw_epoch}.", amount.to_string_native() ); @@ -830,9 +815,7 @@ pub async fn query_and_print_unbonds< } /// Query withdrawable tokens in a validator account for a given epoch -pub async fn query_withdrawable_tokens< - C: crate::ledger::queries::Client + Sync, ->( +pub async fn query_withdrawable_tokens( client: &C, bond_source: &Address, validator: &Address, @@ -847,9 +830,7 @@ pub async fn query_withdrawable_tokens< } /// Query all unbonds for a validator, applying slashes -pub async fn query_unbond_with_slashing< - C: crate::ledger::queries::Client + Sync, ->( +pub async fn query_unbond_with_slashing( client: &C, source: &Address, validator: &Address, @@ -863,16 +844,14 @@ pub async fn query_unbond_with_slashing< } /// Get the givernance parameters -pub async fn query_governance_parameters< - C: crate::ledger::queries::Client + Sync, ->( +pub async fn query_governance_parameters( client: &C, ) -> GovernanceParameters { unwrap_client_response::(RPC.vp().gov().parameters(client).await) } /// Get the givernance parameters -pub async fn query_proposal_votes( +pub async fn query_proposal_votes( client: &C, proposal_id: u64, ) -> Result, error::Error> { @@ -882,7 +861,7 @@ pub async fn query_proposal_votes( } /// Get the bond amount at the given epoch -pub async fn get_bond_amount_at( +pub async fn get_bond_amount_at( client: &C, delegator: &Address, validator: &Address, @@ -899,7 +878,7 @@ pub async fn get_bond_amount_at( /// Get bonds and unbonds with all details (slashes and rewards, if any) /// grouped by their bond IDs. -pub async fn bonds_and_unbonds( +pub async fn bonds_and_unbonds( client: &C, source: &Option
, validator: &Option
, @@ -915,9 +894,7 @@ pub async fn bonds_and_unbonds( /// Get bonds and unbonds with all details (slashes and rewards, if any) /// grouped by their bond IDs, enriched with extra information calculated from /// the data. -pub async fn enriched_bonds_and_unbonds< - C: crate::ledger::queries::Client + Sync, ->( +pub async fn enriched_bonds_and_unbonds( client: &C, current_epoch: Epoch, source: &Option
, @@ -937,11 +914,8 @@ pub async fn enriched_bonds_and_unbonds< } /// Get the correct representation of the amount given the token type. -pub async fn validate_amount< - C: crate::ledger::queries::Client + Sync, - IO: Io, ->( - client: &C, +pub async fn validate_amount<'a, N: Namada<'a>>( + context: &N, amount: InputAmount, token: &Address, force: bool, @@ -951,21 +925,21 @@ pub async fn validate_amount< InputAmount::Unvalidated(amt) => amt.canonical(), InputAmount::Validated(amt) => return Ok(amt), }; - let denom = match convert_response::>( - RPC.vp().token().denomination(client, token).await, + let denom = match convert_response::>( + RPC.vp().token().denomination(context.client(), token).await, )? { Some(denom) => Ok(denom), None => { if force { display_line!( - IO, + context.io(), "No denomination found for token: {token}, but --force \ was passed. Defaulting to the provided denomination." ); Ok(input_amount.denom) } else { display_line!( - IO, + context.io(), "No denomination found for token: {token}, the input \ arguments could not be parsed." ); @@ -977,7 +951,7 @@ pub async fn validate_amount< }?; if denom < input_amount.denom && !force { display_line!( - IO, + context.io(), "The input amount contained a higher precision than allowed by \ {token}." ); @@ -988,7 +962,7 @@ pub async fn validate_amount< } else { input_amount.increase_precision(denom).map_err(|_err| { display_line!( - IO, + context.io(), "The amount provided requires more the 256 bits to represent." ); Error::from(QueryError::General( @@ -1001,10 +975,10 @@ pub async fn validate_amount< } /// Wait for a first block and node to be synced. -pub async fn wait_until_node_is_synched(client: &C) -> Halt<()> -where - C: crate::ledger::queries::Client + Sync, -{ +pub async fn wait_until_node_is_synched<'a>( + client: &(impl Client + Sync), + io: &impl Io, +) -> Halt<()> { let height_one = Height::try_from(1_u64).unwrap(); let try_count = Cell::new(1_u64); const MAX_TRIES: usize = 5; @@ -1026,7 +1000,7 @@ where return ControlFlow::Break(Ok(())); } display_line!( - IO, + io, " Waiting for {} ({}/{} tries)...", if is_at_least_height_one { "a first block" @@ -1041,7 +1015,7 @@ where } Err(e) => { edisplay_line!( - IO, + io, "Failed to query node status with error: {}", e ); @@ -1053,7 +1027,7 @@ where // maybe time out .try_halt(|_| { display_line!( - IO, + io, "Node is still catching up, wait for it to finish synching." ); })? @@ -1061,30 +1035,41 @@ where .try_halt(|_| ()) } -/// Look up the denomination of a token in order to format it -/// correctly as a string. -pub async fn format_denominated_amount< - C: crate::ledger::queries::Client + Sync, - IO: Io, ->( +/// Look up the denomination of a token in order to make a correctly denominated +/// amount. +pub async fn denominate_amount( client: &C, + io: &impl Io, token: &Address, amount: token::Amount, -) -> String { +) -> DenominatedAmount { let denom = convert_response::>( RPC.vp().token().denomination(client, token).await, ) .unwrap_or_else(|t| { - display_line!(IO, "Error in querying for denomination: {t}"); + display_line!(io, "Error in querying for denomination: {t}"); None }) .unwrap_or_else(|| { display_line!( - IO, + io, "No denomination found for token: {token}, defaulting to zero \ decimal places" ); 0.into() }); - DenominatedAmount { amount, denom }.to_string() + DenominatedAmount { amount, denom } +} + +/// Look up the denomination of a token in order to format it +/// correctly as a string. +pub async fn format_denominated_amount( + client: &(impl Client + Sync), + io: &impl Io, + token: &Address, + amount: token::Amount, +) -> String { + denominate_amount(client, io, token, amount) + .await + .to_string() } diff --git a/shared/src/sdk/signing.rs b/sdk/src/signing.rs similarity index 88% rename from shared/src/sdk/signing.rs rename to sdk/src/signing.rs index 042be03a63..db2aab3482 100644 --- a/shared/src/sdk/signing.rs +++ b/sdk/src/signing.rs @@ -9,52 +9,48 @@ use masp_primitives::asset_type::AssetType; use masp_primitives::transaction::components::sapling::fees::{ InputView, OutputView, }; +use namada_core::ledger::parameters::storage as parameter_storage; use namada_core::proto::SignatureIndex; use namada_core::types::account::AccountPublicKeysMap; use namada_core::types::address::{ masp, masp_tx_key, Address, ImplicitAddress, }; +use namada_core::types::key::*; +use namada_core::types::masp::{ExtendedViewingKey, PaymentAddress}; +use namada_core::types::storage::Epoch; use namada_core::types::token; +use namada_core::types::token::Transfer; // use namada_core::types::storage::Key; use namada_core::types::token::{Amount, DenominatedAmount, MaspDenom}; -use namada_core::types::transaction::pos; +use namada_core::types::transaction::account::{InitAccount, UpdateAccount}; +use namada_core::types::transaction::governance::{ + InitProposalData, VoteProposalData, +}; +use namada_core::types::transaction::pos::InitValidator; +use namada_core::types::transaction::{pos, Fee}; use prost::Message; use serde::{Deserialize, Serialize}; use sha2::Digest; use zeroize::Zeroizing; -use crate::display_line; +use super::masp::{ShieldedContext, ShieldedTransfer}; +use crate::args::SdkTypes; +use crate::error::{EncodingError, Error, TxError}; use crate::ibc::applications::transfer::msgs::transfer::MsgTransfer; use crate::ibc_proto::google::protobuf::Any; -use crate::ledger::parameters::storage as parameter_storage; +use crate::io::*; +use crate::masp::make_asset_type; use crate::proto::{MaspBuilder, Section, Tx}; -use crate::sdk::error::{EncodingError, Error, TxError}; -use crate::sdk::masp::{ - make_asset_type, ShieldedContext, ShieldedTransfer, ShieldedUtils, -}; -use crate::sdk::rpc::{ - format_denominated_amount, query_wasm_code_hash, validate_amount, -}; -use crate::sdk::tx::{ +use crate::rpc::{query_wasm_code_hash, validate_amount}; +use crate::tx::{ TX_BOND_WASM, TX_CHANGE_COMMISSION_WASM, TX_IBC_WASM, TX_INIT_ACCOUNT_WASM, TX_INIT_PROPOSAL, TX_INIT_VALIDATOR_WASM, TX_REVEAL_PK, TX_TRANSFER_WASM, TX_UNBOND_WASM, TX_UPDATE_ACCOUNT_WASM, TX_VOTE_PROPOSAL, TX_WITHDRAW_WASM, VP_USER_WASM, }; -pub use crate::sdk::wallet::store::AddressVpType; -use crate::sdk::wallet::{Wallet, WalletUtils}; -use crate::sdk::{args, rpc}; -use crate::types::io::*; -use crate::types::key::*; -use crate::types::masp::{ExtendedViewingKey, PaymentAddress}; -use crate::types::storage::Epoch; -use crate::types::token::Transfer; -use crate::types::transaction::account::{InitAccount, UpdateAccount}; -use crate::types::transaction::governance::{ - InitProposalData, VoteProposalData, -}; -use crate::types::transaction::pos::InitValidator; -use crate::types::transaction::Fee; +pub use crate::wallet::store::AddressVpType; +use crate::wallet::{Wallet, WalletIo}; +use crate::{args, display_line, rpc, Namada}; #[cfg(feature = "std")] /// Env. var specifying where to store signing test vectors @@ -82,31 +78,28 @@ pub struct SigningTxData { /// for it from the wallet. If the keypair is encrypted but a password is not /// supplied, then it is interactively prompted. Errors if the key cannot be /// found or loaded. -pub async fn find_pk< - C: crate::ledger::queries::Client + Sync, - U: WalletUtils, - IO: Io, ->( - client: &C, - wallet: &mut Wallet, +pub async fn find_pk<'a>( + context: &impl Namada<'a>, addr: &Address, password: Option>, ) -> Result { match addr { Address::Established(_) => { display_line!( - IO, + context.io(), "Looking-up public key of {} from the ledger...", addr.encode() ); - rpc::get_public_key_at(client, addr, 0) + rpc::get_public_key_at(context.client(), addr, 0) .await? .ok_or(Error::Other(format!( "No public key found for the address {}", addr.encode() ))) } - Address::Implicit(ImplicitAddress(pkh)) => Ok(wallet + Address::Implicit(ImplicitAddress(pkh)) => Ok(context + .wallet_mut() + .await .find_key_by_pkh(pkh, password) .map_err(|err| { Error::Other(format!( @@ -127,7 +120,7 @@ pub async fn find_pk< /// Load the secret key corresponding to the given public key from the wallet. /// If the keypair is encrypted but a password is not supplied, then it is /// interactively prompted. Errors if the key cannot be found or loaded. -pub fn find_key_by_pk( +pub fn find_key_by_pk( wallet: &mut Wallet, args: &args::Tx, public_key: &common::PublicKey, @@ -136,6 +129,21 @@ pub fn find_key_by_pk( // We already know the secret key corresponding to the MASP sentinal key Ok(masp_tx_key()) } else { + // Try to get the signer from the signing-keys argument + for signing_key in &args.signing_keys { + if signing_key.ref_to() == *public_key { + return Ok(signing_key.clone()); + } + } + // Try to get the signer from the wrapper-fee-payer argument + match &args.wrapper_fee_payer { + Some(wrapper_fee_payer) + if &wrapper_fee_payer.ref_to() == public_key => + { + return Ok(wrapper_fee_payer.clone()); + } + _ => {} + } // Otherwise we need to search the wallet for the secret key wallet .find_key_by_pk(public_key, args.password.clone()) @@ -153,14 +161,9 @@ pub fn find_key_by_pk( /// signer. Return the given signing key or public key of the given signer if /// possible. If no explicit signer given, use the `default`. If no `default` /// is given, an `Error` is returned. -pub async fn tx_signers< - C: crate::sdk::queries::Client + Sync, - U: WalletUtils, - IO: Io, ->( - client: &C, - wallet: &mut Wallet, - args: &args::Tx, +pub async fn tx_signers<'a>( + context: &impl Namada<'a>, + args: &args::Tx, default: Option
, ) -> Result, Error> { let signer = if !&args.signing_keys.is_empty() { @@ -179,8 +182,7 @@ pub async fn tx_signers< Some(signer) if signer == masp() => Ok(vec![masp_tx_key().ref_to()]), Some(signer) => Ok(vec![ - find_pk::(client, wallet, &signer, args.password.clone()) - .await?, + find_pk(context, &signer, args.password.clone()).await?, ]), None => other_err( "All transactions must be signed; please either specify the key \ @@ -201,7 +203,7 @@ pub async fn tx_signers< /// hashes needed for monitoring the tx on chain. /// /// If it is a dry run, it is not put in a wrapper, but returned as is. -pub fn sign_tx( +pub fn sign_tx( wallet: &mut Wallet, args: &args::Tx, tx: &mut Tx, @@ -242,27 +244,22 @@ pub fn sign_tx( /// Return the necessary data regarding an account to be able to generate a /// multisignature section -pub async fn aux_signing_data< - C: crate::sdk::queries::Client + Sync, - U: WalletUtils, - IO: Io, ->( - client: &C, - wallet: &mut Wallet, - args: &args::Tx, +pub async fn aux_signing_data<'a>( + context: &impl Namada<'a>, + args: &args::Tx, owner: Option
, default_signer: Option
, ) -> Result { let public_keys = if owner.is_some() || args.wrapper_fee_payer.is_none() { - tx_signers::(client, wallet, args, default_signer.clone()) - .await? + tx_signers(context, args, default_signer.clone()).await? } else { vec![] }; let (account_public_keys_map, threshold) = match &owner { Some(owner @ Address::Established(_)) => { - let account = rpc::get_account_info::(client, owner).await?; + let account = + rpc::get_account_info(context.client(), owner).await?; if let Some(account) = account { (Some(account.public_keys_map), account.threshold) } else { @@ -282,7 +279,11 @@ pub async fn aux_signing_data< }; let fee_payer = if args.disposable_signing_key { - wallet.generate_disposable_signing_key().to_public() + context + .wallet_mut() + .await + .generate_disposable_signing_key() + .to_public() } else { match &args.wrapper_fee_payer { Some(keypair) => keypair.to_public(), @@ -322,15 +323,10 @@ pub struct TxSourcePostBalance { /// wrapper and its payload which is needed for monitoring its /// progress on chain. #[allow(clippy::too_many_arguments)] -pub async fn wrap_tx< - C: crate::sdk::queries::Client + Sync, - V: ShieldedUtils, - IO: Io, ->( - client: &C, - shielded: &mut ShieldedContext, +pub async fn wrap_tx<'a, N: Namada<'a>>( + context: &N, tx: &mut Tx, - args: &args::Tx, + args: &args::Tx, tx_source_balance: Option, epoch: Epoch, fee_payer: common::PublicKey, @@ -339,9 +335,9 @@ pub async fn wrap_tx< // Validate fee amount and token let gas_cost_key = parameter_storage::get_gas_cost_key(); let minimum_fee = match rpc::query_storage_value::< - C, + _, BTreeMap, - >(client, &gas_cost_key) + >(context.client(), &gas_cost_key) .await .and_then(|map| { map.get(&args.fee_token) @@ -364,14 +360,10 @@ pub async fn wrap_tx< }; let fee_amount = match args.fee_amount { Some(amount) => { - let validated_fee_amount = validate_amount::<_, IO>( - client, - amount, - &args.fee_token, - args.force, - ) - .await - .expect("Expected to be able to validate fee"); + let validated_fee_amount = + validate_amount(context, amount, &args.fee_token, args.force) + .await + .expect("Expected to be able to validate fee"); let amount = Amount::from_uint(validated_fee_amount.amount, 0).unwrap(); @@ -381,7 +373,7 @@ pub async fn wrap_tx< } else if !args.force { // Update the fee amount if it's not enough display_line!( - IO, + context.io(), "The provided gas price {} is less than the minimum \ amount required {}, changing it to match the minimum", amount.to_string_native(), @@ -405,9 +397,12 @@ pub async fn wrap_tx< let balance_key = token::balance_key(&args.fee_token, &fee_payer_address); - rpc::query_storage_value::(client, &balance_key) - .await - .unwrap_or_default() + rpc::query_storage_value::<_, token::Amount>( + context.client(), + &balance_key, + ) + .await + .unwrap_or_default() } }; @@ -441,8 +436,7 @@ pub async fn wrap_tx< tx_code_path: PathBuf::new(), }; - match shielded - .gen_shielded_transfer::<_, IO>(client, transfer_args) + match ShieldedContext::::gen_shielded_transfer(context, &transfer_args) .await { Ok(Some(ShieldedTransfer { @@ -471,8 +465,8 @@ pub async fn wrap_tx< let descriptions_limit_key= parameter_storage::get_fee_unshielding_descriptions_limit_key(); let descriptions_limit = - rpc::query_storage_value::( - client, + rpc::query_storage_value::<_, u64>( + context.client(), &descriptions_limit_key, ) .await @@ -519,19 +513,12 @@ pub async fn wrap_tx< } else { let token_addr = args.fee_token.clone(); if !args.force { - let fee_amount = format_denominated_amount::<_, IO>( - client, - &token_addr, - total_fee, - ) - .await; - - let balance = format_denominated_amount::<_, IO>( - client, - &token_addr, - updated_balance, - ) - .await; + let fee_amount = + context.format_amount(&token_addr, total_fee).await; + + let balance = context + .format_amount(&token_addr, updated_balance) + .await; return Err(Error::from(TxError::BalanceTooLowForFees( fee_payer_address, token_addr, @@ -546,7 +533,7 @@ pub async fn wrap_tx< _ => { if args.fee_unshield.is_some() { display_line!( - IO, + context.io(), "Enough transparent balance to pay fees: the fee \ unshielding spending key will be ignored" ); @@ -614,11 +601,8 @@ fn make_ledger_amount_addr( /// Adds a Ledger output line describing a given transaction amount and asset /// type -async fn make_ledger_amount_asset< - C: crate::ledger::queries::Client + Sync, - IO: Io, ->( - client: &C, +async fn make_ledger_amount_asset<'a>( + context: &impl Namada<'a>, tokens: &HashMap, output: &mut Vec, amount: u64, @@ -628,9 +612,7 @@ async fn make_ledger_amount_asset< ) { if let Some((token, _, _epoch)) = assets.get(token) { // If the AssetType can be decoded, then at least display Addressees - let formatted_amt = - format_denominated_amount::<_, IO>(client, token, amount.into()) - .await; + let formatted_amt = context.format_amount(token, amount.into()).await; if let Some(token) = tokens.get(token) { output .push( @@ -714,11 +696,8 @@ fn format_outputs(output: &mut Vec) { /// Adds a Ledger output for the sender and destination for transparent and MASP /// transactions -pub async fn make_ledger_masp_endpoints< - C: crate::ledger::queries::Client + Sync, - IO: Io, ->( - client: &C, +pub async fn make_ledger_masp_endpoints<'a>( + context: &impl Namada<'a>, tokens: &HashMap, output: &mut Vec, transfer: &Transfer, @@ -740,8 +719,8 @@ pub async fn make_ledger_masp_endpoints< for sapling_input in builder.builder.sapling_inputs() { let vk = ExtendedViewingKey::from(*sapling_input.key()); output.push(format!("Sender : {}", vk)); - make_ledger_amount_asset::<_, IO>( - client, + make_ledger_amount_asset( + context, tokens, output, sapling_input.value(), @@ -767,8 +746,8 @@ pub async fn make_ledger_masp_endpoints< for sapling_output in builder.builder.sapling_outputs() { let pa = PaymentAddress::from(sapling_output.address()); output.push(format!("Destination : {}", pa)); - make_ledger_amount_asset::<_, IO>( - client, + make_ledger_amount_asset( + context, tokens, output, sapling_output.value(), @@ -792,13 +771,8 @@ pub async fn make_ledger_masp_endpoints< /// Internal method used to generate transaction test vectors #[cfg(feature = "std")] -pub async fn generate_test_vector< - C: crate::sdk::queries::Client + Sync, - U: WalletUtils, - IO: Io, ->( - client: &C, - wallet: &mut Wallet, +pub async fn generate_test_vector<'a>( + context: &impl Namada<'a>, tx: &Tx, ) -> Result<(), Error> { use std::env; @@ -810,8 +784,7 @@ pub async fn generate_test_vector< // Contract the large data blobs in the transaction tx.wallet_filter(); // Convert the transaction to Ledger format - let decoding = - to_ledger_vector::<_, _, IO>(client, wallet, &tx).await?; + let decoding = to_ledger_vector(context, &tx).await?; let output = serde_json::to_string(&decoding) .map_err(|e| Error::from(EncodingError::Serde(e.to_string())))?; // Record the transaction at the identified path @@ -849,42 +822,35 @@ pub async fn generate_test_vector< /// Converts the given transaction to the form that is displayed on the Ledger /// device -pub async fn to_ledger_vector< - C: crate::sdk::queries::Client + Sync, - U: WalletUtils, - IO: Io, ->( - client: &C, - wallet: &mut Wallet, +pub async fn to_ledger_vector<'a>( + context: &impl Namada<'a>, tx: &Tx, ) -> Result { let init_account_hash = - query_wasm_code_hash::<_, IO>(client, TX_INIT_ACCOUNT_WASM).await?; + query_wasm_code_hash(context, TX_INIT_ACCOUNT_WASM).await?; let init_validator_hash = - query_wasm_code_hash::<_, IO>(client, TX_INIT_VALIDATOR_WASM).await?; + query_wasm_code_hash(context, TX_INIT_VALIDATOR_WASM).await?; let init_proposal_hash = - query_wasm_code_hash::<_, IO>(client, TX_INIT_PROPOSAL).await?; + query_wasm_code_hash(context, TX_INIT_PROPOSAL).await?; let vote_proposal_hash = - query_wasm_code_hash::<_, IO>(client, TX_VOTE_PROPOSAL).await?; - let reveal_pk_hash = - query_wasm_code_hash::<_, IO>(client, TX_REVEAL_PK).await?; + query_wasm_code_hash(context, TX_VOTE_PROPOSAL).await?; + let reveal_pk_hash = query_wasm_code_hash(context, TX_REVEAL_PK).await?; let update_account_hash = - query_wasm_code_hash::<_, IO>(client, TX_UPDATE_ACCOUNT_WASM).await?; - let transfer_hash = - query_wasm_code_hash::<_, IO>(client, TX_TRANSFER_WASM).await?; - let ibc_hash = query_wasm_code_hash::<_, IO>(client, TX_IBC_WASM).await?; - let bond_hash = query_wasm_code_hash::<_, IO>(client, TX_BOND_WASM).await?; - let unbond_hash = - query_wasm_code_hash::<_, IO>(client, TX_UNBOND_WASM).await?; - let withdraw_hash = - query_wasm_code_hash::<_, IO>(client, TX_WITHDRAW_WASM).await?; + query_wasm_code_hash(context, TX_UPDATE_ACCOUNT_WASM).await?; + let transfer_hash = query_wasm_code_hash(context, TX_TRANSFER_WASM).await?; + let ibc_hash = query_wasm_code_hash(context, TX_IBC_WASM).await?; + let bond_hash = query_wasm_code_hash(context, TX_BOND_WASM).await?; + let unbond_hash = query_wasm_code_hash(context, TX_UNBOND_WASM).await?; + let withdraw_hash = query_wasm_code_hash(context, TX_WITHDRAW_WASM).await?; let change_commission_hash = - query_wasm_code_hash::<_, IO>(client, TX_CHANGE_COMMISSION_WASM) - .await?; - let user_hash = query_wasm_code_hash::<_, IO>(client, VP_USER_WASM).await?; + query_wasm_code_hash(context, TX_CHANGE_COMMISSION_WASM).await?; + let user_hash = query_wasm_code_hash(context, VP_USER_WASM).await?; // To facilitate lookups of human-readable token names - let tokens: HashMap = wallet + let wallet = context.wallet().await; + let tokens: HashMap = context + .wallet() + .await .get_addresses_with_vp_type(AddressVpType::Token) .into_iter() .map(|addr| { @@ -1174,8 +1140,8 @@ pub async fn to_ledger_vector< tv.name = "Transfer 0".to_string(); tv.output.push("Type : Transfer".to_string()); - make_ledger_masp_endpoints::<_, IO>( - client, + make_ledger_masp_endpoints( + context, &tokens, &mut tv.output, &transfer, @@ -1183,8 +1149,8 @@ pub async fn to_ledger_vector< &asset_types, ) .await; - make_ledger_masp_endpoints::<_, IO>( - client, + make_ledger_masp_endpoints( + context, &tokens, &mut tv.output_expert, &transfer, @@ -1356,18 +1322,12 @@ pub async fn to_ledger_vector< if let Some(wrapper) = tx.header.wrapper() { let gas_token = wrapper.fee.token.clone(); - let gas_limit = format_denominated_amount::<_, IO>( - client, - &gas_token, - Amount::from(wrapper.gas_limit), - ) - .await; - let fee_amount_per_gas_unit = format_denominated_amount::<_, IO>( - client, - &gas_token, - wrapper.fee.amount_per_gas_unit, - ) - .await; + let gas_limit = context + .format_amount(&gas_token, Amount::from(wrapper.gas_limit)) + .await; + let fee_amount_per_gas_unit = context + .format_amount(&gas_token, wrapper.fee.amount_per_gas_unit) + .await; tv.output_expert.extend(vec![ format!("Timestamp : {}", tx.header.timestamp.0), format!("PK : {}", wrapper.pk), diff --git a/shared/src/sdk/tx.rs b/sdk/src/tx.rs similarity index 68% rename from shared/src/sdk/tx.rs rename to sdk/src/tx.rs index 9d7fe0cfe4..acfe12f6bd 100644 --- a/shared/src/sdk/tx.rs +++ b/sdk/src/tx.rs @@ -16,60 +16,62 @@ use masp_primitives::transaction::components::transparent::fees::{ InputView as TransparentInputView, OutputView as TransparentOutputView, }; use masp_primitives::transaction::components::I32Sum; +use namada_core::ibc::applications::transfer::msgs::transfer::MsgTransfer; +use namada_core::ibc::applications::transfer::packet::PacketData; +use namada_core::ibc::applications::transfer::PrefixedCoin; +use namada_core::ibc::core::ics04_channel::timeout::TimeoutHeight; +use namada_core::ibc::core::timestamp::Timestamp as IbcTimestamp; +use namada_core::ibc::core::Msg; +use namada_core::ibc::Height as IbcHeight; use namada_core::ledger::governance::cli::onchain::{ DefaultProposal, OnChainProposal, PgfFundingProposal, PgfStewardProposal, ProposalVote, }; use namada_core::ledger::governance::storage::proposal::ProposalType; use namada_core::ledger::governance::storage::vote::StorageProposalVote; +use namada_core::ledger::ibc::storage::ibc_denom_key; use namada_core::ledger::pgf::cli::steward::Commission; use namada_core::types::address::{masp, Address, InternalAddress}; use namada_core::types::dec::Dec; use namada_core::types::hash::Hash; +use namada_core::types::key::*; +use namada_core::types::masp::TransferTarget; +use namada_core::types::storage::Epoch; +use namada_core::types::time::DateTimeUtc; use namada_core::types::token::MaspDenom; +use namada_core::types::transaction::account::{InitAccount, UpdateAccount}; use namada_core::types::transaction::governance::{ InitProposalData, VoteProposalData, }; use namada_core::types::transaction::pgf::UpdateStewardCommission; +use namada_core::types::transaction::{pos, TxType}; +use namada_core::types::{storage, token}; use namada_proof_of_stake::parameters::PosParams; use namada_proof_of_stake::types::{CommissionPair, ValidatorState}; -use crate::ibc::applications::transfer::msgs::transfer::MsgTransfer; -use crate::ibc::applications::transfer::packet::PacketData; -use crate::ibc::applications::transfer::PrefixedCoin; -use crate::ibc::core::ics04_channel::timeout::TimeoutHeight; -use crate::ibc::core::timestamp::Timestamp as IbcTimestamp; -use crate::ibc::core::Msg; -use crate::ibc::Height as IbcHeight; -use crate::ledger::ibc::storage::ibc_denom_key; +use crate::args::{self, InputAmount}; +use crate::control_flow::{time, ProceedOrElse}; +use crate::error::{EncodingError, Error, QueryError, Result, TxError}; +use crate::io::Io; +use crate::masp::TransferErr::Build; +use crate::masp::{ShieldedContext, ShieldedTransfer}; use crate::proto::{MaspBuilder, Tx}; -use crate::sdk::args::{self, InputAmount}; -use crate::sdk::error::{EncodingError, Error, QueryError, Result, TxError}; -use crate::sdk::masp::TransferErr::Build; -use crate::sdk::masp::{ShieldedContext, ShieldedTransfer, ShieldedUtils}; -use crate::sdk::rpc::{ - self, format_denominated_amount, query_wasm_code_hash, validate_amount, - TxBroadcastData, TxResponse, +use crate::queries::Client; +use crate::rpc::{ + self, query_wasm_code_hash, validate_amount, TxBroadcastData, TxResponse, }; -use crate::sdk::signing::{self, TxSourcePostBalance}; -use crate::sdk::wallet::{Wallet, WalletUtils}; +use crate::signing::{self, SigningTxData, TxSourcePostBalance}; use crate::tendermint_rpc::endpoint::broadcast::tx_sync::Response; use crate::tendermint_rpc::error::Error as RpcError; -use crate::types::control_flow::{time, ProceedOrElse}; -use crate::types::io::Io; -use crate::types::key::*; -use crate::types::masp::TransferTarget; -use crate::types::storage::Epoch; -use crate::types::time::DateTimeUtc; -use crate::types::transaction::account::{InitAccount, UpdateAccount}; -use crate::types::transaction::{pos, TxType}; -use crate::types::{storage, token}; -use crate::{display_line, edisplay_line, vm}; +use crate::wallet::WalletIo; +use crate::{display_line, edisplay_line, Namada}; /// Initialize account transaction WASM pub const TX_INIT_ACCOUNT_WASM: &str = "tx_init_account.wasm"; /// Initialize validator transaction WASM path pub const TX_INIT_VALIDATOR_WASM: &str = "tx_init_validator.wasm"; +/// Unjail validator transaction WASM path +pub const TX_UNJAIL_VALIDATOR_WASM: &str = "tx_unjail_validator.wasm"; /// Initialize proposal transaction WASM path pub const TX_INIT_PROPOSAL: &str = "tx_init_proposal.wasm"; /// Vote transaction WASM path @@ -90,15 +92,23 @@ pub const TX_BOND_WASM: &str = "tx_bond.wasm"; pub const TX_UNBOND_WASM: &str = "tx_unbond.wasm"; /// Withdraw WASM path pub const TX_WITHDRAW_WASM: &str = "tx_withdraw.wasm"; +/// Bridge pool WASM path +pub const TX_BRIDGE_POOL_WASM: &str = "tx_bridge_pool.wasm"; /// Change commission WASM path pub const TX_CHANGE_COMMISSION_WASM: &str = "tx_change_validator_commission.wasm"; +/// Resign steward WASM path +pub const TX_RESIGN_STEWARD: &str = "tx_resign_steward.wasm"; +/// Update steward commission WASM path +pub const TX_UPDATE_STEWARD_COMMISSION: &str = + "tx_update_steward_commission.wasm"; /// Default timeout in seconds for requests to the `/accepted` /// and `/applied` ABCI query endpoints. const DEFAULT_NAMADA_EVENTS_MAX_WAIT_TIME_SECONDS: u64 = 60; /// Capture the result of running a transaction +#[derive(Debug)] pub enum ProcessTxResponse { /// Result of submitting a transaction to the blockchain Applied(TxResponse), @@ -121,7 +131,7 @@ impl ProcessTxResponse { } /// Build and dump a transaction either to file or to screen -pub fn dump_tx(args: &args::Tx, tx: Tx) { +pub fn dump_tx(io: &IO, args: &args::Tx, tx: Tx) { let tx_id = tx.header_hash(); let serialized_tx = tx.serialize(); match args.output_folder.to_owned() { @@ -132,14 +142,14 @@ pub fn dump_tx(args: &args::Tx, tx: Tx) { serde_json::to_writer_pretty(out, &serialized_tx) .expect("Should be able to write to file."); display_line!( - IO, + io, "Transaction serialized to {}.", tx_path.to_string_lossy() ); } None => { - display_line!(IO, "Below the serialized transaction: \n"); - display_line!(IO, "{}", serialized_tx) + display_line!(io, "Below the serialized transaction: \n"); + display_line!(io, "{}", serialized_tx) } } } @@ -147,33 +157,18 @@ pub fn dump_tx(args: &args::Tx, tx: Tx) { /// Prepare a transaction for signing and submission by adding a wrapper header /// to it. #[allow(clippy::too_many_arguments)] -pub async fn prepare_tx< - C: crate::sdk::queries::Client + Sync, - U: WalletUtils, - V: ShieldedUtils, - IO: Io, ->( - client: &C, - _wallet: &mut Wallet, - shielded: &mut ShieldedContext, +pub async fn prepare_tx<'a>( + context: &impl Namada<'a>, args: &args::Tx, tx: &mut Tx, fee_payer: common::PublicKey, tx_source_balance: Option, ) -> Result> { if !args.dry_run { - let epoch = rpc::query_epoch(client).await?; + let epoch = rpc::query_epoch(context.client()).await?; - signing::wrap_tx::<_, _, IO>( - client, - shielded, - tx, - args, - tx_source_balance, - epoch, - fee_payer, - ) - .await + signing::wrap_tx(context, tx, args, tx_source_balance, epoch, fee_payer) + .await } else { Ok(None) } @@ -181,13 +176,8 @@ pub async fn prepare_tx< /// Submit transaction and wait for result. Returns a list of addresses /// initialized in the transaction if any. In dry run, this is always empty. -pub async fn process_tx< - C: crate::sdk::queries::Client + Sync, - U: WalletUtils, - IO: Io, ->( - client: &C, - wallet: &mut Wallet, +pub async fn process_tx<'a>( + context: &impl Namada<'a>, args: &args::Tx, tx: Tx, ) -> Result { @@ -202,7 +192,7 @@ pub async fn process_tx< // println!("HTTP request body: {}", request_body); if args.dry_run || args.dry_run_wrapper { - expect_dry_broadcast::<_, IO>(TxBroadcastData::DryRun(tx), client).await + expect_dry_broadcast(TxBroadcastData::DryRun(tx), context).await } else { // We use this to determine when the wrapper tx makes it on-chain let wrapper_hash = tx.header_hash().to_string(); @@ -222,14 +212,14 @@ pub async fn process_tx< // of masp epoch Either broadcast or submit transaction and // collect result into sum type if args.broadcast_only { - broadcast_tx::<_, IO>(client, &to_broadcast) + broadcast_tx(context, &to_broadcast) .await .map(ProcessTxResponse::Broadcast) } else { - match submit_tx::<_, IO>(client, to_broadcast).await { + match submit_tx(context, to_broadcast).await { Ok(x) => { - save_initialized_accounts::( - wallet, + save_initialized_accounts( + context, args, x.initialized_accounts.clone(), ) @@ -243,20 +233,20 @@ pub async fn process_tx< } /// Check if a reveal public key transaction is needed -pub async fn is_reveal_pk_needed( +pub async fn is_reveal_pk_needed( client: &C, address: &Address, force: bool, ) -> Result where - C: crate::sdk::queries::Client + Sync, + C: crate::queries::Client + Sync, { // Check if PK revealed Ok(force || !has_revealed_pk(client, address).await?) } /// Check if the public key for the given address has been revealed -pub async fn has_revealed_pk( +pub async fn has_revealed_pk( client: &C, address: &Address, ) -> Result { @@ -264,45 +254,33 @@ pub async fn has_revealed_pk( } /// Submit transaction to reveal the given public key -pub async fn build_reveal_pk< - C: crate::sdk::queries::Client + Sync, - U: WalletUtils, - V: ShieldedUtils, - IO: Io, ->( - client: &C, - wallet: &mut Wallet, - shielded: &mut ShieldedContext, +pub async fn build_reveal_pk<'a>( + context: &impl Namada<'a>, args: &args::Tx, - address: &Address, public_key: &common::PublicKey, - fee_payer: &common::PublicKey, -) -> Result<(Tx, Option)> { - display_line!( - IO, - "Submitting a tx to reveal the public key for address {address}..." - ); +) -> Result<(Tx, SigningTxData, Option)> { + let signing_data = + signing::aux_signing_data(context, args, None, None).await?; - build::<_, _, _, _, _, IO>( - client, - wallet, - shielded, + build( + context, args, args.tx_reveal_code_path.clone(), public_key, do_nothing, - fee_payer, + &signing_data.fee_payer, None, ) .await + .map(|(tx, epoch)| (tx, signing_data, epoch)) } /// Broadcast a transaction to be included in the blockchain and checks that /// the tx has been successfully included into the mempool of a node /// /// In the case of errors in any of those stages, an error message is returned -pub async fn broadcast_tx( - rpc_cli: &C, +pub async fn broadcast_tx<'a>( + context: &impl Namada<'a>, to_broadcast: &TxBroadcastData, ) -> Result { let (tx, wrapper_tx_hash, decrypted_tx_hash) = match to_broadcast { @@ -322,21 +300,29 @@ pub async fn broadcast_tx( // TODO: configure an explicit timeout value? we need to hack away at // `tendermint-rs` for this, which is currently using a hard-coded 30s // timeout. - let response = - lift_rpc_error(rpc_cli.broadcast_tx_sync(tx.to_bytes().into()).await)?; + let response = lift_rpc_error( + context + .client() + .broadcast_tx_sync(tx.to_bytes().into()) + .await, + )?; if response.code == 0.into() { - display_line!(IO, "Transaction added to mempool: {:?}", response); + display_line!( + context.io(), + "Transaction added to mempool: {:?}", + response + ); // Print the transaction identifiers to enable the extraction of // acceptance/application results later { display_line!( - IO, + context.io(), "Wrapper transaction hash: {:?}", wrapper_tx_hash ); display_line!( - IO, + context.io(), "Inner transaction hash: {:?}", decrypted_tx_hash ); @@ -359,13 +345,10 @@ pub async fn broadcast_tx( /// 3. The decrypted payload of the tx has been included on the blockchain. /// /// In the case of errors in any of those stages, an error message is returned -pub async fn submit_tx( - client: &C, +pub async fn submit_tx<'a>( + context: &impl Namada<'a>, to_broadcast: TxBroadcastData, -) -> Result -where - C: crate::sdk::queries::Client + Sync, -{ +) -> Result { let (_, wrapper_hash, decrypted_hash) = match &to_broadcast { TxBroadcastData::Live { tx, @@ -376,7 +359,7 @@ where }?; // Broadcast the supplied transaction - broadcast_tx::<_, IO>(client, &to_broadcast).await?; + broadcast_tx(context, &to_broadcast).await?; let deadline = time::Instant::now() + time::Duration::from_secs( @@ -391,10 +374,9 @@ where let parsed = { let wrapper_query = rpc::TxEventQuery::Accepted(wrapper_hash.as_str()); - let event = - rpc::query_tx_status::<_, IO>(client, wrapper_query, deadline) - .await - .proceed_or(TxError::AcceptTimeout)?; + let event = rpc::query_tx_status(context, wrapper_query, deadline) + .await + .proceed_or(TxError::AcceptTimeout)?; let parsed = TxResponse::from_event(event); let tx_to_str = |parsed| { serde_json::to_string_pretty(parsed).map_err(|err| { @@ -402,7 +384,7 @@ where }) }; display_line!( - IO, + context.io(), "Transaction accepted with result: {}", tx_to_str(&parsed)? ); @@ -413,16 +395,13 @@ where // payload makes its way onto the blockchain let decrypted_query = rpc::TxEventQuery::Applied(decrypted_hash.as_str()); - let event = rpc::query_tx_status::<_, IO>( - client, - decrypted_query, - deadline, - ) - .await - .proceed_or(TxError::AppliedTimeout)?; + let event = + rpc::query_tx_status(context, decrypted_query, deadline) + .await + .proceed_or(TxError::AppliedTimeout)?; let parsed = TxResponse::from_event(event); display_line!( - IO, + context.io(), "Transaction applied with result: {}", tx_to_str(&parsed)? ); @@ -459,8 +438,8 @@ pub fn decode_component( } /// Save accounts initialized from a tx into the wallet, if any. -pub async fn save_initialized_accounts( - wallet: &mut Wallet, +pub async fn save_initialized_accounts<'a, N: Namada<'a>>( + context: &N, args: &args::Tx, initialized_accounts: Vec
, ) { @@ -468,7 +447,7 @@ pub async fn save_initialized_accounts( if len != 0 { // Store newly initialized account addresses in the wallet display_line!( - IO, + context.io(), "The transaction initialized {} new account{}", len, if len == 1 { "" } else { "s" } @@ -489,10 +468,10 @@ pub async fn save_initialized_accounts( format!("{}{}", initialized_account_alias, ix).into() } } - None => U::read_alias(&encoded).into(), + None => N::WalletUtils::read_alias(&encoded).into(), }; let alias = alias.into_owned(); - let added = wallet.add_address( + let added = context.wallet_mut().await.add_address( alias.clone(), address.clone(), args.wallet_alias_force, @@ -500,14 +479,18 @@ pub async fn save_initialized_accounts( match added { Some(new_alias) if new_alias != encoded => { display_line!( - IO, + context.io(), "Added alias {} for address {}.", new_alias, encoded ); } _ => { - display_line!(IO, "No alias added for address {}.", encoded) + display_line!( + context.io(), + "No alias added for address {}.", + encoded + ) } }; } @@ -515,42 +498,43 @@ pub async fn save_initialized_accounts( } /// Submit validator comission rate change -pub async fn build_validator_commission_change< - C: crate::sdk::queries::Client + Sync, - U: WalletUtils, - V: ShieldedUtils, - IO: Io, ->( - client: &C, - wallet: &mut Wallet, - shielded: &mut ShieldedContext, +pub async fn build_validator_commission_change<'a>( + context: &impl Namada<'a>, args::CommissionRateChange { tx: tx_args, validator, rate, tx_code_path, - }: args::CommissionRateChange, - fee_payer: common::PublicKey, -) -> Result<(Tx, Option)> { - let epoch = rpc::query_epoch(client).await?; + }: &args::CommissionRateChange, +) -> Result<(Tx, SigningTxData, Option)> { + let default_signer = Some(validator.clone()); + let signing_data = signing::aux_signing_data( + context, + tx_args, + Some(validator.clone()), + default_signer, + ) + .await?; + + let epoch = rpc::query_epoch(context.client()).await?; - let params: PosParams = rpc::get_pos_params(client).await?; + let params: PosParams = rpc::get_pos_params(context.client()).await?; let validator = validator.clone(); - if rpc::is_validator(client, &validator).await? { - if rate < Dec::zero() || rate > Dec::one() { + if rpc::is_validator(context.client(), &validator).await? { + if *rate < Dec::zero() || *rate > Dec::one() { edisplay_line!( - IO, + context.io(), "Invalid new commission rate, received {}", rate ); - return Err(Error::from(TxError::InvalidCommissionRate(rate))); + return Err(Error::from(TxError::InvalidCommissionRate(*rate))); } let pipeline_epoch_minus_one = epoch + params.pipeline_len - 1; match rpc::query_commission_rate( - client, + context.client(), &validator, Some(pipeline_epoch_minus_one), ) @@ -564,27 +548,30 @@ pub async fn build_validator_commission_change< > max_commission_change_per_epoch { edisplay_line!( - IO, + context.io(), "New rate is too large of a change with respect to \ the predecessor epoch in which the rate will take \ effect." ); if !tx_args.force { return Err(Error::from( - TxError::InvalidCommissionRate(rate), + TxError::InvalidCommissionRate(*rate), )); } } } None => { - edisplay_line!(IO, "Error retrieving from storage"); + edisplay_line!(context.io(), "Error retrieving from storage"); if !tx_args.force { return Err(Error::from(TxError::Retrieval)); } } } } else { - edisplay_line!(IO, "The given address {validator} is not a validator."); + edisplay_line!( + context.io(), + "The given address {validator} is not a validator." + ); if !tx_args.force { return Err(Error::from(TxError::InvalidValidatorAddress( validator, @@ -594,43 +581,47 @@ pub async fn build_validator_commission_change< let data = pos::CommissionChange { validator: validator.clone(), - new_rate: rate, + new_rate: *rate, }; - build::<_, _, _, _, _, IO>( - client, - wallet, - shielded, - &tx_args, - tx_code_path, + build( + context, + tx_args, + tx_code_path.clone(), data, do_nothing, - &fee_payer, + &signing_data.fee_payer, None, ) .await + .map(|(tx, epoch)| (tx, signing_data, epoch)) } /// Craft transaction to update a steward commission -pub async fn build_update_steward_commission< - C: crate::sdk::queries::Client + Sync, - U: WalletUtils, - V: ShieldedUtils, - IO: Io, ->( - client: &C, - wallet: &mut Wallet, - shielded: &mut ShieldedContext, +pub async fn build_update_steward_commission<'a>( + context: &impl Namada<'a>, args::UpdateStewardCommission { tx: tx_args, steward, commission, tx_code_path, - }: args::UpdateStewardCommission, - gas_payer: &common::PublicKey, -) -> Result<(Tx, Option)> { - if !rpc::is_steward(client, &steward).await && !tx_args.force { - edisplay_line!(IO, "The given address {} is not a steward.", &steward); + }: &args::UpdateStewardCommission, +) -> Result<(Tx, SigningTxData, Option)> { + let default_signer = Some(steward.clone()); + let signing_data = signing::aux_signing_data( + context, + tx_args, + Some(steward.clone()), + default_signer, + ) + .await?; + + if !rpc::is_steward(context.client(), steward).await && !tx_args.force { + edisplay_line!( + context.io(), + "The given address {} is not a steward.", + &steward + ); return Err(Error::from(TxError::InvalidSteward(steward.clone()))); }; @@ -639,7 +630,7 @@ pub async fn build_update_steward_commission< if !commission.is_valid() && !tx_args.force { edisplay_line!( - IO, + context.io(), "The sum of all percentage must not be greater than 1." ); return Err(Error::from(TxError::InvalidStewardCommission( @@ -652,76 +643,80 @@ pub async fn build_update_steward_commission< commission: commission.reward_distribution, }; - build::<_, _, _, _, _, IO>( - client, - wallet, - shielded, - &tx_args, - tx_code_path, + build( + context, + tx_args, + tx_code_path.clone(), data, do_nothing, - gas_payer, + &signing_data.fee_payer, None, ) .await + .map(|(tx, epoch)| (tx, signing_data, epoch)) } /// Craft transaction to resign as a steward -pub async fn build_resign_steward< - C: crate::sdk::queries::Client + Sync, - U: WalletUtils, - V: ShieldedUtils, - IO: Io, ->( - client: &C, - wallet: &mut Wallet, - shielded: &mut ShieldedContext, +pub async fn build_resign_steward<'a>( + context: &impl Namada<'a>, args::ResignSteward { tx: tx_args, steward, tx_code_path, - }: args::ResignSteward, - gas_payer: &common::PublicKey, -) -> Result<(Tx, Option)> { - if !rpc::is_steward(client, &steward).await && !tx_args.force { - edisplay_line!(IO, "The given address {} is not a steward.", &steward); + }: &args::ResignSteward, +) -> Result<(Tx, SigningTxData, Option)> { + let default_signer = Some(steward.clone()); + let signing_data = signing::aux_signing_data( + context, + tx_args, + Some(steward.clone()), + default_signer, + ) + .await?; + + if !rpc::is_steward(context.client(), steward).await && !tx_args.force { + edisplay_line!( + context.io(), + "The given address {} is not a steward.", + &steward + ); return Err(Error::from(TxError::InvalidSteward(steward.clone()))); }; - build::<_, _, _, _, _, IO>( - client, - wallet, - shielded, - &tx_args, - tx_code_path, - steward, + build( + context, + tx_args, + tx_code_path.clone(), + steward.clone(), do_nothing, - gas_payer, + &signing_data.fee_payer, None, ) .await + .map(|(tx, epoch)| (tx, signing_data, epoch)) } /// Submit transaction to unjail a jailed validator -pub async fn build_unjail_validator< - C: crate::sdk::queries::Client + Sync, - U: WalletUtils, - V: ShieldedUtils, - IO: Io, ->( - client: &C, - wallet: &mut Wallet, - shielded: &mut ShieldedContext, +pub async fn build_unjail_validator<'a>( + context: &impl Namada<'a>, args::TxUnjailValidator { tx: tx_args, validator, tx_code_path, - }: args::TxUnjailValidator, - fee_payer: common::PublicKey, -) -> Result<(Tx, Option)> { - if !rpc::is_validator(client, &validator).await? { + }: &args::TxUnjailValidator, +) -> Result<(Tx, SigningTxData, Option)> { + let default_signer = Some(validator.clone()); + let signing_data = signing::aux_signing_data( + context, + tx_args, + Some(validator.clone()), + default_signer, + ) + .await?; + + if !rpc::is_validator(context.client(), validator).await? { edisplay_line!( - IO, + context.io(), "The given address {} is not a validator.", &validator ); @@ -732,21 +727,24 @@ pub async fn build_unjail_validator< } } - let params: PosParams = rpc::get_pos_params(client).await?; - let current_epoch = rpc::query_epoch(client).await?; + let params: PosParams = rpc::get_pos_params(context.client()).await?; + let current_epoch = rpc::query_epoch(context.client()).await?; let pipeline_epoch = current_epoch + params.pipeline_len; - let validator_state_at_pipeline = - rpc::get_validator_state(client, &validator, Some(pipeline_epoch)) - .await? - .ok_or_else(|| { - Error::from(TxError::Other( - "Validator state should be defined.".to_string(), - )) - })?; + let validator_state_at_pipeline = rpc::get_validator_state( + context.client(), + validator, + Some(pipeline_epoch), + ) + .await? + .ok_or_else(|| { + Error::from(TxError::Other( + "Validator state should be defined.".to_string(), + )) + })?; if validator_state_at_pipeline != ValidatorState::Jailed { edisplay_line!( - IO, + context.io(), "The given validator address {} is not jailed at the pipeline \ epoch when it would be restored to one of the validator sets.", &validator @@ -759,17 +757,19 @@ pub async fn build_unjail_validator< } let last_slash_epoch_key = - crate::ledger::pos::validator_last_slash_key(&validator); - let last_slash_epoch = - rpc::query_storage_value::(client, &last_slash_epoch_key) - .await; + namada_proof_of_stake::storage::validator_last_slash_key(validator); + let last_slash_epoch = rpc::query_storage_value::<_, Epoch>( + context.client(), + &last_slash_epoch_key, + ) + .await; match last_slash_epoch { Ok(last_slash_epoch) => { let eligible_epoch = last_slash_epoch + params.slash_processing_epoch_offset(); if current_epoch < eligible_epoch { edisplay_line!( - IO, + context.io(), "The given validator address {} is currently frozen and \ not yet eligible to be unjailed.", &validator @@ -795,53 +795,51 @@ pub async fn build_unjail_validator< Err(err) => return Err(err), } - build::<_, _, _, _, _, IO>( - client, - wallet, - shielded, - &tx_args, - tx_code_path, - validator, + build( + context, + tx_args, + tx_code_path.clone(), + validator.clone(), do_nothing, - &fee_payer, + &signing_data.fee_payer, None, ) .await + .map(|(tx, epoch)| (tx, signing_data, epoch)) } /// Submit transaction to withdraw an unbond -pub async fn build_withdraw< - C: crate::sdk::queries::Client + Sync, - U: WalletUtils, - V: ShieldedUtils, - IO: Io, ->( - client: &C, - wallet: &mut Wallet, - shielded: &mut ShieldedContext, +pub async fn build_withdraw<'a>( + context: &impl Namada<'a>, args::Withdraw { tx: tx_args, validator, source, tx_code_path, - }: args::Withdraw, - fee_payer: common::PublicKey, -) -> Result<(Tx, Option)> { - let epoch = rpc::query_epoch(client).await?; - - let validator = known_validator_or_err::<_, IO>( - validator.clone(), - tx_args.force, - client, + }: &args::Withdraw, +) -> Result<(Tx, SigningTxData, Option)> { + let default_address = source.clone().unwrap_or(validator.clone()); + let default_signer = Some(default_address.clone()); + let signing_data = signing::aux_signing_data( + context, + tx_args, + Some(default_address), + default_signer, ) .await?; + let epoch = rpc::query_epoch(context.client()).await?; + + let validator = + known_validator_or_err(validator.clone(), tx_args.force, context) + .await?; + let source = source.clone(); // Check the source's current unbond amount let bond_source = source.clone().unwrap_or_else(|| validator.clone()); let tokens = rpc::query_withdrawable_tokens( - client, + context.client(), &bond_source, &validator, Some(epoch), @@ -850,83 +848,88 @@ pub async fn build_withdraw< if tokens.is_zero() { edisplay_line!( - IO, + context.io(), "There are no unbonded bonds ready to withdraw in the current \ epoch {}.", epoch ); - rpc::query_and_print_unbonds::<_, IO>(client, &bond_source, &validator) - .await?; + rpc::query_and_print_unbonds(context, &bond_source, &validator).await?; if !tx_args.force { return Err(Error::from(TxError::NoUnbondReady(epoch))); } } else { display_line!( - IO, + context.io(), "Found {} tokens that can be withdrawn.", tokens.to_string_native() ); - display_line!(IO, "Submitting transaction to withdraw them..."); + display_line!( + context.io(), + "Submitting transaction to withdraw them..." + ); } let data = pos::Withdraw { validator, source }; - build::<_, _, _, _, _, IO>( - client, - wallet, - shielded, - &tx_args, - tx_code_path, + build( + context, + tx_args, + tx_code_path.clone(), data, do_nothing, - &fee_payer, + &signing_data.fee_payer, None, ) .await + .map(|(tx, epoch)| (tx, signing_data, epoch)) } /// Submit a transaction to unbond -pub async fn build_unbond< - C: crate::sdk::queries::Client + Sync, - U: WalletUtils, - V: ShieldedUtils, - IO: Io, ->( - client: &C, - wallet: &mut Wallet, - shielded: &mut ShieldedContext, +pub async fn build_unbond<'a>( + context: &impl Namada<'a>, args::Unbond { tx: tx_args, validator, amount, source, tx_code_path, - }: args::Unbond, - fee_payer: common::PublicKey, -) -> Result<(Tx, Option, Option<(Epoch, token::Amount)>)> { + }: &args::Unbond, +) -> Result<( + Tx, + SigningTxData, + Option, + Option<(Epoch, token::Amount)>, +)> { + let default_address = source.clone().unwrap_or(validator.clone()); + let default_signer = Some(default_address.clone()); + let signing_data = signing::aux_signing_data( + context, + tx_args, + Some(default_address), + default_signer, + ) + .await?; + let source = source.clone(); // Check the source's current bond amount let bond_source = source.clone().unwrap_or_else(|| validator.clone()); if !tx_args.force { - known_validator_or_err::<_, IO>( - validator.clone(), - tx_args.force, - client, - ) - .await?; + known_validator_or_err(validator.clone(), tx_args.force, context) + .await?; let bond_amount = - rpc::query_bond(client, &bond_source, &validator, None).await?; + rpc::query_bond(context.client(), &bond_source, validator, None) + .await?; display_line!( - IO, + context.io(), "Bond amount available for unbonding: {} NAM", bond_amount.to_string_native() ); - if amount > bond_amount { + if *amount > bond_amount { edisplay_line!( - IO, + context.io(), "The total bonds of the source {} is lower than the amount to \ be unbonded. Amount to unbond is {} and the total bonds is \ {}.", @@ -945,9 +948,12 @@ pub async fn build_unbond< } // Query the unbonds before submitting the tx - let unbonds = - rpc::query_unbond_with_slashing(client, &bond_source, &validator) - .await?; + let unbonds = rpc::query_unbond_with_slashing( + context.client(), + &bond_source, + validator, + ) + .await?; let mut withdrawable = BTreeMap::::new(); for ((_start_epoch, withdraw_epoch), amount) in unbonds.into_iter() { let to_withdraw = withdrawable.entry(withdraw_epoch).or_default(); @@ -957,28 +963,26 @@ pub async fn build_unbond< let data = pos::Unbond { validator: validator.clone(), - amount, + amount: *amount, source: source.clone(), }; - let (tx, epoch) = build::<_, _, _, _, _, IO>( - client, - wallet, - shielded, - &tx_args, - tx_code_path, + let (tx, epoch) = build( + context, + tx_args, + tx_code_path.clone(), data, do_nothing, - &fee_payer, + &signing_data.fee_payer, None, ) .await?; - Ok((tx, epoch, latest_withdrawal_pre)) + Ok((tx, signing_data, epoch, latest_withdrawal_pre)) } /// Query the unbonds post-tx -pub async fn query_unbonds( - client: &C, +pub async fn query_unbonds<'a>( + context: &impl Namada<'a>, args: args::Unbond, latest_withdrawal_pre: Option<(Epoch, token::Amount)>, ) -> Result<()> { @@ -987,9 +991,12 @@ pub async fn query_unbonds( let bond_source = source.clone().unwrap_or_else(|| args.validator.clone()); // Query the unbonds post-tx - let unbonds = - rpc::query_unbond_with_slashing(client, &bond_source, &args.validator) - .await?; + let unbonds = rpc::query_unbond_with_slashing( + context.client(), + &bond_source, + &args.validator, + ) + .await?; let mut withdrawable = BTreeMap::::new(); for ((_start_epoch, withdraw_epoch), amount) in unbonds.into_iter() { let to_withdraw = withdrawable.entry(withdraw_epoch).or_default(); @@ -1007,7 +1014,7 @@ pub async fn query_unbonds( std::cmp::Ordering::Less => { if args.tx.force { edisplay_line!( - IO, + context.io(), "Unexpected behavior reading the unbonds data has \ occurred" ); @@ -1017,7 +1024,7 @@ pub async fn query_unbonds( } std::cmp::Ordering::Equal => { display_line!( - IO, + context.io(), "Amount {} withdrawable starting from epoch {}", (latest_withdraw_amount_post - latest_withdraw_amount_pre) .to_string_native(), @@ -1026,7 +1033,7 @@ pub async fn query_unbonds( } std::cmp::Ordering::Greater => { display_line!( - IO, + context.io(), "Amount {} withdrawable starting from epoch {}", latest_withdraw_amount_post.to_string_native(), latest_withdraw_epoch_post, @@ -1035,7 +1042,7 @@ pub async fn query_unbonds( } } else { display_line!( - IO, + context.io(), "Amount {} withdrawable starting from epoch {}", latest_withdraw_amount_post.to_string_native(), latest_withdraw_epoch_post, @@ -1045,15 +1052,8 @@ pub async fn query_unbonds( } /// Submit a transaction to bond -pub async fn build_bond< - C: crate::sdk::queries::Client + Sync, - U: WalletUtils, - V: ShieldedUtils, - IO: Io, ->( - client: &C, - wallet: &mut Wallet, - shielded: &mut ShieldedContext, +pub async fn build_bond<'a>( + context: &impl Namada<'a>, args::Bond { tx: tx_args, validator, @@ -1061,76 +1061,72 @@ pub async fn build_bond< source, native_token, tx_code_path, - }: args::Bond, - fee_payer: common::PublicKey, -) -> Result<(Tx, Option)> { - let validator = known_validator_or_err::<_, IO>( - validator.clone(), - tx_args.force, - client, + }: &args::Bond, +) -> Result<(Tx, SigningTxData, Option)> { + let default_address = source.clone().unwrap_or(validator.clone()); + let default_signer = Some(default_address.clone()); + let signing_data = signing::aux_signing_data( + context, + tx_args, + Some(default_address.clone()), + default_signer, ) .await?; + let validator = + known_validator_or_err(validator.clone(), tx_args.force, context) + .await?; + // Check that the source address exists on chain let source = match source.clone() { - Some(source) => { - source_exists_or_err::<_, IO>(source, tx_args.force, client) - .await - .map(Some) - } + Some(source) => source_exists_or_err(source, tx_args.force, context) + .await + .map(Some), None => Ok(source.clone()), }?; // Check bond's source (source for delegation or validator for self-bonds) // balance let bond_source = source.as_ref().unwrap_or(&validator); - let balance_key = token::balance_key(&native_token, bond_source); + let balance_key = token::balance_key(native_token, bond_source); // TODO Should we state the same error message for the native token? - let post_balance = check_balance_too_low_err::<_, IO>( - &native_token, + let post_balance = check_balance_too_low_err( + native_token, bond_source, - amount, + *amount, balance_key, tx_args.force, - client, + context, ) .await?; let tx_source_balance = Some(TxSourcePostBalance { post_balance, source: bond_source.clone(), - token: native_token, + token: native_token.clone(), }); let data = pos::Bond { validator, - amount, + amount: *amount, source, }; - build::<_, _, _, _, _, IO>( - client, - wallet, - shielded, - &tx_args, - tx_code_path, + build( + context, + tx_args, + tx_code_path.clone(), data, do_nothing, - &fee_payer, + &signing_data.fee_payer, tx_source_balance, ) .await + .map(|(tx, epoch)| (tx, signing_data, epoch)) } /// Build a default proposal governance -pub async fn build_default_proposal< - C: crate::sdk::queries::Client + Sync, - U: WalletUtils, - V: ShieldedUtils, - IO: Io, ->( - client: &C, - wallet: &mut Wallet, - shielded: &mut ShieldedContext, +pub async fn build_default_proposal<'a>( + context: &impl Namada<'a>, args::InitProposal { tx, proposal_data: _, @@ -1139,10 +1135,18 @@ pub async fn build_default_proposal< is_pgf_stewards: _, is_pgf_funding: _, tx_code_path, - }: args::InitProposal, + }: &args::InitProposal, proposal: DefaultProposal, - fee_payer: common::PublicKey, -) -> Result<(Tx, Option)> { +) -> Result<(Tx, SigningTxData, Option)> { + let default_signer = Some(proposal.proposal.author.clone()); + let signing_data = signing::aux_signing_data( + context, + tx, + Some(proposal.proposal.author.clone()), + default_signer, + ) + .await?; + let init_proposal_data = InitProposalData::try_from(proposal.clone()) .map_err(|e| TxError::InvalidProposal(e.to_string()))?; @@ -1160,30 +1164,22 @@ pub async fn build_default_proposal< }; Ok(()) }; - build::<_, _, _, _, _, IO>( - client, - wallet, - shielded, - &tx, - tx_code_path, + build( + context, + tx, + tx_code_path.clone(), init_proposal_data, push_data, - &fee_payer, + &signing_data.fee_payer, None, // TODO: need to pay the fee to submit a proposal ) .await + .map(|(tx, epoch)| (tx, signing_data, epoch)) } /// Build a proposal vote -pub async fn build_vote_proposal< - C: crate::sdk::queries::Client + Sync, - U: WalletUtils, - V: ShieldedUtils, - IO: Io, ->( - client: &C, - wallet: &mut Wallet, - shielded: &mut ShieldedContext, +pub async fn build_vote_proposal<'a>( + context: &impl Namada<'a>, args::VoteProposal { tx, proposal_id, @@ -1192,18 +1188,26 @@ pub async fn build_vote_proposal< is_offline: _, proposal_data: _, tx_code_path, - }: args::VoteProposal, + }: &args::VoteProposal, epoch: Epoch, - fee_payer: common::PublicKey, -) -> Result<(Tx, Option)> { - let proposal_vote = ProposalVote::try_from(vote) +) -> Result<(Tx, SigningTxData, Option)> { + let default_signer = Some(voter.clone()); + let signing_data = signing::aux_signing_data( + context, + tx, + Some(voter.clone()), + default_signer.clone(), + ) + .await?; + + let proposal_vote = ProposalVote::try_from(vote.clone()) .map_err(|_| TxError::InvalidProposalVote)?; let proposal_id = proposal_id.ok_or_else(|| { Error::Other("Proposal id must be defined.".to_string()) })?; let proposal = if let Some(proposal) = - rpc::query_proposal_by_id(client, proposal_id).await? + rpc::query_proposal_by_id(context.client(), proposal_id).await? { proposal } else { @@ -1218,7 +1222,7 @@ pub async fn build_vote_proposal< )) })?; - let is_validator = rpc::is_validator(client, &voter).await?; + let is_validator = rpc::is_validator(context.client(), voter).await?; if !proposal.can_be_voted(epoch, is_validator) { if tx.force { @@ -1231,8 +1235,8 @@ pub async fn build_vote_proposal< } let delegations = rpc::get_delegators_delegation_at( - client, - &voter, + context.client(), + voter, proposal.voting_start_epoch, ) .await? @@ -1247,30 +1251,22 @@ pub async fn build_vote_proposal< delegations, }; - build::<_, _, _, _, _, IO>( - client, - wallet, - shielded, - &tx, - tx_code_path, + build( + context, + tx, + tx_code_path.clone(), data, do_nothing, - &fee_payer, + &signing_data.fee_payer, None, ) .await + .map(|(tx, epoch)| (tx, signing_data, epoch)) } /// Build a pgf funding proposal governance -pub async fn build_pgf_funding_proposal< - C: crate::sdk::queries::Client + Sync, - U: WalletUtils, - V: ShieldedUtils, - IO: Io, ->( - client: &C, - wallet: &mut Wallet, - shielded: &mut ShieldedContext, +pub async fn build_pgf_funding_proposal<'a>( + context: &impl Namada<'a>, args::InitProposal { tx, proposal_data: _, @@ -1279,10 +1275,18 @@ pub async fn build_pgf_funding_proposal< is_pgf_stewards: _, is_pgf_funding: _, tx_code_path, - }: args::InitProposal, + }: &args::InitProposal, proposal: PgfFundingProposal, - fee_payer: &common::PublicKey, -) -> Result<(Tx, Option)> { +) -> Result<(Tx, SigningTxData, Option)> { + let default_signer = Some(proposal.proposal.author.clone()); + let signing_data = signing::aux_signing_data( + context, + tx, + Some(proposal.proposal.author.clone()), + default_signer, + ) + .await?; + let init_proposal_data = InitProposalData::try_from(proposal.clone()) .map_err(|e| TxError::InvalidProposal(e.to_string()))?; @@ -1292,30 +1296,22 @@ pub async fn build_pgf_funding_proposal< data.content = extra_section_hash; Ok(()) }; - build::<_, _, _, _, _, IO>( - client, - wallet, - shielded, - &tx, - tx_code_path, + build( + context, + tx, + tx_code_path.clone(), init_proposal_data, add_section, - fee_payer, + &signing_data.fee_payer, None, // TODO: need to pay the fee to submit a proposal ) .await + .map(|(tx, epoch)| (tx, signing_data, epoch)) } /// Build a pgf funding proposal governance -pub async fn build_pgf_stewards_proposal< - C: crate::sdk::queries::Client + Sync, - U: WalletUtils, - V: ShieldedUtils, - IO: Io, ->( - client: &C, - wallet: &mut Wallet, - shielded: &mut ShieldedContext, +pub async fn build_pgf_stewards_proposal<'a>( + context: &impl Namada<'a>, args::InitProposal { tx, proposal_data: _, @@ -1324,10 +1320,18 @@ pub async fn build_pgf_stewards_proposal< is_pgf_stewards: _, is_pgf_funding: _, tx_code_path, - }: args::InitProposal, + }: &args::InitProposal, proposal: PgfStewardProposal, - fee_payer: common::PublicKey, -) -> Result<(Tx, Option)> { +) -> Result<(Tx, SigningTxData, Option)> { + let default_signer = Some(proposal.proposal.author.clone()); + let signing_data = signing::aux_signing_data( + context, + tx, + Some(proposal.proposal.author.clone()), + default_signer, + ) + .await?; + let init_proposal_data = InitProposalData::try_from(proposal.clone()) .map_err(|e| TxError::InvalidProposal(e.to_string()))?; @@ -1338,50 +1342,43 @@ pub async fn build_pgf_stewards_proposal< Ok(()) }; - build::<_, _, _, _, _, IO>( - client, - wallet, - shielded, - &tx, - tx_code_path, + build( + context, + tx, + tx_code_path.clone(), init_proposal_data, add_section, - &fee_payer, + &signing_data.fee_payer, None, // TODO: need to pay the fee to submit a proposal ) .await + .map(|(tx, epoch)| (tx, signing_data, epoch)) } /// Submit an IBC transfer -pub async fn build_ibc_transfer< - C: crate::sdk::queries::Client + Sync, - U: WalletUtils, - V: ShieldedUtils, - IO: Io, ->( - client: &C, - wallet: &mut Wallet, - shielded: &mut ShieldedContext, - args: args::TxIbcTransfer, - fee_payer: common::PublicKey, -) -> Result<(Tx, Option)> { - // Check that the source address exists on chain - let source = source_exists_or_err::<_, IO>( - args.source.clone(), - args.tx.force, - client, +pub async fn build_ibc_transfer<'a>( + context: &impl Namada<'a>, + args: &args::TxIbcTransfer, +) -> Result<(Tx, SigningTxData, Option)> { + let default_signer = Some(args.source.clone()); + let signing_data = signing::aux_signing_data( + context, + &args.tx, + Some(args.source.clone()), + default_signer, ) .await?; + // Check that the source address exists on chain + let source = + source_exists_or_err(args.source.clone(), args.tx.force, context) + .await?; // We cannot check the receiver // validate the amount given - let validated_amount = validate_amount::<_, IO>( - client, - args.amount, - &args.token, - args.tx.force, - ) - .await?; + let validated_amount = + validate_amount(context, args.amount, &args.token, args.tx.force) + .await + .expect("expected to validate amount"); if validated_amount.canonical().denom.0 != 0 { return Err(Error::Other(format!( "The amount for the IBC transfer should be an integer: {}", @@ -1392,13 +1389,13 @@ pub async fn build_ibc_transfer< // Check source balance let balance_key = token::balance_key(&args.token, &source); - let post_balance = check_balance_too_low_err::<_, IO>( + let post_balance = check_balance_too_low_err( &args.token, &source, validated_amount.amount, balance_key, args.tx.force, - client, + context, ) .await?; let tx_source_balance = Some(TxSourcePostBalance { @@ -1407,19 +1404,20 @@ pub async fn build_ibc_transfer< token: args.token.clone(), }); - let tx_code_hash = query_wasm_code_hash::<_, IO>( - client, - args.tx_code_path.to_str().unwrap(), - ) - .await - .map_err(|e| Error::from(QueryError::Wasm(e.to_string())))?; + let tx_code_hash = + query_wasm_code_hash(context, args.tx_code_path.to_str().unwrap()) + .await + .map_err(|e| Error::from(QueryError::Wasm(e.to_string())))?; let ibc_denom = match &args.token { Address::Internal(InternalAddress::IbcToken(hash)) => { let ibc_denom_key = ibc_denom_key(hash); - rpc::query_storage_value::(client, &ibc_denom_key) - .await - .map_err(|_e| TxError::TokenDoesNotExist(args.token.clone()))? + rpc::query_storage_value::<_, String>( + context.client(), + &ibc_denom_key, + ) + .await + .map_err(|_e| TxError::TokenDoesNotExist(args.token.clone()))? } _ => args.token.to_string(), }; @@ -1431,8 +1429,8 @@ pub async fn build_ibc_transfer< let packet_data = PacketData { token, sender: source.to_string().into(), - receiver: args.receiver.into(), - memo: args.memo.unwrap_or_default().into(), + receiver: args.receiver.clone().into(), + memo: args.memo.clone().unwrap_or_default().into(), }; // this height should be that of the destination chain, not this chain @@ -1463,8 +1461,8 @@ pub async fn build_ibc_transfer< }; let msg = MsgTransfer { - port_id_on_a: args.port_id, - chan_id_on_a: args.channel_id, + port_id_on_a: args.port_id.clone(), + chan_id_on_a: args.channel_id.clone(), packet_data, timeout_height_on_b: timeout_height, timeout_timestamp_on_b: timeout_timestamp, @@ -1480,27 +1478,23 @@ pub async fn build_ibc_transfer< tx.add_code_from_hash(tx_code_hash) .add_serialized_data(data); - let epoch = prepare_tx::( - client, - wallet, - shielded, + let epoch = prepare_tx( + context, &args.tx, &mut tx, - fee_payer, + signing_data.fee_payer.clone(), tx_source_balance, ) .await?; - Ok((tx, epoch)) + Ok((tx, signing_data, epoch)) } /// Abstraction for helping build transactions #[allow(clippy::too_many_arguments)] -pub async fn build( - client: &C, - wallet: &mut Wallet, - shielded: &mut ShieldedContext, - tx_args: &crate::sdk::args::Tx, +pub async fn build<'a, F, D>( + context: &impl Namada<'a>, + tx_args: &crate::args::Tx, path: PathBuf, data: D, on_tx: F, @@ -1510,14 +1504,9 @@ pub async fn build( where F: FnOnce(&mut Tx, &mut D) -> Result<()>, D: BorshSerialize, - U: WalletUtils, - V: ShieldedUtils, - IO: Io, { - build_pow_flag::<_, _, _, _, _, IO>( - client, - wallet, - shielded, + build_pow_flag( + context, tx_args, path, data, @@ -1529,18 +1518,9 @@ where } #[allow(clippy::too_many_arguments)] -async fn build_pow_flag< - C: crate::ledger::queries::Client + Sync, - U, - V, - F, - D, - IO: Io, ->( - client: &C, - wallet: &mut Wallet, - shielded: &mut ShieldedContext, - tx_args: &crate::sdk::args::Tx, +async fn build_pow_flag<'a, F, D>( + context: &impl Namada<'a>, + tx_args: &crate::args::Tx, path: PathBuf, mut data: D, on_tx: F, @@ -1550,26 +1530,21 @@ async fn build_pow_flag< where F: FnOnce(&mut Tx, &mut D) -> Result<()>, D: BorshSerialize, - U: WalletUtils, - V: ShieldedUtils, { let chain_id = tx_args.chain_id.clone().unwrap(); let mut tx_builder = Tx::new(chain_id, tx_args.expiration); - let tx_code_hash = - query_wasm_code_hash::<_, IO>(client, path.to_string_lossy()) - .await - .map_err(|e| Error::from(QueryError::Wasm(e.to_string())))?; + let tx_code_hash = query_wasm_code_hash(context, path.to_string_lossy()) + .await + .map_err(|e| Error::from(QueryError::Wasm(e.to_string())))?; on_tx(&mut tx_builder, &mut data)?; tx_builder.add_code_from_hash(tx_code_hash).add_data(data); - let epoch = prepare_tx::( - client, - wallet, - shielded, + let epoch = prepare_tx( + context, tx_args, &mut tx_builder, gas_payer.clone(), @@ -1581,17 +1556,16 @@ where /// Try to decode the given asset type and add its decoding to the supplied set. /// Returns true only if a new decoding has been added to the given set. -async fn add_asset_type< - C: crate::sdk::queries::Client + Sync, - U: ShieldedUtils, ->( +async fn add_asset_type<'a>( asset_types: &mut HashSet<(Address, MaspDenom, Epoch)>, - shielded: &mut ShieldedContext, - client: &C, + context: &impl Namada<'a>, asset_type: AssetType, ) -> bool { - if let Some(asset_type) = - shielded.decode_asset_type(client, asset_type).await + if let Some(asset_type) = context + .shielded_mut() + .await + .decode_asset_type(context.client(), asset_type) + .await { asset_types.insert(asset_type) } else { @@ -1602,95 +1576,76 @@ async fn add_asset_type< /// Collect the asset types used in the given Builder and decode them. This /// function provides the data necessary for offline wallets to present asset /// type information. -async fn used_asset_types< - C: crate::sdk::queries::Client + Sync, - U: ShieldedUtils, - P, - R, - K, - N, ->( - shielded: &mut ShieldedContext, - client: &C, +async fn used_asset_types<'a, P, R, K, N>( + context: &impl Namada<'a>, builder: &Builder, ) -> std::result::Result, RpcError> { let mut asset_types = HashSet::new(); // Collect all the asset types used in the Sapling inputs for input in builder.sapling_inputs() { - add_asset_type(&mut asset_types, shielded, client, input.asset_type()) - .await; + add_asset_type(&mut asset_types, context, input.asset_type()).await; } // Collect all the asset types used in the transparent inputs for input in builder.transparent_inputs() { - add_asset_type( - &mut asset_types, - shielded, - client, - input.coin().asset_type(), - ) - .await; + add_asset_type(&mut asset_types, context, input.coin().asset_type()) + .await; } // Collect all the asset types used in the Sapling outputs for output in builder.sapling_outputs() { - add_asset_type(&mut asset_types, shielded, client, output.asset_type()) - .await; + add_asset_type(&mut asset_types, context, output.asset_type()).await; } // Collect all the asset types used in the transparent outputs for output in builder.transparent_outputs() { - add_asset_type(&mut asset_types, shielded, client, output.asset_type()) - .await; + add_asset_type(&mut asset_types, context, output.asset_type()).await; } // Collect all the asset types used in the Sapling converts for output in builder.sapling_converts() { for (asset_type, _) in I32Sum::from(output.conversion().clone()).components() { - add_asset_type(&mut asset_types, shielded, client, *asset_type) - .await; + add_asset_type(&mut asset_types, context, *asset_type).await; } } Ok(asset_types) } /// Submit an ordinary transfer -pub async fn build_transfer< - C: crate::sdk::queries::Client + Sync, - U: WalletUtils, - V: ShieldedUtils, - IO: Io, ->( - client: &C, - wallet: &mut Wallet, - shielded: &mut ShieldedContext, - mut args: args::TxTransfer, - fee_payer: common::PublicKey, -) -> Result<(Tx, Option)> { +pub async fn build_transfer<'a, N: Namada<'a>>( + context: &N, + args: &mut args::TxTransfer, +) -> Result<(Tx, SigningTxData, Option)> { + let default_signer = Some(args.source.effective_address()); + let signing_data = signing::aux_signing_data( + context, + &args.tx, + Some(args.source.effective_address()), + default_signer, + ) + .await?; + let source = args.source.effective_address(); let target = args.target.effective_address(); let token = args.token.clone(); // Check that the source address exists on chain - source_exists_or_err::<_, IO>(source.clone(), args.tx.force, client) - .await?; + source_exists_or_err(source.clone(), args.tx.force, context).await?; // Check that the target address exists on chain - target_exists_or_err::<_, IO>(target.clone(), args.tx.force, client) - .await?; + target_exists_or_err(target.clone(), args.tx.force, context).await?; // Check source balance let balance_key = token::balance_key(&token, &source); // validate the amount given let validated_amount = - validate_amount::<_, IO>(client, args.amount, &token, args.tx.force) - .await?; + validate_amount(context, args.amount, &token, args.tx.force).await?; args.amount = InputAmount::Validated(validated_amount); - let post_balance = check_balance_too_low_err::( + let post_balance = check_balance_too_low_err( &token, &source, validated_amount.amount, balance_key, args.tx.force, - client, + context, ) .await?; let tx_source_balance = Some(TxSourcePostBalance { @@ -1718,8 +1673,10 @@ pub async fn build_transfer< }; // Construct the shielded part of the transaction, if any - let stx_result = shielded - .gen_shielded_transfer::<_, IO>(client, args.clone()) + let stx_result = + ShieldedContext::::gen_shielded_transfer( + context, args, + ) .await; let shielded_parts = match stx_result { @@ -1741,10 +1698,9 @@ pub async fn build_transfer< Some(transfer) => { // Get the decoded asset types used in the transaction to give // offline wallet users more information - let asset_types = - used_asset_types(shielded, client, &transfer.builder) - .await - .unwrap_or_default(); + let asset_types = used_asset_types(context, &transfer.builder) + .await + .unwrap_or_default(); Some(asset_types) } }; @@ -1788,15 +1744,13 @@ pub async fn build_transfer< }; Ok(()) }; - let (tx, unshielding_epoch) = build_pow_flag::<_, _, _, _, _, IO>( - client, - wallet, - shielded, + let (tx, unshielding_epoch) = build_pow_flag( + context, &args.tx, - args.tx_code_path, + args.tx_code_path.clone(), transfer, add_shielded, - &fee_payer, + &signing_data.fee_payer, tx_source_balance, ) .await?; @@ -1819,33 +1773,27 @@ pub async fn build_transfer< (None, Some(_transfer_unshield_epoch)) => shielded_tx_epoch, (None, None) => None, }; - Ok((tx, masp_epoch)) + Ok((tx, signing_data, masp_epoch)) } /// Submit a transaction to initialize an account -pub async fn build_init_account< - C: crate::sdk::queries::Client + Sync, - U: WalletUtils, - V: ShieldedUtils, - IO: Io, ->( - client: &C, - wallet: &mut Wallet, - shielded: &mut ShieldedContext, +pub async fn build_init_account<'a>( + context: &impl Namada<'a>, args::TxInitAccount { tx: tx_args, vp_code_path, tx_code_path, public_keys, threshold, - }: args::TxInitAccount, - fee_payer: &common::PublicKey, -) -> Result<(Tx, Option)> { - let vp_code_hash = - query_wasm_code_hash_buf::<_, IO>(client, &vp_code_path).await?; + }: &args::TxInitAccount, +) -> Result<(Tx, SigningTxData, Option)> { + let signing_data = + signing::aux_signing_data(context, tx_args, None, None).await?; + + let vp_code_hash = query_wasm_code_hash_buf(context, vp_code_path).await?; let threshold = match threshold { - Some(threshold) => threshold, + Some(threshold) => *threshold, None => { if public_keys.len() == 1 { 1u8 @@ -1856,7 +1804,7 @@ pub async fn build_init_account< }; let data = InitAccount { - public_keys, + public_keys: public_keys.clone(), // We will add the hash inside the add_code_hash function vp_code_hash: Hash::zero(), threshold, @@ -1867,30 +1815,22 @@ pub async fn build_init_account< data.vp_code_hash = extra_section_hash; Ok(()) }; - build::<_, _, _, _, _, IO>( - client, - wallet, - shielded, - &tx_args, - tx_code_path, + build( + context, + tx_args, + tx_code_path.clone(), data, add_code_hash, - fee_payer, + &signing_data.fee_payer, None, ) .await + .map(|(tx, epoch)| (tx, signing_data, epoch)) } /// Submit a transaction to update a VP -pub async fn build_update_account< - C: crate::sdk::queries::Client + Sync, - U: WalletUtils, - V: ShieldedUtils, - IO: Io, ->( - client: &C, - wallet: &mut Wallet, - shielded: &mut ShieldedContext, +pub async fn build_update_account<'a>( + context: &impl Namada<'a>, args::TxUpdateAccount { tx: tx_args, vp_code_path, @@ -1898,22 +1838,30 @@ pub async fn build_update_account< addr, public_keys, threshold, - }: args::TxUpdateAccount, - fee_payer: common::PublicKey, -) -> Result<(Tx, Option)> { - let addr = - if let Some(account) = rpc::get_account_info(client, &addr).await? { - account.address - } else if tx_args.force { - addr - } else { - return Err(Error::from(TxError::LocationDoesNotExist(addr))); - }; + }: &args::TxUpdateAccount, +) -> Result<(Tx, SigningTxData, Option)> { + let default_signer = Some(addr.clone()); + let signing_data = signing::aux_signing_data( + context, + tx_args, + Some(addr.clone()), + default_signer, + ) + .await?; + + let addr = if let Some(account) = + rpc::get_account_info(context.client(), addr).await? + { + account.address + } else if tx_args.force { + addr.clone() + } else { + return Err(Error::from(TxError::LocationDoesNotExist(addr.clone()))); + }; let vp_code_hash = match vp_code_path { Some(code_path) => { - let vp_hash = - query_wasm_code_hash_buf::<_, IO>(client, &code_path).await?; + let vp_hash = query_wasm_code_hash_buf(context, code_path).await?; Some(vp_hash) } None => None, @@ -1927,8 +1875,8 @@ pub async fn build_update_account< let data = UpdateAccount { addr, vp_code_hash: extra_section_hash, - public_keys, - threshold, + public_keys: public_keys.clone(), + threshold: *threshold, }; let add_code_hash = |tx: &mut Tx, data: &mut UpdateAccount| { @@ -1937,81 +1885,77 @@ pub async fn build_update_account< data.vp_code_hash = extra_section_hash; Ok(()) }; - build::<_, _, _, _, _, IO>( - client, - wallet, - shielded, - &tx_args, - tx_code_path, + build( + context, + tx_args, + tx_code_path.clone(), data, add_code_hash, - &fee_payer, + &signing_data.fee_payer, None, ) .await + .map(|(tx, epoch)| (tx, signing_data, epoch)) } /// Submit a custom transaction -pub async fn build_custom< - C: crate::sdk::queries::Client + Sync, - U: WalletUtils, - V: ShieldedUtils, - IO: Io, ->( - client: &C, - wallet: &mut Wallet, - shielded: &mut ShieldedContext, +pub async fn build_custom<'a>( + context: &impl Namada<'a>, args::TxCustom { tx: tx_args, code_path, data_path, serialized_tx, - owner: _, - }: args::TxCustom, - fee_payer: &common::PublicKey, -) -> Result<(Tx, Option)> { + owner, + }: &args::TxCustom, +) -> Result<(Tx, SigningTxData, Option)> { + let default_signer = Some(owner.clone()); + let signing_data = signing::aux_signing_data( + context, + tx_args, + Some(owner.clone()), + default_signer, + ) + .await?; + let mut tx = if let Some(serialized_tx) = serialized_tx { Tx::deserialize(serialized_tx.as_ref()).map_err(|_| { Error::Other("Invalid tx deserialization.".to_string()) })? } else { - let tx_code_hash = query_wasm_code_hash_buf::<_, IO>( - client, - &code_path + let tx_code_hash = query_wasm_code_hash_buf( + context, + code_path + .as_ref() .ok_or(Error::Other("No code path supplied".to_string()))?, ) .await?; let chain_id = tx_args.chain_id.clone().unwrap(); let mut tx = Tx::new(chain_id, tx_args.expiration); tx.add_code_from_hash(tx_code_hash); - data_path.map(|data| tx.add_serialized_data(data)); + data_path.clone().map(|data| tx.add_serialized_data(data)); tx }; - let epoch = prepare_tx::( - client, - wallet, - shielded, - &tx_args, + let epoch = prepare_tx( + context, + tx_args, &mut tx, - fee_payer.clone(), + signing_data.fee_payer.clone(), None, ) .await?; - Ok((tx, epoch)) + Ok((tx, signing_data, epoch)) } -async fn expect_dry_broadcast< - C: crate::ledger::queries::Client + Sync, - IO: Io, ->( +async fn expect_dry_broadcast<'a>( to_broadcast: TxBroadcastData, - client: &C, + context: &impl Namada<'a>, ) -> Result { match to_broadcast { TxBroadcastData::DryRun(tx) => { - rpc::dry_run_tx::<_, IO>(client, tx.to_bytes()).await?; + rpc::dry_run_tx(context, tx.to_bytes()).await?; Ok(ProcessTxResponse::DryRun) } TxBroadcastData::Live { @@ -2029,20 +1973,17 @@ fn lift_rpc_error(res: std::result::Result) -> Result { /// Returns the given validator if the given address is a validator, /// otherwise returns an error, force forces the address through even /// if it isn't a validator -async fn known_validator_or_err< - C: crate::ledger::queries::Client + Sync, - IO: Io, ->( +async fn known_validator_or_err<'a>( validator: Address, force: bool, - client: &C, + context: &impl Namada<'a>, ) -> Result
{ // Check that the validator address exists on chain - let is_validator = rpc::is_validator(client, &validator).await?; + let is_validator = rpc::is_validator(context.client(), &validator).await?; if !is_validator { if force { edisplay_line!( - IO, + context.io(), "The address {} doesn't belong to any known validator account.", validator ); @@ -2058,21 +1999,20 @@ async fn known_validator_or_err< /// general pattern for checking if an address exists on the chain, or /// throwing an error if it's not forced. Takes a generic error /// message and the error type. -async fn address_exists_or_err( +async fn address_exists_or_err<'a, F>( addr: Address, force: bool, - client: &C, + context: &impl Namada<'a>, message: String, err: F, ) -> Result
where - C: crate::sdk::queries::Client + Sync, F: FnOnce(Address) -> Error, { - let addr_exists = rpc::known_address::(client, &addr).await?; + let addr_exists = rpc::known_address(context.client(), &addr).await?; if !addr_exists { if force { - edisplay_line!(IO, "{}", message); + edisplay_line!(context.io(), "{}", message); Ok(addr) } else { Err(err(addr)) @@ -2085,17 +2025,14 @@ where /// Returns the given source address if the given address exists on chain /// otherwise returns an error, force forces the address through even /// if it isn't on chain -async fn source_exists_or_err< - C: crate::ledger::queries::Client + Sync, - IO: Io, ->( +async fn source_exists_or_err<'a>( token: Address, force: bool, - client: &C, + context: &impl Namada<'a>, ) -> Result
{ let message = format!("The source address {} doesn't exist on chain.", token); - address_exists_or_err::<_, _, IO>(token, force, client, message, |err| { + address_exists_or_err(token, force, context, message, |err| { Error::from(TxError::SourceDoesNotExist(err)) }) .await @@ -2104,17 +2041,14 @@ async fn source_exists_or_err< /// Returns the given target address if the given address exists on chain /// otherwise returns an error, force forces the address through even /// if it isn't on chain -async fn target_exists_or_err< - C: crate::ledger::queries::Client + Sync, - IO: Io, ->( +async fn target_exists_or_err<'a>( token: Address, force: bool, - client: &C, + context: &impl Namada<'a>, ) -> Result
{ let message = format!("The target address {} doesn't exist on chain.", token); - address_exists_or_err::<_, _, IO>(token, force, client, message, |err| { + address_exists_or_err(token, force, context, message, |err| { Error::from(TxError::TargetLocationDoesNotExist(err)) }) .await @@ -2123,39 +2057,33 @@ async fn target_exists_or_err< /// Checks the balance at the given address is enough to transfer the /// given amount, along with the balance even existing. Force /// overrides this. Returns the updated balance for fee check if necessary -async fn check_balance_too_low_err< - C: crate::ledger::queries::Client + Sync, - IO: Io, ->( +async fn check_balance_too_low_err<'a, N: Namada<'a>>( token: &Address, source: &Address, amount: token::Amount, balance_key: storage::Key, force: bool, - client: &C, + context: &N, ) -> Result { - match rpc::query_storage_value::(client, &balance_key) - .await + match rpc::query_storage_value::( + context.client(), + &balance_key, + ) + .await { Ok(balance) => match balance.checked_sub(amount) { Some(diff) => Ok(diff), None => { if force { edisplay_line!( - IO, + context.io(), "The balance of the source {} of token {} is lower \ than the amount to be transferred. Amount to \ transfer is {} and the balance is {}.", source, token, - format_denominated_amount::<_, IO>( - client, token, amount - ) - .await, - format_denominated_amount::<_, IO>( - client, token, balance - ) - .await, + context.format_amount(token, amount).await, + context.format_amount(token, balance).await, ); Ok(token::Amount::default()) } else { @@ -2173,7 +2101,7 @@ async fn check_balance_too_low_err< )) => { if force { edisplay_line!( - IO, + context.io(), "No balance found for the source {} of token {}", source, token @@ -2192,34 +2120,11 @@ async fn check_balance_too_low_err< } } -#[allow(dead_code)] -fn validate_untrusted_code_err( - vp_code: &Vec, - force: bool, -) -> Result<()> { - if let Err(err) = vm::validate_untrusted_wasm(vp_code) { - if force { - edisplay_line!( - IO, - "Validity predicate code validation failed with {}", - err - ); - Ok(()) - } else { - Err(Error::from(TxError::WasmValidationFailure(err))) - } - } else { - Ok(()) - } -} -async fn query_wasm_code_hash_buf< - C: crate::ledger::queries::Client + Sync, - IO: Io, ->( - client: &C, +async fn query_wasm_code_hash_buf<'a>( + context: &impl Namada<'a>, path: &Path, ) -> Result { - query_wasm_code_hash::<_, IO>(client, path.to_string_lossy()).await + query_wasm_code_hash(context, path.to_string_lossy()).await } /// A helper for [`fn build`] that can be used for `on_tx` arg that does nothing diff --git a/shared/src/sdk/wallet/alias.rs b/sdk/src/wallet/alias.rs similarity index 100% rename from shared/src/sdk/wallet/alias.rs rename to sdk/src/wallet/alias.rs diff --git a/shared/src/sdk/wallet/derivation_path.rs b/sdk/src/wallet/derivation_path.rs similarity index 98% rename from shared/src/sdk/wallet/derivation_path.rs rename to sdk/src/wallet/derivation_path.rs index 7f639161d2..7751e51701 100644 --- a/shared/src/sdk/wallet/derivation_path.rs +++ b/sdk/src/wallet/derivation_path.rs @@ -2,6 +2,7 @@ use core::fmt; use std::str::FromStr; use derivation_path::{ChildIndex, DerivationPath as DerivationPathInner}; +use namada_core::types::key::SchemeType; use thiserror::Error; use tiny_hderive::bip44::{ DerivationPath as HDeriveDerivationPath, @@ -9,8 +10,6 @@ use tiny_hderive::bip44::{ }; use tiny_hderive::Error as HDeriveError; -use crate::types::key::SchemeType; - const ETH_COIN_TYPE: u32 = 60; const NAMADA_COIN_TYPE: u32 = 877; @@ -114,8 +113,9 @@ impl IntoHDeriveDerivationPath for DerivationPath { #[cfg(test)] mod tests { + use namada_core::types::key::SchemeType; + use super::DerivationPath; - use crate::types::key::SchemeType; #[test] fn path_is_compatible() { diff --git a/shared/src/sdk/wallet/keys.rs b/sdk/src/wallet/keys.rs similarity index 97% rename from shared/src/sdk/wallet/keys.rs rename to sdk/src/wallet/keys.rs index 867a2b1ad0..a8d267c898 100644 --- a/shared/src/sdk/wallet/keys.rs +++ b/sdk/src/wallet/keys.rs @@ -11,7 +11,7 @@ use serde::{Deserialize, Serialize}; use thiserror::Error; use zeroize::Zeroizing; -use crate::sdk::wallet::WalletUtils; +use crate::wallet::WalletIo; const ENCRYPTED_KEY_PREFIX: &str = "encrypted:"; const UNENCRYPTED_KEY_PREFIX: &str = "unencrypted:"; @@ -166,7 +166,7 @@ where /// Get a raw keypair from a stored keypair. If the keypair is encrypted and /// no password is provided in the argument, a password will be prompted /// from stdin. - pub fn get( + pub fn get( &self, decrypt: bool, password: Option>, @@ -174,8 +174,8 @@ where match self { StoredKeypair::Encrypted(encrypted_keypair) => { if decrypt { - let password = password - .unwrap_or_else(|| U::read_decryption_password()); + let password = + password.unwrap_or_else(|| U::read_password(false)); let key = encrypted_keypair.decrypt(password)?; Ok(key) } else { diff --git a/shared/src/sdk/wallet/mod.rs b/sdk/src/wallet/mod.rs similarity index 76% rename from shared/src/sdk/wallet/mod.rs rename to sdk/src/wallet/mod.rs index 371c97806b..4ee3e13947 100644 --- a/shared/src/sdk/wallet/mod.rs +++ b/sdk/src/wallet/mod.rs @@ -13,6 +13,11 @@ use alias::Alias; use bip39::{Language, Mnemonic, MnemonicType, Seed}; use borsh::{BorshDeserialize, BorshSerialize}; use masp_primitives::zip32::ExtendedFullViewingKey; +use namada_core::types::address::Address; +use namada_core::types::key::*; +use namada_core::types::masp::{ + ExtendedSpendingKey, ExtendedViewingKey, PaymentAddress, +}; pub use pre_genesis::gen_key_to_store; use rand_core::RngCore; pub use store::{gen_sk_rng, AddressVpType, Store}; @@ -22,11 +27,6 @@ use zeroize::Zeroizing; use self::derivation_path::{DerivationPath, DerivationPathError}; pub use self::keys::{DecryptionError, StoredKeypair}; pub use self::store::{ConfirmationResponse, ValidatorData, ValidatorKeys}; -use crate::types::address::Address; -use crate::types::key::*; -use crate::types::masp::{ - ExtendedSpendingKey, ExtendedViewingKey, PaymentAddress, -}; /// Errors of key generation / recovery #[derive(Error, Debug)] @@ -40,12 +40,13 @@ pub enum GenRestoreKeyError { /// Mnemonic input error #[error("Mnemonic input error")] MnemonicInputError, + /// Key storage error + #[error("Key storage error")] + KeyStorageError, } /// Captures the interactive parts of the wallet's functioning -pub trait WalletUtils { - /// The location where the wallet is stored - type Storage; +pub trait WalletIo: Sized + Clone { /// Secure random number generator type Rng: RngCore; @@ -67,29 +68,161 @@ pub trait WalletUtils { } /// Read the password for decryption from the file/env/stdin. - fn read_decryption_password() -> Zeroizing; - - /// Read the password for encryption from the file/env/stdin. - /// If the password is read from stdin, the implementation is expected - /// to ask for a confirmation. - fn read_encryption_password() -> Zeroizing; + fn read_password(_confirm: bool) -> Zeroizing { + panic!("attempted to prompt for password in non-interactive mode"); + } /// Read an alias from the file/env/stdin. - fn read_alias(prompt_msg: &str) -> String; + fn read_alias(_prompt_msg: &str) -> String { + panic!("attempted to prompt for alias in non-interactive mode"); + } /// Read mnemonic code from the file/env/stdin. - fn read_mnemonic_code() -> Result; + fn read_mnemonic_code() -> Result { + panic!("attempted to prompt for alias in non-interactive mode"); + } /// Read a mnemonic code from the file/env/stdin. - fn read_mnemonic_passphrase(confirm: bool) -> Zeroizing; + fn read_mnemonic_passphrase(_confirm: bool) -> Zeroizing { + panic!("attempted to prompt for alias in non-interactive mode"); + } /// The given alias has been selected but conflicts with another alias in /// the store. Offer the user to either replace existing mapping, alter the /// chosen alias to a name of their choice, or cancel the aliasing. fn show_overwrite_confirmation( - alias: &Alias, - alias_for: &str, - ) -> store::ConfirmationResponse; + _alias: &Alias, + _alias_for: &str, + ) -> store::ConfirmationResponse { + // Automatically replace aliases in non-interactive mode + store::ConfirmationResponse::Replace + } +} + +/// Errors of wallet loading and storing +#[derive(Error, Debug)] +pub enum LoadStoreError { + /// Wallet store decoding error + #[error("Failed decoding the wallet store: {0}")] + Decode(toml::de::Error), + /// Wallet store reading error + #[error("Failed to read the wallet store from {0}: {1}")] + ReadWallet(String, String), + /// Wallet store writing error + #[error("Failed to write the wallet store: {0}")] + StoreNewWallet(String), +} + +/// Captures the permanent storage parts of the wallet's functioning +pub trait WalletStorage: Sized + Clone { + /// Save the wallet store to a file. + fn save(&self, wallet: &Wallet) -> Result<(), LoadStoreError>; + + /// Load a wallet from the store file. + fn load(&self, wallet: &mut Wallet) -> Result<(), LoadStoreError>; +} + +#[cfg(feature = "std")] +/// Implementation of wallet functionality depending on a standard filesystem +pub mod fs { + use std::fs; + use std::io::{Read, Write}; + use std::path::PathBuf; + + use fd_lock::RwLock; + use rand_core::OsRng; + + use super::*; + + /// A trait for deriving WalletStorage for standard filesystems + pub trait FsWalletStorage: Clone { + /// The directory in which the wallet is supposed to be stored + fn store_dir(&self) -> &PathBuf; + } + + /// Wallet file name + const FILE_NAME: &str = "wallet.toml"; + + impl WalletStorage for F { + fn save(&self, wallet: &Wallet) -> Result<(), LoadStoreError> { + let data = wallet.store.encode(); + let wallet_path = self.store_dir().join(FILE_NAME); + // Make sure the dir exists + let wallet_dir = wallet_path.parent().unwrap(); + fs::create_dir_all(wallet_dir).map_err(|err| { + LoadStoreError::StoreNewWallet(err.to_string()) + })?; + // Write the file + let mut options = fs::OpenOptions::new(); + options.create(true).write(true).truncate(true); + let mut lock = + RwLock::new(options.open(wallet_path).map_err(|err| { + LoadStoreError::StoreNewWallet(err.to_string()) + })?); + let mut guard = lock.write().map_err(|err| { + LoadStoreError::StoreNewWallet(err.to_string()) + })?; + guard + .write_all(&data) + .map_err(|err| LoadStoreError::StoreNewWallet(err.to_string())) + } + + fn load( + &self, + wallet: &mut Wallet, + ) -> Result<(), LoadStoreError> { + let wallet_file = self.store_dir().join(FILE_NAME); + let mut options = fs::OpenOptions::new(); + options.read(true).write(false); + let lock = + RwLock::new(options.open(&wallet_file).map_err(|err| { + LoadStoreError::ReadWallet( + wallet_file.to_string_lossy().into_owned(), + err.to_string(), + ) + })?); + let guard = lock.read().map_err(|err| { + LoadStoreError::ReadWallet( + wallet_file.to_string_lossy().into_owned(), + err.to_string(), + ) + })?; + let mut store = Vec::::new(); + (&*guard).read_to_end(&mut store).map_err(|err| { + LoadStoreError::ReadWallet( + self.store_dir().to_str().unwrap().parse().unwrap(), + err.to_string(), + ) + })?; + wallet.store = + Store::decode(store).map_err(LoadStoreError::Decode)?; + Ok(()) + } + } + + /// For a non-interactive filesystem based wallet + #[derive(Debug, BorshSerialize, BorshDeserialize, Clone)] + pub struct FsWalletUtils { + #[borsh_skip] + store_dir: PathBuf, + } + + impl FsWalletUtils { + /// Initialize a wallet at the given directory + pub fn new(store_dir: PathBuf) -> Wallet { + Wallet::new(Self { store_dir }, Store::default()) + } + } + + impl WalletIo for FsWalletUtils { + type Rng = OsRng; + } + + impl FsWalletStorage for FsWalletUtils { + fn store_dir(&self) -> &PathBuf { + &self.store_dir + } + } } /// The error that is produced when a given key cannot be obtained @@ -105,24 +238,217 @@ pub enum FindKeyError { /// Represents a collection of keys and addresses while caching key decryptions #[derive(Debug)] -pub struct Wallet { - store_dir: U::Storage, +pub struct Wallet { + /// Location where this shielded context is saved + utils: U, store: Store, decrypted_key_cache: HashMap, decrypted_spendkey_cache: HashMap, } -impl Wallet { +impl From> for Store { + fn from(wallet: Wallet) -> Self { + wallet.store + } +} + +impl Wallet { /// Create a new wallet from the given backing store and storage location - pub fn new(store_dir: U::Storage, store: Store) -> Self { + pub fn new(utils: U, store: Store) -> Self { Self { - store_dir, + utils, store, decrypted_key_cache: HashMap::default(), decrypted_spendkey_cache: HashMap::default(), } } + /// Add validator data to the store + pub fn add_validator_data( + &mut self, + address: Address, + keys: ValidatorKeys, + ) { + self.store.add_validator_data(address, keys); + } + + /// Returns a reference to the validator data, if it exists. + pub fn get_validator_data(&self) -> Option<&ValidatorData> { + self.store.get_validator_data() + } + + /// Returns a mut reference to the validator data, if it exists. + pub fn get_validator_data_mut(&mut self) -> Option<&mut ValidatorData> { + self.store.get_validator_data_mut() + } + + /// Take the validator data, if it exists. + pub fn take_validator_data(&mut self) -> Option { + self.store.take_validator_data() + } + + /// Returns the validator data, if it exists. + pub fn into_validator_data(self) -> Option { + self.store.into_validator_data() + } + + /// Provide immutable access to the backing store + pub fn store(&self) -> &Store { + &self.store + } + + /// Provide mutable access to the backing store + pub fn store_mut(&mut self) -> &mut Store { + &mut self.store + } + + /// Extend this wallet from pre-genesis validator wallet. + pub fn extend_from_pre_genesis_validator( + &mut self, + validator_address: Address, + validator_alias: Alias, + other: pre_genesis::ValidatorWallet, + ) { + self.store.extend_from_pre_genesis_validator( + validator_address, + validator_alias, + other, + ) + } + + /// Gets all addresses given a vp_type + pub fn get_addresses_with_vp_type( + &self, + vp_type: AddressVpType, + ) -> HashSet
{ + self.store.get_addresses_with_vp_type(vp_type) + } + + /// Add a vp_type to a given address + pub fn add_vp_type_to_address( + &mut self, + vp_type: AddressVpType, + address: Address, + ) { + // defaults to an empty set + self.store.add_vp_type_to_address(vp_type, address) + } + + /// Get addresses with tokens VP type keyed and ordered by their aliases. + pub fn tokens_with_aliases(&self) -> BTreeMap { + self.get_addresses_with_vp_type(AddressVpType::Token) + .into_iter() + .map(|addr| { + let alias = self.lookup_alias(&addr); + (alias, addr) + }) + .collect() + } + + /// Find the stored address by an alias. + pub fn find_address(&self, alias: impl AsRef) -> Option<&Address> { + self.store.find_address(alias) + } + + /// Find an alias by the address if it's in the wallet. + pub fn find_alias(&self, address: &Address) -> Option<&Alias> { + self.store.find_alias(address) + } + + /// Try to find an alias for a given address from the wallet. If not found, + /// formats the address into a string. + pub fn lookup_alias(&self, addr: &Address) -> String { + match self.find_alias(addr) { + Some(alias) => format!("{}", alias), + None => format!("{}", addr), + } + } + + /// Find the viewing key with the given alias in the wallet and return it + pub fn find_viewing_key( + &mut self, + alias: impl AsRef, + ) -> Result<&ExtendedViewingKey, FindKeyError> { + self.store + .find_viewing_key(alias.as_ref()) + .ok_or(FindKeyError::KeyNotFound) + } + + /// Find the payment address with the given alias in the wallet and return + /// it + pub fn find_payment_addr( + &self, + alias: impl AsRef, + ) -> Option<&PaymentAddress> { + self.store.find_payment_addr(alias.as_ref()) + } + + /// Get all known keys by their alias, paired with PKH, if known. + pub fn get_keys( + &self, + ) -> HashMap< + String, + (&StoredKeypair, Option<&PublicKeyHash>), + > { + self.store + .get_keys() + .into_iter() + .map(|(alias, value)| (alias.into(), value)) + .collect() + } + + /// Get all known addresses by their alias, paired with PKH, if known. + pub fn get_addresses(&self) -> HashMap { + self.store + .get_addresses() + .iter() + .map(|(alias, value)| (alias.into(), value.clone())) + .collect() + } + + /// Get all known payment addresses by their alias + pub fn get_payment_addrs(&self) -> HashMap { + self.store + .get_payment_addrs() + .iter() + .map(|(alias, value)| (alias.into(), *value)) + .collect() + } + + /// Get all known viewing keys by their alias + pub fn get_viewing_keys(&self) -> HashMap { + self.store + .get_viewing_keys() + .iter() + .map(|(alias, value)| (alias.into(), *value)) + .collect() + } + + /// Get all known viewing keys by their alias + pub fn get_spending_keys( + &self, + ) -> HashMap> { + self.store + .get_spending_keys() + .iter() + .map(|(alias, value)| (alias.into(), value)) + .collect() + } +} + +impl Wallet { + /// Load a wallet from the store file. + pub fn load(&mut self) -> Result<(), LoadStoreError> { + self.utils.clone().load(self) + } + + /// Save the wallet store to a file. + pub fn save(&self) -> Result<(), LoadStoreError> { + self.utils.save(self) + } +} + +impl Wallet { fn gen_and_store_key( &mut self, scheme: SchemeType, @@ -161,6 +487,7 @@ impl Wallet { alias: Option, alias_force: bool, derivation_path: Option, + mnemonic_passphrase: Option<(Mnemonic, Zeroizing)>, password: Option>, ) -> Result, GenRestoreKeyError> { let parsed_derivation_path = derivation_path @@ -182,8 +509,12 @@ impl Wallet { ) } println!("Using HD derivation path {}", parsed_derivation_path); - let mnemonic = U::read_mnemonic_code()?; - let passphrase = U::read_mnemonic_passphrase(false); + let (mnemonic, passphrase) = + if let Some(mnemonic_passphrase) = mnemonic_passphrase { + mnemonic_passphrase + } else { + (U::read_mnemonic_code()?, U::read_mnemonic_passphrase(false)) + }; let seed = Seed::new(&mnemonic, &passphrase); Ok(self.gen_and_store_key( @@ -212,9 +543,11 @@ impl Wallet { scheme: SchemeType, alias: Option, alias_force: bool, + passphrase: Option>, password: Option>, derivation_path_and_mnemonic_rng: Option<(String, &mut U::Rng)>, - ) -> Result, GenRestoreKeyError> { + ) -> Result<(String, common::SecretKey, Option), GenRestoreKeyError> + { let parsed_path_and_rng = derivation_path_and_mnemonic_rng .map(|(raw_derivation_path, rng)| { let is_default = @@ -242,27 +575,33 @@ impl Wallet { println!("Using HD derivation path {}", parsed_derivation_path); } + let mut mnemonic_opt = None; let seed_and_derivation_path //: Option> = parsed_path_and_rng.map(|(path, rng)| { const MNEMONIC_TYPE: MnemonicType = MnemonicType::Words24; - let mnemonic = U::generate_mnemonic_code(MNEMONIC_TYPE, rng)?; + let mnemonic = mnemonic_opt + .insert(U::generate_mnemonic_code(MNEMONIC_TYPE, rng)?); println!( "Safely store your {} words mnemonic.", MNEMONIC_TYPE.word_count() ); println!("{}", mnemonic.clone().into_phrase()); - let passphrase = U::read_mnemonic_passphrase(true); - Ok((Seed::new(&mnemonic, &passphrase), path)) + let passphrase = passphrase + .unwrap_or_else(|| U::read_mnemonic_passphrase(true)); + Ok((Seed::new(mnemonic, &passphrase), path)) }).transpose()?; - Ok(self.gen_and_store_key( - scheme, - alias, - alias_force, - seed_and_derivation_path, - password, - )) + let (alias, key) = self + .gen_and_store_key( + scheme, + alias, + alias_force, + seed_and_derivation_path, + password, + ) + .ok_or(GenRestoreKeyError::KeyStorageError)?; + Ok((alias, key, mnemonic_opt)) } /// Generate a disposable signing key for fee payment and store it under the @@ -280,10 +619,9 @@ impl Wallet { // Generate a disposable keypair to sign the wrapper if requested // TODO: once the wrapper transaction has been accepted, this key can be // deleted from wallet - let (alias, disposable_keypair) = self - .gen_key(SchemeType::Ed25519, Some(alias), false, None, None) - .expect("Failed to initialize disposable keypair") - .expect("Missing alias and secret key"); + let (alias, disposable_keypair, _mnemonic) = self + .gen_key(SchemeType::Ed25519, Some(alias), false, None, None, None) + .expect("Failed to initialize disposable keypair"); println!("Created disposable keypair with alias {alias}"); disposable_keypair @@ -304,35 +642,6 @@ impl Wallet { (alias.into(), key) } - /// Add validator data to the store - pub fn add_validator_data( - &mut self, - address: Address, - keys: ValidatorKeys, - ) { - self.store.add_validator_data(address, keys); - } - - /// Returns a reference to the validator data, if it exists. - pub fn get_validator_data(&self) -> Option<&ValidatorData> { - self.store.get_validator_data() - } - - /// Returns a mut reference to the validator data, if it exists. - pub fn get_validator_data_mut(&mut self) -> Option<&mut ValidatorData> { - self.store.get_validator_data_mut() - } - - /// Take the validator data, if it exists. - pub fn take_validator_data(&mut self) -> Option { - self.store.take_validator_data() - } - - /// Returns the validator data, if it exists. - pub fn into_validator_data(self) -> Option { - self.store.into_validator_data() - } - /// Find the stored key by an alias, a public key hash or a public key. /// If the key is encrypted and password not supplied, then password will be /// interactively prompted. Any keys that are decrypted are stored in and @@ -389,25 +698,6 @@ impl Wallet { ) } - /// Find the viewing key with the given alias in the wallet and return it - pub fn find_viewing_key( - &mut self, - alias: impl AsRef, - ) -> Result<&ExtendedViewingKey, FindKeyError> { - self.store - .find_viewing_key(alias.as_ref()) - .ok_or(FindKeyError::KeyNotFound) - } - - /// Find the payment address with the given alias in the wallet and return - /// it - pub fn find_payment_addr( - &self, - alias: impl AsRef, - ) -> Option<&PaymentAddress> { - self.store.find_payment_addr(alias.as_ref()) - } - /// Find the stored key by a public key. /// If the key is encrypted and password not supplied, then password will be /// interactively prompted for. Any keys that are decrypted are stored in @@ -463,7 +753,7 @@ impl Wallet { .store .find_key_by_pkh(pkh) .ok_or(FindKeyError::KeyNotFound)?; - Self::decrypt_stored_key::<_>( + Self::decrypt_stored_key( &mut self.decrypted_key_cache, stored_key, alias, @@ -489,7 +779,7 @@ impl Wallet { match stored_key { StoredKeypair::Encrypted(encrypted) => { let password = - password.unwrap_or_else(U::read_decryption_password); + password.unwrap_or_else(|| U::read_password(false)); let key = encrypted .decrypt(password) .map_err(FindKeyError::KeyDecryptionError)?; @@ -503,77 +793,6 @@ impl Wallet { } } - /// Get all known keys by their alias, paired with PKH, if known. - pub fn get_keys( - &self, - ) -> HashMap< - String, - (&StoredKeypair, Option<&PublicKeyHash>), - > { - self.store - .get_keys() - .into_iter() - .map(|(alias, value)| (alias.into(), value)) - .collect() - } - - /// Find the stored address by an alias. - pub fn find_address(&self, alias: impl AsRef) -> Option<&Address> { - self.store.find_address(alias) - } - - /// Find an alias by the address if it's in the wallet. - pub fn find_alias(&self, address: &Address) -> Option<&Alias> { - self.store.find_alias(address) - } - - /// Try to find an alias for a given address from the wallet. If not found, - /// formats the address into a string. - pub fn lookup_alias(&self, addr: &Address) -> String { - match self.find_alias(addr) { - Some(alias) => format!("{}", alias), - None => format!("{}", addr), - } - } - - /// Get all known addresses by their alias, paired with PKH, if known. - pub fn get_addresses(&self) -> HashMap { - self.store - .get_addresses() - .iter() - .map(|(alias, value)| (alias.into(), value.clone())) - .collect() - } - - /// Get all known payment addresses by their alias - pub fn get_payment_addrs(&self) -> HashMap { - self.store - .get_payment_addrs() - .iter() - .map(|(alias, value)| (alias.into(), *value)) - .collect() - } - - /// Get all known viewing keys by their alias - pub fn get_viewing_keys(&self) -> HashMap { - self.store - .get_viewing_keys() - .iter() - .map(|(alias, value)| (alias.into(), *value)) - .collect() - } - - /// Get all known viewing keys by their alias - pub fn get_spending_keys( - &self, - ) -> HashMap> { - self.store - .get_spending_keys() - .iter() - .map(|(alias, value)| (alias.into(), value)) - .collect() - } - /// Add a new address with the given alias. If the alias is already used, /// will ask whether the existing alias should be replaced, a different /// alias is desired, or the alias creation should be cancelled. Return @@ -664,62 +883,4 @@ impl Wallet { .insert_payment_addr::(alias.into(), payment_addr, force_alias) .map(Into::into) } - - /// Extend this wallet from pre-genesis validator wallet. - pub fn extend_from_pre_genesis_validator( - &mut self, - validator_address: Address, - validator_alias: Alias, - other: pre_genesis::ValidatorWallet, - ) { - self.store.extend_from_pre_genesis_validator( - validator_address, - validator_alias, - other, - ) - } - - /// Gets all addresses given a vp_type - pub fn get_addresses_with_vp_type( - &self, - vp_type: AddressVpType, - ) -> HashSet
{ - self.store.get_addresses_with_vp_type(vp_type) - } - - /// Add a vp_type to a given address - pub fn add_vp_type_to_address( - &mut self, - vp_type: AddressVpType, - address: Address, - ) { - // defaults to an empty set - self.store.add_vp_type_to_address(vp_type, address) - } - - /// Provide immutable access to the backing store - pub fn store(&self) -> &Store { - &self.store - } - - /// Provide mutable access to the backing store - pub fn store_mut(&mut self) -> &mut Store { - &mut self.store - } - - /// Access storage location data - pub fn store_dir(&self) -> &U::Storage { - &self.store_dir - } - - /// Get addresses with tokens VP type keyed and ordered by their aliases. - pub fn tokens_with_aliases(&self) -> BTreeMap { - self.get_addresses_with_vp_type(AddressVpType::Token) - .into_iter() - .map(|addr| { - let alias = self.lookup_alias(&addr); - (alias, addr) - }) - .collect() - } } diff --git a/shared/src/sdk/wallet/pre_genesis.rs b/sdk/src/wallet/pre_genesis.rs similarity index 96% rename from shared/src/sdk/wallet/pre_genesis.rs rename to sdk/src/wallet/pre_genesis.rs index fd66dedbfe..916ec43781 100644 --- a/shared/src/sdk/wallet/pre_genesis.rs +++ b/sdk/src/wallet/pre_genesis.rs @@ -1,11 +1,11 @@ //! Provides functionality for managing validator keys +use namada_core::types::key::{common, SchemeType}; use serde::{Deserialize, Serialize}; use thiserror::Error; use zeroize::Zeroizing; -use crate::sdk::wallet; -use crate::sdk::wallet::{store, StoredKeypair}; -use crate::types::key::{common, SchemeType}; +use crate::wallet; +use crate::wallet::{store, StoredKeypair}; /// Ways in which wallet store operations can fail #[derive(Error, Debug)] diff --git a/shared/src/sdk/wallet/store.rs b/sdk/src/wallet/store.rs similarity index 98% rename from shared/src/sdk/wallet/store.rs rename to sdk/src/wallet/store.rs index 509ff5afe6..201cc885a4 100644 --- a/shared/src/sdk/wallet/store.rs +++ b/sdk/src/wallet/store.rs @@ -8,6 +8,12 @@ use bimap::BiHashMap; use bip39::Seed; use itertools::Itertools; use masp_primitives::zip32::ExtendedFullViewingKey; +use namada_core::types::address::{Address, ImplicitAddress}; +use namada_core::types::key::dkg_session_keys::DkgKeypair; +use namada_core::types::key::*; +use namada_core::types::masp::{ + ExtendedSpendingKey, ExtendedViewingKey, PaymentAddress, +}; #[cfg(feature = "masp-tx-gen")] use rand_core::RngCore; use serde::{Deserialize, Serialize}; @@ -17,13 +23,7 @@ use zeroize::Zeroizing; use super::alias::{self, Alias}; use super::derivation_path::DerivationPath; use super::pre_genesis; -use crate::sdk::wallet::{StoredKeypair, WalletUtils}; -use crate::types::address::{Address, ImplicitAddress}; -use crate::types::key::dkg_session_keys::DkgKeypair; -use crate::types::key::*; -use crate::types::masp::{ - ExtendedSpendingKey, ExtendedViewingKey, PaymentAddress, -}; +use crate::wallet::{StoredKeypair, WalletIo}; /// Actions that can be taken when there is an alias conflict pub enum ConfirmationResponse { @@ -239,7 +239,7 @@ impl Store { /// key. /// Returns None if the alias already exists and the user decides to skip /// it. No changes in the wallet store are made. - pub fn gen_key( + pub fn gen_key( &mut self, scheme: SchemeType, alias: Option, @@ -277,7 +277,7 @@ impl Store { } /// Generate a spending key similarly to how it's done for keypairs - pub fn gen_spending_key( + pub fn gen_spending_key( &mut self, alias: String, password: Option>, @@ -335,7 +335,7 @@ impl Store { /// will prompt for overwrite/reselection confirmation. If declined, then /// keypair is not inserted and nothing is returned, otherwise selected /// alias is returned. - pub fn insert_keypair( + pub fn insert_keypair( &mut self, alias: Alias, keypair: StoredKeypair, @@ -388,7 +388,7 @@ impl Store { } /// Insert spending keys similarly to how it's done for keypairs - pub fn insert_spending_key( + pub fn insert_spending_key( &mut self, alias: Alias, spendkey: StoredKeypair, @@ -418,7 +418,7 @@ impl Store { } /// Insert viewing keys similarly to how it's done for keypairs - pub fn insert_viewing_key( + pub fn insert_viewing_key( &mut self, alias: Alias, viewkey: ExtendedViewingKey, @@ -463,7 +463,7 @@ impl Store { } /// Insert payment addresses similarly to how it's done for keypairs - pub fn insert_payment_addr( + pub fn insert_payment_addr( &mut self, alias: Alias, payment_addr: PaymentAddress, @@ -507,7 +507,7 @@ impl Store { /// will prompt for overwrite/reselection confirmation, which when declined, /// the address won't be added. Return the selected alias if the address has /// been added. - pub fn insert_address( + pub fn insert_address( &mut self, alias: Alias, address: Address, diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 3259afd2c7..21eb023e18 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -19,11 +19,12 @@ default = ["abciplus", "namada-sdk", "wasm-runtime"] mainnet = [ "namada_core/mainnet", ] -std = [] +std = ["fd-lock"] # NOTE "dev" features that shouldn't be used in live networks are enabled by default for now dev = [] ferveo-tpke = [ "namada_core/ferveo-tpke", + "namada_sdk/ferveo-tpke", ] wasm-runtime = [ "namada_core/wasm-runtime", @@ -41,6 +42,7 @@ wasm-runtime = [ # Enable queries support for an async client async-client = [ "async-trait", + "namada_sdk/async-client" ] # Requires async traits to be safe to send across threads @@ -50,6 +52,7 @@ async-send = [] tendermint-rpc = [ "async-client", "dep:tendermint-rpc", + "namada_sdk/tendermint-rpc", ] # tendermint-rpc HttpClient http-client = [ @@ -60,15 +63,18 @@ abciplus = [ "namada_core/abciplus", "namada_proof_of_stake/abciplus", "namada_ethereum_bridge/abciplus", + "namada_sdk/abciplus", ] ibc-mocks = [ "namada_core/ibc-mocks", + "namada_sdk/ibc-mocks", ] masp-tx-gen = [ "rand", "rand_core", + "namada_sdk/masp-tx-gen", ] # for integration tests and test utilies @@ -76,6 +82,7 @@ testing = [ "namada_core/testing", "namada_ethereum_bridge/testing", "namada_proof_of_stake/testing", + "namada_sdk/testing", "async-client", "proptest", "rand_core", @@ -87,13 +94,18 @@ namada-sdk = [ "tendermint-rpc", "masp-tx-gen", "ferveo-tpke", - "masp_primitives/transparent-inputs" + "masp_primitives/transparent-inputs", + "namada_sdk/namada-sdk", ] -multicore = ["masp_proofs/multicore"] +multicore = [ + "masp_proofs/multicore", + "namada_sdk/multicore", +] [dependencies] namada_core = {path = "../core", default-features = false, features = ["secp256k1-sign"]} +namada_sdk = {path = "../sdk", default-features = false} namada_proof_of_stake = {path = "../proof_of_stake", default-features = false} namada_ethereum_bridge = {path = "../ethereum_bridge", default-features = false} async-trait = {version = "0.1.51", optional = true} @@ -107,6 +119,7 @@ derivative.workspace = true ethbridge-bridge-contract.workspace = true ethers.workspace = true eyre.workspace = true +fd-lock = { workspace = true, optional = true } futures.workspace = true itertools.workspace = true loupe = {version = "0.1.3", optional = true} @@ -149,6 +162,7 @@ zeroize.workspace = true tokio = {workspace = true, features = ["full"]} [target.'cfg(target_family = "wasm")'.dependencies] +tokio = {workspace = true, default-features = false, features = ["sync"]} wasmtimer = "0.2.0" [dev-dependencies] diff --git a/shared/src/ledger/governance/utils.rs b/shared/src/ledger/governance/utils.rs index d4b4d1316c..a254556cce 100644 --- a/shared/src/ledger/governance/utils.rs +++ b/shared/src/ledger/governance/utils.rs @@ -3,6 +3,7 @@ use std::collections::HashMap; use namada_core::ledger::governance::utils::TallyResult; +use namada_sdk::events::{Event, EventLevel}; use thiserror::Error; use crate::ledger::events::EventType; @@ -34,6 +35,16 @@ pub struct ProposalEvent { pub attributes: HashMap, } +impl From for Event { + fn from(proposal_event: ProposalEvent) -> Self { + Self { + event_type: EventType::Proposal, + level: EventLevel::Block, + attributes: proposal_event.attributes, + } + } +} + impl ProposalEvent { /// Create a proposal event pub fn new( diff --git a/shared/src/ledger/mod.rs b/shared/src/ledger/mod.rs index 04b5809bc2..676d57190f 100644 --- a/shared/src/ledger/mod.rs +++ b/shared/src/ledger/mod.rs @@ -1,7 +1,6 @@ //! The ledger modules -pub mod eth_bridge; -pub mod events; +pub use namada_sdk::{eth_bridge, events}; pub mod governance; pub mod ibc; pub mod inflation; @@ -10,10 +9,367 @@ pub mod pgf; pub mod pos; #[cfg(all(feature = "wasm-runtime", feature = "ferveo-tpke"))] pub mod protocol; -pub mod queries; +pub use namada_sdk::queries; pub mod storage; pub mod vp_host_fns; +use namada_core::ledger::storage::{DBIter, StorageHasher, DB}; +use namada_core::ledger::storage_api::ResultExt; pub use namada_core::ledger::{ gas, parameters, replay_protection, storage_api, tx_env, vp_env, }; +use namada_sdk::queries::{EncodedResponseQuery, RequestCtx, RequestQuery}; + +#[cfg(all(feature = "wasm-runtime", feature = "ferveo-tpke"))] +use crate::vm::wasm::{TxCache, VpCache}; +use crate::vm::WasmCacheAccess; + +/// Dry run a transaction +#[cfg(all(feature = "wasm-runtime", feature = "ferveo-tpke"))] +pub fn dry_run_tx( + mut ctx: RequestCtx<'_, D, H, VpCache, TxCache>, + request: &RequestQuery, +) -> storage_api::Result +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, + CA: 'static + WasmCacheAccess + Sync, +{ + use borsh::BorshSerialize; + use namada_core::ledger::gas::{Gas, GasMetering, TxGasMeter}; + use namada_core::ledger::storage::TempWlStorage; + use namada_core::proto::Tx; + use namada_core::types::transaction::wrapper::wrapper_tx::PairingEngine; + use namada_core::types::transaction::{ + AffineCurve, DecryptedTx, EllipticCurve, + }; + + use crate::ledger::protocol::ShellParams; + use crate::types::storage::TxIndex; + use crate::types::transaction::TxType; + + let mut tx = Tx::try_from(&request.data[..]).into_storage_result()?; + tx.validate_tx().into_storage_result()?; + + let mut temp_wl_storage = TempWlStorage::new(&ctx.wl_storage.storage); + let mut cumulated_gas = Gas::default(); + + // Wrapper dry run to allow estimating the gas cost of a transaction + let mut tx_gas_meter = match tx.header().tx_type { + TxType::Wrapper(wrapper) => { + let mut tx_gas_meter = + TxGasMeter::new(wrapper.gas_limit.to_owned()); + protocol::apply_wrapper_tx( + &wrapper, + None, + &request.data, + ShellParams::new( + &mut tx_gas_meter, + &mut temp_wl_storage, + &mut ctx.vp_wasm_cache, + &mut ctx.tx_wasm_cache, + ), + None, + ) + .into_storage_result()?; + + temp_wl_storage.write_log.commit_tx(); + cumulated_gas = tx_gas_meter.get_tx_consumed_gas(); + + // NOTE: the encryption key for a dry-run should always be an + // hardcoded, dummy one + let _privkey = + ::G2Affine::prime_subgroup_generator(); + tx.update_header(TxType::Decrypted(DecryptedTx::Decrypted)); + TxGasMeter::new_from_sub_limit(tx_gas_meter.get_available_gas()) + } + TxType::Protocol(_) | TxType::Decrypted(_) => { + // If dry run only the inner tx, use the max block gas as the gas + // limit + TxGasMeter::new( + namada_core::ledger::gas::get_max_block_gas(ctx.wl_storage) + .unwrap() + .into(), + ) + } + TxType::Raw => { + // Cast tx to a decrypted for execution + tx.update_header(TxType::Decrypted(DecryptedTx::Decrypted)); + + // If dry run only the inner tx, use the max block gas as the gas + // limit + TxGasMeter::new( + namada_core::ledger::gas::get_max_block_gas(ctx.wl_storage) + .unwrap() + .into(), + ) + } + }; + + let mut data = protocol::apply_wasm_tx( + tx, + &TxIndex(0), + ShellParams::new( + &mut tx_gas_meter, + &mut temp_wl_storage, + &mut ctx.vp_wasm_cache, + &mut ctx.tx_wasm_cache, + ), + ) + .into_storage_result()?; + cumulated_gas = cumulated_gas + .checked_add(tx_gas_meter.get_tx_consumed_gas()) + .ok_or(namada_core::ledger::storage_api::Error::SimpleMessage( + "Overflow in gas", + ))?; + // Account gas for both inner and wrapper (if available) + data.gas_used = cumulated_gas; + // NOTE: the keys changed by the wrapper transaction (if any) are not + // returned from this function + let data = data.try_to_vec().into_storage_result()?; + Ok(EncodedResponseQuery { + data, + proof: None, + info: Default::default(), + }) +} + +#[cfg(test)] +mod test { + use borsh::{BorshDeserialize, BorshSerialize}; + use namada_core::ledger::storage::testing::TestWlStorage; + use namada_core::ledger::storage_api::{self, StorageWrite}; + use namada_core::types::hash::Hash; + use namada_core::types::storage::{BlockHeight, Key}; + use namada_core::types::transaction::decrypted::DecryptedTx; + use namada_core::types::transaction::TxType; + use namada_core::types::{address, token}; + use namada_sdk::queries::{Router, RPC}; + use namada_test_utils::TestWasms; + use tempfile::TempDir; + use tendermint_rpc::{Error as RpcError, Response}; + + use crate::ledger::events::log::EventLog; + use crate::ledger::queries::Client; + use crate::ledger::{EncodedResponseQuery, RequestCtx, RequestQuery}; + use crate::proto::{Code, Data, Tx}; + use crate::vm::wasm::{TxCache, VpCache}; + use crate::vm::{wasm, WasmCacheRoAccess}; + + /// A test client that has direct access to the storage + pub struct TestClient + where + RPC: Router, + { + /// RPC router + pub rpc: RPC, + /// storage + pub wl_storage: TestWlStorage, + /// event log + pub event_log: EventLog, + /// VP wasm compilation cache + pub vp_wasm_cache: VpCache, + /// tx wasm compilation cache + pub tx_wasm_cache: TxCache, + /// VP wasm compilation cache directory + pub vp_cache_dir: TempDir, + /// tx wasm compilation cache directory + pub tx_cache_dir: TempDir, + } + + impl TestClient + where + RPC: Router, + { + #[allow(dead_code)] + /// Initialize a test client for the given root RPC router + pub fn new(rpc: RPC) -> Self { + // Initialize the `TestClient` + let mut wl_storage = TestWlStorage::default(); + + // Initialize mock gas limit + let max_block_gas_key = + namada_core::ledger::parameters::storage::get_max_block_gas_key( + ); + wl_storage + .storage + .write( + &max_block_gas_key, + namada_core::ledger::storage::types::encode( + &20_000_000_u64, + ), + ) + .expect( + "Max block gas parameter must be initialized in storage", + ); + let event_log = EventLog::default(); + let (vp_wasm_cache, vp_cache_dir) = + wasm::compilation_cache::common::testing::cache(); + let (tx_wasm_cache, tx_cache_dir) = + wasm::compilation_cache::common::testing::cache(); + Self { + rpc, + wl_storage, + event_log, + vp_wasm_cache: vp_wasm_cache.read_only(), + tx_wasm_cache: tx_wasm_cache.read_only(), + vp_cache_dir, + tx_cache_dir, + } + } + } + + #[cfg_attr(feature = "async-send", async_trait::async_trait)] + #[cfg_attr(not(feature = "async-send"), async_trait::async_trait(?Send))] + impl Client for TestClient + where + RPC: Router + Sync, + { + type Error = std::io::Error; + + async fn request( + &self, + path: String, + data: Option>, + height: Option, + prove: bool, + ) -> Result { + let data = data.unwrap_or_default(); + let height = height.unwrap_or_default(); + // Handle a path by invoking the `RPC.handle` directly with the + // borrowed storage + let request = RequestQuery { + data, + path, + height, + prove, + }; + let ctx = RequestCtx { + wl_storage: &self.wl_storage, + event_log: &self.event_log, + vp_wasm_cache: self.vp_wasm_cache.clone(), + tx_wasm_cache: self.tx_wasm_cache.clone(), + storage_read_past_height_limit: None, + }; + // TODO: this is a hack to propagate errors to the caller, we should + // really permit error types other than [`std::io::Error`] + if request.path == "/shell/dry_run_tx" { + super::dry_run_tx(ctx, &request) + } else { + self.rpc.handle(ctx, &request) + } + .map_err(|err| { + std::io::Error::new(std::io::ErrorKind::Other, err.to_string()) + }) + } + + async fn perform(&self, _request: R) -> Result + where + R: tendermint_rpc::SimpleRequest, + { + Response::from_string("TODO") + } + } + + #[tokio::test] + async fn test_shell_queries_router_with_client() -> storage_api::Result<()> + { + // Initialize the `TestClient` + let mut client = TestClient::new(RPC); + // store the wasm code + let tx_no_op = TestWasms::TxNoOp.read_bytes(); + let tx_hash = Hash::sha256(&tx_no_op); + let key = Key::wasm_code(&tx_hash); + let len_key = Key::wasm_code_len(&tx_hash); + client.wl_storage.storage.write(&key, &tx_no_op).unwrap(); + client + .wl_storage + .storage + .write(&len_key, (tx_no_op.len() as u64).try_to_vec().unwrap()) + .unwrap(); + + // Request last committed epoch + let read_epoch = RPC.shell().epoch(&client).await.unwrap(); + let current_epoch = client.wl_storage.storage.last_epoch; + assert_eq!(current_epoch, read_epoch); + + // Request dry run tx + let mut outer_tx = + Tx::from_type(TxType::Decrypted(DecryptedTx::Decrypted)); + outer_tx.header.chain_id = client.wl_storage.storage.chain_id.clone(); + outer_tx.set_code(Code::from_hash(tx_hash)); + outer_tx.set_data(Data::new(vec![])); + let tx_bytes = outer_tx.to_bytes(); + let result = RPC + .shell() + .dry_run_tx(&client, Some(tx_bytes), None, false) + .await + .unwrap(); + assert!(result.data.is_accepted()); + + // Request storage value for a balance key ... + let token_addr = address::testing::established_address_1(); + let owner = address::testing::established_address_2(); + let balance_key = token::balance_key(&token_addr, &owner); + // ... there should be no value yet. + let read_balance = RPC + .shell() + .storage_value(&client, None, None, false, &balance_key) + .await + .unwrap(); + assert!(read_balance.data.is_empty()); + + // Request storage prefix iterator + let balance_prefix = token::balance_prefix(&token_addr); + let read_balances = RPC + .shell() + .storage_prefix(&client, None, None, false, &balance_prefix) + .await + .unwrap(); + assert!(read_balances.data.is_empty()); + + // Request storage has key + let has_balance_key = RPC + .shell() + .storage_has_key(&client, &balance_key) + .await + .unwrap(); + assert!(!has_balance_key); + + // Then write some balance ... + let balance = token::Amount::native_whole(1000); + StorageWrite::write(&mut client.wl_storage, &balance_key, balance)?; + // It has to be committed to be visible in a query + client.wl_storage.commit_tx(); + client.wl_storage.commit_block().unwrap(); + // ... there should be the same value now + let read_balance = RPC + .shell() + .storage_value(&client, None, None, false, &balance_key) + .await + .unwrap(); + assert_eq!( + balance, + token::Amount::try_from_slice(&read_balance.data).unwrap() + ); + + // Request storage prefix iterator + let balance_prefix = token::balance_prefix(&token_addr); + let read_balances = RPC + .shell() + .storage_prefix(&client, None, None, false, &balance_prefix) + .await + .unwrap(); + assert_eq!(read_balances.data.len(), 1); + + // Request storage has key + let has_balance_key = RPC + .shell() + .storage_has_key(&client, &balance_key) + .await + .unwrap(); + assert!(has_balance_key); + + Ok(()) + } +} diff --git a/shared/src/ledger/queries/mod.rs b/shared/src/ledger/queries/mod.rs deleted file mode 100644 index e78313d804..0000000000 --- a/shared/src/ledger/queries/mod.rs +++ /dev/null @@ -1,222 +0,0 @@ -//! Ledger read-only queries can be handled and dispatched via the [`RPC`] -//! defined via `router!` macro. - -// Re-export to show in rustdoc! -pub use shell::Shell; -use shell::SHELL; -pub use types::{ - EncodedResponseQuery, Error, RequestCtx, RequestQuery, ResponseQuery, - Router, -}; -use vp::{Vp, VP}; - -pub use self::shell::eth_bridge::{ - Erc20FlowControl, GenBridgePoolProofReq, GenBridgePoolProofRsp, - TransferToErcArgs, -}; -use super::storage::traits::StorageHasher; -use super::storage::{DBIter, DB}; -use super::storage_api; -#[cfg(any(test, feature = "async-client"))] -pub use crate::sdk::queries::Client; -use crate::types::storage::BlockHeight; - -#[macro_use] -mod router; -mod shell; -mod types; -pub mod vp; - -// Most commonly expected patterns should be declared first -router! {RPC, - // Shell provides storage read access, block metadata and can dry-run a tx - ( "shell" ) = (sub SHELL), - - // Validity-predicate's specific storage queries - ( "vp" ) = (sub VP), -} - -/// Handle RPC query request in the ledger. On success, returns response with -/// borsh-encoded data. -pub fn handle_path( - ctx: RequestCtx<'_, D, H>, - request: &RequestQuery, -) -> storage_api::Result -where - D: 'static + DB + for<'iter> DBIter<'iter> + Sync, - H: 'static + StorageHasher + Sync, -{ - RPC.handle(ctx, request) -} - -// Handler helpers: - -/// For queries that only support latest height, check that the given height is -/// not different from latest height, otherwise return an error. -pub fn require_latest_height( - ctx: &RequestCtx<'_, D, H>, - request: &RequestQuery, -) -> storage_api::Result<()> -where - D: 'static + DB + for<'iter> DBIter<'iter> + Sync, - H: 'static + StorageHasher + Sync, -{ - if request.height != BlockHeight(0) - && request.height != ctx.wl_storage.storage.get_last_block_height() - { - return Err(storage_api::Error::new_const( - "This query doesn't support arbitrary block heights, only the \ - latest committed block height ('0' can be used as a special \ - value that means the latest block height)", - )); - } - Ok(()) -} - -/// For queries that do not support proofs, check that proof is not requested, -/// otherwise return an error. -pub fn require_no_proof(request: &RequestQuery) -> storage_api::Result<()> { - if request.prove { - return Err(storage_api::Error::new_const( - "This query doesn't support proofs", - )); - } - Ok(()) -} - -/// For queries that don't use request data, require that there are no data -/// attached. -pub fn require_no_data(request: &RequestQuery) -> storage_api::Result<()> { - if !request.data.is_empty() { - return Err(storage_api::Error::new_const( - "This query doesn't accept request data", - )); - } - Ok(()) -} - -/// Queries testing helpers -#[cfg(any(test, feature = "testing"))] -mod testing { - - use tempfile::TempDir; - use tendermint_rpc::Response; - - use super::*; - use crate::ledger::events::log::EventLog; - use crate::ledger::storage::testing::TestWlStorage; - use crate::tendermint_rpc::error::Error as RpcError; - use crate::types::storage::BlockHeight; - use crate::vm::wasm::{self, TxCache, VpCache}; - use crate::vm::WasmCacheRoAccess; - - /// A test client that has direct access to the storage - pub struct TestClient - where - RPC: Router, - { - /// RPC router - pub rpc: RPC, - /// storage - pub wl_storage: TestWlStorage, - /// event log - pub event_log: EventLog, - /// VP wasm compilation cache - pub vp_wasm_cache: VpCache, - /// tx wasm compilation cache - pub tx_wasm_cache: TxCache, - /// VP wasm compilation cache directory - pub vp_cache_dir: TempDir, - /// tx wasm compilation cache directory - pub tx_cache_dir: TempDir, - } - - impl TestClient - where - RPC: Router, - { - #[allow(dead_code)] - /// Initialize a test client for the given root RPC router - pub fn new(rpc: RPC) -> Self { - // Initialize the `TestClient` - let mut wl_storage = TestWlStorage::default(); - - // Initialize mock gas limit - let max_block_gas_key = - namada_core::ledger::parameters::storage::get_max_block_gas_key( - ); - wl_storage - .storage - .write( - &max_block_gas_key, - namada_core::ledger::storage::types::encode( - &20_000_000_u64, - ), - ) - .expect( - "Max block gas parameter must be initialized in storage", - ); - let event_log = EventLog::default(); - let (vp_wasm_cache, vp_cache_dir) = - wasm::compilation_cache::common::testing::cache(); - let (tx_wasm_cache, tx_cache_dir) = - wasm::compilation_cache::common::testing::cache(); - Self { - rpc, - wl_storage, - event_log, - vp_wasm_cache: vp_wasm_cache.read_only(), - tx_wasm_cache: tx_wasm_cache.read_only(), - vp_cache_dir, - tx_cache_dir, - } - } - } - - #[cfg_attr(feature = "async-send", async_trait::async_trait)] - #[cfg_attr(not(feature = "async-send"), async_trait::async_trait(?Send))] - impl Client for TestClient - where - RPC: Router + Sync, - { - type Error = std::io::Error; - - async fn request( - &self, - path: String, - data: Option>, - height: Option, - prove: bool, - ) -> Result { - let data = data.unwrap_or_default(); - let height = height.unwrap_or_default(); - // Handle a path by invoking the `RPC.handle` directly with the - // borrowed storage - let request = RequestQuery { - data, - path, - height, - prove, - }; - let ctx = RequestCtx { - wl_storage: &self.wl_storage, - event_log: &self.event_log, - vp_wasm_cache: self.vp_wasm_cache.clone(), - tx_wasm_cache: self.tx_wasm_cache.clone(), - storage_read_past_height_limit: None, - }; - // TODO: this is a hack to propagate errors to the caller, we should - // really permit error types other than [`std::io::Error`] - self.rpc.handle(ctx, &request).map_err(|err| { - std::io::Error::new(std::io::ErrorKind::Other, err.to_string()) - }) - } - - async fn perform(&self, _request: R) -> Result - where - R: tendermint_rpc::SimpleRequest, - { - Response::from_string("TODO") - } - } -} diff --git a/shared/src/lib.rs b/shared/src/lib.rs index 3036c4cb47..d0d1ea8b2b 100644 --- a/shared/src/lib.rs +++ b/shared/src/lib.rs @@ -24,7 +24,7 @@ pub use { }; pub mod ledger; pub use namada_core::proto; -pub mod sdk; +pub use namada_sdk; pub mod types; pub mod vm; diff --git a/shared/src/sdk/args.rs b/shared/src/sdk/args.rs deleted file mode 100644 index b765dece5a..0000000000 --- a/shared/src/sdk/args.rs +++ /dev/null @@ -1,887 +0,0 @@ -//! Structures encapsulating SDK arguments - -use std::collections::HashMap; -use std::path::PathBuf; -use std::time::Duration as StdDuration; - -use namada_core::types::chain::ChainId; -use namada_core::types::dec::Dec; -use namada_core::types::ethereum_events::EthAddress; -use namada_core::types::time::DateTimeUtc; -use serde::{Deserialize, Serialize}; -use zeroize::Zeroizing; - -use crate::ibc::core::ics24_host::identifier::{ChannelId, PortId}; -use crate::types::address::Address; -use crate::types::keccak::KeccakHash; -use crate::types::key::{common, SchemeType}; -use crate::types::masp::MaspValue; -use crate::types::storage::Epoch; -use crate::types::transaction::GasLimit; -use crate::types::{storage, token}; - -/// [`Duration`](StdDuration) wrapper that provides a -/// method to parse a value from a string. -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] -#[repr(transparent)] -pub struct Duration(pub StdDuration); - -impl ::std::str::FromStr for Duration { - type Err = ::parse_duration::parse::Error; - - #[inline] - fn from_str(s: &str) -> Result { - ::parse_duration::parse(s).map(Duration) - } -} - -/// Abstraction of types being used in Namada -pub trait NamadaTypes: Clone + std::fmt::Debug { - /// Represents an address on the ledger - type Address: Clone + std::fmt::Debug; - /// Represents the address of a native token - type NativeAddress: Clone + std::fmt::Debug; - /// Represents a key pair - type Keypair: Clone + std::fmt::Debug; - /// Represents the address of a Tendermint endpoint - type TendermintAddress: Clone + std::fmt::Debug; - /// Represents the address of an Ethereum endpoint - type EthereumAddress: Clone + std::fmt::Debug; - /// Represents a viewing key - type ViewingKey: Clone + std::fmt::Debug; - /// Represents the owner of a balance - type BalanceOwner: Clone + std::fmt::Debug; - /// Represents a public key - type PublicKey: Clone + std::fmt::Debug; - /// Represents the source of a Transfer - type TransferSource: Clone + std::fmt::Debug; - /// Represents the target of a Transfer - type TransferTarget: Clone + std::fmt::Debug; - /// Represents some data that is used in a transaction - type Data: Clone + std::fmt::Debug; - /// Bridge pool recommendations conversion rates table. - type BpConversionTable: Clone + std::fmt::Debug; -} - -/// The concrete types being used in Namada SDK -#[derive(Clone, Debug)] -pub struct SdkTypes; - -/// An entry in the Bridge pool recommendations conversion -/// rates table. -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct BpConversionTableEntry { - /// An alias for the token, or the string representation - /// of its address if none is available. - pub alias: String, - /// Conversion rate from the given token to gwei. - pub conversion_rate: f64, -} - -impl NamadaTypes for SdkTypes { - type Address = Address; - type BalanceOwner = namada_core::types::masp::BalanceOwner; - type BpConversionTable = HashMap; - type Data = Vec; - type EthereumAddress = (); - type Keypair = namada_core::types::key::common::SecretKey; - type NativeAddress = Address; - type PublicKey = namada_core::types::key::common::PublicKey; - type TendermintAddress = (); - type TransferSource = namada_core::types::masp::TransferSource; - type TransferTarget = namada_core::types::masp::TransferTarget; - type ViewingKey = namada_core::types::masp::ExtendedViewingKey; -} - -/// Common query arguments -#[derive(Clone, Debug)] -pub struct Query { - /// The address of the ledger node as host:port - pub ledger_address: C::TendermintAddress, -} - -/// Transaction associated results arguments -#[derive(Clone, Debug)] -pub struct QueryResult { - /// Common query args - pub query: Query, - /// Hash of transaction to lookup - pub tx_hash: String, -} - -/// Custom transaction arguments -#[derive(Clone, Debug)] -pub struct TxCustom { - /// Common tx arguments - pub tx: Tx, - /// Path to the tx WASM code file - pub code_path: Option, - /// Path to the data file - pub data_path: Option, - /// Path to the serialized transaction - pub serialized_tx: Option, - /// The address that correspond to the signatures/signing-keys - pub owner: C::Address, -} - -/// Transfer transaction arguments -#[derive(Clone, Debug)] -pub struct TxTransfer { - /// Common tx arguments - pub tx: Tx, - /// Transfer source address - pub source: C::TransferSource, - /// Transfer target address - pub target: C::TransferTarget, - /// Transferred token address - pub token: C::Address, - /// Transferred token amount - pub amount: InputAmount, - /// Native token address - pub native_token: C::NativeAddress, - /// Path to the TX WASM code file - pub tx_code_path: PathBuf, -} -/// An amount read in by the cli -#[derive(Copy, Clone, Debug)] -pub enum InputAmount { - /// An amount whose representation has been validated - /// against the allowed representation in storage - Validated(token::DenominatedAmount), - /// The parsed amount read in from the cli. It has - /// not yet been validated against the allowed - /// representation in storage. - Unvalidated(token::DenominatedAmount), -} - -/// IBC transfer transaction arguments -#[derive(Clone, Debug)] -pub struct TxIbcTransfer { - /// Common tx arguments - pub tx: Tx, - /// Transfer source address - pub source: C::Address, - /// Transfer target address - pub receiver: String, - /// Transferred token addres s - pub token: C::Address, - /// Transferred token amount - pub amount: InputAmount, - /// Port ID - pub port_id: PortId, - /// Channel ID - pub channel_id: ChannelId, - /// Timeout height of the destination chain - pub timeout_height: Option, - /// Timeout timestamp offset - pub timeout_sec_offset: Option, - /// Memo - pub memo: Option, - /// Path to the TX WASM code file - pub tx_code_path: PathBuf, -} - -/// Transaction to initialize create a new proposal -#[derive(Clone, Debug)] -pub struct InitProposal { - /// Common tx arguments - pub tx: Tx, - /// The proposal data - pub proposal_data: C::Data, - /// Native token address - pub native_token: C::NativeAddress, - /// Flag if proposal should be run offline - pub is_offline: bool, - /// Flag if proposal is of type Pgf stewards - pub is_pgf_stewards: bool, - /// Flag if proposal is of type Pgf funding - pub is_pgf_funding: bool, - /// Path to the tx WASM file - pub tx_code_path: PathBuf, -} - -/// Transaction to vote on a proposal -#[derive(Clone, Debug)] -pub struct VoteProposal { - /// Common tx arguments - pub tx: Tx, - /// Proposal id - pub proposal_id: Option, - /// The vote - pub vote: String, - /// The address of the voter - pub voter: C::Address, - /// Flag if proposal vote should be run offline - pub is_offline: bool, - /// The proposal file path - pub proposal_data: Option, - /// Path to the TX WASM code file - pub tx_code_path: PathBuf, -} - -/// Transaction to initialize a new account -#[derive(Clone, Debug)] -pub struct TxInitAccount { - /// Common tx arguments - pub tx: Tx, - /// Path to the VP WASM code file for the new account - pub vp_code_path: PathBuf, - /// Path to the TX WASM code file - pub tx_code_path: PathBuf, - /// Public key for the new account - pub public_keys: Vec, - /// The account multisignature threshold - pub threshold: Option, -} - -/// Transaction to initialize a new account -#[derive(Clone, Debug)] -pub struct TxInitValidator { - /// Common tx arguments - pub tx: Tx, - /// Signature scheme - pub scheme: SchemeType, - /// Account keys - pub account_keys: Vec, - /// The account multisignature threshold - pub threshold: Option, - /// Consensus key - pub consensus_key: Option, - /// Ethereum cold key - pub eth_cold_key: Option, - /// Ethereum hot key - pub eth_hot_key: Option, - /// Protocol key - pub protocol_key: Option, - /// Commission rate - pub commission_rate: Dec, - /// Maximum commission rate change - pub max_commission_rate_change: Dec, - /// Path to the VP WASM code file - pub validator_vp_code_path: PathBuf, - /// Path to the TX WASM code file - pub tx_code_path: PathBuf, - /// Don't encrypt the keypair - pub unsafe_dont_encrypt: bool, -} - -/// Transaction to update a VP arguments -#[derive(Clone, Debug)] -pub struct TxUpdateAccount { - /// Common tx arguments - pub tx: Tx, - /// Path to the VP WASM code file - pub vp_code_path: Option, - /// Path to the TX WASM code file - pub tx_code_path: PathBuf, - /// Address of the account whose VP is to be updated - pub addr: C::Address, - /// Public keys - pub public_keys: Vec, - /// The account threshold - pub threshold: Option, -} - -/// Bond arguments -#[derive(Clone, Debug)] -pub struct Bond { - /// Common tx arguments - pub tx: Tx, - /// Validator address - pub validator: C::Address, - /// Amount of tokens to stake in a bond - pub amount: token::Amount, - /// Source address for delegations. For self-bonds, the validator is - /// also the source. - pub source: Option, - /// Native token address - pub native_token: C::NativeAddress, - /// Path to the TX WASM code file - pub tx_code_path: PathBuf, -} - -/// Unbond arguments -#[derive(Clone, Debug)] -pub struct Unbond { - /// Common tx arguments - pub tx: Tx, - /// Validator address - pub validator: C::Address, - /// Amount of tokens to unbond from a bond - pub amount: token::Amount, - /// Source address for unbonding from delegations. For unbonding from - /// self-bonds, the validator is also the source - pub source: Option, - /// Path to the TX WASM code file - pub tx_code_path: PathBuf, -} - -/// Reveal public key -#[derive(Clone, Debug)] -pub struct RevealPk { - /// Common tx arguments - pub tx: Tx, - /// A public key to be revealed on-chain - pub public_key: C::PublicKey, -} - -/// Query proposal -#[derive(Clone, Debug)] -pub struct QueryProposal { - /// Common query args - pub query: Query, - /// Proposal id - pub proposal_id: Option, -} - -/// Query protocol parameters -#[derive(Clone, Debug)] -pub struct QueryProtocolParameters { - /// Common query args - pub query: Query, -} - -/// Query pgf data -#[derive(Clone, Debug)] -pub struct QueryPgf { - /// Common query args - pub query: Query, -} - -/// Withdraw arguments -#[derive(Clone, Debug)] -pub struct Withdraw { - /// Common tx arguments - pub tx: Tx, - /// Validator address - pub validator: C::Address, - /// Source address for withdrawing from delegations. For withdrawing - /// from self-bonds, the validator is also the source - pub source: Option, - /// Path to the TX WASM code file - pub tx_code_path: PathBuf, -} - -/// Query asset conversions -#[derive(Clone, Debug)] -pub struct QueryConversions { - /// Common query args - pub query: Query, - /// Address of a token - pub token: Option, - /// Epoch of the asset - pub epoch: Option, -} - -/// Query token balance(s) -#[derive(Clone, Debug)] -pub struct QueryAccount { - /// Common query args - pub query: Query, - /// Address of an owner - pub owner: C::Address, -} - -/// Query token balance(s) -#[derive(Clone, Debug)] -pub struct QueryBalance { - /// Common query args - pub query: Query, - /// Address of an owner - pub owner: Option, - /// Address of a token - pub token: Option, - /// Whether not to convert balances - pub no_conversions: bool, -} - -/// Query historical transfer(s) -#[derive(Clone, Debug)] -pub struct QueryTransfers { - /// Common query args - pub query: Query, - /// Address of an owner - pub owner: Option, - /// Address of a token - pub token: Option, -} - -/// Query PoS bond(s) -#[derive(Clone, Debug)] -pub struct QueryBonds { - /// Common query args - pub query: Query, - /// Address of an owner - pub owner: Option, - /// Address of a validator - pub validator: Option, -} - -/// Query PoS bonded stake -#[derive(Clone, Debug)] -pub struct QueryBondedStake { - /// Common query args - pub query: Query, - /// Address of a validator - pub validator: Option, - /// Epoch in which to find bonded stake - pub epoch: Option, -} - -/// Query the state of a validator (its validator set or if it is jailed) -#[derive(Clone, Debug)] -pub struct QueryValidatorState { - /// Common query args - pub query: Query, - /// Address of a validator - pub validator: C::Address, - /// Epoch in which to find the validator state - pub epoch: Option, -} - -#[derive(Clone, Debug)] -/// Commission rate change args -pub struct CommissionRateChange { - /// Common tx arguments - pub tx: Tx, - /// Validator address (should be self) - pub validator: C::Address, - /// Value to which the tx changes the commission rate - pub rate: Dec, - /// Path to the TX WASM code file - pub tx_code_path: PathBuf, -} - -#[derive(Clone, Debug)] -/// Commission rate change args -pub struct UpdateStewardCommission { - /// Common tx arguments - pub tx: Tx, - /// Steward address - pub steward: C::Address, - /// Value to which the tx changes the commission rate - pub commission: C::Data, - /// Path to the TX WASM code file - pub tx_code_path: PathBuf, -} - -#[derive(Clone, Debug)] -/// Commission rate change args -pub struct ResignSteward { - /// Common tx arguments - pub tx: Tx, - /// Validator address - pub steward: C::Address, - /// Path to the TX WASM code file - pub tx_code_path: PathBuf, -} - -#[derive(Clone, Debug)] -/// Re-activate a jailed validator args -pub struct TxUnjailValidator { - /// Common tx arguments - pub tx: Tx, - /// Validator address (should be self) - pub validator: C::Address, - /// Path to the TX WASM code file - pub tx_code_path: PathBuf, -} - -#[derive(Clone, Debug)] -/// Sign a transaction offline -pub struct SignTx { - /// Common tx arguments - pub tx: Tx, - /// Transaction data - pub tx_data: C::Data, - /// The account address - pub owner: C::Address, -} - -/// Query PoS commission rate -#[derive(Clone, Debug)] -pub struct QueryCommissionRate { - /// Common query args - pub query: Query, - /// Address of a validator - pub validator: C::Address, - /// Epoch in which to find commission rate - pub epoch: Option, -} - -/// Query PoS slashes -#[derive(Clone, Debug)] -pub struct QuerySlashes { - /// Common query args - pub query: Query, - /// Address of a validator - pub validator: Option, -} - -/// Query PoS delegations -#[derive(Clone, Debug)] -pub struct QueryDelegations { - /// Common query args - pub query: Query, - /// Address of an owner - pub owner: C::Address, -} - -/// Query PoS to find a validator -#[derive(Clone, Debug)] -pub struct QueryFindValidator { - /// Common query args - pub query: Query, - /// Tendermint address - pub tm_addr: String, -} - -/// Query the raw bytes of given storage key -#[derive(Clone, Debug)] -pub struct QueryRawBytes { - /// The storage key to query - pub storage_key: storage::Key, - /// Common query args - pub query: Query, -} - -/// Common transaction arguments -#[derive(Clone, Debug)] -pub struct Tx { - /// Simulate applying the transaction - pub dry_run: bool, - /// Simulate applying both the wrapper and inner transactions - pub dry_run_wrapper: bool, - /// Dump the transaction bytes to file - pub dump_tx: bool, - /// The output directory path to where serialize the data - pub output_folder: Option, - /// Submit the transaction even if it doesn't pass client checks - pub force: bool, - /// Do not wait for the transaction to be added to the blockchain - pub broadcast_only: bool, - /// The address of the ledger node as host:port - pub ledger_address: C::TendermintAddress, - /// If any new account is initialized by the tx, use the given alias to - /// save it in the wallet. - pub initialized_account_alias: Option, - /// Whether to force overwrite the above alias, if it is provided, in the - /// wallet. - pub wallet_alias_force: bool, - /// The amount being payed (for gas unit) to include the transaction - pub fee_amount: Option, - /// The fee payer signing key - pub wrapper_fee_payer: Option, - /// The token in which the fee is being paid - pub fee_token: C::Address, - /// The optional spending key for fee unshielding - pub fee_unshield: Option, - /// The max amount of gas used to process tx - pub gas_limit: GasLimit, - /// The optional expiration of the transaction - pub expiration: Option, - /// Generate an ephimeral signing key to be used only once to sign a - /// wrapper tx - pub disposable_signing_key: bool, - /// The chain id for which the transaction is intended - pub chain_id: Option, - /// Sign the tx with the key for the given alias from your wallet - pub signing_keys: Vec, - /// List of signatures to attach to the transaction - pub signatures: Vec, - /// Path to the TX WASM code file to reveal PK - pub tx_reveal_code_path: PathBuf, - /// Sign the tx with the public key for the given alias from your wallet - pub verification_key: Option, - /// Password to decrypt key - pub password: Option>, -} - -/// MASP add key or address arguments -#[derive(Clone, Debug)] -pub struct MaspAddrKeyAdd { - /// Key alias - pub alias: String, - /// Whether to force overwrite the alias - pub alias_force: bool, - /// Any MASP value - pub value: MaspValue, - /// Don't encrypt the keypair - pub unsafe_dont_encrypt: bool, -} - -/// MASP generate spending key arguments -#[derive(Clone, Debug)] -pub struct MaspSpendKeyGen { - /// Key alias - pub alias: String, - /// Whether to force overwrite the alias - pub alias_force: bool, - /// Don't encrypt the keypair - pub unsafe_dont_encrypt: bool, -} - -/// MASP generate payment address arguments -#[derive(Clone, Debug)] -pub struct MaspPayAddrGen { - /// Key alias - pub alias: String, - /// Whether to force overwrite the alias - pub alias_force: bool, - /// Viewing key - pub viewing_key: C::ViewingKey, - /// Pin - pub pin: bool, -} - -/// Wallet generate key and implicit address arguments -#[derive(Clone, Debug)] -pub struct KeyAndAddressGen { - /// Scheme type - pub scheme: SchemeType, - /// Key alias - pub alias: Option, - /// Whether to force overwrite the alias, if provided - pub alias_force: bool, - /// Don't encrypt the keypair - pub unsafe_dont_encrypt: bool, - /// BIP44 derivation path - pub derivation_path: Option, -} - -/// Wallet restore key and implicit address arguments -#[derive(Clone, Debug)] -pub struct KeyAndAddressRestore { - /// Scheme type - pub scheme: SchemeType, - /// Key alias - pub alias: Option, - /// Whether to force overwrite the alias, if provided - pub alias_force: bool, - /// Don't encrypt the keypair - pub unsafe_dont_encrypt: bool, - /// BIP44 derivation path - pub derivation_path: Option, -} - -/// Wallet key lookup arguments -#[derive(Clone, Debug)] -pub struct KeyFind { - /// Public key to lookup keypair with - pub public_key: Option, - /// Key alias to lookup keypair with - pub alias: Option, - /// Public key hash to lookup keypair with - pub value: Option, - /// Show secret keys to user - pub unsafe_show_secret: bool, -} - -/// Wallet find shielded address or key arguments -#[derive(Clone, Debug)] -pub struct AddrKeyFind { - /// Address/key alias - pub alias: String, - /// Show secret keys to user - pub unsafe_show_secret: bool, -} - -/// Wallet list shielded keys arguments -#[derive(Clone, Debug)] -pub struct MaspKeysList { - /// Don't decrypt spending keys - pub decrypt: bool, - /// Show secret keys to user - pub unsafe_show_secret: bool, -} - -/// Wallet list keys arguments -#[derive(Clone, Debug)] -pub struct KeyList { - /// Don't decrypt keypairs - pub decrypt: bool, - /// Show secret keys to user - pub unsafe_show_secret: bool, -} - -/// Wallet key export arguments -#[derive(Clone, Debug)] -pub struct KeyExport { - /// Key alias - pub alias: String, -} - -/// Wallet address lookup arguments -#[derive(Clone, Debug)] -pub struct AddressOrAliasFind { - /// Alias to find - pub alias: Option, - /// Address to find - pub address: Option
, -} - -/// Wallet address add arguments -#[derive(Clone, Debug)] -pub struct AddressAdd { - /// Address alias - pub alias: String, - /// Whether to force overwrite the alias - pub alias_force: bool, - /// Address to add - pub address: Address, -} - -/// Bridge pool batch recommendation. -#[derive(Clone, Debug)] -pub struct RecommendBatch { - /// The query parameters. - pub query: Query, - /// The maximum amount of gas to spend. - pub max_gas: Option, - /// An optional parameter indicating how much net - /// gas the relayer is willing to pay. - pub gas: Option, - /// Bridge pool recommendations conversion rates table. - pub conversion_table: C::BpConversionTable, -} - -/// A transfer to be added to the Ethereum bridge pool. -#[derive(Clone, Debug)] -pub struct EthereumBridgePool { - /// Whether the transfer is for a NUT. - /// - /// By default, we add wrapped ERC20s onto the - /// Bridge pool. - pub nut: bool, - /// The args for building a tx to the bridge pool - pub tx: Tx, - /// The type of token - pub asset: EthAddress, - /// The recipient address - pub recipient: EthAddress, - /// The sender of the transfer - pub sender: C::Address, - /// The amount to be transferred - pub amount: InputAmount, - /// The amount of gas fees - pub fee_amount: InputAmount, - /// The account of fee payer. - /// - /// If unset, it is the same as the sender. - pub fee_payer: Option, - /// The token in which the gas is being paid - pub fee_token: C::Address, - /// Path to the tx WASM code file - pub code_path: PathBuf, -} - -/// Bridge pool proof arguments. -#[derive(Debug, Clone)] -pub struct BridgePoolProof { - /// The query parameters. - pub query: Query, - /// The keccak hashes of transfers to - /// acquire a proof of. - pub transfers: Vec, - /// The address of the node responsible for relaying - /// the transfers. - /// - /// This node will receive the gas fees escrowed in - /// the Bridge pool, to compensate the Ethereum relay - /// procedure. - pub relayer: Address, -} - -/// Arguments to an Ethereum Bridge pool relay operation. -#[derive(Debug, Clone)] -pub struct RelayBridgePoolProof { - /// The query parameters. - pub query: Query, - /// The hashes of the transfers to be relayed - pub transfers: Vec, - /// The Namada address for receiving fees for relaying - pub relayer: Address, - /// The number of confirmations to wait for on Ethereum - pub confirmations: u64, - /// The Ethereum RPC endpoint. - pub eth_rpc_endpoint: C::EthereumAddress, - /// The Ethereum gas that can be spent during - /// the relay call. - pub gas: Option, - /// The price of Ethereum gas, during the - /// relay call. - pub gas_price: Option, - /// The address of the Ethereum wallet to pay the gas fees. - /// If unset, the default wallet is used. - pub eth_addr: Option, - /// Synchronize with the network, or exit immediately, - /// if the Ethereum node has fallen behind. - pub sync: bool, - /// Safe mode overrides keyboard interrupt signals, to ensure - /// Ethereum transfers aren't canceled midway through. - pub safe_mode: bool, -} - -/// Bridge validator set arguments. -#[derive(Debug, Clone)] -pub struct BridgeValidatorSet { - /// The query parameters. - pub query: Query, - /// The epoch to query. - pub epoch: Option, -} - -/// Governance validator set arguments. -#[derive(Debug, Clone)] -pub struct GovernanceValidatorSet { - /// The query parameters. - pub query: Query, - /// The epoch to query. - pub epoch: Option, -} - -/// Validator set proof arguments. -#[derive(Debug, Clone)] -pub struct ValidatorSetProof { - /// The query parameters. - pub query: Query, - /// The epoch to query. - pub epoch: Option, -} - -/// Validator set update relayer arguments. -#[derive(Debug, Clone)] -pub struct ValidatorSetUpdateRelay { - /// Run in daemon mode, which will continuously - /// perform validator set updates. - pub daemon: bool, - /// The query parameters. - pub query: Query, - /// The number of block confirmations on Ethereum. - pub confirmations: u64, - /// The Ethereum RPC endpoint. - pub eth_rpc_endpoint: C::EthereumAddress, - /// The epoch of the validator set to relay. - pub epoch: Option, - /// The Ethereum gas that can be spent during - /// the relay call. - pub gas: Option, - /// The price of Ethereum gas, during the - /// relay call. - pub gas_price: Option, - /// The address of the Ethereum wallet to pay the gas fees. - /// If unset, the default wallet is used. - pub eth_addr: Option, - /// Synchronize with the network, or exit immediately, - /// if the Ethereum node has fallen behind. - pub sync: bool, - /// The amount of time to sleep between failed - /// daemon mode relays. - pub retry_dur: Option, - /// The amount of time to sleep between successful - /// daemon mode relays. - pub success_dur: Option, - /// Safe mode overrides keyboard interrupt signals, to ensure - /// Ethereum transfers aren't canceled midway through. - pub safe_mode: bool, -} diff --git a/shared/src/sdk/mod.rs b/shared/src/sdk/mod.rs deleted file mode 100644 index 381bac03d1..0000000000 --- a/shared/src/sdk/mod.rs +++ /dev/null @@ -1,12 +0,0 @@ -//! Namada's SDK API -pub mod rpc; - -pub mod args; -pub mod masp; -pub mod signing; -#[allow(clippy::result_large_err)] -pub mod tx; - -pub mod error; -pub mod queries; -pub mod wallet; diff --git a/shared/src/types/mod.rs b/shared/src/types/mod.rs index 83e58f0fa8..04801cf6c5 100644 --- a/shared/src/types/mod.rs +++ b/shared/src/types/mod.rs @@ -1,8 +1,8 @@ //! Types definitions. -pub mod control_flow; +pub use namada_sdk::control_flow; pub mod ibc; -pub mod io; +pub use namada_sdk::io; pub mod key; pub use namada_core::types::{ diff --git a/shared/src/vm/host_env.rs b/shared/src/vm/host_env.rs index 7806d1abef..5229c65908 100644 --- a/shared/src/vm/host_env.rs +++ b/shared/src/vm/host_env.rs @@ -1885,7 +1885,7 @@ where // TODO: once the runtime gas meter is implemented we need to benchmark // this funcion and charge the gas here. For the moment, the cost of // this is included in the benchmark of the masp vp - HostEnvResult::from(crate::sdk::masp::verify_shielded_tx(&shielded)) + HostEnvResult::from(namada_sdk::masp::verify_shielded_tx(&shielded)) .to_i64(), ) } diff --git a/tests/Cargo.toml b/tests/Cargo.toml index 674bce9538..5ab740b9ca 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -31,6 +31,7 @@ wasm-runtime = ["namada/wasm-runtime"] [dependencies] namada = {path = "../shared", features = ["testing"]} namada_core = {path = "../core", features = ["testing"]} +namada_sdk = {path = "../sdk"} namada_test_utils = {path = "../test_utils"} namada_vp_prelude = {path = "../vp_prelude"} namada_tx_prelude = {path = "../tx_prelude"} diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 2f5bbe4ea7..7bfbf12e2b 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -20,10 +20,8 @@ use borsh::BorshSerialize; use color_eyre::eyre::Result; use data_encoding::HEXLOWER; use namada::types::address::Address; -use namada::types::io::DefaultIo; use namada::types::storage::Epoch; use namada::types::token; -use namada_apps::client::tx::CLIShieldedUtils; use namada_apps::config::ethereum_bridge; use namada_apps::config::genesis::genesis_config::{ GenesisConfig, ParametersConfig, PgfParametersConfig, PosParamsConfig, @@ -33,6 +31,7 @@ use namada_apps::facade::tendermint_config::net::Address as TendermintAddress; use namada_core::ledger::governance::cli::onchain::{ PgfFunding, PgfFundingTarget, StewardsUpdate, }; +use namada_sdk::masp::fs::FsShieldedUtils; use namada_test_utils::TestWasms; use namada_vp_prelude::BTreeSet; use serde_json::json; @@ -688,7 +687,7 @@ fn ledger_txs_and_queries() -> Result<()> { #[test] fn masp_txs_and_queries() -> Result<()> { // Download the shielded pool parameters before starting node - let _ = CLIShieldedUtils::new::(PathBuf::new()); + let _ = FsShieldedUtils::new(PathBuf::new()); // Lengthen epoch to ensure that a transaction can be constructed and // submitted within the same block. Necessary to ensure that conversion is // not invalidated. @@ -836,7 +835,7 @@ fn masp_txs_and_queries() -> Result<()> { #[test] fn wrapper_disposable_signer() -> Result<()> { // Download the shielded pool parameters before starting node - let _ = CLIShieldedUtils::new::(PathBuf::new()); + let _ = FsShieldedUtils::new(PathBuf::new()); // Lengthen epoch to ensure that a transaction can be constructed and // submitted within the same block. Necessary to ensure that conversion is // not invalidated. diff --git a/tests/src/integration/masp.rs b/tests/src/integration/masp.rs index ecd1b34465..28d5375f79 100644 --- a/tests/src/integration/masp.rs +++ b/tests/src/integration/masp.rs @@ -2,13 +2,12 @@ use std::path::PathBuf; use color_eyre::eyre::Result; use color_eyre::owo_colors::OwoColorize; -use namada::types::io::DefaultIo; -use namada_apps::client::tx::CLIShieldedUtils; use namada_apps::node::ledger::shell::testing::client::run; use namada_apps::node::ledger::shell::testing::utils::{Bin, CapturedOutput}; use namada_core::types::address::{btc, eth, masp_rewards}; use namada_core::types::token; use namada_core::types::token::{DenominatedAmount, NATIVE_MAX_DECIMAL_PLACES}; +use namada_sdk::masp::fs::FsShieldedUtils; use test_log::test; use super::setup; @@ -29,7 +28,7 @@ fn masp_incentives() -> Result<()> { // This address doesn't matter for tests. But an argument is required. let validator_one_rpc = "127.0.0.1:26567"; // Download the shielded pool parameters before starting node - let _ = CLIShieldedUtils::new::(PathBuf::new()); + let _ = FsShieldedUtils::new(PathBuf::new()); // Lengthen epoch to ensure that a transaction can be constructed and // submitted within the same block. Necessary to ensure that conversion is // not invalidated. @@ -765,7 +764,7 @@ fn masp_pinned_txs() -> Result<()> { // This address doesn't matter for tests. But an argument is required. let validator_one_rpc = "127.0.0.1:26567"; // Download the shielded pool parameters before starting node - let _ = CLIShieldedUtils::new::(PathBuf::new()); + let _ = FsShieldedUtils::new(PathBuf::new()); let mut node = setup::setup()?; // Wait till epoch boundary @@ -928,7 +927,7 @@ fn masp_txs_and_queries() -> Result<()> { // This address doesn't matter for tests. But an argument is required. let validator_one_rpc = "127.0.0.1:26567"; // Download the shielded pool parameters before starting node - let _ = CLIShieldedUtils::new::(PathBuf::new()); + let _ = FsShieldedUtils::new(PathBuf::new()); enum Response { Ok(&'static str), @@ -1234,7 +1233,7 @@ fn wrapper_fee_unshielding() { // This address doesn't matter for tests. But an argument is required. let validator_one_rpc = "127.0.0.1:26567"; // Download the shielded pool parameters before starting node - let _ = CLIShieldedUtils::new::(PathBuf::new()); + let _ = FsShieldedUtils::new(PathBuf::new()); // Lengthen epoch to ensure that a transaction can be constructed and // submitted within the same block. Necessary to ensure that conversion is // not invalidated. diff --git a/tests/src/native_vp/eth_bridge_pool.rs b/tests/src/native_vp/eth_bridge_pool.rs index 364dcd074c..6fc304f909 100644 --- a/tests/src/native_vp/eth_bridge_pool.rs +++ b/tests/src/native_vp/eth_bridge_pool.rs @@ -4,10 +4,6 @@ mod test_bridge_pool_vp { use borsh::{BorshDeserialize, BorshSerialize}; use namada::core::ledger::eth_bridge::storage::bridge_pool::BRIDGE_POOL_ADDRESS; - use namada::ledger::eth_bridge::{ - wrapped_erc20s, Contracts, Erc20WhitelistEntry, EthereumBridgeConfig, - UpgradeableContract, - }; use namada::ledger::native_vp::ethereum_bridge::bridge_pool_vp::BridgePoolVp; use namada::proto::Tx; use namada::types::address::{nam, wnam}; @@ -20,6 +16,10 @@ mod test_bridge_pool_vp { use namada::types::token::Amount; use namada_apps::wallet::defaults::{albert_address, bertha_address}; use namada_apps::wasm_loader; + use namada_sdk::eth_bridge::{ + wrapped_erc20s, Contracts, Erc20WhitelistEntry, EthereumBridgeConfig, + UpgradeableContract, + }; use crate::native_vp::TestNativeVpEnv; use crate::tx::{tx_host_env, TestTxEnv}; diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index 5dbe91c1e4..8984c1708f 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -3332,6 +3332,7 @@ dependencies = [ "namada_core", "namada_ethereum_bridge", "namada_proof_of_stake", + "namada_sdk", "num256", "orion", "owo-colors", @@ -3464,6 +3465,49 @@ dependencies = [ "tracing", ] +[[package]] +name = "namada_sdk" +version = "0.23.0" +dependencies = [ + "async-trait", + "bimap", + "borsh 0.9.4", + "circular-queue", + "data-encoding", + "derivation-path", + "ethbridge-bridge-contract", + "ethers", + "futures", + "itertools", + "masp_primitives", + "masp_proofs", + "namada_core", + "namada_ethereum_bridge", + "namada_proof_of_stake", + "num256", + "orion", + "owo-colors", + "parse_duration", + "paste", + "prost", + "rand 0.8.5", + "rand_core 0.6.4", + "ripemd", + "serde", + "serde_json", + "sha2 0.9.9", + "slip10_ed25519", + "tendermint-rpc", + "thiserror", + "tiny-bip39", + "tiny-hderive", + "tokio", + "toml 0.5.11", + "tracing", + "wasmtimer", + "zeroize", +] + [[package]] name = "namada_test_utils" version = "0.23.0" @@ -3486,6 +3530,7 @@ dependencies = [ "lazy_static", "namada", "namada_core", + "namada_sdk", "namada_test_utils", "namada_tx_prelude", "namada_vp_prelude", diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 8e3bc2bb20..43c75ed658 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -3332,6 +3332,7 @@ dependencies = [ "namada_core", "namada_ethereum_bridge", "namada_proof_of_stake", + "namada_sdk", "num256", "orion", "owo-colors", @@ -3464,6 +3465,49 @@ dependencies = [ "tracing", ] +[[package]] +name = "namada_sdk" +version = "0.23.0" +dependencies = [ + "async-trait", + "bimap", + "borsh 0.9.4", + "circular-queue", + "data-encoding", + "derivation-path", + "ethbridge-bridge-contract", + "ethers", + "futures", + "itertools", + "masp_primitives", + "masp_proofs", + "namada_core", + "namada_ethereum_bridge", + "namada_proof_of_stake", + "num256", + "orion", + "owo-colors", + "parse_duration", + "paste", + "prost", + "rand 0.8.5", + "rand_core 0.6.4", + "ripemd", + "serde", + "serde_json", + "sha2 0.9.9", + "slip10_ed25519", + "tendermint-rpc", + "thiserror", + "tiny-bip39", + "tiny-hderive", + "tokio", + "toml 0.5.11", + "tracing", + "wasmtimer", + "zeroize", +] + [[package]] name = "namada_test_utils" version = "0.23.0" @@ -3486,6 +3530,7 @@ dependencies = [ "lazy_static", "namada", "namada_core", + "namada_sdk", "namada_test_utils", "namada_tx_prelude", "namada_vp_prelude",