diff --git a/.changelog/unreleased/bug-fixes/1667-faucet-limit-fix.md b/.changelog/unreleased/bug-fixes/1667-faucet-limit-fix.md new file mode 100644 index 0000000000..49c3647b55 --- /dev/null +++ b/.changelog/unreleased/bug-fixes/1667-faucet-limit-fix.md @@ -0,0 +1,2 @@ +- Fix genesis `faucet_withdrawal_limit` parser to respect tokens' denomination. + ([\#1667](https://github.com/anoma/namada/pull/1667)) \ No newline at end of file diff --git a/.changelog/unreleased/bug-fixes/1686-delete-prefix.md b/.changelog/unreleased/bug-fixes/1686-delete-prefix.md new file mode 100644 index 0000000000..dc8a2dd175 --- /dev/null +++ b/.changelog/unreleased/bug-fixes/1686-delete-prefix.md @@ -0,0 +1,3 @@ +- PoS: ensure that the size of genesis validator set + is limited by the `max_validator_slots` parameter. + ([\#1686](https://github.com/anoma/namada/pull/1686)) \ No newline at end of file diff --git a/.changelog/unreleased/bug-fixes/1709-fix_changes_before_commit.md b/.changelog/unreleased/bug-fixes/1709-fix_changes_before_commit.md new file mode 100644 index 0000000000..33eb543193 --- /dev/null +++ b/.changelog/unreleased/bug-fixes/1709-fix_changes_before_commit.md @@ -0,0 +1,2 @@ +- Fix inconsistency state before commit + ([\#1709](https://github.com/anoma/namada/issues/1709)) \ No newline at end of file diff --git a/.changelog/unreleased/bug-fixes/1729-pos-fix-rewards-boundary.md b/.changelog/unreleased/bug-fixes/1729-pos-fix-rewards-boundary.md new file mode 100644 index 0000000000..3b6fcaa903 --- /dev/null +++ b/.changelog/unreleased/bug-fixes/1729-pos-fix-rewards-boundary.md @@ -0,0 +1,3 @@ +- PoS: Fixed an epoch boundary issue in which a validator who's being slashed + on a start of a new epoch is disregarded during processing of block votes. + ([\#1729](https://github.com/anoma/namada/pull/1729)) \ No newline at end of file diff --git a/.changelog/unreleased/features/1606-multisignature-draft.md b/.changelog/unreleased/features/1606-multisignature-draft.md new file mode 100644 index 0000000000..9311b2c4d5 --- /dev/null +++ b/.changelog/unreleased/features/1606-multisignature-draft.md @@ -0,0 +1,2 @@ +- Introduce multisignature account and transaction format. + ([\#1606](https://github.com/anoma/namada/pull/1606)) \ No newline at end of file diff --git a/.changelog/unreleased/improvements/1129-clear-out-validator-sets-for-old-epochs.md b/.changelog/unreleased/improvements/1129-clear-out-validator-sets-for-old-epochs.md new file mode 100644 index 0000000000..e7fb6e9063 --- /dev/null +++ b/.changelog/unreleased/improvements/1129-clear-out-validator-sets-for-old-epochs.md @@ -0,0 +1,2 @@ +- PoS: purge validator sets for old epochs from the storage; store total + validator stake ([\#1129](https://github.com/anoma/namada/issues/1129)) \ No newline at end of file diff --git a/.changelog/unreleased/improvements/1173-token-balance-query.md b/.changelog/unreleased/improvements/1173-token-balance-query.md new file mode 100644 index 0000000000..e5a9ea2322 --- /dev/null +++ b/.changelog/unreleased/improvements/1173-token-balance-query.md @@ -0,0 +1,2 @@ +- Added a reusable token balance query method. + ([\#1173](https://github.com/anoma/namada/pull/1173)) \ No newline at end of file diff --git a/.changelog/unreleased/improvements/1605-win-build.md b/.changelog/unreleased/improvements/1605-win-build.md new file mode 100644 index 0000000000..ee4a8d026e --- /dev/null +++ b/.changelog/unreleased/improvements/1605-win-build.md @@ -0,0 +1,2 @@ +- Replaced file-lock with fd-lock dependency to support Windows build. + ([\#1605](https://github.com/anoma/namada/pull/1605)) \ No newline at end of file diff --git a/.changelog/unreleased/improvements/1621-utils-next-epoch.md b/.changelog/unreleased/improvements/1621-utils-next-epoch.md new file mode 100644 index 0000000000..5f663a8e00 --- /dev/null +++ b/.changelog/unreleased/improvements/1621-utils-next-epoch.md @@ -0,0 +1,2 @@ +- Added a command to wait for the next epoch: `client utils epoch-sleep`. + ([\#1621](https://github.com/anoma/namada/pull/1621)) \ No newline at end of file diff --git a/.changelog/unreleased/improvements/1656-pos-cli-queries.md b/.changelog/unreleased/improvements/1656-pos-cli-queries.md new file mode 100644 index 0000000000..a114c6f2f6 --- /dev/null +++ b/.changelog/unreleased/improvements/1656-pos-cli-queries.md @@ -0,0 +1,2 @@ +- Added a client query for `validator-state` and improved the slashes query to + show more info. ([\#1656](https://github.com/anoma/namada/pull/1656)) \ No newline at end of file diff --git a/.changelog/unreleased/improvements/1670-remove-unused-assoc-ty.md b/.changelog/unreleased/improvements/1670-remove-unused-assoc-ty.md new file mode 100644 index 0000000000..b4db773566 --- /dev/null +++ b/.changelog/unreleased/improvements/1670-remove-unused-assoc-ty.md @@ -0,0 +1,4 @@ +- Removed associated type on `masp::ShieldedUtils`. This type was an + attempt to reduce the number of generic parameters needed when interacting + with MASP but resulted in making code re-use extremely difficult. + ([\#1670](https://github.com/anoma/namada/pull/1670)) \ No newline at end of file diff --git a/.changelog/unreleased/improvements/1692-rm-from-u64-on-ethbridge-stake.md b/.changelog/unreleased/improvements/1692-rm-from-u64-on-ethbridge-stake.md new file mode 100644 index 0000000000..4c6813670c --- /dev/null +++ b/.changelog/unreleased/improvements/1692-rm-from-u64-on-ethbridge-stake.md @@ -0,0 +1,2 @@ +- Removed `impl From for EthBridgeVotingPower` and replaced it with a + `TryFrom`. ([\#1692](https://github.com/anoma/namada/pull/1692)) \ No newline at end of file diff --git a/.changelog/unreleased/improvements/1695-update-sysinfo.md b/.changelog/unreleased/improvements/1695-update-sysinfo.md new file mode 100644 index 0000000000..13dd88558c --- /dev/null +++ b/.changelog/unreleased/improvements/1695-update-sysinfo.md @@ -0,0 +1,2 @@ +- Updated sysinfo dependency. + ([\#1695](https://github.com/anoma/namada/pull/1695)) \ No newline at end of file diff --git a/.changelog/unreleased/improvements/1717-storage-refactor.md b/.changelog/unreleased/improvements/1717-storage-refactor.md new file mode 100644 index 0000000000..ae44ad180a --- /dev/null +++ b/.changelog/unreleased/improvements/1717-storage-refactor.md @@ -0,0 +1,2 @@ +- Refactored storage code to only use an immutable reference when reading and + writing to a batch. ([\#1717](https://github.com/anoma/namada/pull/1717)) \ No newline at end of file diff --git a/.changelog/unreleased/improvements/1725-remove-native-vp-addr.md b/.changelog/unreleased/improvements/1725-remove-native-vp-addr.md new file mode 100644 index 0000000000..5d9bb3c4f0 --- /dev/null +++ b/.changelog/unreleased/improvements/1725-remove-native-vp-addr.md @@ -0,0 +1,2 @@ +- Removed the associated type for an address from `trait NativeVp`. + ([\#1725](https://github.com/anoma/namada/pull/1725)) \ No newline at end of file diff --git a/.changelog/unreleased/miscellaneous/1733-pos-data-history.md b/.changelog/unreleased/miscellaneous/1733-pos-data-history.md new file mode 100644 index 0000000000..aa3adccc66 --- /dev/null +++ b/.changelog/unreleased/miscellaneous/1733-pos-data-history.md @@ -0,0 +1,2 @@ +- PoS: Keep the data for last two epochs by default. + ([\#1733](https://github.com/anoma/namada/pull/1733)) \ No newline at end of file diff --git a/.github/workflows/scripts/e2e.json b/.github/workflows/scripts/e2e.json index 74b08e0fb8..db009b60f6 100644 --- a/.github/workflows/scripts/e2e.json +++ b/.github/workflows/scripts/e2e.json @@ -10,6 +10,7 @@ "e2e::ledger_tests::masp_pinned_txs": 75, "e2e::ledger_tests::masp_txs_and_queries": 282, "e2e::ledger_tests::pos_bonds": 77, + "e2e::ledger_tests::implicit_account_reveal_pk": 30, "e2e::ledger_tests::pos_init_validator": 40, "e2e::ledger_tests::proposal_offline": 21, "e2e::ledger_tests::pgf_governance_proposal": 100, @@ -20,6 +21,7 @@ "e2e::ledger_tests::test_namada_shuts_down_if_tendermint_dies": 2, "e2e::ledger_tests::test_genesis_validators": 14, "e2e::ledger_tests::test_node_connectivity_and_consensus": 28, + "e2e::ledger_tests::test_epoch_sleep": 12, "e2e::wallet_tests::wallet_address_cmds": 1, "e2e::wallet_tests::wallet_encrypted_key_cmds": 1, "e2e::wallet_tests::wallet_encrypted_key_cmds_env_var": 1, diff --git a/Cargo.lock b/Cargo.lock index 568de9a745..5a76fbfa4d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2455,7 +2455,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b36f34b0325008d05b0e9be8361bfa8a0fb905f10de0d951c2621c59e811cb91" dependencies = [ "conpty", - "nix 0.26.2", + "nix", "ptyprocess", "regex", ] @@ -2491,6 +2491,17 @@ dependencies = [ "instant", ] +[[package]] +name = "fd-lock" +version = "3.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef033ed5e9bad94e55838ca0ca906db0e043f517adda0c8b79c7a8c66c93c1b5" +dependencies = [ + "cfg-if 1.0.0", + "rustix 0.38.3", + "windows-sys 0.48.0", +] + [[package]] name = "ferveo" version = "0.1.1" @@ -2552,18 +2563,6 @@ dependencies = [ "subtle 2.4.1", ] -[[package]] -name = "file-lock" -version = "2.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0815fc2a1924e651b71ae6d13df07b356a671a09ecaf857dbd344a2ba937a496" -dependencies = [ - "cc", - "libc", - "mktemp", - "nix 0.24.2", -] - [[package]] name = "file-serve" version = "0.2.1" @@ -4157,15 +4156,6 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94c7128ba23c81f6471141b90f17654f89ef44a56e14b8a4dd0fddfccd655277" -[[package]] -name = "mktemp" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "975de676448231fcde04b9149d2543077e166b78fc29eae5aa219e7928410da2" -dependencies = [ - "uuid 0.8.2", -] - [[package]] name = "moka" version = "0.9.7" @@ -4316,9 +4306,9 @@ dependencies = [ "ethbridge-events", "ethbridge-governance-events", "eyre", + "fd-lock", "ferveo", "ferveo-common", - "file-lock", "flate2", "futures", "git2", @@ -4600,17 +4590,6 @@ dependencies = [ "tempfile", ] -[[package]] -name = "nix" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "195cdbc1741b8134346d515b3a56a1c94b0912758009cfd53f99ea0f57b065fc" -dependencies = [ - "bitflags 1.3.2", - "cfg-if 1.0.0", - "libc", -] - [[package]] name = "nix" version = "0.26.2" @@ -4654,9 +4633,9 @@ checksum = "e9e591e719385e6ebaeb5ce5d3887f7d5676fceca6411d1925ccc95745f3d6f7" [[package]] name = "ntapi" -version = "0.3.7" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f" +checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" dependencies = [ "winapi", ] @@ -5537,7 +5516,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e05aef7befb11a210468a2d77d978dde2c6381a0381e33beb575e91f57fe8cf" dependencies = [ - "nix 0.26.2", + "nix", ] [[package]] @@ -6840,9 +6819,9 @@ dependencies = [ [[package]] name = "sysinfo" -version = "0.21.1" +version = "0.29.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb6c2c4a6ca462f07ca89841a2618dca6e405304d19ae238997e64915d89f513" +checksum = "751e810399bba86e9326f5762b7f32ac5a085542df78da6a78d94e07d14d7c11" dependencies = [ "cfg-if 1.0.0", "core-foundation-sys", diff --git a/Cargo.toml b/Cargo.toml index a7bec0a2c4..40921fb5ea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -68,9 +68,9 @@ ethabi = "18.0.0" ethers = "2.0.0" expectrl = "0.7.0" eyre = "0.6.5" +fd-lock = "3.0.12" ferveo = {git = "https://github.com/anoma/ferveo", rev = "e5abd0acc938da90140351a65a26472eb495ce4d"} ferveo-common = {git = "https://github.com/anoma/ferveo", rev = "e5abd0acc938da90140351a65a26472eb495ce4d"} -file-lock = "2.0.2" file-serve = "0.2.0" flate2 = "1.0.22" fs_extra = "1.2.0" @@ -116,7 +116,7 @@ sha2 = "0.9.3" signal-hook = "0.3.9" slip10_ed25519 = "0.1.3" # sysinfo with disabled multithread feature -sysinfo = {version = "=0.21.1", default-features = false} +sysinfo = {version = "0.29.4", default-features = false} tar = "0.4.37" tempfile = {version = "3.2.0"} tendermint-config = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "b7d1e5afc6f2ccb3fd1545c2174bab1cc48d7fa7"} diff --git a/apps/Cargo.toml b/apps/Cargo.toml index b82a4966b7..2d44bc0443 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -90,9 +90,9 @@ ethbridge-bridge-events = {git = "https://github.com/heliaxdev/ethbridge-rs", ta ethbridge-events = {git = "https://github.com/heliaxdev/ethbridge-rs", tag = "v0.18.0"} ethbridge-governance-events = {git = "https://github.com/heliaxdev/ethbridge-rs", tag = "v0.18.0"} eyre.workspace = true +fd-lock.workspace = true ferveo-common.workspace = true ferveo.workspace = true -file-lock.workspace = true flate2.workspace = true futures.workspace = true itertools.workspace = true diff --git a/apps/src/bin/namada-client/cli.rs b/apps/src/bin/namada-client/cli.rs index 609588045d..935dcc7619 100644 --- a/apps/src/bin/namada-client/cli.rs +++ b/apps/src/bin/namada-client/cli.rs @@ -67,7 +67,7 @@ pub async fn main() -> Result<()> { tx::submit_ibc_transfer::(&client, ctx, args) .await?; } - Sub::TxUpdateVp(TxUpdateVp(mut args)) => { + Sub::TxUpdateAccount(TxUpdateAccount(mut args)) => { let client = HttpClient::new(utils::take_config_address( &mut args.tx.ledger_address, )) @@ -76,8 +76,10 @@ pub async fn main() -> Result<()> { .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - tx::submit_update_vp::(&client, &mut ctx, args) - .await?; + tx::submit_update_account::( + &client, &mut ctx, args, + ) + .await?; } Sub::TxInitAccount(TxInitAccount(mut args)) => { let client = HttpClient::new(utils::take_config_address( @@ -213,27 +215,56 @@ pub async fn main() -> Result<()> { .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); let tx_args = args.tx.clone(); - let (mut tx, addr, pk) = bridge_pool::build_bridge_pool_tx( - &client, - &mut ctx.wallet, - args, - ) - .await - .unwrap(); + let (mut tx, addr, public_keys) = + bridge_pool::build_bridge_pool_tx( + &client, + &mut ctx.wallet, + args, + ) + .await + .unwrap(); tx::submit_reveal_aux( &client, &mut ctx, &tx_args, - addr, - pk.clone(), + addr.clone(), + &public_keys, &mut tx, ) .await?; - signing::sign_tx(&mut ctx.wallet, &mut tx, &tx_args, &pk) - .await?; + let (account_public_keys_map, threshold) = + signing::aux_signing_data( + &client, + addr, + public_keys.clone(), + ) + .await; + signing::sign_tx( + &mut ctx.wallet, + &mut tx, + &tx_args, + &account_public_keys_map, + &public_keys, + threshold, + ) + .await?; sdk_tx::process_tx(&client, &mut ctx.wallet, &tx_args, tx) .await?; } + Sub::TxUnjailValidator(TxUnjailValidator(mut args)) => { + let client = HttpClient::new(utils::take_config_address( + &mut args.tx.ledger_address, + )) + .unwrap(); + wait_until_node_is_synched(&client) + .await + .proceed_or_else(error)?; + let args = args.to_sdk(&mut ctx); + tx::submit_unjail_validator::( + &client, ctx, args, + ) + .await?; + } // Ledger queries Sub::QueryEpoch(QueryEpoch(mut args)) => { let client = HttpClient::new(utils::take_config_address( @@ -325,6 +356,22 @@ pub async fn main() -> Result<()> { let args = args.to_sdk(&mut ctx); rpc::query_bonded_stake(&client, args).await; } + Sub::QueryValidatorState(QueryValidatorState(mut args)) => { + let client = HttpClient::new(utils::take_config_address( + &mut args.query.ledger_address, + )) + .unwrap(); + wait_until_node_is_synched(&client) + .await + .proceed_or_else(error)?; + let args = args.to_sdk(&mut ctx); + rpc::query_and_print_validator_state( + &client, + &mut ctx.wallet, + args, + ) + .await; + } Sub::QueryCommissionRate(QueryCommissionRate(mut args)) => { let client = HttpClient::new(utils::take_config_address( &mut args.query.ledger_address, @@ -433,6 +480,16 @@ pub async fn main() -> Result<()> { let args = args.to_sdk(&mut ctx); rpc::query_protocol_parameters(&client, args).await; } + Sub::QueryAccount(QueryAccount(args)) => { + let client = + HttpClient::new(args.query.ledger_address.clone()) + .unwrap(); + wait_until_node_is_synched(&client) + .await + .proceed_or_else(error)?; + let args = args.to_sdk(&mut ctx); + rpc::query_account(&client, args).await; + } } } cli::NamadaClient::WithoutContext(cmd, global_args) => match cmd { @@ -455,6 +512,15 @@ pub async fn main() -> Result<()> { Utils::DefaultBaseDir(DefaultBaseDir(args)) => { utils::default_base_dir(global_args, args) } + Utils::EpochSleep(EpochSleep(args)) => { + let mut ctx = cli::Context::new(global_args) + .expect("expected to construct a context"); + let ledger_address = args.ledger_address.clone(); + let client = HttpClient::new(ledger_address).unwrap(); + wait_until_node_is_synched(&client).await; + let args = args.to_sdk(&mut ctx); + rpc::epoch_sleep(&client, args).await; + } }, } Ok(()) diff --git a/apps/src/bin/namada-client/main.rs b/apps/src/bin/namada-client/main.rs index ccdc0bb2eb..a9e1fb4948 100644 --- a/apps/src/bin/namada-client/main.rs +++ b/apps/src/bin/namada-client/main.rs @@ -1,7 +1,7 @@ -mod cli; - use color_eyre::eyre::Result; -use namada_apps::logging; +use namada_apps::cli::api::CliApi; +use namada_apps::facade::tendermint_rpc::HttpClient; +use namada_apps::{cli, logging}; use tracing_subscriber::filter::LevelFilter; #[tokio::main] @@ -13,5 +13,9 @@ async fn main() -> Result<()> { let _log_guard = logging::init_from_env_or(LevelFilter::INFO)?; // run the CLI - cli::main().await + CliApi::<()>::handle_client_command::( + None, + cli::namada_client_cli()?, + ) + .await } diff --git a/apps/src/bin/namada-relayer/cli.rs b/apps/src/bin/namada-relayer/cli.rs deleted file mode 100644 index fa816dbeae..0000000000 --- a/apps/src/bin/namada-relayer/cli.rs +++ /dev/null @@ -1,143 +0,0 @@ -//! Namada relayer CLI. - -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::ledger::rpc::wait_until_node_is_synched; -use namada::types::control_flow::ProceedOrElse; -use namada_apps::cli::args::CliToSdkCtxless; -use namada_apps::cli::{self, cmds}; -use namada_apps::client::utils; -use namada_apps::facade::tendermint_rpc::HttpClient; - -fn error() -> Report { - eyre!("Fatal error") -} - -pub async fn main() -> Result<()> { - let (cmd, _) = cli::namada_relayer_cli()?; - match cmd { - cmds::NamadaRelayer::EthBridgePool(sub) => match sub { - cmds::EthBridgePool::RecommendBatch(mut args) => { - let client = HttpClient::new(utils::take_config_address( - &mut args.query.ledger_address, - )) - .unwrap(); - wait_until_node_is_synched(&client) - .await - .proceed_or_else(error)?; - let args = args.to_sdk_ctxless(); - bridge_pool::recommend_batch(&client, args) - .await - .proceed_or_else(error)?; - } - cmds::EthBridgePool::ConstructProof(mut args) => { - let client = HttpClient::new(utils::take_config_address( - &mut args.query.ledger_address, - )) - .unwrap(); - wait_until_node_is_synched(&client) - .await - .proceed_or_else(error)?; - let args = args.to_sdk_ctxless(); - bridge_pool::construct_proof(&client, args) - .await - .proceed_or_else(error)?; - } - cmds::EthBridgePool::RelayProof(mut args) => { - let client = HttpClient::new(utils::take_config_address( - &mut args.query.ledger_address, - )) - .unwrap(); - wait_until_node_is_synched(&client) - .await - .proceed_or_else(error)?; - let eth_client = Arc::new( - Provider::::try_from(&args.eth_rpc_endpoint).unwrap(), - ); - let args = args.to_sdk_ctxless(); - bridge_pool::relay_bridge_pool_proof(eth_client, &client, args) - .await - .proceed_or_else(error)?; - } - cmds::EthBridgePool::QueryPool(mut query) => { - let client = HttpClient::new(utils::take_config_address( - &mut query.ledger_address, - )) - .unwrap(); - wait_until_node_is_synched(&client) - .await - .proceed_or_else(error)?; - bridge_pool::query_bridge_pool(&client).await; - } - cmds::EthBridgePool::QuerySigned(mut query) => { - let client = HttpClient::new(utils::take_config_address( - &mut query.ledger_address, - )) - .unwrap(); - wait_until_node_is_synched(&client) - .await - .proceed_or_else(error)?; - bridge_pool::query_signed_bridge_pool(&client) - .await - .proceed_or_else(error)?; - } - cmds::EthBridgePool::QueryRelays(mut query) => { - let client = HttpClient::new(utils::take_config_address( - &mut query.ledger_address, - )) - .unwrap(); - wait_until_node_is_synched(&client) - .await - .proceed_or_else(error)?; - bridge_pool::query_relay_progress(&client).await; - } - }, - cmds::NamadaRelayer::ValidatorSet(sub) => match sub { - cmds::ValidatorSet::ConsensusValidatorSet(mut args) => { - let client = HttpClient::new(utils::take_config_address( - &mut args.query.ledger_address, - )) - .unwrap(); - wait_until_node_is_synched(&client) - .await - .proceed_or_else(error)?; - let args = args.to_sdk_ctxless(); - validator_set::query_validator_set_args(&client, args).await; - } - cmds::ValidatorSet::ValidatorSetProof(mut args) => { - let client = HttpClient::new(utils::take_config_address( - &mut args.query.ledger_address, - )) - .unwrap(); - wait_until_node_is_synched(&client) - .await - .proceed_or_else(error)?; - let args = args.to_sdk_ctxless(); - validator_set::query_validator_set_update_proof(&client, args) - .await; - } - cmds::ValidatorSet::ValidatorSetUpdateRelay(mut args) => { - let client = HttpClient::new(utils::take_config_address( - &mut args.query.ledger_address, - )) - .unwrap(); - wait_until_node_is_synched(&client) - .await - .proceed_or_else(error)?; - let eth_client = Arc::new( - Provider::::try_from(&args.eth_rpc_endpoint).unwrap(), - ); - let args = args.to_sdk_ctxless(); - validator_set::relay_validator_set_update( - eth_client, &client, args, - ) - .await - .proceed_or_else(error)?; - } - }, - } - Ok(()) -} diff --git a/apps/src/bin/namada-relayer/main.rs b/apps/src/bin/namada-relayer/main.rs index 73876fe7d2..0b314cb9fa 100644 --- a/apps/src/bin/namada-relayer/main.rs +++ b/apps/src/bin/namada-relayer/main.rs @@ -1,7 +1,7 @@ -mod cli; - use color_eyre::eyre::Result; -use namada_apps::logging; +use namada::tendermint_rpc::HttpClient; +use namada_apps::cli::api::CliApi; +use namada_apps::{cli, logging}; use tracing_subscriber::filter::LevelFilter; #[tokio::main] @@ -12,6 +12,7 @@ async fn main() -> Result<()> { // init logging logging::init_from_env_or(LevelFilter::INFO)?; + let (cmd, _) = cli::namada_relayer_cli()?; // run the CLI - cli::main().await + CliApi::<()>::handle_relayer_command::(None, cmd).await } diff --git a/apps/src/bin/namada-wallet/main.rs b/apps/src/bin/namada-wallet/main.rs index 252ecb7b88..7459234c79 100644 --- a/apps/src/bin/namada-wallet/main.rs +++ b/apps/src/bin/namada-wallet/main.rs @@ -1,9 +1,10 @@ -mod cli; use color_eyre::eyre::Result; +use namada_apps::cli; +use namada_apps::cli::api::CliApi; pub fn main() -> Result<()> { color_eyre::install()?; - + let (cmd, ctx) = cli::namada_wallet_cli()?; // run the CLI - cli::main() + CliApi::<()>::handle_wallet_command(cmd, ctx) } diff --git a/apps/src/bin/namada/cli.rs b/apps/src/bin/namada/cli.rs index 8c7a1e0b49..0259ba525a 100644 --- a/apps/src/bin/namada/cli.rs +++ b/apps/src/bin/namada/cli.rs @@ -47,7 +47,7 @@ fn handle_command(cmd: cli::cmds::Namada, raw_sub_cmd: String) -> Result<()> { | cli::cmds::Namada::TxCustom(_) | cli::cmds::Namada::TxTransfer(_) | cli::cmds::Namada::TxIbcTransfer(_) - | cli::cmds::Namada::TxUpdateVp(_) + | cli::cmds::Namada::TxUpdateAccount(_) | cli::cmds::Namada::TxRevealPk(_) | cli::cmds::Namada::TxInitProposal(_) | cli::cmds::Namada::TxVoteProposal(_) => { diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 06d6f3f406..c71f996e73 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -6,8 +6,12 @@ //! client can be dispatched via `namada node ...` or `namada client ...`, //! respectively. +pub mod api; +pub mod client; pub mod context; +pub mod relayer; mod utils; +pub mod wallet; use clap::{ArgGroup, ArgMatches, ColorChoice}; use color_eyre::eyre::Result; @@ -27,6 +31,7 @@ const WALLET_CMD: &str = "wallet"; const RELAYER_CMD: &str = "relayer"; pub mod cmds { + use super::utils::*; use super::{ args, ArgMatches, CLIENT_CMD, NODE_CMD, RELAYER_CMD, WALLET_CMD, @@ -52,7 +57,7 @@ pub mod cmds { TxCustom(TxCustom), TxTransfer(TxTransfer), TxIbcTransfer(TxIbcTransfer), - TxUpdateVp(TxUpdateVp), + TxUpdateAccount(TxUpdateAccount), TxInitProposal(TxInitProposal), TxVoteProposal(TxVoteProposal), TxRevealPk(TxRevealPk), @@ -69,7 +74,7 @@ pub mod cmds { .subcommand(TxCustom::def()) .subcommand(TxTransfer::def()) .subcommand(TxIbcTransfer::def()) - .subcommand(TxUpdateVp::def()) + .subcommand(TxUpdateAccount::def()) .subcommand(TxInitProposal::def()) .subcommand(TxVoteProposal::def()) .subcommand(TxRevealPk::def()) @@ -87,7 +92,8 @@ pub mod cmds { let tx_transfer = SubCmd::parse(matches).map(Self::TxTransfer); let tx_ibc_transfer = SubCmd::parse(matches).map(Self::TxIbcTransfer); - let tx_update_vp = SubCmd::parse(matches).map(Self::TxUpdateVp); + let tx_update_account = + SubCmd::parse(matches).map(Self::TxUpdateAccount); let tx_init_proposal = SubCmd::parse(matches).map(Self::TxInitProposal); let tx_vote_proposal = @@ -101,7 +107,7 @@ pub mod cmds { .or(tx_custom) .or(tx_transfer) .or(tx_ibc_transfer) - .or(tx_update_vp) + .or(tx_update_account) .or(tx_init_proposal) .or(tx_vote_proposal) .or(tx_reveal_pk) @@ -208,7 +214,7 @@ pub mod cmds { .subcommand(TxCustom::def().display_order(1)) .subcommand(TxTransfer::def().display_order(1)) .subcommand(TxIbcTransfer::def().display_order(1)) - .subcommand(TxUpdateVp::def().display_order(1)) + .subcommand(TxUpdateAccount::def().display_order(1)) .subcommand(TxInitAccount::def().display_order(1)) .subcommand(TxRevealPk::def().display_order(1)) // Proposal transactions @@ -216,6 +222,7 @@ pub mod cmds { .subcommand(TxVoteProposal::def().display_order(1)) // PoS transactions .subcommand(TxInitValidator::def().display_order(2)) + .subcommand(TxUnjailValidator::def().display_order(2)) .subcommand(Bond::def().display_order(2)) .subcommand(Unbond::def().display_order(2)) .subcommand(Withdraw::def().display_order(2)) @@ -224,6 +231,7 @@ pub mod cmds { .subcommand(AddToEthBridgePool::def().display_order(3)) // Queries .subcommand(QueryEpoch::def().display_order(4)) + .subcommand(QueryAccount::def().display_order(4)) .subcommand(QueryTransfers::def().display_order(4)) .subcommand(QueryConversions::def().display_order(4)) .subcommand(QueryBlock::def().display_order(4)) @@ -238,6 +246,7 @@ pub mod cmds { .subcommand(QueryProposal::def().display_order(4)) .subcommand(QueryProposalResult::def().display_order(4)) .subcommand(QueryProtocolParameters::def().display_order(4)) + .subcommand(QueryValidatorState::def().display_order(4)) // Utils .subcommand(Utils::def().display_order(5)) } @@ -247,10 +256,13 @@ pub mod cmds { let tx_custom = Self::parse_with_ctx(matches, TxCustom); let tx_transfer = Self::parse_with_ctx(matches, TxTransfer); let tx_ibc_transfer = Self::parse_with_ctx(matches, TxIbcTransfer); - let tx_update_vp = Self::parse_with_ctx(matches, TxUpdateVp); + let tx_update_account = + Self::parse_with_ctx(matches, TxUpdateAccount); let tx_init_account = Self::parse_with_ctx(matches, TxInitAccount); let tx_init_validator = Self::parse_with_ctx(matches, TxInitValidator); + let tx_unjail_validator = + Self::parse_with_ctx(matches, TxUnjailValidator); let tx_reveal_pk = Self::parse_with_ctx(matches, TxRevealPk); let tx_init_proposal = Self::parse_with_ctx(matches, TxInitProposal); @@ -262,6 +274,7 @@ pub mod cmds { let unbond = Self::parse_with_ctx(matches, Unbond); let withdraw = Self::parse_with_ctx(matches, Withdraw); let query_epoch = Self::parse_with_ctx(matches, QueryEpoch); + let query_account = Self::parse_with_ctx(matches, QueryAccount); let query_transfers = Self::parse_with_ctx(matches, QueryTransfers); let query_conversions = Self::parse_with_ctx(matches, QueryConversions); @@ -282,19 +295,22 @@ pub mod cmds { Self::parse_with_ctx(matches, QueryProposalResult); let query_protocol_parameters = Self::parse_with_ctx(matches, QueryProtocolParameters); + let query_validator_state = + Self::parse_with_ctx(matches, QueryValidatorState); let add_to_eth_bridge_pool = Self::parse_with_ctx(matches, AddToEthBridgePool); let utils = SubCmd::parse(matches).map(Self::WithoutContext); tx_custom .or(tx_transfer) .or(tx_ibc_transfer) - .or(tx_update_vp) + .or(tx_update_account) .or(tx_init_account) .or(tx_reveal_pk) .or(tx_init_proposal) .or(tx_vote_proposal) .or(tx_init_validator) .or(tx_commission_rate_change) + .or(tx_unjail_validator) .or(bond) .or(unbond) .or(withdraw) @@ -314,6 +330,8 @@ pub mod cmds { .or(query_proposal) .or(query_proposal_result) .or(query_protocol_parameters) + .or(query_validator_state) + .or(query_account) .or(utils) } } @@ -355,10 +373,11 @@ pub mod cmds { TxTransfer(TxTransfer), TxIbcTransfer(TxIbcTransfer), QueryResult(QueryResult), - TxUpdateVp(TxUpdateVp), + TxUpdateAccount(TxUpdateAccount), TxInitAccount(TxInitAccount), TxInitValidator(TxInitValidator), TxCommissionRateChange(TxCommissionRateChange), + TxUnjailValidator(TxUnjailValidator), TxInitProposal(TxInitProposal), TxVoteProposal(TxVoteProposal), TxRevealPk(TxRevealPk), @@ -367,6 +386,7 @@ pub mod cmds { Withdraw(Withdraw), AddToEthBridgePool(AddToEthBridgePool), QueryEpoch(QueryEpoch), + QueryAccount(QueryAccount), QueryTransfers(QueryTransfers), QueryConversions(QueryConversions), QueryBlock(QueryBlock), @@ -381,6 +401,7 @@ pub mod cmds { QueryProposal(QueryProposal), QueryProposalResult(QueryProposalResult), QueryProtocolParameters(QueryProtocolParameters), + QueryValidatorState(QueryValidatorState), } #[allow(clippy::large_enum_variant)] @@ -1217,15 +1238,15 @@ pub mod cmds { } #[derive(Clone, Debug)] - pub struct TxUpdateVp(pub args::TxUpdateVp); + pub struct TxUpdateAccount(pub args::TxUpdateAccount); - impl SubCmd for TxUpdateVp { - const CMD: &'static str = "update"; + impl SubCmd for TxUpdateAccount { + const CMD: &'static str = "update-account"; fn parse(matches: &ArgMatches) -> Option { - matches - .subcommand_matches(Self::CMD) - .map(|matches| TxUpdateVp(args::TxUpdateVp::parse(matches))) + matches.subcommand_matches(Self::CMD).map(|matches| { + TxUpdateAccount(args::TxUpdateAccount::parse(matches)) + }) } fn def() -> App { @@ -1234,7 +1255,7 @@ pub mod cmds { "Send a signed transaction to update account's validity \ predicate.", ) - .add_args::>() + .add_args::>() } } @@ -1282,6 +1303,27 @@ pub mod cmds { } } + #[derive(Clone, Debug)] + pub struct TxUnjailValidator(pub args::TxUnjailValidator); + + impl SubCmd for TxUnjailValidator { + const CMD: &'static str = "unjail-validator"; + + fn parse(matches: &ArgMatches) -> Option { + matches.subcommand_matches(Self::CMD).map(|matches| { + TxUnjailValidator(args::TxUnjailValidator::parse(matches)) + }) + } + + fn def() -> App { + App::new(Self::CMD) + .about( + "Send a signed transaction to unjail a jailed validator.", + ) + .add_args::>() + } + } + #[derive(Clone, Debug)] pub struct Bond(pub args::Bond); @@ -1358,6 +1400,28 @@ pub mod cmds { } } + #[derive(Clone, Debug)] + pub struct QueryAccount(pub args::QueryAccount); + + impl SubCmd for QueryAccount { + const CMD: &'static str = "query-account"; + + fn parse(matches: &ArgMatches) -> Option { + matches + .subcommand_matches(Self::CMD) + .map(|matches| QueryAccount(args::QueryAccount::parse(matches))) + } + + fn def() -> App { + App::new(Self::CMD) + .about( + "Query the substorage space of a specific enstablished \ + address.", + ) + .add_args::>() + } + } + #[derive(Clone, Debug)] pub struct QueryConversions(pub args::QueryConversions); @@ -1453,6 +1517,27 @@ pub mod cmds { } } + #[derive(Clone, Debug)] + pub struct QueryValidatorState( + pub args::QueryValidatorState, + ); + + impl SubCmd for QueryValidatorState { + const CMD: &'static str = "validator-state"; + + fn parse(matches: &ArgMatches) -> Option { + matches.subcommand_matches(Self::CMD).map(|matches| { + QueryValidatorState(args::QueryValidatorState::parse(matches)) + }) + } + + fn def() -> App { + App::new(Self::CMD) + .about("Query the state of a PoS validator.") + .add_args::>() + } + } + #[derive(Clone, Debug)] pub struct QueryTransfers(pub args::QueryTransfers); @@ -1488,7 +1573,7 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) - .about("Query commission rate.") + .about("Query a validator's commission rate.") .add_args::>() } } @@ -1678,6 +1763,28 @@ pub mod cmds { } } + #[derive(Clone, Debug)] + pub struct EpochSleep(pub args::Query); + + impl SubCmd for EpochSleep { + const CMD: &'static str = "epoch-sleep"; + + fn parse(matches: &ArgMatches) -> Option { + matches + .subcommand_matches(Self::CMD) + .map(|matches| Self(args::Query::parse(matches))) + } + + fn def() -> App { + App::new(Self::CMD) + .about( + "Query for the current epoch, then sleep until the next \ + epoch.", + ) + .add_args::>() + } + } + #[derive(Clone, Debug)] pub enum Utils { JoinNetwork(JoinNetwork), @@ -1686,6 +1793,7 @@ pub mod cmds { InitGenesisValidator(InitGenesisValidator), PkToTmAddress(PkToTmAddress), DefaultBaseDir(DefaultBaseDir), + EpochSleep(EpochSleep), } impl SubCmd for Utils { @@ -1704,12 +1812,14 @@ pub mod cmds { SubCmd::parse(matches).map(Self::PkToTmAddress); let default_base_dir = SubCmd::parse(matches).map(Self::DefaultBaseDir); + let epoch_sleep = SubCmd::parse(matches).map(Self::EpochSleep); join_network .or(fetch_wasms) .or(init_network) .or(init_genesis) .or(pk_to_tm_address) .or(default_base_dir) + .or(epoch_sleep) }) } @@ -1722,6 +1832,7 @@ pub mod cmds { .subcommand(InitGenesisValidator::def()) .subcommand(PkToTmAddress::def()) .subcommand(DefaultBaseDir::def()) + .subcommand(EpochSleep::def()) .subcommand_required(true) .arg_required_else_help(true) } @@ -2233,6 +2344,7 @@ pub mod args { pub const TX_INIT_PROPOSAL: &str = "tx_init_proposal.wasm"; pub const TX_INIT_VALIDATOR_WASM: &str = "tx_init_validator.wasm"; pub const TX_REVEAL_PK: &str = "tx_reveal_pk.wasm"; + pub const TX_UPDATE_ACCOUNT_WASM: &str = "tx_update_account.wasm"; pub const TX_TRANSFER_WASM: &str = "tx_transfer.wasm"; pub const TX_UNBOND_WASM: &str = "tx_unbond.wasm"; pub const TX_UNJAIL_VALIDATOR_WASM: &str = "tx_unjail_validator.wasm"; @@ -2304,6 +2416,7 @@ pub mod args { ); pub const FEE_PAYER: Arg = arg("fee-payer"); pub const FORCE: ArgFlag = flag("force"); + pub const GAS_PAYER: ArgOpt = arg("fee-payer").opt(); pub const GAS_AMOUNT: ArgDefault = arg_default( "gas-amount", DefaultFn(|| token::DenominatedAmount { @@ -2350,6 +2463,8 @@ pub mod args { pub const NAMADA_START_TIME: ArgOpt = arg_opt("time"); pub const NO_CONVERSIONS: ArgFlag = flag("no-conversions"); pub const OUT_FILE_PATH_OPT: ArgOpt = arg_opt("out-file-path"); + pub const OUTPUT_FOLDER_PATH: ArgOpt = + arg_opt("output-folder-path"); pub const OWNER: Arg = arg("owner"); pub const OWNER_OPT: ArgOpt = OWNER.opt(); pub const PIN: ArgFlag = flag("pin"); @@ -2361,6 +2476,7 @@ pub mod args { pub const PROTOCOL_KEY: ArgOpt = arg_opt("protocol-key"); pub const PRE_GENESIS_PATH: ArgOpt = arg_opt("pre-genesis-path"); pub const PUBLIC_KEY: Arg = arg("public-key"); + pub const PUBLIC_KEYS: ArgMulti = arg_multi("public-keys"); pub const PROPOSAL_ID: Arg = arg("proposal-id"); pub const PROPOSAL_ID_OPT: ArgOpt = arg_opt("proposal-id"); pub const PROPOSAL_VOTE_PGF_OPT: ArgOpt = arg_opt("pgf"); @@ -2376,13 +2492,12 @@ pub mod args { pub const SAFE_MODE: ArgFlag = flag("safe-mode"); pub const SCHEME: ArgDefault = arg_default("scheme", DefaultFn(|| SchemeType::Ed25519)); - pub const SIGNER: ArgOpt = arg_opt("signer"); - pub const SIGNING_KEY_OPT: ArgOpt = SIGNING_KEY.opt(); - pub const SIGNING_KEY: Arg = arg("signing-key"); + pub const SIGNING_KEYS: ArgMulti = arg_multi("signing-keys"); + pub const SIGNATURES: ArgMulti = + arg_multi("signatures-paths"); pub const SOURCE: Arg = arg("source"); pub const SOURCE_OPT: ArgOpt = SOURCE.opt(); pub const STORAGE_KEY: Arg = arg("storage-key"); - pub const SUB_PREFIX: ArgOpt = arg_opt("sub-prefix"); pub const SUSPEND_ACTION: ArgFlag = flag("suspend"); pub const TIMEOUT_HEIGHT: ArgOpt = arg_opt("timeout-height"); pub const TIMEOUT_SEC_OFFSET: ArgOpt = arg_opt("timeout-sec-offset"); @@ -2392,16 +2507,19 @@ pub mod args { pub const TRANSFER_SOURCE: Arg = arg("source"); pub const TRANSFER_TARGET: Arg = arg("target"); pub const TX_HASH: Arg = arg("tx-hash"); + pub const THRESOLD: ArgOpt = arg_opt("threshold"); pub const UNSAFE_DONT_ENCRYPT: ArgFlag = flag("unsafe-dont-encrypt"); pub const UNSAFE_SHOW_SECRET: ArgFlag = flag("unsafe-show-secret"); pub const VALIDATOR: Arg = arg("validator"); pub const VALIDATOR_OPT: ArgOpt = VALIDATOR.opt(); pub const VALIDATOR_ACCOUNT_KEY: ArgOpt = arg_opt("account-key"); - pub const VALIDATOR_CODE_PATH: ArgOpt = - arg_opt("validator-code-path"); + pub const VALIDATOR_ACCOUNT_KEYS: ArgMulti = + arg_multi("account-keys"); pub const VALIDATOR_CONSENSUS_KEY: ArgOpt = arg_opt("consensus-key"); + pub const VALIDATOR_CODE_PATH: ArgOpt = + arg_opt("validator-code-path"); pub const VALIDATOR_ETH_COLD_KEY: ArgOpt = arg_opt("eth-cold-key"); pub const VALIDATOR_ETH_HOT_KEY: ArgOpt = @@ -3032,6 +3150,7 @@ pub mod args { std::fs::read(data_path) .expect("Expected a file at given data path") }), + owner: ctx.get(&self.owner), } } } @@ -3041,10 +3160,12 @@ pub mod args { let tx = Tx::parse(matches); let code_path = CODE_PATH.parse(matches); let data_path = DATA_PATH_OPT.parse(matches); + let owner = OWNER.parse(matches); Self { tx, code_path, data_path, + owner, } } @@ -3060,6 +3181,10 @@ pub mod args { will be passed to the transaction code when it's \ executed.", )) + .arg(OWNER.def().help( + "The address corresponding to the signatures or signing \ + keys.", + )) } } @@ -3070,7 +3195,6 @@ pub mod args { source: ctx.get_cached(&self.source), target: ctx.get(&self.target), token: ctx.get(&self.token), - sub_prefix: self.sub_prefix, amount: self.amount, native_token: ctx.native_token.clone(), tx_code_path: self.tx_code_path.to_path_buf(), @@ -3084,7 +3208,6 @@ pub mod args { let source = TRANSFER_SOURCE.parse(matches); let target = TRANSFER_TARGET.parse(matches); let token = TOKEN.parse(matches); - let sub_prefix = SUB_PREFIX.parse(matches); let amount = InputAmount::Unvalidated(AMOUNT.parse(matches)); let tx_code_path = PathBuf::from(TX_TRANSFER_WASM); Self { @@ -3092,7 +3215,6 @@ pub mod args { source, target, token, - sub_prefix, amount, native_token: (), tx_code_path, @@ -3110,7 +3232,6 @@ pub mod args { to produce the signature.", )) .arg(TOKEN.def().help("The transfer token.")) - .arg(SUB_PREFIX.def().help("The token's sub prefix.")) .arg(AMOUNT.def().help("The amount to transfer in decimal.")) } } @@ -3122,7 +3243,6 @@ pub mod args { source: ctx.get(&self.source), receiver: self.receiver, token: ctx.get(&self.token), - sub_prefix: self.sub_prefix, amount: self.amount, port_id: self.port_id, channel_id: self.channel_id, @@ -3139,7 +3259,6 @@ pub mod args { let source = SOURCE.parse(matches); let receiver = RECEIVER.parse(matches); let token = TOKEN.parse(matches); - let sub_prefix = SUB_PREFIX.parse(matches); let amount = AMOUNT.parse(matches); let port_id = PORT_ID.parse(matches); let channel_id = CHANNEL_ID.parse(matches); @@ -3151,7 +3270,6 @@ pub mod args { source, receiver, token, - sub_prefix, amount: amount.amount, port_id, channel_id, @@ -3171,7 +3289,6 @@ pub mod args { "The receiver address on the destination chain as string.", )) .arg(TOKEN.def().help("The transfer token.")) - .arg(SUB_PREFIX.def().help("The token's sub prefix.")) .arg(AMOUNT.def().help("The amount to transfer in decimal.")) .arg(PORT_ID.def().help("The port ID.")) .arg(CHANNEL_ID.def().help("The channel ID.")) @@ -3188,10 +3305,14 @@ pub mod args { fn to_sdk(self, ctx: &mut Context) -> TxInitAccount { TxInitAccount:: { tx: self.tx.to_sdk(ctx), - source: ctx.get(&self.source), - vp_code_path: self.vp_code_path.to_path_buf(), - tx_code_path: self.tx_code_path.to_path_buf(), - public_key: ctx.get_cached(&self.public_key), + vp_code_path: self.vp_code_path, + tx_code_path: self.tx_code_path, + public_keys: self + .public_keys + .iter() + .map(|pk| ctx.get_cached(pk)) + .collect(), + threshold: self.threshold, } } } @@ -3199,34 +3320,36 @@ pub mod args { impl Args for TxInitAccount { fn parse(matches: &ArgMatches) -> Self { let tx = Tx::parse(matches); - let source = SOURCE.parse(matches); let vp_code_path = CODE_PATH_OPT .parse(matches) .unwrap_or_else(|| PathBuf::from(VP_USER_WASM)); let tx_code_path = PathBuf::from(TX_INIT_ACCOUNT_WASM); - let public_key = PUBLIC_KEY.parse(matches); + let public_keys = PUBLIC_KEYS.parse(matches); + let threshold = THRESOLD.parse(matches); Self { tx, - source, vp_code_path, - public_key, + public_keys, + threshold, tx_code_path, } } fn def(app: App) -> App { app.add_args::>() - .arg(SOURCE.def().help( - "The source account's address that signs the transaction.", - )) .arg(CODE_PATH_OPT.def().help( "The path to the validity predicate WASM code to be used \ for the new account. Uses the default user VP if none \ specified.", )) - .arg(PUBLIC_KEY.def().help( - "A public key to be used for the new account in \ - hexadecimal encoding.", + .arg(PUBLIC_KEYS.def().help( + "A list public keys to be associated with the new account \ + in hexadecimal encoding.", + )) + .arg(THRESOLD.def().help( + "The minimum number of signature to be provided for \ + authorization. Must be less then the maximum number of \ + public keys provided.", )) } } @@ -3235,9 +3358,13 @@ pub mod args { fn to_sdk(self, ctx: &mut Context) -> TxInitValidator { TxInitValidator:: { tx: self.tx.to_sdk(ctx), - source: ctx.get(&self.source), scheme: self.scheme, - account_key: self.account_key.map(|x| ctx.get_cached(&x)), + account_keys: self + .account_keys + .iter() + .map(|x| ctx.get_cached(x)) + .collect(), + threshold: self.threshold, consensus_key: self.consensus_key.map(|x| ctx.get_cached(&x)), eth_cold_key: self.eth_cold_key.map(|x| ctx.get_cached(&x)), eth_hot_key: self.eth_hot_key.map(|x| ctx.get_cached(&x)), @@ -3256,9 +3383,8 @@ pub mod args { impl Args for TxInitValidator { fn parse(matches: &ArgMatches) -> Self { let tx = Tx::parse(matches); - let source = SOURCE.parse(matches); let scheme = SCHEME.parse(matches); - let account_key = VALIDATOR_ACCOUNT_KEY.parse(matches); + let account_keys = VALIDATOR_ACCOUNT_KEYS.parse(matches); let consensus_key = VALIDATOR_CONSENSUS_KEY.parse(matches); let eth_cold_key = VALIDATOR_ETH_COLD_KEY.parse(matches); let eth_hot_key = VALIDATOR_ETH_HOT_KEY.parse(matches); @@ -3271,11 +3397,12 @@ pub mod args { .unwrap_or_else(|| PathBuf::from(VP_USER_WASM)); let unsafe_dont_encrypt = UNSAFE_DONT_ENCRYPT.parse(matches); let tx_code_path = PathBuf::from(TX_INIT_VALIDATOR_WASM); + let threshold = THRESOLD.parse(matches); Self { tx, - source, scheme, - account_key, + account_keys, + threshold, consensus_key, eth_cold_key, eth_hot_key, @@ -3290,16 +3417,14 @@ pub mod args { fn def(app: App) -> App { app.add_args::>() - .arg(SOURCE.def().help( - "The source account's address that signs the transaction.", - )) .arg(SCHEME.def().help( "The key scheme/type used for the validator keys. \ Currently supports ed25519 and secp256k1.", )) - .arg(VALIDATOR_ACCOUNT_KEY.def().help( - "A public key for the validator account. A new one will \ - be generated if none given.", + .arg(VALIDATOR_ACCOUNT_KEYS.def().help( + "A list public keys to be associated with the new account \ + in hexadecimal encoding. A new one will be generated if \ + none given.", )) .arg(VALIDATOR_CONSENSUS_KEY.def().help( "A consensus key for the validator account. A new one \ @@ -3340,38 +3465,53 @@ pub mod args { "UNSAFE: Do not encrypt the generated keypairs. Do not \ use this for keys used in a live network.", )) + .arg(THRESOLD.def().help( + "The minimum number of signature to be provided for \ + authorization. Must be less then the maximum number of \ + public keys provided.", + )) } } - impl CliToSdk> for TxUpdateVp { - fn to_sdk(self, ctx: &mut Context) -> TxUpdateVp { - TxUpdateVp:: { + impl CliToSdk> for TxUpdateAccount { + fn to_sdk(self, ctx: &mut Context) -> TxUpdateAccount { + TxUpdateAccount:: { tx: self.tx.to_sdk(ctx), vp_code_path: self.vp_code_path, tx_code_path: self.tx_code_path, addr: ctx.get(&self.addr), + public_keys: self + .public_keys + .iter() + .map(|pk| ctx.get_cached(pk)) + .collect(), + threshold: self.threshold, } } } - impl Args for TxUpdateVp { + impl Args for TxUpdateAccount { fn parse(matches: &ArgMatches) -> Self { let tx = Tx::parse(matches); - let vp_code_path = CODE_PATH.parse(matches); + let vp_code_path = CODE_PATH_OPT.parse(matches); let addr = ADDRESS.parse(matches); - let tx_code_path = PathBuf::from(TX_UPDATE_VP_WASM); + let tx_code_path = PathBuf::from(TX_UPDATE_ACCOUNT_WASM); + let public_keys = PUBLIC_KEYS.parse(matches); + let threshold = THRESOLD.parse(matches); Self { tx, vp_code_path, addr, tx_code_path, + public_keys, + threshold, } } fn def(app: App) -> App { app.add_args::>() .arg( - CODE_PATH.def().help( + CODE_PATH_OPT.def().help( "The path to the new validity predicate WASM code.", ), ) @@ -3379,6 +3519,15 @@ pub mod args { "The account's address. It's key is used to produce the \ signature.", )) + .arg(PUBLIC_KEYS.def().help( + "A list public keys to be associated with the new account \ + in hexadecimal encoding.", + )) + .arg(THRESOLD.def().help( + "The minimum number of signature to be provided for \ + authorization. Must be less then the maximum number of \ + public keys provided.", + )) } } @@ -3545,6 +3694,8 @@ pub mod args { pub proposal_id: Option, /// The vote pub vote: String, + /// The address of the voter + pub voter_address: C::Address, /// PGF proposal pub proposal_pgf: Option, /// ETH proposal @@ -3563,6 +3714,7 @@ pub mod args { tx: self.tx.to_sdk(ctx), proposal_id: self.proposal_id, vote: self.vote, + voter_address: ctx.get(&self.voter_address), offline: self.offline, proposal_data: self.proposal_data, tx_code_path: self.tx_code_path.to_path_buf(), @@ -3579,6 +3731,7 @@ pub mod args { let proposal_pgf = PROPOSAL_VOTE_PGF_OPT.parse(matches); let proposal_eth = PROPOSAL_VOTE_ETH_OPT.parse(matches); let vote = PROPOSAL_VOTE.parse(matches); + let voter_address = ADDRESS.parse(matches); let offline = PROPOSAL_OFFLINE.parse(matches); let proposal_data = DATA_PATH_OPT.parse(matches); let tx_code_path = PathBuf::from(TX_VOTE_PROPOSAL); @@ -3590,6 +3743,7 @@ pub mod args { proposal_pgf, proposal_eth, offline, + voter_address, proposal_data, tx_code_path, } @@ -3648,6 +3802,7 @@ pub mod args { ) .conflicts_with(PROPOSAL_ID.name), ) + .arg(ADDRESS.def().help("The address of the voter.")) } } @@ -3857,6 +4012,31 @@ pub mod args { } } + impl CliToSdk> for QueryAccount { + fn to_sdk(self, ctx: &mut Context) -> QueryAccount { + QueryAccount:: { + query: self.query.to_sdk(ctx), + owner: ctx.get(&self.owner), + } + } + } + + impl Args for QueryAccount { + fn parse(matches: &ArgMatches) -> Self { + let query = Query::parse(matches); + let owner = OWNER.parse(matches); + Self { query, owner } + } + + fn def(app: App) -> App { + app.add_args::>().arg( + BALANCE_OWNER + .def() + .help("The substorage space address to query."), + ) + } + } + impl CliToSdk> for QueryBalance { fn to_sdk(self, ctx: &mut Context) -> QueryBalance { QueryBalance:: { @@ -3864,7 +4044,6 @@ pub mod args { owner: self.owner.map(|x| ctx.get_cached(&x)), token: self.token.map(|x| ctx.get(&x)), no_conversions: self.no_conversions, - sub_prefix: self.sub_prefix, } } } @@ -3875,13 +4054,11 @@ pub mod args { let owner = BALANCE_OWNER.parse(matches); let token = TOKEN_OPT.parse(matches); let no_conversions = NO_CONVERSIONS.parse(matches); - let sub_prefix = SUB_PREFIX.parse(matches); Self { query, owner, token, no_conversions, - sub_prefix, } } @@ -3902,11 +4079,6 @@ pub mod args { "Whether not to automatically perform conversions.", ), ) - .arg( - SUB_PREFIX - .def() - .help("The token's sub prefix whose balance to query."), - ) } } @@ -3916,7 +4088,6 @@ pub mod args { query: self.query.to_sdk(ctx), owner: self.owner.map(|x| ctx.get_cached(&x)), token: self.token.map(|x| ctx.get(&x)), - sub_prefix: self.sub_prefix, } } } @@ -3926,12 +4097,10 @@ pub mod args { let query = Query::parse(matches); let owner = BALANCE_OWNER.parse(matches); let token = TOKEN_OPT.parse(matches); - let sub_prefix = SUB_PREFIX.parse(matches); Self { query, owner, token, - sub_prefix, } } @@ -3943,11 +4112,6 @@ pub mod args { .arg(TOKEN_OPT.def().help( "The token address that queried transfers must involve.", )) - .arg( - SUB_PREFIX - .def() - .help("The token's sub prefix whose balance to query."), - ) } } @@ -4016,8 +4180,44 @@ pub mod args { "The validator's address whose bonded stake to query.", )) .arg(EPOCH.def().help( - "The epoch at which to query (last committed, if not \ - specified).", + "The epoch at which to query (corresponding to the last \ + committed block, if not specified).", + )) + } + } + + impl CliToSdk> for QueryValidatorState { + fn to_sdk(self, ctx: &mut Context) -> QueryValidatorState { + QueryValidatorState:: { + query: self.query.to_sdk(ctx), + validator: ctx.get(&self.validator), + epoch: self.epoch, + } + } + } + + impl Args for QueryValidatorState { + fn parse(matches: &ArgMatches) -> Self { + let query = Query::parse(matches); + let validator = VALIDATOR.parse(matches); + let epoch = EPOCH.parse(matches); + Self { + query, + validator, + epoch, + } + } + + fn def(app: App) -> App { + app.add_args::>() + .arg( + VALIDATOR.def().help( + "The validator's address whose state is queried.", + ), + ) + .arg(EPOCH.def().help( + "The epoch at which to query (corresponding to the last \ + committed block, if not specified).", )) } } @@ -4064,16 +4264,10 @@ pub mod args { impl CliToSdk> for TxUnjailValidator { fn to_sdk(self, ctx: &mut Context) -> TxUnjailValidator { - TxUnjailValidator { + TxUnjailValidator:: { tx: self.tx.to_sdk(ctx), validator: ctx.get(&self.validator), - tx_code_path: self - .tx_code_path - .as_path() - .to_str() - .unwrap() - .to_string() - .into_bytes(), + tx_code_path: self.tx_code_path.to_path_buf(), } } } @@ -4127,8 +4321,8 @@ pub mod args { "The validator's address whose commission rate to query.", )) .arg(EPOCH.def().help( - "The epoch at which to query (last committed, if not \ - specified).", + "The epoch at which to query (corresponding to the last \ + committed block, if not specified).", )) } } @@ -4253,19 +4447,24 @@ pub mod args { Tx:: { dry_run: self.dry_run, dump_tx: self.dump_tx, + output_folder: self.output_folder, force: self.force, broadcast_only: self.broadcast_only, ledger_address: (), initialized_account_alias: self.initialized_account_alias, wallet_alias_force: self.wallet_alias_force, + fee_payer: ctx.get_opt_cached(&self.fee_payer), fee_amount: self.fee_amount, fee_token: ctx.get(&self.fee_token), gas_limit: self.gas_limit, - signing_key: self.signing_key.map(|x| ctx.get_cached(&x)), + signing_keys: self + .signing_keys + .iter() + .map(|key| ctx.get_cached(key)) + .collect(), verification_key: self .verification_key - .map(|x| ctx.get_cached(&x)), - signer: self.signer.map(|x| ctx.get(&x)), + .map(|public_key| ctx.get_cached(&public_key)), tx_reveal_code_path: self.tx_reveal_code_path, password: self.password, expiration: self.expiration, @@ -4307,6 +4506,10 @@ pub mod args { .arg(WALLET_ALIAS_FORCE.def().help( "Override the alias without confirmation if it already exists.", )) + .arg(GAS_PAYER.def().help( + "The implicit address of the gas payer. It defaults to the \ + address associated to the first key passed to --signing-keys.", + )) .arg(GAS_AMOUNT.def().help( "The amount being paid for the inclusion of this transaction", )) @@ -4322,27 +4525,13 @@ pub mod args { equivalent:\n2012-12-12T12:12:12Z\n2012-12-12 \ 12:12:12Z\n2012- 12-12T12: 12:12Z", )) - .arg( - SIGNING_KEY_OPT - .def() - .help( - "Sign the transaction with the key for the given \ - public key, public key hash or alias from your \ - wallet.", - ) - .conflicts_with(SIGNER.name) - .conflicts_with(VERIFICATION_KEY.name), - ) - .arg( - SIGNER - .def() - .help( - "Sign the transaction with the keypair of the public \ - key of the given address.", - ) - .conflicts_with(SIGNING_KEY_OPT.name) - .conflicts_with(VERIFICATION_KEY.name), - ) + .arg(SIGNING_KEYS.def().help( + "Sign the transaction with the key for the given public key, \ + public key hash or alias from your wallet.", + )) + .arg(OUTPUT_FOLDER_PATH.def().help( + "The output folder path where the artifact will be stored.", + )) .arg( VERIFICATION_KEY .def() @@ -4351,8 +4540,7 @@ pub mod args { public key, public key hash or alias from your \ wallet.", ) - .conflicts_with(SIGNER.name) - .conflicts_with(SIGNING_KEY_OPT.name), + .conflicts_with(SIGNING_KEYS.name), ) .arg(CHAIN_ID_OPT.def().help("The chain ID.")) } @@ -4365,17 +4553,18 @@ pub mod args { let ledger_address = LEDGER_ADDRESS_DEFAULT.parse(matches); let initialized_account_alias = ALIAS_OPT.parse(matches); let wallet_alias_force = WALLET_ALIAS_FORCE.parse(matches); + let fee_payer = GAS_PAYER.parse(matches); let fee_amount = InputAmount::Unvalidated(GAS_AMOUNT.parse(matches)); let fee_token = GAS_TOKEN.parse(matches); let gas_limit = GAS_LIMIT.parse(matches).amount.into(); let expiration = EXPIRATION_OPT.parse(matches); - let signing_key = SIGNING_KEY_OPT.parse(matches); + let signing_keys = SIGNING_KEYS.parse(matches); let verification_key = VERIFICATION_KEY.parse(matches); - let signer = SIGNER.parse(matches); let tx_reveal_code_path = PathBuf::from(TX_REVEAL_PK); let chain_id = CHAIN_ID_OPT.parse(matches); let password = None; + let output_folder = OUTPUT_FOLDER_PATH.parse(matches); Self { dry_run, dump_tx, @@ -4384,16 +4573,17 @@ pub mod args { ledger_address, initialized_account_alias, wallet_alias_force, + fee_payer, fee_amount, fee_token, gas_limit, expiration, - signing_key, + signing_keys, verification_key, - signer, tx_reveal_code_path, password, chain_id, + output_folder, } } } diff --git a/apps/src/lib/cli/api.rs b/apps/src/lib/cli/api.rs new file mode 100644 index 0000000000..c22fe39fd3 --- /dev/null +++ b/apps/src/lib/cli/api.rs @@ -0,0 +1,29 @@ +use std::marker::PhantomData; + +use namada::ledger::queries::Client; +use namada::ledger::rpc::wait_until_node_is_synched; +use namada::tendermint_rpc::HttpClient; +use namada::types::control_flow::Halt; +use tendermint_config::net::Address as TendermintAddress; + +use crate::client::utils; + +/// Trait for clients that can be used with the CLI. +#[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_trait::async_trait(?Send)] +impl CliClient for HttpClient { + fn from_tendermint_address(address: &mut TendermintAddress) -> Self { + HttpClient::new(utils::take_config_address(address)).unwrap() + } + + async fn wait_until_node_is_synced(&self) -> Halt<()> { + wait_until_node_is_synched(self).await + } +} + +pub struct CliApi(PhantomData); diff --git a/apps/src/lib/cli/client.rs b/apps/src/lib/cli/client.rs new file mode 100644 index 0000000000..430cbdc02a --- /dev/null +++ b/apps/src/lib/cli/client.rs @@ -0,0 +1,592 @@ +use color_eyre::eyre::{eyre, Report, Result}; +use namada::ledger::eth_bridge::bridge_pool; +use namada::ledger::{signing, tx as sdk_tx}; +use namada::types::control_flow::ProceedOrElse; + +use crate::cli; +use crate::cli::api::{CliApi, CliClient}; +use crate::cli::args::CliToSdk; +use crate::cli::cmds::*; +use crate::client::{rpc, tx, utils}; + +fn error() -> Report { + eyre!("Fatal error") +} + +impl CliApi { + pub async fn handle_client_command( + client: Option, + cmd: cli::NamadaClient, + ) -> Result<()> + where + C: CliClient, + { + match cmd { + cli::NamadaClient::WithContext(cmd_box) => { + let (cmd, mut ctx) = *cmd_box; + use NamadaClientWithContext as Sub; + match cmd { + // Ledger cmds + Sub::TxCustom(TxCustom(mut args)) => { + let client = client.unwrap_or_else(|| { + C::from_tendermint_address( + &mut args.tx.ledger_address, + ) + }); + client + .wait_until_node_is_synced() + .await + .proceed_or_else(error)?; + let args = args.to_sdk(&mut ctx); + let dry_run = args.tx.dry_run; + tx::submit_custom(&client, &mut ctx, args).await?; + if !dry_run { + crate::wallet::save(&ctx.wallet) + .unwrap_or_else(|err| eprintln!("{}", err)); + } else { + println!( + "Transaction dry run. No addresses have been \ + saved." + ) + } + } + Sub::TxTransfer(TxTransfer(mut args)) => { + let client = client.unwrap_or_else(|| { + C::from_tendermint_address( + &mut args.tx.ledger_address, + ) + }); + client + .wait_until_node_is_synced() + .await + .proceed_or_else(error)?; + let args = args.to_sdk(&mut ctx); + tx::submit_transfer(&client, ctx, args).await?; + } + Sub::TxIbcTransfer(TxIbcTransfer(mut args)) => { + let client = client.unwrap_or_else(|| { + C::from_tendermint_address( + &mut args.tx.ledger_address, + ) + }); + client + .wait_until_node_is_synced() + .await + .proceed_or_else(error)?; + let args = args.to_sdk(&mut ctx); + tx::submit_ibc_transfer(&client, ctx, args).await?; + } + Sub::TxUpdateAccount(TxUpdateAccount(mut args)) => { + let client = client.unwrap_or_else(|| { + C::from_tendermint_address( + &mut args.tx.ledger_address, + ) + }); + client + .wait_until_node_is_synced() + .await + .proceed_or_else(error)?; + let args = args.to_sdk(&mut ctx); + tx::submit_update_account(&client, &mut ctx, args) + .await?; + } + Sub::TxInitAccount(TxInitAccount(mut args)) => { + let client = client.unwrap_or_else(|| { + C::from_tendermint_address( + &mut args.tx.ledger_address, + ) + }); + client + .wait_until_node_is_synced() + .await + .proceed_or_else(error)?; + let args = args.to_sdk(&mut ctx); + let dry_run = args.tx.dry_run; + tx::submit_init_account(&client, &mut ctx, args) + .await?; + if !dry_run { + crate::wallet::save(&ctx.wallet) + .unwrap_or_else(|err| eprintln!("{}", err)); + } else { + println!( + "Transaction dry run. No addresses have been \ + saved." + ) + } + } + Sub::TxInitValidator(TxInitValidator(mut args)) => { + let client = client.unwrap_or_else(|| { + C::from_tendermint_address( + &mut args.tx.ledger_address, + ) + }); + client + .wait_until_node_is_synced() + .await + .proceed_or_else(error)?; + let args = args.to_sdk(&mut ctx); + tx::submit_init_validator(&client, ctx, args).await?; + } + Sub::TxInitProposal(TxInitProposal(mut args)) => { + let client = client.unwrap_or_else(|| { + C::from_tendermint_address( + &mut args.tx.ledger_address, + ) + }); + client + .wait_until_node_is_synced() + .await + .proceed_or_else(error)?; + let args = args.to_sdk(&mut ctx); + tx::submit_init_proposal(&client, ctx, args).await?; + } + Sub::TxVoteProposal(TxVoteProposal(mut args)) => { + let client = client.unwrap_or_else(|| { + C::from_tendermint_address( + &mut args.tx.ledger_address, + ) + }); + client + .wait_until_node_is_synced() + .await + .proceed_or_else(error)?; + let args = args.to_sdk(&mut ctx); + tx::submit_vote_proposal(&client, ctx, args).await?; + } + Sub::TxRevealPk(TxRevealPk(mut args)) => { + let client = client.unwrap_or_else(|| { + C::from_tendermint_address( + &mut args.tx.ledger_address, + ) + }); + client + .wait_until_node_is_synced() + .await + .proceed_or_else(error)?; + let args = args.to_sdk(&mut ctx); + tx::submit_reveal_pk(&client, &mut ctx, args).await?; + } + Sub::Bond(Bond(mut args)) => { + let client = client.unwrap_or_else(|| { + C::from_tendermint_address( + &mut args.tx.ledger_address, + ) + }); + client + .wait_until_node_is_synced() + .await + .proceed_or_else(error)?; + let args = args.to_sdk(&mut ctx); + tx::submit_bond(&client, &mut ctx, args).await?; + } + Sub::Unbond(Unbond(mut args)) => { + let client = client.unwrap_or_else(|| { + C::from_tendermint_address( + &mut args.tx.ledger_address, + ) + }); + client + .wait_until_node_is_synced() + .await + .proceed_or_else(error)?; + let args = args.to_sdk(&mut ctx); + tx::submit_unbond(&client, &mut ctx, args).await?; + } + Sub::Withdraw(Withdraw(mut args)) => { + let client = client.unwrap_or_else(|| { + C::from_tendermint_address( + &mut args.tx.ledger_address, + ) + }); + client + .wait_until_node_is_synced() + .await + .proceed_or_else(error)?; + let args = args.to_sdk(&mut ctx); + tx::submit_withdraw(&client, ctx, args).await?; + } + Sub::TxCommissionRateChange(TxCommissionRateChange( + mut args, + )) => { + let client = client.unwrap_or_else(|| { + C::from_tendermint_address( + &mut args.tx.ledger_address, + ) + }); + client + .wait_until_node_is_synced() + .await + .proceed_or_else(error)?; + let args = args.to_sdk(&mut ctx); + tx::submit_validator_commission_change( + &client, ctx, args, + ) + .await?; + } + // Eth bridge + Sub::AddToEthBridgePool(args) => { + let mut args = args.0; + let client = client.unwrap_or_else(|| { + C::from_tendermint_address( + &mut args.tx.ledger_address, + ) + }); + client + .wait_until_node_is_synced() + .await + .proceed_or_else(error)?; + let args = args.to_sdk(&mut ctx); + let tx_args = args.tx.clone(); + let (mut tx, addr, public_keys) = + bridge_pool::build_bridge_pool_tx( + &client, + &mut ctx.wallet, + args, + ) + .await + .unwrap(); + tx::submit_reveal_aux( + &client, + &mut ctx, + &tx_args, + addr.clone(), + &public_keys, + &mut tx, + ) + .await?; + let (account_public_keys_map, threshold) = + signing::aux_signing_data( + &client, + addr, + public_keys.clone(), + ) + .await; + signing::sign_tx( + &mut ctx.wallet, + &mut tx, + &tx_args, + &account_public_keys_map, + &public_keys, + threshold, + ) + .await?; + sdk_tx::process_tx( + &client, + &mut ctx.wallet, + &tx_args, + tx, + ) + .await?; + } + Sub::TxUnjailValidator(TxUnjailValidator(mut args)) => { + let client = client.unwrap_or_else(|| { + C::from_tendermint_address( + &mut args.tx.ledger_address, + ) + }); + client + .wait_until_node_is_synced() + .await + .proceed_or_else(error)?; + let args = args.to_sdk(&mut ctx); + tx::submit_unjail_validator(&client, ctx, args).await?; + } + // Ledger queries + Sub::QueryEpoch(QueryEpoch(mut args)) => { + let client = client.unwrap_or_else(|| { + C::from_tendermint_address(&mut args.ledger_address) + }); + client + .wait_until_node_is_synced() + .await + .proceed_or_else(error)?; + rpc::query_and_print_epoch(&client).await; + } + Sub::QueryValidatorState(QueryValidatorState(mut args)) => { + let client = client.unwrap_or_else(|| { + C::from_tendermint_address( + &mut args.query.ledger_address, + ) + }); + client + .wait_until_node_is_synced() + .await + .proceed_or_else(error)?; + let args = args.to_sdk(&mut ctx); + rpc::query_and_print_validator_state( + &client, + &mut ctx.wallet, + args, + ) + .await; + } + Sub::QueryTransfers(QueryTransfers(mut args)) => { + let client = client.unwrap_or_else(|| { + C::from_tendermint_address( + &mut args.query.ledger_address, + ) + }); + client + .wait_until_node_is_synced() + .await + .proceed_or_else(error)?; + let args = args.to_sdk(&mut ctx); + rpc::query_transfers( + &client, + &mut ctx.wallet, + &mut ctx.shielded, + args, + ) + .await; + } + Sub::QueryConversions(QueryConversions(mut args)) => { + let client = client.unwrap_or_else(|| { + C::from_tendermint_address( + &mut args.query.ledger_address, + ) + }); + client + .wait_until_node_is_synced() + .await + .proceed_or_else(error)?; + let args = args.to_sdk(&mut ctx); + rpc::query_conversions(&client, &mut ctx.wallet, 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() + .await + .proceed_or_else(error)?; + rpc::query_block(&client).await; + } + Sub::QueryBalance(QueryBalance(mut args)) => { + let client = client.unwrap_or_else(|| { + C::from_tendermint_address( + &mut args.query.ledger_address, + ) + }); + client + .wait_until_node_is_synced() + .await + .proceed_or_else(error)?; + let args = args.to_sdk(&mut ctx); + rpc::query_balance( + &client, + &mut ctx.wallet, + &mut ctx.shielded, + args, + ) + .await; + } + Sub::QueryBonds(QueryBonds(mut args)) => { + let client = client.unwrap_or_else(|| { + C::from_tendermint_address( + &mut args.query.ledger_address, + ) + }); + client + .wait_until_node_is_synced() + .await + .proceed_or_else(error)?; + let args = args.to_sdk(&mut ctx); + rpc::query_bonds(&client, &mut ctx.wallet, args) + .await + .expect("expected successful query of bonds"); + } + Sub::QueryBondedStake(QueryBondedStake(mut args)) => { + let client = client.unwrap_or_else(|| { + C::from_tendermint_address( + &mut args.query.ledger_address, + ) + }); + client + .wait_until_node_is_synced() + .await + .proceed_or_else(error)?; + let args = args.to_sdk(&mut ctx); + rpc::query_bonded_stake(&client, args).await; + } + Sub::QueryCommissionRate(QueryCommissionRate(mut args)) => { + let client = client.unwrap_or_else(|| { + C::from_tendermint_address( + &mut args.query.ledger_address, + ) + }); + client + .wait_until_node_is_synced() + .await + .proceed_or_else(error)?; + let args = args.to_sdk(&mut ctx); + rpc::query_and_print_commission_rate( + &client, + &mut ctx.wallet, + args, + ) + .await; + } + Sub::QuerySlashes(QuerySlashes(mut args)) => { + let client = client.unwrap_or_else(|| { + C::from_tendermint_address( + &mut args.query.ledger_address, + ) + }); + client + .wait_until_node_is_synced() + .await + .proceed_or_else(error)?; + let args = args.to_sdk(&mut ctx); + rpc::query_slashes(&client, &mut ctx.wallet, args) + .await; + } + Sub::QueryDelegations(QueryDelegations(mut args)) => { + let client = client.unwrap_or_else(|| { + C::from_tendermint_address( + &mut args.query.ledger_address, + ) + }); + client + .wait_until_node_is_synced() + .await + .proceed_or_else(error)?; + let args = args.to_sdk(&mut ctx); + rpc::query_delegations(&client, &mut ctx.wallet, args) + .await; + } + Sub::QueryFindValidator(QueryFindValidator(mut args)) => { + let client = client.unwrap_or_else(|| { + C::from_tendermint_address( + &mut args.query.ledger_address, + ) + }); + client + .wait_until_node_is_synced() + .await + .proceed_or_else(error)?; + let args = args.to_sdk(&mut ctx); + rpc::query_find_validator(&client, args).await; + } + Sub::QueryResult(QueryResult(mut args)) => { + let client = client.unwrap_or_else(|| { + C::from_tendermint_address( + &mut args.query.ledger_address, + ) + }); + client + .wait_until_node_is_synced() + .await + .proceed_or_else(error)?; + let args = args.to_sdk(&mut ctx); + rpc::query_result(&client, args).await; + } + Sub::QueryRawBytes(QueryRawBytes(mut args)) => { + let client = client.unwrap_or_else(|| { + C::from_tendermint_address( + &mut args.query.ledger_address, + ) + }); + client + .wait_until_node_is_synced() + .await + .proceed_or_else(error)?; + let args = args.to_sdk(&mut ctx); + rpc::query_raw_bytes(&client, args).await; + } + + Sub::QueryProposal(QueryProposal(mut args)) => { + let client = client.unwrap_or_else(|| { + C::from_tendermint_address( + &mut args.query.ledger_address, + ) + }); + client + .wait_until_node_is_synced() + .await + .proceed_or_else(error)?; + let args = args.to_sdk(&mut ctx); + rpc::query_proposal(&client, args).await; + } + Sub::QueryProposalResult(QueryProposalResult(mut args)) => { + let client = client.unwrap_or_else(|| { + C::from_tendermint_address( + &mut args.query.ledger_address, + ) + }); + client + .wait_until_node_is_synced() + .await + .proceed_or_else(error)?; + let args = args.to_sdk(&mut ctx); + rpc::query_proposal_result(&client, args).await; + } + Sub::QueryProtocolParameters(QueryProtocolParameters( + mut args, + )) => { + let client = client.unwrap_or_else(|| { + C::from_tendermint_address( + &mut args.query.ledger_address, + ) + }); + client + .wait_until_node_is_synced() + .await + .proceed_or_else(error)?; + let args = args.to_sdk(&mut ctx); + rpc::query_protocol_parameters(&client, args).await; + } + Sub::QueryAccount(QueryAccount(mut args)) => { + let client = client.unwrap_or_else(|| { + C::from_tendermint_address( + &mut args.query.ledger_address, + ) + }); + client + .wait_until_node_is_synced() + .await + .proceed_or_else(error)?; + let args = args.to_sdk(&mut ctx); + rpc::query_account(&client, args).await; + } + } + } + cli::NamadaClient::WithoutContext(cmd, global_args) => match cmd { + // Utils cmds + Utils::JoinNetwork(JoinNetwork(args)) => { + utils::join_network(global_args, args).await + } + Utils::FetchWasms(FetchWasms(args)) => { + utils::fetch_wasms(global_args, args).await + } + Utils::InitNetwork(InitNetwork(args)) => { + utils::init_network(global_args, args) + } + Utils::InitGenesisValidator(InitGenesisValidator(args)) => { + utils::init_genesis_validator(global_args, args) + } + Utils::PkToTmAddress(PkToTmAddress(args)) => { + utils::pk_to_tm_address(global_args, args) + } + Utils::DefaultBaseDir(DefaultBaseDir(args)) => { + utils::default_base_dir(global_args, args) + } + Utils::EpochSleep(EpochSleep(args)) => { + let mut ctx = cli::Context::new(global_args) + .expect("expected to construct a context"); + let mut ledger_address = args.ledger_address.clone(); + let client = + C::from_tendermint_address(&mut ledger_address); + client + .wait_until_node_is_synced() + .await + .proceed_or_else(error)?; + let args = args.to_sdk(&mut ctx); + rpc::epoch_sleep(&client, args).await; + } + }, + } + Ok(()) + } +} diff --git a/apps/src/lib/cli/relayer.rs b/apps/src/lib/cli/relayer.rs new file mode 100644 index 0000000000..531051d27a --- /dev/null +++ b/apps/src/lib/cli/relayer.rs @@ -0,0 +1,166 @@ +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 crate::cli::api::{CliApi, CliClient}; +use crate::cli::args::CliToSdkCtxless; +use crate::cli::cmds; + +fn error() -> Report { + eyre!("Fatal error") +} + +impl CliApi { + pub async fn handle_relayer_command( + client: Option, + cmd: cmds::NamadaRelayer, + ) -> Result<()> + where + C: CliClient, + { + match cmd { + cmds::NamadaRelayer::EthBridgePool(sub) => match sub { + cmds::EthBridgePool::RecommendBatch(mut args) => { + let client = client.unwrap_or_else(|| { + C::from_tendermint_address( + &mut args.query.ledger_address, + ) + }); + client + .wait_until_node_is_synced() + .await + .proceed_or_else(error)?; + let args = args.to_sdk_ctxless(); + bridge_pool::recommend_batch(&client, args) + .await + .proceed_or_else(error)?; + } + cmds::EthBridgePool::ConstructProof(mut args) => { + let client = client.unwrap_or_else(|| { + C::from_tendermint_address( + &mut args.query.ledger_address, + ) + }); + client + .wait_until_node_is_synced() + .await + .proceed_or_else(error)?; + let args = args.to_sdk_ctxless(); + bridge_pool::construct_proof(&client, args) + .await + .proceed_or_else(error)?; + } + cmds::EthBridgePool::RelayProof(mut args) => { + let client = client.unwrap_or_else(|| { + C::from_tendermint_address( + &mut args.query.ledger_address, + ) + }); + client + .wait_until_node_is_synced() + .await + .proceed_or_else(error)?; + let eth_client = Arc::new( + Provider::::try_from(&args.eth_rpc_endpoint) + .unwrap(), + ); + let args = args.to_sdk_ctxless(); + bridge_pool::relay_bridge_pool_proof( + eth_client, &client, args, + ) + .await + .proceed_or_else(error)?; + } + cmds::EthBridgePool::QueryPool(mut query) => { + let client = client.unwrap_or_else(|| { + C::from_tendermint_address(&mut query.ledger_address) + }); + client + .wait_until_node_is_synced() + .await + .proceed_or_else(error)?; + bridge_pool::query_bridge_pool(&client).await; + } + cmds::EthBridgePool::QuerySigned(mut query) => { + let client = client.unwrap_or_else(|| { + C::from_tendermint_address(&mut query.ledger_address) + }); + client + .wait_until_node_is_synced() + .await + .proceed_or_else(error)?; + bridge_pool::query_signed_bridge_pool(&client) + .await + .proceed_or_else(error)?; + } + cmds::EthBridgePool::QueryRelays(mut query) => { + let client = client.unwrap_or_else(|| { + C::from_tendermint_address(&mut query.ledger_address) + }); + client + .wait_until_node_is_synced() + .await + .proceed_or_else(error)?; + bridge_pool::query_relay_progress(&client).await; + } + }, + cmds::NamadaRelayer::ValidatorSet(sub) => match sub { + cmds::ValidatorSet::ConsensusValidatorSet(mut args) => { + let client = client.unwrap_or_else(|| { + C::from_tendermint_address( + &mut args.query.ledger_address, + ) + }); + client + .wait_until_node_is_synced() + .await + .proceed_or_else(error)?; + let args = args.to_sdk_ctxless(); + validator_set::query_validator_set_args(&client, args) + .await; + } + cmds::ValidatorSet::ValidatorSetProof(mut args) => { + let client = client.unwrap_or_else(|| { + C::from_tendermint_address( + &mut args.query.ledger_address, + ) + }); + client + .wait_until_node_is_synced() + .await + .proceed_or_else(error)?; + let args = args.to_sdk_ctxless(); + validator_set::query_validator_set_update_proof( + &client, args, + ) + .await; + } + cmds::ValidatorSet::ValidatorSetUpdateRelay(mut args) => { + let client = client.unwrap_or_else(|| { + C::from_tendermint_address( + &mut args.query.ledger_address, + ) + }); + client + .wait_until_node_is_synced() + .await + .proceed_or_else(error)?; + let eth_client = Arc::new( + Provider::::try_from(&args.eth_rpc_endpoint) + .unwrap(), + ); + let args = args.to_sdk_ctxless(); + validator_set::relay_validator_set_update( + eth_client, &client, args, + ) + .await + .proceed_or_else(error)?; + } + }, + } + Ok(()) + } +} diff --git a/apps/src/lib/cli/utils.rs b/apps/src/lib/cli/utils.rs index eac233ed28..a50f58f5de 100644 --- a/apps/src/lib/cli/utils.rs +++ b/apps/src/lib/cli/utils.rs @@ -222,6 +222,27 @@ where } } +impl ArgMulti> +where + T: FromStr, + ::Err: Debug, +{ + pub fn def(&self) -> ClapArg { + ClapArg::new(self.name) + .long(self.name) + .num_args(1..) + .value_delimiter(',') + } + + pub fn parse(&self, matches: &ArgMatches) -> Vec> { + matches + .get_many(self.name) + .unwrap_or_default() + .map(|raw: &String| FromContext::new(raw.to_string())) + .collect() + } +} + impl ArgDefaultFromCtx> where T: FromStr, diff --git a/apps/src/bin/namada-wallet/cli.rs b/apps/src/lib/cli/wallet.rs similarity index 83% rename from apps/src/bin/namada-wallet/cli.rs rename to apps/src/lib/cli/wallet.rs index 685ed7f116..7505c59efe 100644 --- a/apps/src/bin/namada-wallet/cli.rs +++ b/apps/src/lib/cli/wallet.rs @@ -11,68 +11,78 @@ use namada::ledger::masp::find_valid_diversifier; use namada::ledger::wallet::{DecryptionError, FindKeyError}; use namada::types::key::*; use namada::types::masp::{MaspValue, PaymentAddress}; -use namada_apps::cli; -use namada_apps::cli::args::CliToSdk; -use namada_apps::cli::{args, cmds, Context}; -use namada_apps::wallet::{ - read_and_confirm_encryption_password, CliWalletUtils, -}; use rand_core::OsRng; -pub fn main() -> Result<()> { - let (cmd, mut ctx) = cli::namada_wallet_cli()?; - match cmd { - cmds::NamadaWallet::Key(sub) => match sub { - cmds::WalletKey::Restore(cmds::KeyRestore(args)) => { - key_and_address_restore(ctx, args) - } - cmds::WalletKey::Gen(cmds::KeyGen(args)) => { - key_and_address_gen(ctx, args) - } - cmds::WalletKey::Find(cmds::KeyFind(args)) => key_find(ctx, args), - cmds::WalletKey::List(cmds::KeyList(args)) => key_list(ctx, args), - cmds::WalletKey::Export(cmds::Export(args)) => { - key_export(ctx, args) - } - }, - cmds::NamadaWallet::Address(sub) => match sub { - cmds::WalletAddress::Gen(cmds::AddressGen(args)) => { - key_and_address_gen(ctx, args) - } - cmds::WalletAddress::Restore(cmds::AddressRestore(args)) => { - key_and_address_restore(ctx, args) - } - cmds::WalletAddress::Find(cmds::AddressOrAliasFind(args)) => { - address_or_alias_find(ctx, args) - } - cmds::WalletAddress::List(cmds::AddressList) => address_list(ctx), - cmds::WalletAddress::Add(cmds::AddressAdd(args)) => { - address_add(ctx, args) - } - }, - cmds::NamadaWallet::Masp(sub) => match sub { - cmds::WalletMasp::GenSpendKey(cmds::MaspGenSpendKey(args)) => { - spending_key_gen(ctx, args) - } - cmds::WalletMasp::GenPayAddr(cmds::MaspGenPayAddr(args)) => { - let args = args.to_sdk(&mut ctx); - payment_address_gen(ctx, args) - } - cmds::WalletMasp::AddAddrKey(cmds::MaspAddAddrKey(args)) => { - address_key_add(ctx, args) - } - cmds::WalletMasp::ListPayAddrs(cmds::MaspListPayAddrs) => { - payment_addresses_list(ctx) - } - cmds::WalletMasp::ListKeys(cmds::MaspListKeys(args)) => { - spending_keys_list(ctx, args) - } - cmds::WalletMasp::FindAddrKey(cmds::MaspFindAddrKey(args)) => { - address_key_find(ctx, args) - } - }, +use crate::cli; +use crate::cli::api::CliApi; +use crate::cli::args::CliToSdk; +use crate::cli::{args, cmds, Context}; +use crate::wallet::{read_and_confirm_encryption_password, CliWalletUtils}; + +impl CliApi { + pub fn handle_wallet_command( + cmd: cmds::NamadaWallet, + mut ctx: Context, + ) -> Result<()> { + match cmd { + cmds::NamadaWallet::Key(sub) => match sub { + cmds::WalletKey::Restore(cmds::KeyRestore(args)) => { + key_and_address_restore(ctx, args) + } + cmds::WalletKey::Gen(cmds::KeyGen(args)) => { + key_and_address_gen(ctx, args) + } + cmds::WalletKey::Find(cmds::KeyFind(args)) => { + key_find(ctx, args) + } + cmds::WalletKey::List(cmds::KeyList(args)) => { + key_list(ctx, args) + } + cmds::WalletKey::Export(cmds::Export(args)) => { + key_export(ctx, args) + } + }, + cmds::NamadaWallet::Address(sub) => match sub { + cmds::WalletAddress::Gen(cmds::AddressGen(args)) => { + key_and_address_gen(ctx, args) + } + cmds::WalletAddress::Restore(cmds::AddressRestore(args)) => { + key_and_address_restore(ctx, args) + } + cmds::WalletAddress::Find(cmds::AddressOrAliasFind(args)) => { + address_or_alias_find(ctx, args) + } + cmds::WalletAddress::List(cmds::AddressList) => { + address_list(ctx) + } + cmds::WalletAddress::Add(cmds::AddressAdd(args)) => { + address_add(ctx, args) + } + }, + cmds::NamadaWallet::Masp(sub) => match sub { + cmds::WalletMasp::GenSpendKey(cmds::MaspGenSpendKey(args)) => { + spending_key_gen(ctx, args) + } + cmds::WalletMasp::GenPayAddr(cmds::MaspGenPayAddr(args)) => { + let args = args.to_sdk(&mut ctx); + payment_address_gen(ctx, args) + } + cmds::WalletMasp::AddAddrKey(cmds::MaspAddAddrKey(args)) => { + address_key_add(ctx, args) + } + cmds::WalletMasp::ListPayAddrs(cmds::MaspListPayAddrs) => { + payment_addresses_list(ctx) + } + cmds::WalletMasp::ListKeys(cmds::MaspListKeys(args)) => { + spending_keys_list(ctx, args) + } + cmds::WalletMasp::FindAddrKey(cmds::MaspFindAddrKey(args)) => { + address_key_find(ctx, args) + } + }, + } + Ok(()) } - Ok(()) } /// Find shielded address or key @@ -213,8 +223,7 @@ fn spending_key_gen( 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); - namada_apps::wallet::save(&wallet) - .unwrap_or_else(|err| eprintln!("{}", err)); + crate::wallet::save(&wallet).unwrap_or_else(|err| eprintln!("{}", err)); println!( "Successfully added a spending key with alias: \"{}\"", alias @@ -248,8 +257,7 @@ fn payment_address_gen( eprintln!("Payment address not added"); cli::safe_exit(1); }); - namada_apps::wallet::save(&wallet) - .unwrap_or_else(|err| eprintln!("{}", err)); + crate::wallet::save(&wallet).unwrap_or_else(|err| eprintln!("{}", err)); println!( "Successfully generated a payment address with the following alias: {}", alias, @@ -306,8 +314,7 @@ fn address_key_add( (alias, "payment address") } }; - namada_apps::wallet::save(&ctx.wallet) - .unwrap_or_else(|err| eprintln!("{}", err)); + crate::wallet::save(&ctx.wallet).unwrap_or_else(|err| eprintln!("{}", err)); println!( "Successfully added a {} with the following alias to wallet: {}", typ, alias, @@ -345,8 +352,7 @@ fn key_and_address_restore( println!("No changes are persisted. Exiting."); cli::safe_exit(0); }); - namada_apps::wallet::save(&wallet) - .unwrap_or_else(|err| eprintln!("{}", err)); + crate::wallet::save(&wallet).unwrap_or_else(|err| eprintln!("{}", err)); println!( "Successfully added a key and an address with alias: \"{}\"", alias @@ -387,8 +393,7 @@ fn key_and_address_gen( println!("No changes are persisted. Exiting."); cli::safe_exit(0); }); - namada_apps::wallet::save(&wallet) - .unwrap_or_else(|err| eprintln!("{}", err)); + crate::wallet::save(&wallet).unwrap_or_else(|err| eprintln!("{}", err)); println!( "Successfully added a key and an address with alias: \"{}\"", alias @@ -573,8 +578,7 @@ fn address_add(ctx: Context, args: args::AddressAdd) { eprintln!("Address not added"); cli::safe_exit(1); } - namada_apps::wallet::save(&wallet) - .unwrap_or_else(|err| eprintln!("{}", err)); + crate::wallet::save(&wallet).unwrap_or_else(|err| eprintln!("{}", err)); println!( "Successfully added a key and an address with alias: \"{}\"", args.alias.to_lowercase() diff --git a/apps/src/lib/client/mod.rs b/apps/src/lib/client/mod.rs index 57f3c5a043..8862c5a564 100644 --- a/apps/src/lib/client/mod.rs +++ b/apps/src/lib/client/mod.rs @@ -1,4 +1,3 @@ pub mod rpc; -pub mod signing; pub mod tx; pub mod utils; diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 44955967d0..9bba4f1edb 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -1,7 +1,7 @@ //! Client RPC queries use std::cmp::Ordering; -use std::collections::{HashMap, HashSet}; +use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use std::fs::File; use std::io::{self, Write}; use std::iter::Iterator; @@ -16,7 +16,6 @@ use masp_primitives::merkle_tree::MerklePath; use masp_primitives::sapling::{Node, ViewingKey}; use masp_primitives::zip32::ExtendedFullViewingKey; use namada::core::types::transaction::governance::ProposalType; -use namada::ledger::args::InputAmount; use namada::ledger::events::Event; use namada::ledger::governance::parameters::GovParams; use namada::ledger::governance::storage as gov_storage; @@ -31,12 +30,12 @@ use namada::ledger::pos::{ }; use namada::ledger::queries::RPC; use namada::ledger::rpc::{ - enriched_bonds_and_unbonds, format_denominated_amount, query_epoch, + self, enriched_bonds_and_unbonds, format_denominated_amount, query_epoch, TxResponse, }; use namada::ledger::storage::ConversionState; use namada::ledger::wallet::{AddressVpType, Wallet}; -use namada::proof_of_stake::types::WeightedValidator; +use namada::proof_of_stake::types::{ValidatorState, WeightedValidator}; use namada::types::address::{masp, Address}; use namada::types::control_flow::ProceedOrElse; use namada::types::governance::{ @@ -46,7 +45,7 @@ use namada::types::hash::Hash; use namada::types::key::*; use namada::types::masp::{BalanceOwner, ExtendedViewingKey, PaymentAddress}; use namada::types::storage::{BlockHeight, BlockResults, Epoch, Key, KeySeg}; -use namada::types::token::{Change, Denomination, MaspDenom, TokenAddress}; +use namada::types::token::{Change, MaspDenom}; use namada::types::{storage, token}; use tokio::time::Instant; @@ -111,7 +110,7 @@ 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, + U: ShieldedUtils, >( client: &C, wallet: &mut Wallet, @@ -119,7 +118,6 @@ pub async fn query_transfers< args: args::QueryTransfers, ) { let query_token = args.token; - let sub_prefix = args.sub_prefix.map(|s| Key::parse(s).unwrap()); let query_owner = args.owner.map_or_else( || Either::Right(wallet.get_addresses().into_values().collect()), Either::Left, @@ -173,10 +171,8 @@ pub async fn query_transfers< // Check if this transfer pertains to the supplied token relevant &= match &query_token { Some(token) => { - let check = |(tok, chg): (&TokenAddress, &Change)| { - tok.sub_prefix == sub_prefix - && &tok.address == token - && !chg.is_zero() + let check = |(tok, chg): (&Address, &Change)| { + tok == token && !chg.is_zero() }; tfer_delta.values().cloned().any( |MaspChange { ref asset, change }| check((asset, &change)), @@ -196,7 +192,7 @@ pub async fn query_transfers< for (account, MaspChange { ref asset, change }) in tfer_delta { if account != masp() { print!(" {}:", account); - let token_alias = lookup_alias(wallet, &asset.address); + let token_alias = lookup_alias(wallet, asset); let sign = match change.cmp(&Change::zero()) { Ordering::Greater => "+", Ordering::Less => "-", @@ -207,7 +203,7 @@ pub async fn query_transfers< sign, format_denominated_amount(client, asset, change.into(),) .await, - asset.format_with_alias(&token_alias) + token_alias ); } println!(); @@ -219,7 +215,7 @@ pub async fn query_transfers< if fvk_map.contains_key(&account) { print!(" {}:", fvk_map[&account]); for (token_addr, val) in masp_change { - let token_alias = lookup_alias(wallet, &token_addr.address); + let token_alias = lookup_alias(wallet, &token_addr); let sign = match val.cmp(&Change::zero()) { Ordering::Greater => "+", Ordering::Less => "-", @@ -234,7 +230,7 @@ pub async fn query_transfers< val.into(), ) .await, - token_addr.format_with_alias(&token_alias), + token_alias, ); } println!(); @@ -263,7 +259,7 @@ pub async fn query_raw_bytes( /// Query token balance(s) pub async fn query_balance< C: namada::ledger::queries::Client + Sync, - U: ShieldedUtils, + U: ShieldedUtils, >( client: &C, wallet: &mut Wallet, @@ -302,50 +298,23 @@ pub async fn query_transparent_balance< wallet: &mut Wallet, args: args::QueryBalance, ) { - let tokens = wallet.get_addresses_with_vp_type(AddressVpType::Token); + let prefix = Key::from( + Address::Internal(namada::types::address::InternalAddress::Multitoken) + .to_db_key(), + ); match (args.token, args.owner) { (Some(token), Some(owner)) => { - let (balance_key, sub_prefix) = match &args.sub_prefix { - Some(sub_prefix) => { - let sub_prefix = Key::parse(sub_prefix).unwrap(); - let prefix = - token::multitoken_balance_prefix(&token, &sub_prefix); - ( - token::multitoken_balance_key( - &prefix, - &owner.address().unwrap(), - ), - Some(sub_prefix), - ) - } - None => ( - token::balance_key(&token, &owner.address().unwrap()), - None, - ), - }; + let balance_key = + token::balance_key(&token, &owner.address().unwrap()); let token_alias = lookup_alias(wallet, &token); match query_storage_value::(client, &balance_key) .await { Some(balance) => { - let balance = format_denominated_amount( - client, - &TokenAddress { - address: token, - sub_prefix, - }, - balance, - ) - .await; - match &args.sub_prefix { - Some(sub_prefix) => { - println!( - "{} with {}: {}", - token_alias, sub_prefix, balance - ); - } - None => println!("{}: {}", token_alias, balance), - } + let balance = + format_denominated_amount(client, &token, balance) + .await; + println!("{}: {}", token_alias, balance); } None => { println!("No {} balance found for {}", token_alias, owner) @@ -353,22 +322,17 @@ pub async fn query_transparent_balance< } } (None, Some(owner)) => { - for token in tokens { - let prefix = - token::balance_key(&token, &owner.address().unwrap()); - let balances = - query_storage_prefix::(client, &prefix) - .await; - if let Some(balances) = balances { - print_balances( - client, - wallet, - balances, - &token, - owner.address().as_ref(), - ) - .await; - } + let balances = + query_storage_prefix::(client, &prefix).await; + if let Some(balances) = balances { + print_balances( + client, + wallet, + balances, + None, + owner.address().as_ref(), + ) + .await; } } (Some(token), None) => { @@ -376,19 +340,15 @@ pub async fn query_transparent_balance< let balances = query_storage_prefix::(client, &prefix).await; if let Some(balances) = balances { - print_balances(client, wallet, balances, &token, None).await; + print_balances(client, wallet, balances, Some(&token), None) + .await; } } (None, None) => { - for token in tokens { - let key = token::balance_prefix(&token); - let balances = - query_storage_prefix::(client, &key) - .await; - if let Some(balances) = balances { - print_balances(client, wallet, balances, &token, None) - .await; - } + let balances = + query_storage_prefix::(client, &prefix).await; + if let Some(balances) = balances { + print_balances(client, wallet, balances, None, None).await; } } } @@ -397,7 +357,7 @@ pub async fn query_transparent_balance< /// Query the token pinned balance(s) pub async fn query_pinned_balance< C: namada::ledger::queries::Client + Sync, - U: ShieldedUtils, + U: ShieldedUtils, >( client: &C, wallet: &mut Wallet, @@ -458,24 +418,19 @@ pub async fn query_pinned_balance< } // Now print out the received quantities according to CLI arguments - match (balance, args.token.as_ref(), args.sub_prefix.as_ref()) { - (Err(PinnedBalanceError::InvalidViewingKey), _, _) => println!( + match (balance, args.token.as_ref()) { + (Err(PinnedBalanceError::InvalidViewingKey), _) => println!( "Supplied viewing key cannot decode transactions to given \ payment address." ), - (Err(PinnedBalanceError::NoTransactionPinned), _, _) => { + (Err(PinnedBalanceError::NoTransactionPinned), _) => { println!("Payment address {} has not yet been consumed.", owner) } - (Ok((balance, epoch)), Some(token), sub_prefix) => { + (Ok((balance, epoch)), Some(token)) => { let token_alias = lookup_alias(wallet, token); - let token_address = TokenAddress { - address: token.clone(), - sub_prefix: sub_prefix - .map(|string| Key::parse(string).unwrap()), - }; let total_balance = balance - .get(&(epoch, token_address.clone())) + .get(&(epoch, token.clone())) .cloned() .unwrap_or_default(); @@ -483,28 +438,23 @@ pub async fn query_pinned_balance< println!( "Payment address {} was consumed during epoch {}. \ Received no shielded {}", - owner, - epoch, - token_address.format_with_alias(&token_alias) + owner, epoch, token_alias ); } else { let formatted = format_denominated_amount( client, - &token_address, + token, total_balance.into(), ) .await; println!( "Payment address {} was consumed during epoch {}. \ Received {} {}", - owner, - epoch, - formatted, - token_address.format_with_alias(&token_alias), + owner, epoch, formatted, token_alias, ); } } - (Ok((balance, epoch)), None, _) => { + (Ok((balance, epoch)), None) => { let mut found_any = false; for ((_, token_addr), value) in balance @@ -526,14 +476,10 @@ pub async fn query_pinned_balance< ) .await; let token_alias = tokens - .get(&token_addr.address) + .get(token_addr) .map(|a| a.to_string()) - .unwrap_or_else(|| token_addr.address.to_string()); - println!( - " {}: {}", - token_addr.format_with_alias(&token_alias), - formatted, - ); + .unwrap_or_else(|| token_addr.to_string()); + println!(" {}: {}", token_alias, formatted,); } if !found_any { println!( @@ -551,77 +497,74 @@ async fn print_balances( client: &C, wallet: &Wallet, balances: impl Iterator, - token: &Address, + token: Option<&Address>, target: Option<&Address>, ) { let stdout = io::stdout(); let mut w = stdout.lock(); - let token_alias = lookup_alias(wallet, token); - writeln!(w, "Token {}", token_alias).unwrap(); let mut print_num = 0; + let mut print_token = None; for (key, balance) in balances { - let (o, s) = match token::is_any_multitoken_balance_key(&key) { - Some((sub_prefix, [tok, owner])) => ( + // Get the token, the owner, and the balance with the token and the + // owner + let (t, o, s) = match token::is_any_token_balance_key(&key) { + Some([tok, owner]) => ( + tok.clone(), owner.clone(), format!( - "with {}: {}, owned by {}", - sub_prefix.clone(), - format_denominated_amount( - client, - &TokenAddress { - address: tok.clone(), - sub_prefix: Some(sub_prefix) - }, - balance - ) - .await, + ": {}, owned by {}", + format_denominated_amount(client, tok, balance).await, lookup_alias(wallet, owner) ), ), - None => { - if let Some([tok, owner]) = - token::is_any_token_balance_key(&key) - { - ( - owner.clone(), - format!( - ": {}, owned by {}", - format_denominated_amount( - client, - &TokenAddress { - address: tok.clone(), - sub_prefix: None - }, - balance - ) - .await, - lookup_alias(wallet, owner) - ), - ) - } else { - continue; - } - } + None => continue, }; - let s = match target { - Some(t) if o == *t => s, - Some(_) => continue, - None => s, + // Get the token and the balance + let (t, s) = match (token, target) { + // the given token and the given target are the same as the + // retrieved ones + (Some(token), Some(target)) if t == *token && o == *target => { + (t, s) + } + // the given token is the same as the retrieved one + (Some(token), None) if t == *token => (t, s), + // the given target is the same as the retrieved one + (None, Some(target)) if o == *target => (t, s), + // no specified token or target + (None, None) => (t, s), + // otherwise, this balance will not be printed + _ => continue, }; + // Print the token if it isn't printed yet + match &print_token { + Some(token) if *token == t => { + // the token has been already printed + } + _ => { + let token_alias = lookup_alias(wallet, &t); + writeln!(w, "Token {}", token_alias).unwrap(); + print_token = Some(t); + } + } + // Print the balance writeln!(w, "{}", s).unwrap(); print_num += 1; } if print_num == 0 { - match target { - Some(t) => { - writeln!(w, "No balances owned by {}", lookup_alias(wallet, t)) - .unwrap() - } - None => { + match (token, target) { + (Some(_), Some(target)) | (None, Some(target)) => writeln!( + w, + "No balances owned by {}", + lookup_alias(wallet, target) + ) + .unwrap(), + (Some(token), None) => { + let token_alias = lookup_alias(wallet, token); writeln!(w, "No balances for token {}", token_alias).unwrap() } + (None, None) => writeln!(w, "No balances").unwrap(), } } } @@ -761,7 +704,7 @@ pub async fn query_proposal( /// Query token shielded balance(s) pub async fn query_shielded_balance< C: namada::ledger::queries::Client + Sync, - U: ShieldedUtils, + U: ShieldedUtils, >( client: &C, wallet: &mut Wallet, @@ -811,27 +754,22 @@ pub async fn query_shielded_balance< let token_alias = lookup_alias(wallet, &token); - let token_address = TokenAddress { - address: token, - sub_prefix: args.sub_prefix.map(|k| Key::parse(k).unwrap()), - }; - let total_balance = balance - .get(&(epoch, token_address.clone())) + .get(&(epoch, token.clone())) .cloned() .unwrap_or_default(); if total_balance.is_zero() { println!( "No shielded {} balance found for given key", - token_address.format_with_alias(&token_alias) + token_alias ); } else { println!( "{}: {}", - token_address.format_with_alias(&token_alias), + token_alias, format_denominated_amount( client, - &token_address, + &token, token::Amount::from(total_balance) ) .await @@ -864,9 +802,6 @@ pub async fn query_shielded_balance< } } - // These are the asset types for which we have human-readable names - let mut read_tokens: HashMap>> = - HashMap::new(); // Print non-zero balances whose asset types can be decoded // TODO Implement a function for this @@ -886,72 +821,21 @@ pub async fn query_shielded_balance< } } } - for ( - ( - fvk, - TokenAddress { - address: addr, - sub_prefix, - }, - ), - token_balance, - ) in balance_map - { - read_tokens - .entry(addr.clone()) - .and_modify(|addr_vec| addr_vec.push(sub_prefix.clone())) - .or_insert_with(|| vec![sub_prefix.clone()]); - let token_address = TokenAddress { - address: addr, - sub_prefix, - }; + for ((fvk, token), token_balance) in balance_map { // Only assets with the current timestamp count let alias = tokens - .get(&token_address.address) + .get(&token) .map(|a| a.to_string()) - .unwrap_or_else(|| token_address.address.to_string()); - println!( - "Shielded Token {}:", - token_address.format_with_alias(&alias), - ); + .unwrap_or_else(|| token.to_string()); + println!("Shielded Token {}:", alias); let formatted = format_denominated_amount( client, - &token_address, + &token, token_balance.into(), ) .await; println!(" {}, owned by {}", formatted, fvk); } - // Print zero balances for remaining assets - for token in tokens { - if let Some(sub_addrs) = read_tokens.get(&token) { - let token_alias = lookup_alias(wallet, &token); - for sub_addr in sub_addrs { - match sub_addr { - // abstract out these prints - Some(sub_addr) => { - println!( - "Shielded Token {}/{}:", - token_alias, sub_addr - ); - println!( - "No shielded {}/{} balance found for any \ - wallet key", - token_alias, sub_addr - ); - } - None => { - println!("Shielded Token {}:", token_alias,); - println!( - "No shielded {} balance found for any \ - wallet key", - token_alias - ); - } - } - } - } - } } // Here the user wants to know the balance for a specific token across // users @@ -969,17 +853,7 @@ pub async fn query_shielded_balance< println!("Shielded Token {}:", token_alias); let mut found_any = false; let token_alias = lookup_alias(wallet, &token); - let token_address = TokenAddress { - address: token.clone(), - sub_prefix: args - .sub_prefix - .as_ref() - .map(|k| Key::parse(k).unwrap()), - }; - println!( - "Shielded Token {}:", - token_address.format_with_alias(&token_alias), - ); + println!("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; @@ -1011,7 +885,7 @@ pub async fn query_shielded_balance< if !found_any { println!( "No shielded {} balance found for any wallet key", - token_address.format_with_alias(&token_alias), + token_alias, ); } } @@ -1056,10 +930,7 @@ pub async fn print_decoded_balance< { println!( "{} : {}", - token_addr.format_with_alias(&lookup_alias( - wallet, - &token_addr.address - )), + lookup_alias(wallet, token_addr), format_denominated_amount(client, token_addr, (*amount).into()) .await, ); @@ -1081,12 +952,12 @@ pub async fn print_decoded_balance_with_epoch< for ((epoch, token_addr), value) in decoded_balance.iter() { let asset_value = (*value).into(); let alias = tokens - .get(&token_addr.address) + .get(token_addr) .map(|a| a.to_string()) .unwrap_or_else(|| token_addr.to_string()); println!( "{} | {} : {}", - token_addr.format_with_alias(&alias), + alias, epoch, format_denominated_amount(client, token_addr, asset_value).await, ); @@ -1098,7 +969,7 @@ pub async fn get_token_balance( client: &C, token: &Address, owner: &Address, -) -> Option { +) -> token::Amount { namada::ledger::rpc::get_token_balance(client, token, owner).await } @@ -1222,10 +1093,13 @@ pub async fn query_proposal_result< "JSON was not well-formatted for proposal.", ); - let public_key = - get_public_key(client, &proposal.address) - .await - .expect("Public key should exist."); + let public_key = rpc::get_public_key_at( + client, + &proposal.address, + 0, + ) + .await + .expect("Public key should exist."); if !proposal.check_signature(&public_key) { eprintln!("Bad proposal signature."); @@ -1276,6 +1150,23 @@ pub async fn query_proposal_result< } } +pub async fn query_account( + client: &C, + args: args::QueryAccount, +) { + let account = rpc::get_account_info(client, &args.owner).await; + if let Some(account) = account { + println!("Address: {}", account.address); + println!("Threshold: {}", account.threshold); + println!("Public keys:"); + for (public_key, _) in account.public_keys_map.pk_to_idx { + println!("- {}", public_key); + } + } else { + println!("No account exists for {}", args.owner); + } +} + pub async fn query_protocol_parameters< C: namada::ledger::queries::Client + Sync, >( @@ -1567,14 +1458,14 @@ pub async fn query_bonded_stake( } None => { let consensus = - unwrap_client_response::>( + unwrap_client_response::>( RPC.vp() .pos() .consensus_validator_set(client, &Some(epoch)) .await, ); let below_capacity = - unwrap_client_response::>( + unwrap_client_response::>( RPC.vp() .pos() .below_capacity_validator_set(client, &Some(epoch)) @@ -1586,7 +1477,7 @@ pub async fn query_bonded_stake( let mut w = stdout.lock(); writeln!(w, "Consensus validators:").unwrap(); - for val in consensus { + for val in consensus.into_iter().rev() { writeln!( w, " {}: {}", @@ -1597,7 +1488,7 @@ pub async fn query_bonded_stake( } if !below_capacity.is_empty() { writeln!(w, "Below capacity validators:").unwrap(); - for val in &below_capacity { + for val in below_capacity.into_iter().rev() { writeln!( w, " {}: {}", @@ -1634,6 +1525,60 @@ pub async fn query_commission_rate< ) } +/// Query and return validator's state +pub async fn query_validator_state< + C: namada::ledger::queries::Client + Sync, +>( + client: &C, + validator: &Address, + epoch: Option, +) -> Option { + unwrap_client_response::>( + RPC.vp() + .pos() + .validator_state(client, validator, &epoch) + .await, + ) +} + +/// Query a validator's state information +pub async fn query_and_print_validator_state< + C: namada::ledger::queries::Client + Sync, +>( + client: &C, + _wallet: &mut Wallet, + args: args::QueryValidatorState, +) { + let validator = args.validator; + let state: Option = + query_validator_state(client, &validator, args.epoch).await; + + match state { + Some(state) => match state { + ValidatorState::Consensus => { + println!("Validator {validator} is in the consensus set") + } + ValidatorState::BelowCapacity => { + println!("Validator {validator} is in the below-capacity set") + } + ValidatorState::BelowThreshold => { + println!("Validator {validator} is in the below-threshold set") + } + ValidatorState::Inactive => { + println!("Validator {validator} is inactive") + } + ValidatorState::Jailed => { + println!("Validator {validator} is jailed") + } + }, + None => println!( + "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)" + ), + } +} + /// Query PoS validator's commission rate information pub async fn query_and_print_commission_rate< C: namada::ledger::queries::Client + Sync, @@ -1674,11 +1619,6 @@ pub async fn query_slashes( _wallet: &mut Wallet, args: args::QuerySlashes, ) { - let params_key = pos::params_key(); - let params = query_storage_value::(client, ¶ms_key) - .await - .expect("Parameter should be defined."); - match args.validator { Some(validator) => { let validator = validator; @@ -1687,18 +1627,54 @@ pub async fn query_slashes( RPC.vp().pos().validator_slashes(client, &validator).await, ); if !slashes.is_empty() { + println!("Processed slashes:"); let stdout = io::stdout(); let mut w = stdout.lock(); for slash in slashes { writeln!( w, - "Slash epoch {}, type {}, rate {}", - slash.epoch, slash.r#type, slash.rate + "Infraction epoch {}, block height {}, type {}, rate \ + {}", + slash.epoch, + slash.block_height, + slash.r#type, + slash.rate ) .unwrap(); } } else { - println!("No slashes found for {}", validator.encode()) + println!( + "No processed slashes found for {}", + validator.encode() + ) + } + // Find enqueued slashes to be processed in the future for the given + // validator + let enqueued_slashes: HashMap< + Address, + BTreeMap>, + > = unwrap_client_response::< + C, + HashMap>>, + >(RPC.vp().pos().enqueued_slashes(client).await); + let enqueued_slashes = enqueued_slashes.get(&validator).cloned(); + if let Some(enqueued) = enqueued_slashes { + println!("\nEnqueued slashes for future processing"); + for (epoch, slashes) in enqueued { + println!("To be processed in epoch {}", epoch); + for slash in slashes { + let stdout = io::stdout(); + let mut w = stdout.lock(); + writeln!( + w, + "Infraction epoch {}, block height {}, type {}", + slash.epoch, slash.block_height, slash.r#type, + ) + .unwrap(); + } + } + } else { + println!("No enqueued slashes found for {}", validator.encode()) } } None => { @@ -1710,15 +1686,16 @@ pub async fn query_slashes( if !all_slashes.is_empty() { let stdout = io::stdout(); let mut w = stdout.lock(); + println!("Processed slashes:"); for (validator, slashes) in all_slashes.into_iter() { for slash in slashes { writeln!( w, - "Slash epoch {}, block height {}, rate {}, type \ - {}, validator {}", + "Infraction epoch {}, block height {}, rate {}, \ + type {}, validator {}", slash.epoch, slash.block_height, - slash.r#type.get_slash_rate(¶ms), + slash.rate, slash.r#type, validator, ) @@ -1726,7 +1703,41 @@ pub async fn query_slashes( } } } else { - println!("No slashes found") + println!("No processed slashes found") + } + + // Find enqueued slashes to be processed in the future for the given + // validator + let enqueued_slashes: HashMap< + Address, + BTreeMap>, + > = unwrap_client_response::< + C, + HashMap>>, + >(RPC.vp().pos().enqueued_slashes(client).await); + if !enqueued_slashes.is_empty() { + println!("\nEnqueued slashes for future processing"); + for (validator, slashes_by_epoch) in enqueued_slashes { + for (epoch, slashes) in slashes_by_epoch { + println!("\nTo be processed in epoch {}", epoch); + for slash in slashes { + let stdout = io::stdout(); + let mut w = stdout.lock(); + writeln!( + w, + "Infraction epoch {}, block height {}, type \ + {}, validator {}", + slash.epoch, + slash.block_height, + slash.r#type, + validator + ) + .unwrap(); + } + } + } + } else { + println!("\nNo enqueued slashes found for future processing") } } } @@ -1791,8 +1802,9 @@ where pub async fn get_public_key( client: &C, address: &Address, + index: u8, ) -> Option { - namada::ledger::rpc::get_public_key(client, address).await + namada::ledger::rpc::get_public_key_at(client, address, index).await } /// Check if the given address is a known validator. @@ -1850,7 +1862,7 @@ pub async fn query_conversions( .expect("Conversions should be defined"); // Track whether any non-sentinel conversions are found let mut conversions_found = false; - for ((addr, sub, _), epoch, conv, _) in conv_state.assets.values() { + for ((addr, _), epoch, conv, _) in conv_state.assets.values() { let amt: masp_primitives::transaction::components::Amount = conv.clone().into(); // If the user has specified any targets, then meet them @@ -1864,9 +1876,8 @@ pub async fn query_conversions( conversions_found = true; // Print the asset to which the conversion applies print!( - "{}{}[{}]: ", + "{}[{}]: ", tokens.get(addr).cloned().unwrap_or_else(|| addr.clone()), - sub.as_ref().map(|k| format!("/{}", k)).unwrap_or_default(), epoch, ); // Now print out the components of the allowed conversion @@ -1874,14 +1885,13 @@ pub async fn query_conversions( for (asset_type, val) in amt.components() { // Look up the address and epoch of asset to facilitate pretty // printing - let ((addr, sub, _), epoch, _, _) = &conv_state.assets[asset_type]; + let ((addr, _), epoch, _, _) = &conv_state.assets[asset_type]; // Now print out this component of the conversion print!( - "{}{} {}{}[{}]", + "{}{} {}[{}]", prefix, val, tokens.get(addr).cloned().unwrap_or_else(|| addr.clone()), - sub.as_ref().map(|k| format!("/{}", k)).unwrap_or_default(), epoch ); // Future iterations need to be prefixed with + @@ -1901,7 +1911,6 @@ pub async fn query_conversion( asset_type: AssetType, ) -> Option<( Address, - Option, MaspDenom, Epoch, masp_primitives::transaction::components::Amount, @@ -2030,6 +2039,21 @@ pub async fn query_result( } } +pub async fn epoch_sleep( + client: &C, + _args: args::Query, +) { + let start_epoch = query_and_print_epoch(client).await; + loop { + tokio::time::sleep(core::time::Duration::from_secs(1)).await; + let current_epoch = query_epoch(client).await; + if current_epoch > start_epoch { + println!("Reached epoch {}", current_epoch); + break; + } + } +} + pub async fn get_proposal_votes( client: &C, epoch: Epoch, @@ -2061,10 +2085,10 @@ pub async fn get_proposal_offline_votes< let proposal_vote: OfflineVote = serde_json::from_reader(file) .expect("JSON was not well-formatted for offline vote."); - let key = pk_key(&proposal_vote.address); - let public_key = query_storage_value(client, &key) - .await - .expect("Public key should exist."); + let public_key = + rpc::get_public_key_at(client, &proposal_vote.address, 0) + .await + .expect("Public key should exist."); if !proposal_vote.proposal_hash.eq(&proposal_hash) || !proposal_vote.check_signature(&public_key) @@ -2307,52 +2331,3 @@ fn unwrap_client_response( cli::safe_exit(1) }) } - -/// Get the correct representation of the amount given the token type. -pub async fn validate_amount( - client: &C, - amount: InputAmount, - token: &Address, - sub_prefix: &Option, - force: bool, -) -> token::DenominatedAmount { - let input_amount = match amount { - InputAmount::Unvalidated(amt) => amt.canonical(), - InputAmount::Validated(amt) => return amt, - }; - let denom = unwrap_client_response::>( - RPC.vp() - .token() - .denomination(client, token, sub_prefix) - .await, - ) - .unwrap_or_else(|| { - if force { - println!( - "No denomination found for token: {token}, but --force was \ - passed. Defaulting to the provided denomination." - ); - input_amount.denom - } else { - println!( - "No denomination found for token: {token}, the input \ - arguments could not be parsed." - ); - cli::safe_exit(1); - } - }); - if denom < input_amount.denom && !force { - println!( - "The input amount contained a higher precision than allowed by \ - {token}." - ); - cli::safe_exit(1); - } else { - input_amount.increase_precision(denom).unwrap_or_else(|_| { - println!( - "The amount provided requires more the 256 bits to represent." - ); - cli::safe_exit(1); - }) - } -} diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index d70f8c7e6f..c6b889faff 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -9,9 +9,11 @@ use async_trait::async_trait; use borsh::{BorshDeserialize, BorshSerialize}; use data_encoding::HEXLOWER_PERMISSIVE; use masp_proofs::prover::LocalTxProver; +use namada::core::types::account::AccountPublicKeysMap; use namada::ledger::governance::storage as gov_storage; +use namada::ledger::queries::Client; use namada::ledger::rpc::{TxBroadcastData, TxResponse}; -use namada::ledger::signing::TxSigningKey; +use namada::ledger::signing::{find_pk, TxSigningKey}; use namada::ledger::wallet::{Wallet, WalletUtils}; use namada::ledger::{masp, pos, signing, tx}; use namada::proof_of_stake::parameters::PosParams; @@ -25,17 +27,16 @@ use namada::types::key::{self, *}; use namada::types::storage::{Epoch, Key}; use namada::types::token; use namada::types::transaction::governance::{ProposalType, VoteProposalData}; -use namada::types::transaction::{InitValidator, TxType}; +use namada::types::transaction::pos::InitValidator; +use namada::types::transaction::TxType; use super::rpc; use crate::cli::context::WalletAddress; use crate::cli::{args, safe_exit, Context}; use crate::client::rpc::query_wasm_code_hash; -use crate::client::signing::find_pk; use crate::client::tx::tx::ProcessTxResponse; use crate::config::TendermintMode; use crate::facade::tendermint_rpc::endpoint::broadcast::tx_sync::Response; -use crate::facade::tendermint_rpc::HttpClient; use crate::node::ledger::tendermint_node; use crate::wallet::{gen_validator_keys, read_and_confirm_encryption_password}; @@ -45,27 +46,44 @@ pub async fn submit_reveal_aux( ctx: &mut Context, args: &args::Tx, addr: Option
, - pk: common::PublicKey, + public_keys: &[common::PublicKey], tx: &mut Tx, ) -> Result<(), tx::Error> { if let Some(Address::Implicit(_)) = addr { - let reveal_pk = tx::build_reveal_pk( - client, - &mut ctx.wallet, - args::RevealPk { - tx: args.clone(), - public_key: pk.clone(), - }, - ) - .await?; - if let Some((mut rtx, _, pk)) = reveal_pk { - // Sign the reveal public key transaction with the fee payer - signing::sign_tx(&mut ctx.wallet, &mut rtx, args, &pk).await?; - // Submit the reveal public key transaction first - tx::process_tx(client, &mut ctx.wallet, args, rtx).await?; - // Update the stateful PoW challenge of the outer transaction - #[cfg(not(feature = "mainnet"))] - signing::update_pow_challenge(client, args, tx, &pk, false).await; + for public_key in public_keys { + let reveal_pk = tx::build_reveal_pk( + client, + &mut ctx.wallet, + args::RevealPk { + tx: args.clone(), + public_key: public_key.clone(), + }, + ) + .await?; + if let Some((mut rtx, _, public_keys)) = reveal_pk { + // Sign the reveal public key transaction with the fee payer + signing::sign_tx( + &mut ctx.wallet, + &mut rtx, + args, + &AccountPublicKeysMap::from_iter(public_keys.clone()), + &public_keys, + 1, + ) + .await?; + // Submit the reveal public key transaction first + tx::process_tx(client, &mut ctx.wallet, args, rtx).await?; + // Update the stateful PoW challenge of the outer transaction + #[cfg(not(feature = "mainnet"))] + signing::update_pow_challenge( + client, + args, + tx, + public_keys.get(0).unwrap(), + false, + ) + .await; + } } } Ok(()) @@ -80,27 +98,63 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - let (mut tx, addr, pk) = + let (mut tx, addr, public_keys) = tx::build_custom(client, &mut ctx.wallet, args.clone()).await?; - submit_reveal_aux(client, ctx, &args.tx, addr, pk.clone(), &mut tx).await?; - signing::sign_tx(&mut ctx.wallet, &mut tx, &args.tx, &pk).await?; + submit_reveal_aux( + client, + ctx, + &args.tx, + addr.clone(), + &public_keys, + &mut tx, + ) + .await?; + let (account_public_keys_map, threshold) = + signing::aux_signing_data(client, addr, public_keys.clone()).await; + signing::sign_tx( + &mut ctx.wallet, + &mut tx, + &args.tx, + &account_public_keys_map, + &public_keys, + threshold, + ) + .await?; tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; Ok(()) } -pub async fn submit_update_vp( +pub async fn submit_update_account( client: &C, ctx: &mut Context, - args: args::TxUpdateVp, + args: args::TxUpdateAccount, ) -> Result<(), tx::Error> where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - let (mut tx, addr, pk) = - tx::build_update_vp(client, &mut ctx.wallet, args.clone()).await?; - submit_reveal_aux(client, ctx, &args.tx, addr, pk.clone(), &mut tx).await?; - signing::sign_tx(&mut ctx.wallet, &mut tx, &args.tx, &pk).await?; + let (mut tx, addr, public_keys) = + tx::build_update_account(client, &mut ctx.wallet, args.clone()).await?; + submit_reveal_aux( + client, + ctx, + &args.tx, + addr.clone(), + &public_keys, + &mut tx, + ) + .await?; + let (account_public_keys_map, threshold) = + signing::aux_signing_data(client, addr, public_keys.clone()).await; + signing::sign_tx( + &mut ctx.wallet, + &mut tx, + &args.tx, + &account_public_keys_map, + &public_keys, + threshold, + ) + .await?; tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; Ok(()) } @@ -114,10 +168,29 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - let (mut tx, addr, pk) = + let (mut tx, addr, public_keys) = tx::build_init_account(client, &mut ctx.wallet, args.clone()).await?; - submit_reveal_aux(client, ctx, &args.tx, addr, pk.clone(), &mut tx).await?; - signing::sign_tx(&mut ctx.wallet, &mut tx, &args.tx, &pk).await?; + + submit_reveal_aux( + client, + ctx, + &args.tx, + addr.clone(), + &public_keys, + &mut tx, + ) + .await?; + let (account_public_keys_map, threshold) = + signing::aux_signing_data(client, addr, public_keys.clone()).await; + signing::sign_tx( + &mut ctx.wallet, + &mut tx, + &args.tx, + &account_public_keys_map, + &public_keys, + threshold, + ) + .await?; tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; Ok(()) } @@ -127,9 +200,9 @@ pub async fn submit_init_validator( mut ctx: Context, args::TxInitValidator { tx: tx_args, - source, scheme, - account_key, + account_keys, + threshold, consensus_key, eth_cold_key, eth_hot_key, @@ -160,25 +233,19 @@ where let validator_key_alias = format!("{}-key", alias); let consensus_key_alias = format!("{}-consensus-key", alias); + + let threshold = match threshold { + Some(threshold) => threshold, + None => { + if account_keys.len() == 1 { + 1u8 + } else { + safe_exit(1) + } + } + }; let eth_hot_key_alias = format!("{}-eth-hot-key", alias); let eth_cold_key_alias = format!("{}-eth-cold-key", alias); - let account_key = account_key.unwrap_or_else(|| { - println!("Generating validator account key..."); - let password = - read_and_confirm_encryption_password(unsafe_dont_encrypt); - ctx.wallet - .gen_key( - scheme, - Some(validator_key_alias.clone()), - tx_args.wallet_alias_force, - password, - None, - ) - .expect("Key generation should not fail.") - .expect("No existing alias expected.") - .1 - .ref_to() - }); let consensus_key = consensus_key .map(|key| match key { @@ -316,7 +383,8 @@ where validator_vp_code_hash, ))); let data = InitValidator { - account_key, + account_keys, + threshold, consensus_key: consensus_key.ref_to(), eth_cold_key: key::secp256k1::PublicKey::try_from_pk(ð_cold_pk) .unwrap(), @@ -334,19 +402,37 @@ where tx.set_data(Data::new(data)); tx.set_code(Code::from_hash(tx_code_hash)); - let (mut tx, addr, pk) = tx::prepare_tx( + let (mut tx, addr, public_keys) = tx::prepare_tx( client, &mut ctx.wallet, &tx_args, tx, - TxSigningKey::WalletAddress(source), + None, + TxSigningKey::None, #[cfg(not(feature = "mainnet"))] false, ) .await?; - submit_reveal_aux(client, &mut ctx, &tx_args, addr, pk.clone(), &mut tx) - .await?; - signing::sign_tx(&mut ctx.wallet, &mut tx, &tx_args, &pk).await?; + submit_reveal_aux( + client, + &mut ctx, + &tx_args, + addr.clone(), + &public_keys, + &mut tx, + ) + .await?; + let (account_public_keys_map, threshold) = + signing::aux_signing_data(client, addr, public_keys.clone()).await; + signing::sign_tx( + &mut ctx.wallet, + &mut tx, + &tx_args, + &account_public_keys_map, + &public_keys, + threshold, + ) + .await?; let result = tx::process_tx(client, &mut ctx.wallet, &tx_args, tx) .await? .initialized_accounts(); @@ -463,8 +549,6 @@ impl Default for CLIShieldedUtils { #[async_trait(?Send)] impl masp::ShieldedUtils for CLIShieldedUtils { - type C = crate::facade::tendermint_rpc::HttpClient; - 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); @@ -525,26 +609,36 @@ impl masp::ShieldedUtils for CLIShieldedUtils { } } -pub async fn submit_transfer( - client: &HttpClient, +pub async fn submit_transfer( + client: &C, mut ctx: Context, args: args::TxTransfer, ) -> Result<(), tx::Error> { for _ in 0..2 { let arg = args.clone(); - let (mut tx, addr, pk, tx_epoch, _isf) = + let (mut tx, addr, public_keys, tx_epoch, _isf) = tx::build_transfer(client, &mut ctx.wallet, &mut ctx.shielded, arg) .await?; submit_reveal_aux( client, &mut ctx, &args.tx, - addr, - pk.clone(), + addr.clone(), + &public_keys, + &mut tx, + ) + .await?; + let (account_public_keys_map, threshold) = + signing::aux_signing_data(client, addr, public_keys.clone()).await; + signing::sign_tx( + &mut ctx.wallet, &mut tx, + &args.tx, + &account_public_keys_map, + &public_keys, + threshold, ) .await?; - signing::sign_tx(&mut ctx.wallet, &mut tx, &args.tx, &pk).await?; let result = tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; // Query the epoch in which the transaction was probably submitted @@ -583,11 +677,28 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - let (mut tx, addr, pk) = + let (mut tx, addr, public_keys) = tx::build_ibc_transfer(client, &mut ctx.wallet, args.clone()).await?; - submit_reveal_aux(client, &mut ctx, &args.tx, addr, pk.clone(), &mut tx) - .await?; - signing::sign_tx(&mut ctx.wallet, &mut tx, &args.tx, &pk).await?; + submit_reveal_aux( + client, + &mut ctx, + &args.tx, + addr.clone(), + &public_keys, + &mut tx, + ) + .await?; + let (account_public_keys_map, threshold) = + signing::aux_signing_data(client, addr, public_keys.clone()).await; + signing::sign_tx( + &mut ctx.wallet, + &mut tx, + &args.tx, + &account_public_keys_map, + &public_keys, + threshold, + ) + .await?; tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; Ok(()) } @@ -665,7 +776,7 @@ where if args.offline { let signer = ctx.get(&signer); - let key = find_pk(client, &mut ctx.wallet, &signer).await?; + let key = find_pk(client, &mut ctx.wallet, &signer, None).await?; let signing_key = signing::find_key_by_pk(&mut ctx.wallet, &args.tx, &key)?; let offline_proposal = @@ -702,8 +813,7 @@ where let balance = rpc::get_token_balance(client, &ctx.native_token, &proposal.author) - .await - .unwrap_or_default(); + .await; if balance < token::Amount::from_uint( governance_parameters.min_proposal_fund, @@ -753,11 +863,12 @@ where tx.set_data(Data::new(data)); tx.set_code(Code::from_hash(tx_code_hash)); - let (mut tx, addr, pk) = tx::prepare_tx( + let (mut tx, addr, public_keys) = tx::prepare_tx( client, &mut ctx.wallet, &args.tx, tx, + Some(signer.clone()), TxSigningKey::WalletAddress(signer), #[cfg(not(feature = "mainnet"))] false, @@ -767,13 +878,24 @@ where client, &mut ctx, &args.tx, - addr, - pk.clone(), + addr.clone(), + &public_keys, + &mut tx, + ) + .await?; + let (account_public_keys_map, threshold) = + signing::aux_signing_data(client, addr, public_keys.clone()).await; + signing::sign_tx( + &mut ctx.wallet, &mut tx, + &args.tx, + &account_public_keys_map, + &public_keys, + threshold, ) .await?; - signing::sign_tx(&mut ctx.wallet, &mut tx, &args.tx, &pk).await?; tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; + Ok(()) } } @@ -787,13 +909,6 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - let signer = if let Some(addr) = &args.tx.signer { - addr - } else { - eprintln!("Missing mandatory argument --signer."); - safe_exit(1) - }; - // Construct vote let proposal_vote = match args.vote.to_ascii_lowercase().as_str() { "yay" => { @@ -864,28 +979,33 @@ where let proposal: OfflineProposal = serde_json::from_reader(file).expect("JSON was not well-formatted"); - let public_key = rpc::get_public_key(client, &proposal.address) - .await - .expect("Public key should exist."); + let public_key = namada::ledger::rpc::get_public_key_at( + client, + &proposal.address, + 0, + ) + .await + .expect("Public key should exist."); if !proposal.check_signature(&public_key) { eprintln!("Proposal signature mismatch!"); safe_exit(1) } - let key = find_pk(client, &mut ctx.wallet, signer).await?; + let key = + find_pk(client, &mut ctx.wallet, &args.voter_address, None).await?; let signing_key = signing::find_key_by_pk(&mut ctx.wallet, &args.tx, &key)?; let offline_vote = OfflineVote::new( &proposal, proposal_vote, - signer.clone(), + args.voter_address.clone(), &signing_key, ); let proposal_vote_filename = proposal_file_path .parent() .expect("No parent found") - .join(format!("proposal-vote-{}", &signer.to_string())); + .join(format!("proposal-vote-{}", &args.voter_address.to_string())); let out = File::create(&proposal_vote_filename).unwrap(); match serde_json::to_writer_pretty(out, &offline_vote) { Ok(_) => { @@ -903,7 +1023,7 @@ where } else { let current_epoch = rpc::query_and_print_epoch(client).await; - let voter_address = signer.clone(); + let voter_address = args.voter_address.clone(); let proposal_id = args.proposal_id.unwrap(); let proposal_start_epoch_key = gov_storage::get_voting_start_epoch_key(proposal_id); @@ -1022,12 +1142,13 @@ where tx.set_data(Data::new(data)); tx.set_code(Code::from_hash(tx_code_hash)); - let (mut tx, addr, pk) = tx::prepare_tx( + let (mut tx, addr, public_keys) = tx::prepare_tx( client, &mut ctx.wallet, &args.tx, tx, - TxSigningKey::WalletAddress(signer.clone()), + Some(args.voter_address.clone()), + TxSigningKey::WalletAddress(args.voter_address.clone()), #[cfg(not(feature = "mainnet"))] false, ) @@ -1036,13 +1157,27 @@ where client, &mut ctx, &args.tx, - addr, - pk.clone(), + addr.clone(), + &public_keys, &mut tx, ) .await?; - signing::sign_tx(&mut ctx.wallet, &mut tx, &args.tx, &pk) - .await?; + let (account_public_keys_map, threshold) = + signing::aux_signing_data( + client, + addr, + public_keys.clone(), + ) + .await; + signing::sign_tx( + &mut ctx.wallet, + &mut tx, + &args.tx, + &account_public_keys_map, + &public_keys, + threshold, + ) + .await?; tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; Ok(()) } @@ -1068,8 +1203,18 @@ where { let reveal_tx = tx::build_reveal_pk(client, &mut ctx.wallet, args.clone()).await?; - if let Some((mut tx, _, pk)) = reveal_tx { - signing::sign_tx(&mut ctx.wallet, &mut tx, &args.tx, &pk).await?; + if let Some((mut tx, _, public_keys)) = reveal_tx { + let (account_pks_index_map, threshold) = + signing::aux_signing_data(client, None, public_keys.clone()).await; + signing::sign_tx( + &mut ctx.wallet, + &mut tx, + &args.tx, + &account_pks_index_map, + &public_keys, + threshold, + ) + .await?; tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; } Ok(()) @@ -1142,10 +1287,28 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - let (mut tx, addr, pk) = + let (mut tx, addr, public_keys) = tx::build_bond::(client, &mut ctx.wallet, args.clone()).await?; - submit_reveal_aux(client, ctx, &args.tx, addr, pk.clone(), &mut tx).await?; - signing::sign_tx(&mut ctx.wallet, &mut tx, &args.tx, &pk).await?; + submit_reveal_aux( + client, + ctx, + &args.tx, + addr.clone(), + &public_keys, + &mut tx, + ) + .await?; + let (account_public_keys_map, threshold) = + signing::aux_signing_data(client, addr, public_keys.clone()).await; + signing::sign_tx( + &mut ctx.wallet, + &mut tx, + &args.tx, + &account_public_keys_map, + &public_keys, + threshold, + ) + .await?; tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; Ok(()) } @@ -1159,10 +1322,28 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - let (mut tx, addr, pk, latest_withdrawal_pre) = + let (mut tx, addr, public_keys, latest_withdrawal_pre) = tx::build_unbond(client, &mut ctx.wallet, args.clone()).await?; - submit_reveal_aux(client, ctx, &args.tx, addr, pk.clone(), &mut tx).await?; - signing::sign_tx(&mut ctx.wallet, &mut tx, &args.tx, &pk).await?; + submit_reveal_aux( + client, + ctx, + &args.tx, + addr.clone(), + &public_keys, + &mut tx, + ) + .await?; + let (account_public_keys_map, threshold) = + signing::aux_signing_data(client, addr, public_keys.clone()).await; + signing::sign_tx( + &mut ctx.wallet, + &mut tx, + &args.tx, + &account_public_keys_map, + &public_keys, + threshold, + ) + .await?; tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; tx::query_unbonds(client, args.clone(), latest_withdrawal_pre).await?; Ok(()) @@ -1177,11 +1358,28 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - let (mut tx, addr, pk) = + let (mut tx, addr, public_keys) = tx::build_withdraw(client, &mut ctx.wallet, args.clone()).await?; - submit_reveal_aux(client, &mut ctx, &args.tx, addr, pk.clone(), &mut tx) - .await?; - signing::sign_tx(&mut ctx.wallet, &mut tx, &args.tx, &pk).await?; + submit_reveal_aux( + client, + &mut ctx, + &args.tx, + addr.clone(), + &public_keys, + &mut tx, + ) + .await?; + let (account_public_keys_map, threshold) = + signing::aux_signing_data(client, addr, public_keys.clone()).await; + signing::sign_tx( + &mut ctx.wallet, + &mut tx, + &args.tx, + &account_public_keys_map, + &public_keys, + threshold, + ) + .await?; tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; Ok(()) } @@ -1196,12 +1394,29 @@ where C::Error: std::fmt::Display, { let arg = args.clone(); - let (mut tx, addr, pk) = + let (mut tx, addr, public_keys) = tx::build_validator_commission_change(client, &mut ctx.wallet, arg) .await?; - submit_reveal_aux(client, &mut ctx, &args.tx, addr, pk.clone(), &mut tx) - .await?; - signing::sign_tx(&mut ctx.wallet, &mut tx, &args.tx, &pk).await?; + submit_reveal_aux( + client, + &mut ctx, + &args.tx, + addr.clone(), + &public_keys, + &mut tx, + ) + .await?; + let (account_public_keys_map, threshold) = + signing::aux_signing_data(client, addr, public_keys.clone()).await; + signing::sign_tx( + &mut ctx.wallet, + &mut tx, + &args.tx, + &account_public_keys_map, + &public_keys, + threshold, + ) + .await?; tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; Ok(()) } @@ -1217,12 +1432,29 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - let (mut tx, addr, pk) = + let (mut tx, addr, public_keys) = tx::build_unjail_validator(client, &mut ctx.wallet, args.clone()) .await?; - submit_reveal_aux(client, &mut ctx, &args.tx, addr, pk.clone(), &mut tx) - .await?; - signing::sign_tx(&mut ctx.wallet, &mut tx, &args.tx, &pk).await?; + submit_reveal_aux( + client, + &mut ctx, + &args.tx, + addr.clone(), + &public_keys, + &mut tx, + ) + .await?; + let (account_public_keys_map, threshold) = + signing::aux_signing_data(client, addr, public_keys.clone()).await; + signing::sign_tx( + &mut ctx.wallet, + &mut tx, + &args.tx, + &account_public_keys_map, + &public_keys, + threshold, + ) + .await?; tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; Ok(()) } @@ -1275,7 +1507,6 @@ mod test_tx { use masp_primitives::transaction::components::Amount; use namada::ledger::masp::{make_asset_type, MaspAmount}; use namada::types::address::testing::gen_established_address; - use namada::types::storage::DbKeySeg; use namada::types::token::MaspDenom; use super::*; @@ -1283,25 +1514,14 @@ mod test_tx { #[test] fn test_masp_add_amount() { let address_1 = gen_established_address(); - let prefix_1: Key = - DbKeySeg::StringSeg("eth_seg".parse().unwrap()).into(); - let prefix_2: Key = - DbKeySeg::StringSeg("crypto_kitty".parse().unwrap()).into(); let denom_1 = MaspDenom::One; let denom_2 = MaspDenom::Three; let epoch = Epoch::default(); let _masp_amount = MaspAmount::default(); - let asset_base = make_asset_type( - Some(epoch), - &address_1, - &Some(prefix_1.clone()), - denom_1, - ); - let _asset_denom = - make_asset_type(Some(epoch), &address_1, &Some(prefix_1), denom_2); - let _asset_prefix = - make_asset_type(Some(epoch), &address_1, &Some(prefix_2), denom_1); + let asset_base = make_asset_type(Some(epoch), &address_1, denom_1); + let _asset_denom = make_asset_type(Some(epoch), &address_1, denom_2); + let _asset_prefix = make_asset_type(Some(epoch), &address_1, denom_1); let _amount_base = Amount::from_pair(asset_base, 16).expect("Test failed"); diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index fcd57be745..26dd629847 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -22,6 +22,7 @@ use namada::types::key::dkg_session_keys::DkgPublicKey; use namada::types::key::*; use namada::types::time::{DateTimeUtc, DurationSecs}; use namada::types::token::Denomination; +use namada::types::uint::Uint; use namada::types::{storage, token}; /// Genesis configuration file format @@ -45,6 +46,7 @@ pub mod genesis_config { use namada::types::key::*; use namada::types::time::Rfc3339String; use namada::types::token::Denomination; + use namada::types::uint::Uint; use namada::types::{storage, token}; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -122,8 +124,9 @@ pub mod genesis_config { /// Testnet faucet PoW difficulty - defaults to `0` when not set pub faucet_pow_difficulty: Option, #[cfg(not(feature = "mainnet"))] - /// Testnet faucet withdrawal limit - defaults to 1000 NAM when not set - pub faucet_withdrawal_limit: Option, + /// Testnet faucet withdrawal limit - defaults to 1000 tokens when not + /// set + pub faucet_withdrawal_limit: Option, // Initial validator set pub validator: HashMap, // Token accounts present at genesis @@ -264,6 +267,8 @@ pub mod genesis_config { pub implicit_vp: String, /// Expected number of epochs per year pub epochs_per_year: u64, + /// Max signature per transaction + pub max_signatures_per_transaction: u8, /// PoS gain p pub pos_gain_p: Dec, /// PoS gain d @@ -392,27 +397,13 @@ pub mod genesis_config { fn load_token( config: &TokenAccountConfig, - wasm: &HashMap, validators: &HashMap, established_accounts: &HashMap, implicit_accounts: &HashMap, ) -> TokenAccount { - let token_vp_name = config.vp.as_ref().unwrap(); - let token_vp_config = wasm.get(token_vp_name).unwrap(); - TokenAccount { address: Address::decode(config.address.as_ref().unwrap()).unwrap(), denom: config.denom, - vp_code_path: token_vp_config.filename.to_owned(), - vp_sha256: token_vp_config - .sha256 - .clone() - .unwrap_or_else(|| { - eprintln!("Unknown token VP WASM sha256"); - cli::safe_exit(1); - }) - .to_sha256_bytes() - .unwrap(), balances: config .balances .as_ref() @@ -557,7 +548,6 @@ pub mod genesis_config { .expect("Missing native token address"), ) .expect("Invalid address"); - let validators: HashMap = validator .iter() .map(|(name, cfg)| (name.clone(), load_validator(cfg, &wasm))) @@ -579,7 +569,6 @@ pub mod genesis_config { .map(|(_name, cfg)| { load_token( cfg, - &wasm, &validators, &established_accounts, &implicit_accounts, @@ -620,10 +609,13 @@ pub mod genesis_config { implicit_vp_code_path, implicit_vp_sha256, epochs_per_year: parameters.epochs_per_year, + max_signatures_per_transaction: parameters + .max_signatures_per_transaction, pos_gain_p: parameters.pos_gain_p, pos_gain_d: parameters.pos_gain_d, staked_ratio: Dec::zero(), pos_inflation_amount: token::Amount::zero(), + #[cfg(not(feature = "mainnet"))] wrapper_tx_fees: parameters.wrapper_tx_fees, }; @@ -732,7 +724,7 @@ pub struct Genesis { #[cfg(not(feature = "mainnet"))] pub faucet_pow_difficulty: Option, #[cfg(not(feature = "mainnet"))] - pub faucet_withdrawal_limit: Option, + pub faucet_withdrawal_limit: Option, pub validators: Vec, pub token_accounts: Vec, pub established_accounts: Vec, @@ -814,10 +806,6 @@ pub struct TokenAccount { pub address: Address, /// The number of decimal places amounts of this token has pub denom: Denomination, - /// Validity predicate code WASM - pub vp_code_path: String, - /// Expected SHA-256 hash of the validity predicate wasm - pub vp_sha256: [u8; 32], /// Accounts' balances of this token #[derivative(PartialOrd = "ignore", Ord = "ignore")] pub balances: HashMap, @@ -871,6 +859,8 @@ pub struct Parameters { pub implicit_vp_sha256: [u8; 32], /// Expected number of epochs per year (read only) pub epochs_per_year: u64, + /// Maximum amount of signatures per transaction + pub max_signatures_per_transaction: u8, /// PoS gain p (read only) pub pos_gain_p: Dec, /// PoS gain d (read only) @@ -903,7 +893,6 @@ pub fn genesis(num_validators: u64) -> Genesis { use crate::wallet; let vp_implicit_path = "vp_implicit.wasm"; - let vp_token_path = "vp_token.wasm"; let vp_user_path = "vp_user.wasm"; // NOTE When the validator's key changes, tendermint must be reset with @@ -991,12 +980,14 @@ pub fn genesis(num_validators: u64) -> Genesis { tx_whitelist: vec![], implicit_vp_code_path: vp_implicit_path.into(), implicit_vp_sha256: Default::default(), + max_signatures_per_transaction: 15, epochs_per_year: 525_600, /* seconds in yr (60*60*24*365) div seconds * per epoch (60 = min_duration) */ pos_gain_p: Dec::new(1, 1).expect("This can't fail"), pos_gain_d: Dec::new(1, 1).expect("This can't fail"), staked_ratio: Dec::zero(), pos_inflation_amount: token::Amount::zero(), + #[cfg(not(feature = "mainnet"))] wrapper_tx_fees: Some(token::Amount::native_whole(0)), }; let albert = EstablishedAccount { @@ -1081,8 +1072,6 @@ pub fn genesis(num_validators: u64) -> Genesis { .map(|(address, (_, denom))| TokenAccount { address, denom, - vp_code_path: vp_token_path.into(), - vp_sha256: Default::default(), balances: balances.clone(), }) .collect(); diff --git a/apps/src/lib/config/mod.rs b/apps/src/lib/config/mod.rs index 0bcb1b5f61..f6307c6bd1 100644 --- a/apps/src/lib/config/mod.rs +++ b/apps/src/lib/config/mod.rs @@ -283,7 +283,7 @@ impl Config { .and_then(|c| c.merge(config::File::with_name(file_name))) .and_then(|c| { c.merge( - config::Environment::with_prefix("namada").separator("__"), + config::Environment::with_prefix("NAMADA").separator("__"), ) }) .map_err(Error::ReadError)?; diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 3af8ab9e3f..2f178eb47e 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -4,7 +4,6 @@ use std::collections::HashMap; use data_encoding::HEXUPPER; use namada::ledger::parameters::storage as params_storage; -use namada::ledger::pos::types::into_tm_voting_power; use namada::ledger::pos::{namada_proof_of_stake, staking_token_address}; use namada::ledger::storage::EPOCH_SWITCH_BLOCKS_DELAY; use namada::ledger::storage_api::token::credit_tokens; @@ -21,7 +20,7 @@ use namada::types::address::Address; use namada::types::dec::Dec; use namada::types::key::tm_raw_hash_to_string; use namada::types::storage::{BlockHash, BlockResults, Epoch, Header}; -use namada::types::token::{total_supply_key, Amount}; +use namada::types::token::Amount; use namada::types::transaction::protocol::{ ethereum_tx_data_variants, ProtocolTxType, }; @@ -32,7 +31,6 @@ use super::*; use crate::facade::tendermint_proto::abci::{ Misbehavior as Evidence, VoteInfo, }; -use crate::facade::tendermint_proto::crypto::PublicKey as TendermintPublicKey; use crate::node::ledger::shell::stats::InternalStats; impl Shell @@ -105,11 +103,25 @@ where &mut self.wl_storage, current_epoch, current_epoch + pos_params.pipeline_len, - &namada_proof_of_stake::consensus_validator_set_handle(), - &namada_proof_of_stake::below_capacity_validator_set_handle(), + )?; + namada_proof_of_stake::store_total_consensus_stake( + &mut self.wl_storage, + current_epoch, + )?; + namada_proof_of_stake::purge_validator_sets_for_old_epoch( + &mut self.wl_storage, + current_epoch, )?; } + // Invariant: Has to be applied before `record_slashes_from_evidence` + // because it potentially needs to be able to read validator state from + // previous epoch and jailing validator removes the historical state + self.log_block_rewards(&req.votes, height, current_epoch, new_epoch)?; + if new_epoch { + self.apply_inflation(current_epoch)?; + } + // Invariant: This has to be applied after // `copy_validator_sets_and_positions` and before `self.update_epoch`. self.record_slashes_from_evidence(); @@ -201,7 +213,6 @@ where let tx_hash_key = replay_protection::get_tx_hash_key(&tx_hash); self.wl_storage - .storage .delete(&tx_hash_key) .expect("Error while deleting tx hash from storage"); } @@ -221,16 +232,14 @@ where processed_tx.header_hash().0, )); self.wl_storage - .storage - .write(&wrapper_tx_hash_key, vec![]) + .write_bytes(&wrapper_tx_hash_key, vec![]) .expect("Error while writing tx hash to storage"); let inner_tx_hash_key = replay_protection::get_tx_hash_key( &tx.clone().update_header(TxType::Raw).header_hash(), ); self.wl_storage - .storage - .write(&inner_tx_hash_key, vec![]) + .write_bytes(&inner_tx_hash_key, vec![]) .expect("Error while writing tx hash to storage"); #[cfg(not(feature = "mainnet"))] @@ -256,11 +265,7 @@ where match balance.checked_sub(wrapper_fees) { Some(amount) => { self.wl_storage - .storage - .write( - &balance_key, - amount.try_to_vec().unwrap(), - ) + .write(&balance_key, amount) .unwrap(); } None => { @@ -271,12 +276,9 @@ where if reject { // Burn remaining funds self.wl_storage - .storage .write( &balance_key, - Amount::native_whole(0) - .try_to_vec() - .unwrap(), + Amount::native_whole(0), ) .unwrap(); tx_event["info"] = @@ -481,6 +483,7 @@ where ); stats.increment_errored_txs(); + self.wl_storage.drop_tx(); // If transaction type is Decrypted and failed because of // out of gas, remove its hash from storage to allow // rewrapping it @@ -491,15 +494,16 @@ where let tx_hash_key = replay_protection::get_tx_hash_key(&hash); self.wl_storage - .storage .delete(&tx_hash_key) .expect( "Error while deleting tx hash key from storage", ); + // Apply only to remove its hash, + // since all other changes have already been dropped + self.wl_storage.commit_tx(); } } - self.wl_storage.drop_tx(); tx_event["gas_used"] = self .gas_meter .get_current_transaction_gas() @@ -530,42 +534,6 @@ where self.update_eth_oracle(); } - // Read the block proposer of the previously committed block in storage - // (n-1 if we are in the process of finalizing n right now). - match read_last_block_proposer_address(&self.wl_storage)? { - Some(proposer_address) => { - tracing::debug!( - "Found last block proposer: {proposer_address}" - ); - let votes = pos_votes_from_abci(&self.wl_storage, &req.votes); - namada_proof_of_stake::log_block_rewards( - &mut self.wl_storage, - if new_epoch { - current_epoch.prev() - } else { - current_epoch - }, - &proposer_address, - votes, - )?; - } - None => { - if height > BlockHeight::default().next_height() { - tracing::error!( - "Can't find the last block proposer at height {height}" - ); - } else { - tracing::debug!( - "No last block proposer at height {height}" - ); - } - } - } - - if new_epoch { - self.apply_inflation(current_epoch)?; - } - if !req.proposer_address.is_empty() { let tm_raw_hash_string = tm_raw_hash_to_string(req.proposer_address); @@ -634,45 +602,9 @@ where /// changes to the validator sets and consensus parameters fn update_epoch(&mut self, response: &mut shim::response::FinalizeBlock) { // Apply validator set update - let (current_epoch, _gas) = self.wl_storage.storage.get_current_epoch(); - let pos_params = - namada_proof_of_stake::read_pos_params(&self.wl_storage) - .expect("Could not find the PoS parameters"); - // TODO ABCI validator updates on block H affects the validator set - // on block H+2, do we need to update a block earlier? - response.validator_updates = - namada_proof_of_stake::validator_set_update_tendermint( - &self.wl_storage, - &pos_params, - current_epoch, - |update| { - let (consensus_key, power) = match update { - ValidatorSetUpdate::Consensus(ConsensusValidator { - consensus_key, - bonded_stake, - }) => { - let power: i64 = into_tm_voting_power( - pos_params.tm_votes_per_token, - bonded_stake, - ); - (consensus_key, power) - } - ValidatorSetUpdate::Deactivated(consensus_key) => { - // Any validators that have been dropped from the - // consensus set must have voting power set to 0 to - // remove them from the conensus set - let power = 0_i64; - (consensus_key, power) - } - }; - let pub_key = TendermintPublicKey { - sum: Some(key_to_tendermint(&consensus_key).unwrap()), - }; - let pub_key = Some(pub_key); - ValidatorUpdate { pub_key, power } - }, - ) - .expect("Must be able to update validator sets"); + response.validator_updates = self + .get_abci_validator_updates(false) + .expect("Must be able to update validator set"); } /// Calculate the new inflation rate, mint the new tokens to the PoS @@ -707,9 +639,9 @@ where .expect("PoS inflation amount should exist in storage"); // Read from PoS storage let total_tokens = self - .read_storage_key(&total_supply_key(&staking_token_address( - &self.wl_storage, - ))) + .read_storage_key(&token::minted_balance_key( + &staking_token_address(&self.wl_storage), + )) .expect("Total NAM balance should exist in storage"); let pos_locked_supply = read_total_stake(&self.wl_storage, ¶ms, last_epoch)?; @@ -887,6 +819,48 @@ where Ok(()) } + + // Process the proposer and votes in the block to assign their PoS rewards. + fn log_block_rewards( + &mut self, + votes: &[VoteInfo], + height: BlockHeight, + current_epoch: Epoch, + new_epoch: bool, + ) -> Result<()> { + // Read the block proposer of the previously committed block in storage + // (n-1 if we are in the process of finalizing n right now). + match read_last_block_proposer_address(&self.wl_storage)? { + Some(proposer_address) => { + tracing::debug!( + "Found last block proposer: {proposer_address}" + ); + let votes = pos_votes_from_abci(&self.wl_storage, votes); + namada_proof_of_stake::log_block_rewards( + &mut self.wl_storage, + if new_epoch { + current_epoch.prev() + } else { + current_epoch + }, + &proposer_address, + votes, + )?; + } + None => { + if height > BlockHeight::default().next_height() { + tracing::error!( + "Can't find the last block proposer at height {height}" + ); + } else { + tracing::debug!( + "No last block proposer at height {height}" + ); + } + } + } + Ok(()) + } } /// Convert ABCI vote info to PoS vote info. Any info which fails the conversion @@ -1026,6 +1000,85 @@ mod test_finalize_block { FinalizeBlock, ProcessedTx, }; + /// Make a wrapper tx and a processed tx from the wrapped tx that can be + /// added to `FinalizeBlock` request. + fn mk_wrapper_tx( + shell: &TestShell, + keypair: &common::SecretKey, + ) -> (Tx, ProcessedTx) { + let mut wrapper_tx = + Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( + Fee { + amount: MIN_FEE_AMOUNT, + token: shell.wl_storage.storage.native_token.clone(), + }, + keypair.ref_to(), + Epoch(0), + Default::default(), + #[cfg(not(feature = "mainnet"))] + None, + )))); + wrapper_tx.header.chain_id = shell.chain_id.clone(); + wrapper_tx.set_code(Code::new("wasm_code".as_bytes().to_owned())); + wrapper_tx.set_data(Data::new( + "Encrypted transaction data".as_bytes().to_owned(), + )); + wrapper_tx.add_section(Section::Signature(Signature::new( + wrapper_tx.sechashes(), + keypair, + ))); + let tx = wrapper_tx.to_bytes(); + ( + wrapper_tx, + ProcessedTx { + tx, + result: TxResult { + code: ErrorCodes::Ok.into(), + info: "".into(), + }, + }, + ) + } + + /// Make a wrapper tx and a processed tx from the wrapped tx that can be + /// added to `FinalizeBlock` request. + fn mk_decrypted_tx( + shell: &mut TestShell, + keypair: &common::SecretKey, + ) -> ProcessedTx { + let tx_code = TestWasms::TxNoOp.read_bytes(); + let mut outer_tx = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( + Fee { + amount: MIN_FEE_AMOUNT, + token: shell.wl_storage.storage.native_token.clone(), + }, + keypair.ref_to(), + Epoch(0), + Default::default(), + #[cfg(not(feature = "mainnet"))] + None, + )))); + outer_tx.header.chain_id = shell.chain_id.clone(); + outer_tx.set_code(Code::new(tx_code)); + outer_tx.set_data(Data::new( + "Decrypted transaction data".as_bytes().to_owned(), + )); + shell.enqueue_tx(outer_tx.clone()); + outer_tx.update_header(TxType::Decrypted(DecryptedTx::Decrypted { + #[cfg(not(feature = "mainnet"))] + has_valid_pow: false, + })); + outer_tx.decrypt(::G2Affine::prime_subgroup_generator()) + .expect("Test failed"); + ProcessedTx { + tx: outer_tx.to_bytes(), + result: TxResult { + code: ErrorCodes::Ok.into(), + info: "".into(), + }, + } + } + /// Check that if a wrapper tx was rejected by [`process_proposal`], /// check that the correct event is returned. Check that it does /// not appear in the queue of txs to be decrypted @@ -1052,36 +1105,11 @@ mod test_finalize_block { // create some wrapper txs for i in 1u64..5 { - let mut wrapper = - Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( - Fee { - amount: MIN_FEE_AMOUNT, - token: shell.wl_storage.storage.native_token.clone(), - }, - keypair.ref_to(), - Epoch(0), - Default::default(), - #[cfg(not(feature = "mainnet"))] - None, - )))); - wrapper.header.chain_id = shell.chain_id.clone(); - wrapper.set_data(Data::new("wasm_code".as_bytes().to_owned())); - wrapper.set_code(Code::new( - format!("transaction data: {}", i).as_bytes().to_owned(), - )); - wrapper.add_section(Section::Signature(Signature::new( - wrapper.sechashes(), - &keypair, - ))); + let (wrapper, mut processed_tx) = mk_wrapper_tx(&shell, &keypair); if i > 1 { - processed_txs.push(ProcessedTx { - tx: wrapper.to_bytes(), - result: TxResult { - code: u32::try_from(i.rem_euclid(2)) - .expect("Test failed"), - info: "".into(), - }, - }); + processed_tx.result.code = + u32::try_from(i.rem_euclid(2)).unwrap(); + processed_txs.push(processed_tx); } else { shell.enqueue_tx(wrapper.clone()); } @@ -1247,75 +1275,14 @@ mod test_finalize_block { .unwrap(); // create two decrypted txs - let tx_code = TestWasms::TxNoOp.read_bytes(); - for i in 0..2 { - let mut outer_tx = - Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( - Fee { - amount: MIN_FEE_AMOUNT, - token: shell.wl_storage.storage.native_token.clone(), - }, - keypair.ref_to(), - Epoch(0), - Default::default(), - #[cfg(not(feature = "mainnet"))] - None, - )))); - outer_tx.header.chain_id = shell.chain_id.clone(); - outer_tx.set_code(Code::new(tx_code.clone())); - outer_tx.set_data(Data::new( - format!("Decrypted transaction data: {}", i) - .as_bytes() - .to_owned(), - )); - shell.enqueue_tx(outer_tx.clone()); - outer_tx.update_header(TxType::Decrypted(DecryptedTx::Decrypted { - #[cfg(not(feature = "mainnet"))] - has_valid_pow: false, - })); - outer_tx.decrypt(::G2Affine::prime_subgroup_generator()) - .expect("Test failed"); - processed_txs.push(ProcessedTx { - tx: outer_tx.to_bytes(), - result: TxResult { - code: ErrorCodes::Ok.into(), - info: "".into(), - }, - }); + for _ in 0..2 { + processed_txs.push(mk_decrypted_tx(&mut shell, &keypair)); } // create two wrapper txs - for i in 0..2 { - let mut wrapper_tx = - Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( - Fee { - amount: MIN_FEE_AMOUNT, - token: shell.wl_storage.storage.native_token.clone(), - }, - keypair.ref_to(), - Epoch(0), - Default::default(), - #[cfg(not(feature = "mainnet"))] - None, - )))); - wrapper_tx.header.chain_id = shell.chain_id.clone(); - wrapper_tx.set_code(Code::new("wasm_code".as_bytes().to_owned())); - wrapper_tx.set_data(Data::new( - format!("Encrypted transaction data: {}", i) - .as_bytes() - .to_owned(), - )); - wrapper_tx.add_section(Section::Signature(Signature::new( - wrapper_tx.sechashes(), - &keypair, - ))); - valid_txs.push(wrapper_tx.clone()); - processed_txs.push(ProcessedTx { - tx: wrapper_tx.to_bytes(), - result: TxResult { - code: ErrorCodes::Ok.into(), - info: "".into(), - }, - }); + for _ in 0..2 { + let (tx, processed_tx) = mk_wrapper_tx(&shell, &keypair); + valid_txs.push(tx.clone()); + processed_txs.push(processed_tx); } // Put the wrapper txs in front of the decrypted txs processed_txs.rotate_left(2); @@ -1628,10 +1595,12 @@ mod test_finalize_block { let bertha = crate::wallet::defaults::bertha_address(); // add bertha's escrowed `asset` to the pool { - let asset_key = wrapped_erc20s::Keys::from(&asset); - let owner_key = - asset_key.balance(&bridge_pool::BRIDGE_POOL_ADDRESS); - let supply_key = asset_key.supply(); + let token = wrapped_erc20s::token(&asset); + let owner_key = token::balance_key( + &token, + &bridge_pool::BRIDGE_POOL_ADDRESS, + ); + let supply_key = token::minted_balance_key(&token); let amt: Amount = 999_999_u64.into(); shell .wl_storage @@ -1734,6 +1703,21 @@ mod test_finalize_block { shell.wl_storage.storage.next_epoch_min_start_height = BlockHeight(5); shell.wl_storage.storage.next_epoch_min_start_time = DateTimeUtc::now(); + let txs_key = gen_keypair(); + // Add unshielded balance for fee payment + let balance_key = token::balance_key( + &shell.wl_storage.storage.native_token, + &Address::from(&txs_key.ref_to()), + ); + shell + .wl_storage + .storage + .write( + &balance_key, + Amount::native_whole(1000).try_to_vec().unwrap(), + ) + .unwrap(); + // Add a proposal to be executed on next epoch change. let mut add_proposal = |proposal_id, vote| { let validator = shell.mode.get_validator_address().unwrap().clone(); @@ -1829,12 +1813,32 @@ mod test_finalize_block { // Need to supply a proposer address and votes to flow through the // inflation code for _ in 0..20 { + // Add some txs + let mut txs = vec![]; + // create two decrypted txs + for _ in 0..2 { + txs.push(mk_decrypted_tx(&mut shell, &txs_key)); + } + // create two wrapper txs + for _ in 0..2 { + let (_tx, processed_tx) = mk_wrapper_tx(&shell, &txs_key); + txs.push(processed_tx); + } + let req = FinalizeBlock { + txs, proposer_address: proposer_address.clone(), votes: votes.clone(), ..Default::default() }; + // merkle tree root before finalize_block + let root_pre = shell.shell.wl_storage.storage.block.tree.root(); + let _events = shell.finalize_block(req).unwrap(); + + // the merkle tree root should not change after finalize_block + let root_post = shell.shell.wl_storage.storage.block.tree.root(); + assert_eq!(root_pre.0, root_post.0); let new_state = store_block_state(&shell); // The new state must be unchanged itertools::assert_equal( @@ -2226,6 +2230,8 @@ mod test_finalize_block { }, }; shell.enqueue_tx(wrapper_tx); + // merkle tree root before finalize_block + let root_pre = shell.shell.wl_storage.storage.block.tree.root(); let _event = &shell .finalize_block(FinalizeBlock { @@ -2234,6 +2240,10 @@ mod test_finalize_block { }) .expect("Test failed")[0]; + // the merkle tree root should not change after finalize_block + let root_post = shell.shell.wl_storage.storage.block.tree.root(); + assert_eq!(root_pre.0, root_post.0); + // FIXME: uncomment when proper gas metering is in place // // Check inner tx hash has been removed from storage // assert_eq!(event.event_type.to_string(), String::from("applied")); @@ -2406,8 +2416,11 @@ mod test_finalize_block { ); // Advance to the processing epoch - let votes = get_default_true_votes(&shell.wl_storage, Epoch::default()); loop { + let votes = get_default_true_votes( + &shell.wl_storage, + shell.wl_storage.storage.block.epoch, + ); next_block_for_inflation( &mut shell, pkh1.clone(), @@ -2946,10 +2959,14 @@ mod test_finalize_block { total_voting_power: Default::default(), }, ]; + let votes = get_default_true_votes( + &shell.wl_storage, + shell.wl_storage.storage.block.epoch, + ); next_block_for_inflation( &mut shell, pkh1.clone(), - votes.clone(), + votes, Some(misbehaviors), ); assert_eq!(current_epoch.0, 7_u64); diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index 91f48e3e05..41bc88c03b 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -6,9 +6,7 @@ use std::hash::Hash; use namada::core::ledger::testnet_pow; use namada::ledger::eth_bridge::EthBridgeStatus; use namada::ledger::parameters::{self, Parameters}; -use namada::ledger::pos::{ - into_tm_voting_power, staking_token_address, PosParams, -}; +use namada::ledger::pos::{staking_token_address, PosParams}; use namada::ledger::storage::traits::StorageHasher; use namada::ledger::storage::{DBIter, DB}; use namada::ledger::storage_api::token::{ @@ -23,8 +21,6 @@ use namada::types::time::{DateTimeUtc, TimeZone, Utc}; use namada::types::token; use super::*; -use crate::facade::tendermint_proto::abci; -use crate::facade::tendermint_proto::crypto::PublicKey as TendermintPublicKey; use crate::facade::tendermint_proto::google::protobuf; use crate::facade::tower_abci::{request, response}; use crate::wasm_loader; @@ -94,6 +90,7 @@ where implicit_vp_code_path, implicit_vp_sha256, epochs_per_year, + max_signatures_per_transaction, pos_gain_p, pos_gain_d, staked_ratio, @@ -186,6 +183,7 @@ where tx_whitelist, implicit_vp_code_hash, epochs_per_year, + max_signatures_per_transaction, pos_gain_p, pos_gain_d, staked_ratio, @@ -236,10 +234,7 @@ where self.initialize_implicit_accounts(genesis.implicit_accounts); // Initialize genesis token accounts - self.initialize_token_accounts( - genesis.token_accounts, - &implicit_vp_code_path, - ); + self.initialize_token_accounts(genesis.token_accounts); // Initialize genesis validator accounts let staking_token = staking_token_address(&self.wl_storage); @@ -249,18 +244,18 @@ where &implicit_vp_code_path, ); // set the initial validators set - Ok(self.set_initial_validators( + self.set_initial_validators( &staking_token, genesis.validators, &genesis.pos_params, - )) + ) } /// Initialize genesis established accounts fn initialize_established_accounts( &mut self, faucet_pow_difficulty: Option, - faucet_withdrawal_limit: Option, + faucet_withdrawal_limit: Option, accounts: Vec, implicit_vp_code_path: &str, ) -> Result<()> { @@ -296,10 +291,12 @@ where .unwrap(); if let Some(pk) = public_key { - let pk_storage_key = pk_key(&address); - self.wl_storage - .write_bytes(&pk_storage_key, pk.try_to_vec().unwrap()) - .unwrap(); + storage_api::account::set_public_key_at( + &mut self.wl_storage, + &address, + &pk, + 0, + )?; } for (key, value) in storage { @@ -311,8 +308,11 @@ where if vp_code_path == "vp_testnet_faucet.wasm" { let difficulty = faucet_pow_difficulty.unwrap_or_default(); // withdrawal limit defaults to 1000 NAM when not set - let withdrawal_limit = faucet_withdrawal_limit - .unwrap_or_else(|| token::Amount::native_whole(1_000)); + let withdrawal_limit = + faucet_withdrawal_limit.unwrap_or_else(|| { + token::Amount::native_whole(1_000).into() + }); + testnet_pow::init_faucet_storage( &mut self.wl_storage, &address, @@ -332,9 +332,14 @@ where ) { // Initialize genesis implicit for genesis::ImplicitAccount { public_key } in accounts { - let address: Address = (&public_key).into(); - let pk_storage_key = pk_key(&address); - self.wl_storage.write(&pk_storage_key, public_key).unwrap(); + let address: address::Address = (&public_key).into(); + storage_api::account::set_public_key_at( + &mut self.wl_storage, + &address, + &public_key, + 0, + ) + .unwrap(); } } @@ -342,52 +347,16 @@ where fn initialize_token_accounts( &mut self, accounts: Vec, - implicit_vp_code_path: &str, ) { // Initialize genesis token accounts for genesis::TokenAccount { address, denom, - vp_code_path, - vp_sha256, balances, } in accounts { // associate a token with its denomination. - write_denom( - &mut self.wl_storage, - &address, - // TODO: Should we support multi-tokens at genesis? - None, - denom, - ) - .unwrap(); - let vp_code_hash = - read_wasm_hash(&self.wl_storage, vp_code_path.clone()) - .unwrap() - .ok_or(Error::LoadingWasm(format!( - "Unknown vp code path: {}", - implicit_vp_code_path - ))) - .expect("Reading wasms should succeed"); - - // In dev, we don't check the hash - #[cfg(feature = "dev")] - let _ = vp_sha256; - #[cfg(not(feature = "dev"))] - { - assert_eq!( - vp_code_hash.0.as_slice(), - &vp_sha256, - "Invalid token account's VP sha256 hash for {}", - vp_code_path - ); - } - - self.wl_storage - .write_bytes(&Key::validity_predicate(&address), vp_code_hash) - .unwrap(); - + write_denom(&mut self.wl_storage, &address, denom).unwrap(); for (owner, amount) in balances { credit_tokens(&mut self.wl_storage, &address, &owner, amount) .unwrap(); @@ -430,10 +399,13 @@ where .write_bytes(&Key::validity_predicate(addr), vp_code_hash) .expect("Unable to write user VP"); // Validator account key - let pk_key = pk_key(addr); - self.wl_storage - .write(&pk_key, &validator.account_key) - .expect("Unable to set genesis user public key"); + storage_api::account::set_public_key_at( + &mut self.wl_storage, + addr, + &validator.account_key, + 0, + ) + .unwrap(); // Balances // Account balance (tokens not staked in PoS) @@ -464,7 +436,7 @@ where staking_token: &Address, validators: Vec, pos_params: &PosParams, - ) -> response::InitChain { + ) -> Result { let mut response = response::InitChain::default(); // PoS system depends on epoch being initialized. Write the total // genesis staking token balance to storage after @@ -473,20 +445,15 @@ where pos::init_genesis_storage( &mut self.wl_storage, pos_params, - validators - .clone() - .into_iter() - .map(|validator| validator.pos_data), + validators.into_iter().map(|validator| validator.pos_data), current_epoch, ); - let total_nam = - read_total_supply(&self.wl_storage, staking_token).unwrap(); + let total_nam = read_total_supply(&self.wl_storage, staking_token)?; // At this stage in the chain genesis, the PoS address balance is the // same as the number of staked tokens let total_staked_nam = - read_balance(&self.wl_storage, staking_token, &address::POS) - .unwrap(); + read_balance(&self.wl_storage, staking_token, &address::POS)?; tracing::info!( "Genesis total native tokens: {}.", @@ -507,21 +474,12 @@ where ibc::init_genesis_storage(&mut self.wl_storage); // Set the initial validator set - for validator in validators { - let mut abci_validator = abci::ValidatorUpdate::default(); - let consensus_key: common::PublicKey = - validator.pos_data.consensus_key.clone(); - let pub_key = TendermintPublicKey { - sum: Some(key_to_tendermint(&consensus_key).unwrap()), - }; - abci_validator.pub_key = Some(pub_key); - abci_validator.power = into_tm_voting_power( - pos_params.tm_votes_per_token, - validator.pos_data.tokens, - ); - response.validators.push(abci_validator); - } - response + response.validators = self + .get_abci_validator_updates(true) + .expect("Must be able to set genesis validator set"); + debug_assert!(!response.validators.is_empty()); + + Ok(response) } } diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index e1b7f08883..3c818e64c7 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -28,6 +28,7 @@ use namada::ledger::eth_bridge::{EthBridgeQueries, EthereumBridgeConfig}; use namada::ledger::events::log::EventLog; use namada::ledger::events::Event; use namada::ledger::gas::BlockGasMeter; +use namada::ledger::pos::into_tm_voting_power; use namada::ledger::pos::namada_proof_of_stake::types::{ ConsensusValidator, ValidatorSetUpdate, }; @@ -1283,38 +1284,6 @@ where response } - /// Lookup a validator's keypair for their established account from their - /// wallet. If the node is not validator, this function returns None - #[allow(dead_code)] - fn get_account_keypair(&self) -> Option { - let wallet_path = &self.base_dir.join(self.chain_id.as_str()); - let genesis_path = &self - .base_dir - .join(format!("{}.toml", self.chain_id.as_str())); - let mut wallet = crate::wallet::load_or_new_from_genesis( - wallet_path, - genesis::genesis_config::open_genesis_config(genesis_path).unwrap(), - ); - self.mode.get_validator_address().map(|addr| { - let sk: common::SecretKey = self - .wl_storage - .read(&pk_key(addr)) - .expect( - "A validator should have a public key associated with \ - it's established account", - ) - .expect( - "A validator should have a public key associated with \ - it's established account", - ); - let pk = sk.ref_to(); - wallet.find_key_by_pk(&pk, None).expect( - "A validator's established keypair should be stored in its \ - wallet", - ) - }) - } - #[cfg(not(feature = "mainnet"))] /// Check if the tx has a valid PoW solution. Unlike /// `apply_pow_solution_if_valid`, this won't invalidate the solution. @@ -1374,6 +1343,59 @@ where } false } + + fn get_abci_validator_updates( + &self, + is_genesis: bool, + ) -> storage_api::Result> + { + use namada::ledger::pos::namada_proof_of_stake; + + use crate::facade::tendermint_proto::crypto::PublicKey as TendermintPublicKey; + + let (current_epoch, _gas) = self.wl_storage.storage.get_current_epoch(); + let pos_params = + namada_proof_of_stake::read_pos_params(&self.wl_storage) + .expect("Could not find the PoS parameters"); + + let validator_set_update_fn = if is_genesis { + namada_proof_of_stake::genesis_validator_set_tendermint + } else { + namada_proof_of_stake::validator_set_update_tendermint + }; + + validator_set_update_fn( + &self.wl_storage, + &pos_params, + current_epoch, + |update| { + let (consensus_key, power) = match update { + ValidatorSetUpdate::Consensus(ConsensusValidator { + consensus_key, + bonded_stake, + }) => { + let power: i64 = into_tm_voting_power( + pos_params.tm_votes_per_token, + bonded_stake, + ); + (consensus_key, power) + } + ValidatorSetUpdate::Deactivated(consensus_key) => { + // Any validators that have been dropped from the + // consensus set must have voting power set to 0 to + // remove them from the conensus set + let power = 0_i64; + (consensus_key, power) + } + }; + let pub_key = TendermintPublicKey { + sum: Some(key_to_tendermint(&consensus_key).unwrap()), + }; + let pub_key = Some(pub_key); + ValidatorUpdate { pub_key, power } + }, + ) + } } impl<'a, D, H> From<&'a mut Shell> diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index ff3bd60236..3a5b4935db 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -1537,12 +1537,13 @@ mod test_process_proposal { fn test_unsigned_wrapper_rejected() { let (mut shell, _recv, _, _) = test_utils::setup_at_height(3u64); let keypair = gen_keypair(); + let public_key = keypair.ref_to(); let mut outer_tx = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( Fee { amount: Default::default(), token: shell.wl_storage.storage.native_token.clone(), }, - keypair.ref_to(), + public_key, Epoch(0), Default::default(), #[cfg(not(feature = "mainnet"))] @@ -1551,6 +1552,7 @@ mod test_process_proposal { outer_tx.header.chain_id = shell.chain_id.clone(); outer_tx.set_code(Code::new("wasm_code".as_bytes().to_owned())); outer_tx.set_data(Data::new("transaction data".as_bytes().to_owned())); + let tx = outer_tx.to_bytes(); let response = { @@ -1568,12 +1570,14 @@ mod test_process_proposal { } }; + println!("{}", response.result.info); + assert_eq!(response.result.code, u32::from(ErrorCodes::InvalidSig)); assert_eq!( response.result.info, String::from( - "WrapperTx signature verification failed: Transaction doesn't \ - have any data with a signature." + "WrapperTx signature verification failed: The wrapper \ + signature is invalid." ) ); } @@ -1622,8 +1626,8 @@ mod test_process_proposal { panic!("Test failed") }; let expected_error = "WrapperTx signature verification \ - failed: Transaction doesn't have any \ - data with a signature."; + failed: The wrapper signature is \ + invalid."; assert_eq!( response.result.code, u32::from(ErrorCodes::InvalidSig) diff --git a/apps/src/lib/node/ledger/storage/rocksdb.rs b/apps/src/lib/node/ledger/storage/rocksdb.rs index 9b6077f50a..f679ca9232 100644 --- a/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -176,14 +176,6 @@ impl RocksDB { .ok_or(Error::DBError("No {cf_name} column family".to_string())) } - fn flush(&self, wait: bool) -> Result<()> { - let mut flush_opts = FlushOptions::default(); - flush_opts.set_wait(wait); - self.0 - .flush_opt(&flush_opts) - .map_err(|e| Error::DBError(e.into_string())) - } - /// Persist the diff of an account subspace key-val under the height where /// it was changed. fn write_subspace_diff( @@ -512,7 +504,7 @@ impl DB for RocksDB { .map_err(|e| Error::DBError(e.into_string())) } - fn read_last_block(&mut self) -> Result> { + fn read_last_block(&self) -> Result> { // Block height let state_cf = self.get_column_family(STATE_CF)?; let height: BlockHeight = match self @@ -730,8 +722,8 @@ impl DB for RocksDB { } } - fn write_block( - &mut self, + fn add_block_to_batch( + &self, state: BlockStateWrite, batch: &mut Self::WriteBatch, is_full_commit: bool, @@ -1543,7 +1535,7 @@ mod test { ) .unwrap(); - write_block(&mut db, &mut batch, BlockHeight::default()).unwrap(); + add_block_to_batch(&db, &mut batch, BlockHeight::default()).unwrap(); db.exec_batch(batch.0).unwrap(); let _state = db @@ -1734,7 +1726,7 @@ mod test { ) .unwrap(); - write_block(&mut db, &mut batch, height_0).unwrap(); + add_block_to_batch(&db, &mut batch, height_0).unwrap(); db.exec_batch(batch.0).unwrap(); // Write second block @@ -1754,7 +1746,7 @@ mod test { db.batch_delete_subspace_val(&mut batch, height_1, &delete_key) .unwrap(); - write_block(&mut db, &mut batch, height_1).unwrap(); + add_block_to_batch(&db, &mut batch, height_1).unwrap(); db.exec_batch(batch.0).unwrap(); // Check that the values are as expected from second block @@ -1778,8 +1770,8 @@ mod test { } /// A test helper to write a block - fn write_block( - db: &mut RocksDB, + fn add_block_to_batch( + db: &RocksDB, batch: &mut RocksDBWriteBatch, height: BlockHeight, ) -> Result<()> { @@ -1814,6 +1806,6 @@ mod test { eth_events_queue: ð_events_queue, }; - db.write_block(block, batch, true) + db.add_block_to_batch(block, batch, true) } } diff --git a/apps/src/lib/wallet/pre_genesis.rs b/apps/src/lib/wallet/pre_genesis.rs index a0ec978887..3b05bb214f 100644 --- a/apps/src/lib/wallet/pre_genesis.rs +++ b/apps/src/lib/wallet/pre_genesis.rs @@ -2,7 +2,7 @@ use std::fs; use std::path::{Path, PathBuf}; use ark_serialize::{Read, Write}; -use file_lock::{FileLock, FileOptions}; +use fd_lock::RwLock; use namada::ledger::wallet::pre_genesis::{ ReadError, ValidatorStore, ValidatorWallet, }; @@ -36,10 +36,11 @@ pub fn gen_and_store( let wallet_dir = wallet_path.parent().unwrap(); fs::create_dir_all(wallet_dir)?; // Write the file - let options = FileOptions::new().create(true).write(true).truncate(true); - let mut filelock = - FileLock::lock(wallet_path.to_str().unwrap(), true, options)?; - filelock.file.write_all(&data)?; + 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)?; Ok(validator) } @@ -47,59 +48,60 @@ pub fn gen_and_store( /// from a TOML file. pub fn load(store_dir: &Path) -> Result { let wallet_file = validator_file_name(store_dir); - match FileLock::lock( - wallet_file.to_str().unwrap(), - true, - FileOptions::new().read(true).write(false), - ) { - Ok(mut filelock) => { - let mut store = Vec::::new(); - filelock.file.read_to_end(&mut store).map_err(|err| { - ReadError::ReadWallet( - store_dir.to_str().unwrap().into(), - err.to_string(), - ) - })?; - let store = - ValidatorStore::decode(store).map_err(ReadError::Decode)?; + let mut options = fs::OpenOptions::new(); + options.read(true).write(false); + let lock = RwLock::new(options.open(&wallet_file).map_err(|err| { + ReadError::ReadWallet( + wallet_file.to_string_lossy().into_owned(), + err.to_string(), + ) + })?); + let guard = lock.read().map_err(|err| { + ReadError::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| { + ReadError::ReadWallet( + store_dir.to_str().unwrap().into(), + err.to_string(), + ) + })?; + let store = ValidatorStore::decode(store).map_err(ReadError::Decode)?; - let password = if store.account_key.is_encrypted() - || store.consensus_key.is_encrypted() - || store.account_key.is_encrypted() - { - Some(CliWalletUtils::read_decryption_password()) - } else { - None - }; + let password = if store.account_key.is_encrypted() + || store.consensus_key.is_encrypted() + || store.account_key.is_encrypted() + { + Some(CliWalletUtils::read_decryption_password()) + } else { + None + }; - let account_key = store - .account_key - .get::(true, password.clone())?; - let consensus_key = store - .consensus_key - .get::(true, password.clone())?; - let eth_cold_key = store - .eth_cold_key - .get::(true, password.clone())?; - let eth_hot_key = store.validator_keys.eth_bridge_keypair.clone(); - let tendermint_node_key = store - .tendermint_node_key - .get::(true, password)?; + let account_key = store + .account_key + .get::(true, password.clone())?; + let consensus_key = store + .consensus_key + .get::(true, password.clone())?; + let eth_cold_key = store + .eth_cold_key + .get::(true, password.clone())?; + let eth_hot_key = store.validator_keys.eth_bridge_keypair.clone(); + let tendermint_node_key = store + .tendermint_node_key + .get::(true, password)?; - Ok(ValidatorWallet { - store, - account_key, - consensus_key, - eth_cold_key, - eth_hot_key, - tendermint_node_key, - }) - } - Err(err) => Err(ReadError::ReadWallet( - wallet_file.to_string_lossy().into_owned(), - err.to_string(), - )), - } + Ok(ValidatorWallet { + store, + account_key, + consensus_key, + eth_cold_key, + eth_hot_key, + tendermint_node_key, + }) } /// Generate a new [`ValidatorWallet`] with required pre-genesis keys. Will diff --git a/apps/src/lib/wallet/store.rs b/apps/src/lib/wallet/store.rs index 925e9c6bf9..6ae0d023d9 100644 --- a/apps/src/lib/wallet/store.rs +++ b/apps/src/lib/wallet/store.rs @@ -7,7 +7,7 @@ use std::str::FromStr; use ark_std::rand::prelude::*; use ark_std::rand::SeedableRng; -use file_lock::{FileLock, FileOptions}; +use fd_lock::RwLock; #[cfg(not(feature = "dev"))] use namada::ledger::wallet::store::AddressVpType; #[cfg(feature = "dev")] @@ -48,10 +48,11 @@ pub fn save(store: &Store, store_dir: &Path) -> std::io::Result<()> { let wallet_dir = wallet_path.parent().unwrap(); fs::create_dir_all(wallet_dir)?; // Write the file - let options = FileOptions::new().create(true).write(true).truncate(true); - let mut filelock = - FileLock::lock(wallet_path.to_str().unwrap(), true, options)?; - filelock.file.write_all(&data) + 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. @@ -88,26 +89,28 @@ pub fn load_or_new_from_genesis( /// Attempt to load the store file. pub fn load(store_dir: &Path) -> Result { let wallet_file = wallet_file(store_dir); - match FileLock::lock( - wallet_file.to_str().unwrap(), - true, - FileOptions::new().read(true).write(false), - ) { - Ok(mut filelock) => { - let mut store = Vec::::new(); - filelock.file.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) - } - Err(err) => Err(LoadStoreError::ReadWallet( + 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) } /// Add addresses from a genesis configuration. diff --git a/core/src/ledger/eth_bridge/storage/mod.rs b/core/src/ledger/eth_bridge/storage/mod.rs index 958b002af0..0906be3d5d 100644 --- a/core/src/ledger/eth_bridge/storage/mod.rs +++ b/core/src/ledger/eth_bridge/storage/mod.rs @@ -29,6 +29,7 @@ pub fn escrow_key(nam_addr: &Address) -> Key { pub fn is_eth_bridge_key(nam_addr: &Address, key: &Key) -> bool { key == &escrow_key(nam_addr) || matches!(key.segments.get(0), Some(first_segment) if first_segment == &ADDRESS.to_db_key()) + || wrapped_erc20s::has_erc20_segment(key) } /// A key for storing the active / inactive status @@ -62,6 +63,7 @@ mod test { use super::*; use crate::types::address; use crate::types::address::nam; + use crate::types::ethereum_events::testing::arbitrary_eth_address; #[test] fn test_is_eth_bridge_key_returns_true_for_eth_bridge_address() { @@ -77,6 +79,17 @@ mod test { assert!(is_eth_bridge_key(&nam(), &key)); } + #[test] + fn test_is_eth_bridge_key_returns_true_for_eth_bridge_balance_key() { + let eth_addr = arbitrary_eth_address(); + let token = address::Address::Internal( + address::InternalAddress::Erc20(eth_addr), + ); + let key = + balance_key(&token, &address::testing::established_address_1()); + assert!(is_eth_bridge_key(&nam(), &key)); + } + #[test] fn test_is_eth_bridge_key_returns_false_for_different_address() { let key = @@ -92,4 +105,11 @@ mod test { .expect("Could not set up test"); assert!(!is_eth_bridge_key(&nam(), &key)); } + + #[test] + fn test_is_eth_bridge_key_returns_false_for_non_eth_bridge_balance_key() { + let key = + balance_key(&nam(), &address::testing::established_address_1()); + assert!(!is_eth_bridge_key(&nam(), &key)); + } } diff --git a/core/src/ledger/eth_bridge/storage/wrapped_erc20s.rs b/core/src/ledger/eth_bridge/storage/wrapped_erc20s.rs index 6d2f6de4da..0062dd50c9 100644 --- a/core/src/ledger/eth_bridge/storage/wrapped_erc20s.rs +++ b/core/src/ledger/eth_bridge/storage/wrapped_erc20s.rs @@ -1,68 +1,17 @@ //! Functionality for accessing the multitoken subspace -use std::str::FromStr; use eyre::eyre; -use crate::types::address::Address; +use crate::types::address::{Address, InternalAddress}; use crate::types::ethereum_events::EthAddress; -use crate::types::storage::{self, DbKeySeg, KeySeg}; - -#[allow(missing_docs)] -pub const MULTITOKEN_KEY_SEGMENT: &str = "ERC20"; - -/// Get the key prefix corresponding to the storage subspace that holds wrapped -/// ERC20 tokens -pub fn prefix() -> storage::Key { - super::prefix() - .push(&MULTITOKEN_KEY_SEGMENT.to_owned()) - .expect("should always be able to construct this key") -} - -const BALANCE_KEY_SEGMENT: &str = "balance"; -const SUPPLY_KEY_SEGMENT: &str = "supply"; - -/// Generator for the keys under which details of an ERC20 token are stored -pub struct Keys { - /// The prefix of keys under which the details for a specific ERC20 token - /// are stored - prefix: storage::Key, -} - -impl Keys { - /// Get the `balance` key for a specific owner - there should be a - /// [`crate::types::token::Amount`] stored here - pub fn balance(&self, owner: &Address) -> storage::Key { - self.prefix - .push(&BALANCE_KEY_SEGMENT.to_owned()) - .expect("should always be able to construct this key") - .push(&owner.to_db_key()) - .expect("should always be able to construct this key") - } - - /// Get the `supply` key - there should be a - /// [`crate::types::token::Amount`] stored here - pub fn supply(&self) -> storage::Key { - self.prefix - .push(&SUPPLY_KEY_SEGMENT.to_owned()) - .expect("should always be able to construct this key") - } -} - -impl From<&EthAddress> for Keys { - fn from(address: &EthAddress) -> Self { - Keys { - prefix: prefix() - .push(&address.to_canonical()) - .expect("should always be able to construct this key"), - } - } -} - -/// Construct a sub-prefix from an ERC20 address. -pub fn sub_prefix(address: &EthAddress) -> storage::Key { - storage::Key::from(MULTITOKEN_KEY_SEGMENT.to_owned().to_db_key()) - .push(&address.to_db_key()) - .expect("should always be able to construct this key") +use crate::types::storage::{self, DbKeySeg}; +use crate::types::token::{ + balance_key, minted_balance_key, MINTED_STORAGE_KEY, +}; + +/// Construct a token address from an ERC20 address. +pub fn token(address: &EthAddress) -> Address { + Address::Internal(InternalAddress::Erc20(*address)) } /// Represents the type of a key relating to a wrapped ERC20 @@ -88,18 +37,21 @@ pub struct Key { impl From<&Key> for storage::Key { fn from(mt_key: &Key) -> Self { - let keys = Keys::from(&mt_key.asset); + let token = token(&mt_key.asset); match &mt_key.suffix { - KeyType::Balance { owner } => keys.balance(owner), - KeyType::Supply => keys.supply(), + KeyType::Balance { owner } => balance_key(&token, owner), + KeyType::Supply => minted_balance_key(&token), } } } -fn has_erc20_segment(key: &storage::Key) -> bool { +/// Returns true if the given key has an ERC20 token +pub fn has_erc20_segment(key: &storage::Key) -> bool { matches!( key.segments.get(1), - Some(segment) if segment == &DbKeySeg::StringSeg(MULTITOKEN_KEY_SEGMENT.to_owned()), + Some(DbKeySeg::AddressSeg(Address::Internal( + InternalAddress::Erc20(_addr), + ))) ) } @@ -116,52 +68,41 @@ impl TryFrom<(&Address, &storage::Key)> for Key { return Err(eyre!("key does not have ERC20 segment")); } - let asset = - if let Some(DbKeySeg::StringSeg(segment)) = key.segments.get(2) { - EthAddress::from_str(segment)? - } else { - return Err(eyre!( - "key has an incorrect segment at index #2, expected an \ - Ethereum address" - )); - }; - - let segment_3 = - if let Some(DbKeySeg::StringSeg(segment)) = key.segments.get(3) { - segment.to_owned() - } else { - return Err(eyre!( - "key has an incorrect segment at index #3, expected a \ - string segment" - )); - }; - - match segment_3.as_str() { - SUPPLY_KEY_SEGMENT => { - let supply_key = Key { + let asset = if let Some(DbKeySeg::AddressSeg(Address::Internal( + InternalAddress::Erc20(addr), + ))) = key.segments.get(1) + { + *addr + } else { + return Err(eyre!( + "key has an incorrect segment at index #2, expected an \ + Ethereum address" + )); + }; + + match key.segments.get(3) { + Some(DbKeySeg::AddressSeg(owner)) => { + let balance_key = Key { asset, - suffix: KeyType::Supply, + suffix: KeyType::Balance { + owner: owner.clone(), + }, }; - Ok(supply_key) + Ok(balance_key) } - BALANCE_KEY_SEGMENT => { - let owner = if let Some(DbKeySeg::AddressSeg(address)) = - key.segments.get(4) - { - address.to_owned() - } else { - return Err(eyre!( - "key has an incorrect segment at index #4, expected \ - an address segment" - )); - }; - let balance_key = Key { + Some(DbKeySeg::StringSeg(segment)) + if segment == MINTED_STORAGE_KEY => + { + let supply_key = Key { asset, - suffix: KeyType::Balance { owner }, + suffix: KeyType::Supply, }; - Ok(balance_key) + Ok(supply_key) } - _ => Err(eyre!("key has unrecognized string segment at index #3")), + _ => Err(eyre!( + "key has an incorrect segment at index #3, expected a string \ + segment" + )), } } } @@ -174,97 +115,77 @@ mod test { use super::*; use crate::ledger::eth_bridge::ADDRESS; use crate::types::address::{nam, Address}; - use crate::types::ethereum_events::testing::{ - DAI_ERC20_ETH_ADDRESS, DAI_ERC20_ETH_ADDRESS_CHECKSUMMED, - }; + use crate::types::ethereum_events::testing::DAI_ERC20_ETH_ADDRESS; use crate::types::storage::DbKeySeg; + use crate::types::token::BALANCE_STORAGE_KEY; + const MULTITOKEN_ADDRESS: Address = + Address::Internal(InternalAddress::Multitoken); const ARBITRARY_OWNER_ADDRESS: &str = "atest1d9khqw36x9zyxwfhgfpygv2pgc65gse4gy6rjs34gfzr2v69gy6y23zpggurjv2yx5m52sesu6r4y4"; - #[test] - fn test_prefix() { - assert_matches!( - &prefix().segments[..], - [ - DbKeySeg::AddressSeg(multitoken_addr), - DbKeySeg::StringSeg(multitoken_path), - ] if multitoken_addr == &ADDRESS && - multitoken_path == MULTITOKEN_KEY_SEGMENT - ) - } - - #[test] - fn test_keys_from_eth_address() { - let keys: Keys = (&DAI_ERC20_ETH_ADDRESS).into(); - assert_matches!( - &keys.prefix.segments[..], - [ - DbKeySeg::AddressSeg(multitoken_addr), - DbKeySeg::StringSeg(multitoken_path), - DbKeySeg::StringSeg(token_id), - ] if multitoken_addr == &ADDRESS && - multitoken_path == MULTITOKEN_KEY_SEGMENT && - token_id == &DAI_ERC20_ETH_ADDRESS_CHECKSUMMED.to_ascii_lowercase() - ) + fn dai_erc20_token() -> Address { + Address::Internal(InternalAddress::Erc20(DAI_ERC20_ETH_ADDRESS)) } #[test] fn test_keys_balance() { - let keys: Keys = (&DAI_ERC20_ETH_ADDRESS).into(); - let key = - keys.balance(&Address::from_str(ARBITRARY_OWNER_ADDRESS).unwrap()); + let token = token(&DAI_ERC20_ETH_ADDRESS); + let key = balance_key( + &token, + &Address::from_str(ARBITRARY_OWNER_ADDRESS).unwrap(), + ); assert_matches!( &key.segments[..], [ DbKeySeg::AddressSeg(multitoken_addr), - DbKeySeg::StringSeg(multitoken_path), - DbKeySeg::StringSeg(token_id), + DbKeySeg::AddressSeg(token_addr), DbKeySeg::StringSeg(balance_key_seg), DbKeySeg::AddressSeg(owner_addr), - ] if multitoken_addr == &ADDRESS && - multitoken_path == MULTITOKEN_KEY_SEGMENT && - token_id == &DAI_ERC20_ETH_ADDRESS_CHECKSUMMED.to_ascii_lowercase() && - balance_key_seg == BALANCE_KEY_SEGMENT && + ] if multitoken_addr == &MULTITOKEN_ADDRESS && + token_addr == &dai_erc20_token() && + balance_key_seg == BALANCE_STORAGE_KEY && owner_addr == &Address::decode(ARBITRARY_OWNER_ADDRESS).unwrap() ) } #[test] fn test_keys_balance_to_string() { - let keys: Keys = (&DAI_ERC20_ETH_ADDRESS).into(); - let key = - keys.balance(&Address::from_str(ARBITRARY_OWNER_ADDRESS).unwrap()); + let token = token(&DAI_ERC20_ETH_ADDRESS); + let key = balance_key( + &token, + &Address::from_str(ARBITRARY_OWNER_ADDRESS).unwrap(), + ); assert_eq!( - "#atest1v9hx7w36g42ysgzzwf5kgem9ypqkgerjv4ehxgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpq8f99ew/ERC20/0x6b175474e89094c44da98b954eedeac495271d0f/balance/#atest1d9khqw36x9zyxwfhgfpygv2pgc65gse4gy6rjs34gfzr2v69gy6y23zpggurjv2yx5m52sesu6r4y4", + "#atest1v9hx7w36f46kcarfw3hkketwyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpq4w0mck/#atest1v46xsw36xe3rzde4xsmngefc8ycrjdrrxs6xgcfe8p3rjdf5v4jkgetpvv6rjdfjxuckgvrxqhdj5x/balance/#atest1d9khqw36x9zyxwfhgfpygv2pgc65gse4gy6rjs34gfzr2v69gy6y23zpggurjv2yx5m52sesu6r4y4", key.to_string() ) } #[test] fn test_keys_supply() { - let keys: Keys = (&DAI_ERC20_ETH_ADDRESS).into(); - let key = keys.supply(); + let token = token(&DAI_ERC20_ETH_ADDRESS); + let key = minted_balance_key(&token); assert_matches!( &key.segments[..], [ DbKeySeg::AddressSeg(multitoken_addr), - DbKeySeg::StringSeg(multitoken_path), - DbKeySeg::StringSeg(token_id), + DbKeySeg::AddressSeg(token_addr), + DbKeySeg::StringSeg(balance_key_seg), DbKeySeg::StringSeg(supply_key_seg), - ] if multitoken_addr == &ADDRESS && - multitoken_path == MULTITOKEN_KEY_SEGMENT && - token_id == &DAI_ERC20_ETH_ADDRESS_CHECKSUMMED.to_ascii_lowercase() && - supply_key_seg == SUPPLY_KEY_SEGMENT + ] if multitoken_addr == &MULTITOKEN_ADDRESS && + token_addr == &dai_erc20_token() && + balance_key_seg == BALANCE_STORAGE_KEY && + supply_key_seg == MINTED_STORAGE_KEY ) } #[test] fn test_keys_supply_to_string() { - let keys: Keys = (&DAI_ERC20_ETH_ADDRESS).into(); - let key = keys.supply(); + let token = token(&DAI_ERC20_ETH_ADDRESS); + let key = minted_balance_key(&token); assert_eq!( - "#atest1v9hx7w36g42ysgzzwf5kgem9ypqkgerjv4ehxgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpq8f99ew/ERC20/0x6b175474e89094c44da98b954eedeac495271d0f/supply", + "#atest1v9hx7w36f46kcarfw3hkketwyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpq4w0mck/#atest1v46xsw36xe3rzde4xsmngefc8ycrjdrrxs6xgcfe8p3rjdf5v4jkgetpvv6rjdfjxuckgvrxqhdj5x/balance/minted", key.to_string(), ) } @@ -281,13 +202,13 @@ mod test { &key.segments[..], [ DbKeySeg::AddressSeg(multitoken_addr), - DbKeySeg::StringSeg(multitoken_path), - DbKeySeg::StringSeg(token_id), + DbKeySeg::AddressSeg(token_addr), + DbKeySeg::StringSeg(balance_key_seg), DbKeySeg::StringSeg(supply_key_seg), - ] if multitoken_addr == &ADDRESS && - multitoken_path == MULTITOKEN_KEY_SEGMENT && - token_id == &DAI_ERC20_ETH_ADDRESS_CHECKSUMMED.to_ascii_lowercase() && - supply_key_seg == SUPPLY_KEY_SEGMENT + ] if multitoken_addr == &MULTITOKEN_ADDRESS && + token_addr == &dai_erc20_token() && + balance_key_seg == BALANCE_STORAGE_KEY && + supply_key_seg == MINTED_STORAGE_KEY ); // balance key @@ -302,14 +223,12 @@ mod test { &key.segments[..], [ DbKeySeg::AddressSeg(multitoken_addr), - DbKeySeg::StringSeg(multitoken_path), - DbKeySeg::StringSeg(token_id), + DbKeySeg::AddressSeg(token_addr), DbKeySeg::StringSeg(balance_key_seg), DbKeySeg::AddressSeg(owner_addr), - ] if multitoken_addr == &ADDRESS && - multitoken_path == MULTITOKEN_KEY_SEGMENT && - token_id == &DAI_ERC20_ETH_ADDRESS_CHECKSUMMED.to_ascii_lowercase() && - balance_key_seg == BALANCE_KEY_SEGMENT && + ] if multitoken_addr == &MULTITOKEN_ADDRESS && + token_addr == &dai_erc20_token() && + balance_key_seg == BALANCE_STORAGE_KEY && owner_addr == &Address::decode(ARBITRARY_OWNER_ADDRESS).unwrap() ); } @@ -318,9 +237,10 @@ mod test { fn test_try_from_key_for_multitoken_key_supply() { // supply key let key = storage::Key::from_str(&format!( - "#{}/ERC20/{}/supply", - ADDRESS, - DAI_ERC20_ETH_ADDRESS_CHECKSUMMED.to_ascii_lowercase(), + "#{}/#{}/balance/{}", + MULTITOKEN_ADDRESS, + dai_erc20_token(), + MINTED_STORAGE_KEY, )) .expect("Should be able to construct key for test"); @@ -344,9 +264,9 @@ mod test { fn test_try_from_key_for_multitoken_key_balance() { // supply key let key = storage::Key::from_str(&format!( - "#{}/ERC20/{}/balance/#{}", + "#{}/#{}/balance/#{}", ADDRESS, - DAI_ERC20_ETH_ADDRESS_CHECKSUMMED.to_ascii_lowercase(), + dai_erc20_token(), ARBITRARY_OWNER_ADDRESS )) .expect("Should be able to construct key for test"); @@ -375,9 +295,9 @@ mod test { #[test] fn test_has_erc20_segment() { let key = storage::Key::from_str(&format!( - "#{}/ERC20/{}/balance/#{}", + "#{}/#{}/balance/#{}", ADDRESS, - DAI_ERC20_ETH_ADDRESS_CHECKSUMMED.to_ascii_lowercase(), + dai_erc20_token(), ARBITRARY_OWNER_ADDRESS )) .expect("Should be able to construct key for test"); @@ -385,16 +305,21 @@ mod test { assert!(has_erc20_segment(&key)); let key = storage::Key::from_str(&format!( - "#{}/ERC20/{}/supply", + "#{}/#{}/balance/{}", ADDRESS, - DAI_ERC20_ETH_ADDRESS_CHECKSUMMED.to_ascii_lowercase(), + dai_erc20_token(), + MINTED_STORAGE_KEY, )) .expect("Should be able to construct key for test"); assert!(has_erc20_segment(&key)); - let key = storage::Key::from_str(&format!("#{}/ERC20", ADDRESS)) - .expect("Should be able to construct key for test"); + let key = storage::Key::from_str(&format!( + "#{}/#{}", + MULTITOKEN_ADDRESS, + dai_erc20_token() + )) + .expect("Should be able to construct key for test"); assert!(has_erc20_segment(&key)); } diff --git a/core/src/ledger/ibc/actions.rs b/core/src/ledger/ibc/actions.rs deleted file mode 100644 index e925a98d92..0000000000 --- a/core/src/ledger/ibc/actions.rs +++ /dev/null @@ -1,1605 +0,0 @@ -//! Functions to handle IBC modules - -use std::str::FromStr; - -use sha2::Digest; -use thiserror::Error; - -use crate::ibc::applications::ics20_fungible_token_transfer::msgs::transfer::MsgTransfer; -use crate::ibc::clients::ics07_tendermint::consensus_state::ConsensusState as TmConsensusState; -use crate::ibc::core::ics02_client::client_consensus::{ - AnyConsensusState, ConsensusState, -}; -use crate::ibc::core::ics02_client::client_state::{ - AnyClientState, ClientState, -}; -use crate::ibc::core::ics02_client::client_type::ClientType; -use crate::ibc::core::ics02_client::events::{ - Attributes as ClientAttributes, CreateClient, UpdateClient, UpgradeClient, -}; -use crate::ibc::core::ics02_client::header::{AnyHeader, Header}; -use crate::ibc::core::ics02_client::height::Height; -use crate::ibc::core::ics02_client::msgs::create_client::MsgCreateAnyClient; -use crate::ibc::core::ics02_client::msgs::update_client::MsgUpdateAnyClient; -use crate::ibc::core::ics02_client::msgs::upgrade_client::MsgUpgradeAnyClient; -use crate::ibc::core::ics02_client::msgs::ClientMsg; -use crate::ibc::core::ics03_connection::connection::{ - ConnectionEnd, Counterparty as ConnCounterparty, State as ConnState, -}; -use crate::ibc::core::ics03_connection::events::{ - Attributes as ConnectionAttributes, OpenAck as ConnOpenAck, - OpenConfirm as ConnOpenConfirm, OpenInit as ConnOpenInit, - OpenTry as ConnOpenTry, -}; -use crate::ibc::core::ics03_connection::msgs::conn_open_ack::MsgConnectionOpenAck; -use crate::ibc::core::ics03_connection::msgs::conn_open_confirm::MsgConnectionOpenConfirm; -use crate::ibc::core::ics03_connection::msgs::conn_open_init::MsgConnectionOpenInit; -use crate::ibc::core::ics03_connection::msgs::conn_open_try::MsgConnectionOpenTry; -use crate::ibc::core::ics03_connection::msgs::ConnectionMsg; -use crate::ibc::core::ics04_channel::channel::{ - ChannelEnd, Counterparty as ChanCounterparty, Order, State as ChanState, -}; -use crate::ibc::core::ics04_channel::commitment::PacketCommitment; -use crate::ibc::core::ics04_channel::events::{ - AcknowledgePacket, CloseConfirm as ChanCloseConfirm, - CloseInit as ChanCloseInit, OpenAck as ChanOpenAck, - OpenConfirm as ChanOpenConfirm, OpenInit as ChanOpenInit, - OpenTry as ChanOpenTry, SendPacket, TimeoutPacket, WriteAcknowledgement, -}; -use crate::ibc::core::ics04_channel::msgs::acknowledgement::MsgAcknowledgement; -use crate::ibc::core::ics04_channel::msgs::chan_close_confirm::MsgChannelCloseConfirm; -use crate::ibc::core::ics04_channel::msgs::chan_close_init::MsgChannelCloseInit; -use crate::ibc::core::ics04_channel::msgs::chan_open_ack::MsgChannelOpenAck; -use crate::ibc::core::ics04_channel::msgs::chan_open_confirm::MsgChannelOpenConfirm; -use crate::ibc::core::ics04_channel::msgs::chan_open_init::MsgChannelOpenInit; -use crate::ibc::core::ics04_channel::msgs::chan_open_try::MsgChannelOpenTry; -use crate::ibc::core::ics04_channel::msgs::recv_packet::MsgRecvPacket; -use crate::ibc::core::ics04_channel::msgs::timeout::MsgTimeout; -use crate::ibc::core::ics04_channel::msgs::timeout_on_close::MsgTimeoutOnClose; -use crate::ibc::core::ics04_channel::msgs::{ChannelMsg, PacketMsg}; -use crate::ibc::core::ics04_channel::packet::{Packet, Sequence}; -use crate::ibc::core::ics23_commitment::commitment::CommitmentPrefix; -use crate::ibc::core::ics24_host::error::ValidationError as Ics24Error; -use crate::ibc::core::ics24_host::identifier::{ - ChannelId, ClientId, ConnectionId, PortChannelId, PortId, -}; -use crate::ibc::core::ics26_routing::msgs::Ics26Envelope; -use crate::ibc::events::IbcEvent; -#[cfg(any(feature = "ibc-mocks-abcipp", feature = "ibc-mocks"))] -use crate::ibc::mock::client_state::{MockClientState, MockConsensusState}; -use crate::ibc::timestamp::Timestamp; -use crate::ledger::ibc::data::{ - Error as IbcDataError, FungibleTokenPacketData, IbcMessage, PacketAck, - PacketReceipt, -}; -use crate::ledger::ibc::storage; -use crate::ledger::storage_api; -use crate::tendermint::Time; -use crate::tendermint_proto::{Error as ProtoError, Protobuf}; -use crate::types::address::{Address, InternalAddress}; -use crate::types::ibc::IbcEvent as NamadaIbcEvent; -use crate::types::storage::{BlockHeight, Key}; -use crate::types::time::Rfc3339String; -use crate::types::token::{self, Amount}; - -const COMMITMENT_PREFIX: &[u8] = b"ibc"; - -#[allow(missing_docs)] -#[derive(Error, Debug)] -pub enum Error { - #[error("Invalid client error: {0}")] - ClientId(Ics24Error), - #[error("Invalid port error: {0}")] - PortId(Ics24Error), - #[error("Updating a client error: {0}")] - ClientUpdate(String), - #[error("IBC data error: {0}")] - IbcData(IbcDataError), - #[error("Decoding IBC data error: {0}")] - Decoding(ProtoError), - #[error("Client error: {0}")] - Client(String), - #[error("Connection error: {0}")] - Connection(String), - #[error("Channel error: {0}")] - Channel(String), - #[error("Counter error: {0}")] - Counter(String), - #[error("Sequence error: {0}")] - Sequence(String), - #[error("Time error: {0}")] - Time(String), - #[error("Invalid transfer message: {0}")] - TransferMessage(token::TransferError), - #[error("Sending a token error: {0}")] - SendingToken(String), - #[error("Receiving a token error: {0}")] - ReceivingToken(String), - #[error("IBC storage error: {0}")] - IbcStorage(storage::Error), -} - -// This is needed to use `ibc::Handler::Error` with `IbcActions` in -// `tx_prelude/src/ibc.rs` -impl From for storage_api::Error { - fn from(err: Error) -> Self { - storage_api::Error::new(err) - } -} - -/// for handling IBC modules -pub type Result = std::result::Result; - -/// IBC trait to be implemented in integration that can read and write -pub trait IbcActions { - /// IBC action error - type Error: From; - - /// Read IBC-related data - fn read_ibc_data( - &self, - key: &Key, - ) -> std::result::Result>, Self::Error>; - - /// Write IBC-related data - fn write_ibc_data( - &mut self, - key: &Key, - data: impl AsRef<[u8]>, - ) -> std::result::Result<(), Self::Error>; - - /// Delete IBC-related data - fn delete_ibc_data( - &mut self, - key: &Key, - ) -> std::result::Result<(), Self::Error>; - - /// Emit an IBC event - fn emit_ibc_event( - &mut self, - event: NamadaIbcEvent, - ) -> std::result::Result<(), Self::Error>; - - /// Transfer token - fn transfer_token( - &mut self, - src: &Key, - dest: &Key, - amount: Amount, - ) -> std::result::Result<(), Self::Error>; - - /// Get the current height of this chain - fn get_height(&self) -> std::result::Result; - - /// Get the current time of the tendermint header of this chain - fn get_header_time( - &self, - ) -> std::result::Result; - - /// dispatch according to ICS26 routing - fn dispatch_ibc_action( - &mut self, - tx_data: &[u8], - ) -> std::result::Result<(), Self::Error> { - let ibc_msg = IbcMessage::decode(tx_data).map_err(Error::IbcData)?; - match &ibc_msg.0 { - Ics26Envelope::Ics2Msg(ics02_msg) => match ics02_msg { - ClientMsg::CreateClient(msg) => self.create_client(msg), - ClientMsg::UpdateClient(msg) => self.update_client(msg), - ClientMsg::Misbehaviour(_msg) => todo!(), - ClientMsg::UpgradeClient(msg) => self.upgrade_client(msg), - }, - Ics26Envelope::Ics3Msg(ics03_msg) => match ics03_msg { - ConnectionMsg::ConnectionOpenInit(msg) => { - self.init_connection(msg) - } - ConnectionMsg::ConnectionOpenTry(msg) => { - self.try_connection(msg) - } - ConnectionMsg::ConnectionOpenAck(msg) => { - self.ack_connection(msg) - } - ConnectionMsg::ConnectionOpenConfirm(msg) => { - self.confirm_connection(msg) - } - }, - Ics26Envelope::Ics4ChannelMsg(ics04_msg) => match ics04_msg { - ChannelMsg::ChannelOpenInit(msg) => self.init_channel(msg), - ChannelMsg::ChannelOpenTry(msg) => self.try_channel(msg), - ChannelMsg::ChannelOpenAck(msg) => self.ack_channel(msg), - ChannelMsg::ChannelOpenConfirm(msg) => { - self.confirm_channel(msg) - } - ChannelMsg::ChannelCloseInit(msg) => { - self.close_init_channel(msg) - } - ChannelMsg::ChannelCloseConfirm(msg) => { - self.close_confirm_channel(msg) - } - }, - Ics26Envelope::Ics4PacketMsg(ics04_msg) => match ics04_msg { - PacketMsg::AckPacket(msg) => self.acknowledge_packet(msg), - PacketMsg::RecvPacket(msg) => self.receive_packet(msg), - PacketMsg::ToPacket(msg) => self.timeout_packet(msg), - PacketMsg::ToClosePacket(msg) => { - self.timeout_on_close_packet(msg) - } - }, - Ics26Envelope::Ics20Msg(msg) => self.send_token(msg), - } - } - - /// Create a new client - fn create_client( - &mut self, - msg: &MsgCreateAnyClient, - ) -> std::result::Result<(), Self::Error> { - let counter_key = storage::client_counter_key(); - let counter = self.get_and_inc_counter(&counter_key)?; - let client_type = msg.client_state.client_type(); - let client_id = client_id(client_type, counter)?; - // client type - let client_type_key = storage::client_type_key(&client_id); - self.write_ibc_data(&client_type_key, client_type.as_str().as_bytes())?; - // client state - let client_state_key = storage::client_state_key(&client_id); - self.write_ibc_data( - &client_state_key, - msg.client_state - .encode_vec() - .expect("encoding shouldn't fail"), - )?; - // consensus state - let height = msg.client_state.latest_height(); - let consensus_state_key = - storage::consensus_state_key(&client_id, height); - self.write_ibc_data( - &consensus_state_key, - msg.consensus_state - .encode_vec() - .expect("encoding shouldn't fail"), - )?; - - self.set_client_update_time(&client_id)?; - - let event = make_create_client_event(&client_id, msg) - .try_into() - .unwrap(); - self.emit_ibc_event(event)?; - - Ok(()) - } - - /// Update a client - fn update_client( - &mut self, - msg: &MsgUpdateAnyClient, - ) -> std::result::Result<(), Self::Error> { - // get and update the client - let client_id = msg.client_id.clone(); - let client_state_key = storage::client_state_key(&client_id); - let value = - self.read_ibc_data(&client_state_key)?.ok_or_else(|| { - Error::Client(format!( - "The client to be updated doesn't exist: ID {}", - client_id - )) - })?; - let client_state = - AnyClientState::decode_vec(&value).map_err(Error::Decoding)?; - let (new_client_state, new_consensus_state) = - update_client(client_state, msg.header.clone())?; - - let height = new_client_state.latest_height(); - self.write_ibc_data( - &client_state_key, - new_client_state - .encode_vec() - .expect("encoding shouldn't fail"), - )?; - let consensus_state_key = - storage::consensus_state_key(&client_id, height); - self.write_ibc_data( - &consensus_state_key, - new_consensus_state - .encode_vec() - .expect("encoding shouldn't fail"), - )?; - - self.set_client_update_time(&client_id)?; - - let event = make_update_client_event(&client_id, msg) - .try_into() - .unwrap(); - self.emit_ibc_event(event)?; - - Ok(()) - } - - /// Upgrade a client - fn upgrade_client( - &mut self, - msg: &MsgUpgradeAnyClient, - ) -> std::result::Result<(), Self::Error> { - let client_state_key = storage::client_state_key(&msg.client_id); - let height = msg.client_state.latest_height(); - let consensus_state_key = - storage::consensus_state_key(&msg.client_id, height); - self.write_ibc_data( - &client_state_key, - msg.client_state - .encode_vec() - .expect("encoding shouldn't fail"), - )?; - self.write_ibc_data( - &consensus_state_key, - msg.consensus_state - .encode_vec() - .expect("encoding shouldn't fail"), - )?; - - self.set_client_update_time(&msg.client_id)?; - - let event = make_upgrade_client_event(&msg.client_id, msg) - .try_into() - .unwrap(); - self.emit_ibc_event(event)?; - - Ok(()) - } - - /// Initialize a connection for ConnectionOpenInit - fn init_connection( - &mut self, - msg: &MsgConnectionOpenInit, - ) -> std::result::Result<(), Self::Error> { - let counter_key = storage::connection_counter_key(); - let counter = self.get_and_inc_counter(&counter_key)?; - // new connection - let conn_id = connection_id(counter); - let conn_key = storage::connection_key(&conn_id); - let connection = init_connection(msg); - self.write_ibc_data( - &conn_key, - connection.encode_vec().expect("encoding shouldn't fail"), - )?; - - let event = make_open_init_connection_event(&conn_id, msg) - .try_into() - .unwrap(); - self.emit_ibc_event(event)?; - - Ok(()) - } - - /// Initialize a connection for ConnectionOpenTry - fn try_connection( - &mut self, - msg: &MsgConnectionOpenTry, - ) -> std::result::Result<(), Self::Error> { - let counter_key = storage::connection_counter_key(); - let counter = self.get_and_inc_counter(&counter_key)?; - // new connection - let conn_id = connection_id(counter); - let conn_key = storage::connection_key(&conn_id); - let connection = try_connection(msg); - self.write_ibc_data( - &conn_key, - connection.encode_vec().expect("encoding shouldn't fail"), - )?; - - let event = make_open_try_connection_event(&conn_id, msg) - .try_into() - .unwrap(); - self.emit_ibc_event(event)?; - - Ok(()) - } - - /// Open the connection for ConnectionOpenAck - fn ack_connection( - &mut self, - msg: &MsgConnectionOpenAck, - ) -> std::result::Result<(), Self::Error> { - let conn_key = storage::connection_key(&msg.connection_id); - let value = self.read_ibc_data(&conn_key)?.ok_or_else(|| { - Error::Connection(format!( - "The connection to be opened doesn't exist: ID {}", - msg.connection_id - )) - })?; - let mut connection = - ConnectionEnd::decode_vec(&value).map_err(Error::Decoding)?; - open_connection(&mut connection); - let mut counterparty = connection.counterparty().clone(); - counterparty.connection_id = - Some(msg.counterparty_connection_id.clone()); - connection.set_counterparty(counterparty); - self.write_ibc_data( - &conn_key, - connection.encode_vec().expect("encoding shouldn't fail"), - )?; - - let event = make_open_ack_connection_event(msg).try_into().unwrap(); - self.emit_ibc_event(event)?; - - Ok(()) - } - - /// Open the connection for ConnectionOpenConfirm - fn confirm_connection( - &mut self, - msg: &MsgConnectionOpenConfirm, - ) -> std::result::Result<(), Self::Error> { - let conn_key = storage::connection_key(&msg.connection_id); - let value = self.read_ibc_data(&conn_key)?.ok_or_else(|| { - Error::Connection(format!( - "The connection to be opend doesn't exist: ID {}", - msg.connection_id - )) - })?; - let mut connection = - ConnectionEnd::decode_vec(&value).map_err(Error::Decoding)?; - open_connection(&mut connection); - self.write_ibc_data( - &conn_key, - connection.encode_vec().expect("encoding shouldn't fail"), - )?; - - let event = make_open_confirm_connection_event(msg).try_into().unwrap(); - self.emit_ibc_event(event)?; - - Ok(()) - } - - /// Initialize a channel for ChannelOpenInit - fn init_channel( - &mut self, - msg: &MsgChannelOpenInit, - ) -> std::result::Result<(), Self::Error> { - self.bind_port(&msg.port_id)?; - let counter_key = storage::channel_counter_key(); - let counter = self.get_and_inc_counter(&counter_key)?; - let channel_id = channel_id(counter); - let port_channel_id = port_channel_id(msg.port_id.clone(), channel_id); - let channel_key = storage::channel_key(&port_channel_id); - self.write_ibc_data( - &channel_key, - msg.channel.encode_vec().expect("encoding shouldn't fail"), - )?; - - let event = make_open_init_channel_event(&channel_id, msg) - .try_into() - .unwrap(); - self.emit_ibc_event(event)?; - - Ok(()) - } - - /// Initialize a channel for ChannelOpenTry - fn try_channel( - &mut self, - msg: &MsgChannelOpenTry, - ) -> std::result::Result<(), Self::Error> { - self.bind_port(&msg.port_id)?; - let counter_key = storage::channel_counter_key(); - let counter = self.get_and_inc_counter(&counter_key)?; - let channel_id = channel_id(counter); - let port_channel_id = port_channel_id(msg.port_id.clone(), channel_id); - let channel_key = storage::channel_key(&port_channel_id); - self.write_ibc_data( - &channel_key, - msg.channel.encode_vec().expect("encoding shouldn't fail"), - )?; - - let event = make_open_try_channel_event(&channel_id, msg) - .try_into() - .unwrap(); - self.emit_ibc_event(event)?; - - Ok(()) - } - - /// Open the channel for ChannelOpenAck - fn ack_channel( - &mut self, - msg: &MsgChannelOpenAck, - ) -> std::result::Result<(), Self::Error> { - let port_channel_id = - port_channel_id(msg.port_id.clone(), msg.channel_id); - let channel_key = storage::channel_key(&port_channel_id); - let value = self.read_ibc_data(&channel_key)?.ok_or_else(|| { - Error::Channel(format!( - "The channel to be opened doesn't exist: Port/Channel {}", - port_channel_id - )) - })?; - let mut channel = - ChannelEnd::decode_vec(&value).map_err(Error::Decoding)?; - channel.set_counterparty_channel_id(msg.counterparty_channel_id); - open_channel(&mut channel); - self.write_ibc_data( - &channel_key, - channel.encode_vec().expect("encoding shouldn't fail"), - )?; - - let event = make_open_ack_channel_event(msg, &channel)? - .try_into() - .unwrap(); - self.emit_ibc_event(event)?; - - Ok(()) - } - - /// Open the channel for ChannelOpenConfirm - fn confirm_channel( - &mut self, - msg: &MsgChannelOpenConfirm, - ) -> std::result::Result<(), Self::Error> { - let port_channel_id = - port_channel_id(msg.port_id.clone(), msg.channel_id); - let channel_key = storage::channel_key(&port_channel_id); - let value = self.read_ibc_data(&channel_key)?.ok_or_else(|| { - Error::Channel(format!( - "The channel to be opened doesn't exist: Port/Channel {}", - port_channel_id - )) - })?; - let mut channel = - ChannelEnd::decode_vec(&value).map_err(Error::Decoding)?; - open_channel(&mut channel); - self.write_ibc_data( - &channel_key, - channel.encode_vec().expect("encoding shouldn't fail"), - )?; - - let event = make_open_confirm_channel_event(msg, &channel)? - .try_into() - .unwrap(); - self.emit_ibc_event(event)?; - - Ok(()) - } - - /// Close the channel for ChannelCloseInit - fn close_init_channel( - &mut self, - msg: &MsgChannelCloseInit, - ) -> std::result::Result<(), Self::Error> { - let port_channel_id = - port_channel_id(msg.port_id.clone(), msg.channel_id); - let channel_key = storage::channel_key(&port_channel_id); - let value = self.read_ibc_data(&channel_key)?.ok_or_else(|| { - Error::Channel(format!( - "The channel to be closed doesn't exist: Port/Channel {}", - port_channel_id - )) - })?; - let mut channel = - ChannelEnd::decode_vec(&value).map_err(Error::Decoding)?; - close_channel(&mut channel); - self.write_ibc_data( - &channel_key, - channel.encode_vec().expect("encoding shouldn't fail"), - )?; - - let event = make_close_init_channel_event(msg, &channel)? - .try_into() - .unwrap(); - self.emit_ibc_event(event)?; - - Ok(()) - } - - /// Close the channel for ChannelCloseConfirm - fn close_confirm_channel( - &mut self, - msg: &MsgChannelCloseConfirm, - ) -> std::result::Result<(), Self::Error> { - let port_channel_id = - port_channel_id(msg.port_id.clone(), msg.channel_id); - let channel_key = storage::channel_key(&port_channel_id); - let value = self.read_ibc_data(&channel_key)?.ok_or_else(|| { - Error::Channel(format!( - "The channel to be closed doesn't exist: Port/Channel {}", - port_channel_id - )) - })?; - let mut channel = - ChannelEnd::decode_vec(&value).map_err(Error::Decoding)?; - close_channel(&mut channel); - self.write_ibc_data( - &channel_key, - channel.encode_vec().expect("encoding shouldn't fail"), - )?; - - let event = make_close_confirm_channel_event(msg, &channel)? - .try_into() - .unwrap(); - self.emit_ibc_event(event)?; - - Ok(()) - } - - /// Send a packet - fn send_packet( - &mut self, - port_channel_id: PortChannelId, - data: Vec, - timeout_height: Height, - timeout_timestamp: Timestamp, - ) -> std::result::Result<(), Self::Error> { - // get and increment the next sequence send - let seq_key = storage::next_sequence_send_key(&port_channel_id); - let sequence = self.get_and_inc_sequence(&seq_key)?; - - // get the channel for the destination info. - let channel_key = storage::channel_key(&port_channel_id); - let channel = self - .read_ibc_data(&channel_key)? - .expect("cannot get the channel to be closed"); - let channel = - ChannelEnd::decode_vec(&channel).expect("cannot get the channel"); - let counterparty = channel.counterparty(); - - // make a packet - let packet = Packet { - sequence, - source_port: port_channel_id.port_id.clone(), - source_channel: port_channel_id.channel_id, - destination_port: counterparty.port_id.clone(), - destination_channel: *counterparty - .channel_id() - .expect("the counterparty channel should exist"), - data, - timeout_height, - timeout_timestamp, - }; - // store the commitment of the packet - let commitment_key = storage::commitment_key( - &port_channel_id.port_id, - &port_channel_id.channel_id, - packet.sequence, - ); - let commitment = commitment(&packet); - self.write_ibc_data(&commitment_key, commitment.into_vec())?; - - let event = make_send_packet_event(packet).try_into().unwrap(); - self.emit_ibc_event(event)?; - - Ok(()) - } - - /// Receive a packet - fn receive_packet( - &mut self, - msg: &MsgRecvPacket, - ) -> std::result::Result<(), Self::Error> { - // check the packet data - let packet_ack = - if let Ok(data) = serde_json::from_slice(&msg.packet.data) { - match self.receive_token(&msg.packet, &data) { - Ok(_) => PacketAck::result_success(), - Err(_) => PacketAck::result_error( - "receiving a token failed".to_string(), - ), - } - } else { - PacketAck::result_error("unknown packet data".to_string()) - }; - - // store the receipt - let receipt_key = storage::receipt_key( - &msg.packet.destination_port, - &msg.packet.destination_channel, - msg.packet.sequence, - ); - self.write_ibc_data(&receipt_key, PacketReceipt::default().as_bytes())?; - - // store the ack - let ack_key = storage::ack_key( - &msg.packet.destination_port, - &msg.packet.destination_channel, - msg.packet.sequence, - ); - let ack = packet_ack.encode_to_vec(); - let ack_commitment = sha2::Sha256::digest(&ack).to_vec(); - self.write_ibc_data(&ack_key, ack_commitment)?; - - // increment the next sequence receive - let port_channel_id = port_channel_id( - msg.packet.destination_port.clone(), - msg.packet.destination_channel, - ); - let seq_key = storage::next_sequence_recv_key(&port_channel_id); - self.get_and_inc_sequence(&seq_key)?; - - let event = make_write_ack_event(msg.packet.clone(), ack) - .try_into() - .unwrap(); - self.emit_ibc_event(event)?; - - Ok(()) - } - - /// Receive a acknowledgement - fn acknowledge_packet( - &mut self, - msg: &MsgAcknowledgement, - ) -> std::result::Result<(), Self::Error> { - let ack = PacketAck::try_from(msg.acknowledgement.clone()) - .map_err(Error::IbcData)?; - if !ack.is_success() { - if let Ok(data) = serde_json::from_slice(&msg.packet.data) { - self.refund_token(&msg.packet, &data)?; - } - } - - let commitment_key = storage::commitment_key( - &msg.packet.source_port, - &msg.packet.source_channel, - msg.packet.sequence, - ); - self.delete_ibc_data(&commitment_key)?; - - // get and increment the next sequence ack - let port_channel_id = port_channel_id( - msg.packet.source_port.clone(), - msg.packet.source_channel, - ); - let seq_key = storage::next_sequence_ack_key(&port_channel_id); - self.get_and_inc_sequence(&seq_key)?; - - let event = make_ack_event(msg.packet.clone()).try_into().unwrap(); - self.emit_ibc_event(event)?; - - Ok(()) - } - - /// Receive a timeout - fn timeout_packet( - &mut self, - msg: &MsgTimeout, - ) -> std::result::Result<(), Self::Error> { - // check the packet data - if let Ok(data) = serde_json::from_slice(&msg.packet.data) { - self.refund_token(&msg.packet, &data)?; - } - - // delete the commitment of the packet - let commitment_key = storage::commitment_key( - &msg.packet.source_port, - &msg.packet.source_channel, - msg.packet.sequence, - ); - self.delete_ibc_data(&commitment_key)?; - - // close the channel - let port_channel_id = port_channel_id( - msg.packet.source_port.clone(), - msg.packet.source_channel, - ); - let channel_key = storage::channel_key(&port_channel_id); - let value = self.read_ibc_data(&channel_key)?.ok_or_else(|| { - Error::Channel(format!( - "The channel to be closed doesn't exist: Port/Channel {}", - port_channel_id - )) - })?; - let mut channel = - ChannelEnd::decode_vec(&value).map_err(Error::Decoding)?; - if channel.order_matches(&Order::Ordered) { - close_channel(&mut channel); - self.write_ibc_data( - &channel_key, - channel.encode_vec().expect("encoding shouldn't fail"), - )?; - } - - let event = make_timeout_event(msg.packet.clone()).try_into().unwrap(); - self.emit_ibc_event(event)?; - - Ok(()) - } - - /// Receive a timeout for TimeoutOnClose - fn timeout_on_close_packet( - &mut self, - msg: &MsgTimeoutOnClose, - ) -> std::result::Result<(), Self::Error> { - // check the packet data - if let Ok(data) = serde_json::from_slice(&msg.packet.data) { - self.refund_token(&msg.packet, &data)?; - } - - // delete the commitment of the packet - let commitment_key = storage::commitment_key( - &msg.packet.source_port, - &msg.packet.source_channel, - msg.packet.sequence, - ); - self.delete_ibc_data(&commitment_key)?; - - // close the channel - let port_channel_id = port_channel_id( - msg.packet.source_port.clone(), - msg.packet.source_channel, - ); - let channel_key = storage::channel_key(&port_channel_id); - let value = self.read_ibc_data(&channel_key)?.ok_or_else(|| { - Error::Channel(format!( - "The channel to be closed doesn't exist: Port/Channel {}", - port_channel_id - )) - })?; - let mut channel = - ChannelEnd::decode_vec(&value).map_err(Error::Decoding)?; - if channel.order_matches(&Order::Ordered) { - close_channel(&mut channel); - self.write_ibc_data( - &channel_key, - channel.encode_vec().expect("encoding shouldn't fail"), - )?; - } - - Ok(()) - } - - /// Set the timestamp and the height for the client update - fn set_client_update_time( - &mut self, - client_id: &ClientId, - ) -> std::result::Result<(), Self::Error> { - let time = Time::parse_from_rfc3339(&self.get_header_time()?.0) - .map_err(|e| { - Error::Time(format!("The time of the header is invalid: {}", e)) - })?; - let key = storage::client_update_timestamp_key(client_id); - self.write_ibc_data( - &key, - time.encode_vec().expect("encoding shouldn't fail"), - )?; - - // the revision number is always 0 - let height = Height::new(0, self.get_height()?.0); - let height_key = storage::client_update_height_key(client_id); - // write the current height as u64 - self.write_ibc_data( - &height_key, - height.encode_vec().expect("Encoding shouldn't fail"), - )?; - - Ok(()) - } - - /// Get and increment the counter - fn get_and_inc_counter( - &mut self, - key: &Key, - ) -> std::result::Result { - let value = self.read_ibc_data(key)?.ok_or_else(|| { - Error::Counter(format!("The counter doesn't exist: {}", key)) - })?; - let value: [u8; 8] = value.try_into().map_err(|_| { - Error::Counter(format!("The counter value wasn't u64: Key {}", key)) - })?; - let counter = u64::from_be_bytes(value); - self.write_ibc_data(key, (counter + 1).to_be_bytes())?; - Ok(counter) - } - - /// Get and increment the sequence - fn get_and_inc_sequence( - &mut self, - key: &Key, - ) -> std::result::Result { - let index = match self.read_ibc_data(key)? { - Some(v) => { - let index: [u8; 8] = v.try_into().map_err(|_| { - Error::Sequence(format!( - "The sequence index wasn't u64: Key {}", - key - )) - })?; - u64::from_be_bytes(index) - } - // when the sequence has never been used, returns the initial value - None => 1, - }; - self.write_ibc_data(key, (index + 1).to_be_bytes())?; - Ok(index.into()) - } - - /// Bind a new port - fn bind_port( - &mut self, - port_id: &PortId, - ) -> std::result::Result<(), Self::Error> { - let port_key = storage::port_key(port_id); - match self.read_ibc_data(&port_key)? { - Some(_) => {} - None => { - // create a new capability and claim it - let index_key = storage::capability_index_key(); - let cap_index = self.get_and_inc_counter(&index_key)?; - self.write_ibc_data(&port_key, cap_index.to_be_bytes())?; - let cap_key = storage::capability_key(cap_index); - self.write_ibc_data(&cap_key, port_id.as_bytes())?; - } - } - Ok(()) - } - - /// Send the specified token by escrowing or burning - fn send_token( - &mut self, - msg: &MsgTransfer, - ) -> std::result::Result<(), Self::Error> { - let mut data = FungibleTokenPacketData::from(msg.clone()); - if let Some(hash) = storage::token_hash_from_denom(&data.denom) - .map_err(Error::IbcStorage)? - { - let denom_key = storage::ibc_denom_key(hash); - let denom_bytes = - self.read_ibc_data(&denom_key)?.ok_or_else(|| { - Error::SendingToken(format!( - "No original denom: denom_key {}", - denom_key - )) - })?; - let denom = std::str::from_utf8(&denom_bytes).map_err(|e| { - Error::SendingToken(format!( - "Decoding the denom failed: denom_key {}, error {}", - denom_key, e - )) - })?; - data.denom = denom.to_string(); - } - let token = storage::token(&data.denom).map_err(Error::IbcStorage)?; - let amount = Amount::from_str(&data.amount, 0).map_err(|e| { - Error::SendingToken(format!( - "Invalid amount: amount {}, error {}", - data.amount, e - )) - })?; - - let source_addr = Address::decode(&data.sender).map_err(|e| { - Error::SendingToken(format!( - "Invalid sender address: sender {}, error {}", - data.sender, e - )) - })?; - - // check the denomination field - let prefix = format!( - "{}/{}/", - msg.source_port.clone(), - msg.source_channel.clone() - ); - let (source, target) = if data.denom.starts_with(&prefix) { - // the receiver's chain was the source - // transfer from the origin-specific account of the token - let key_prefix = storage::ibc_token_prefix(&data.denom) - .map_err(Error::IbcStorage)?; - let src = token::multitoken_balance_key(&key_prefix, &source_addr); - - let key_prefix = storage::ibc_account_prefix( - &msg.source_port, - &msg.source_channel, - &token, - ); - let burn = token::multitoken_balance_key( - &key_prefix, - &Address::Internal(InternalAddress::IbcBurn), - ); - (src, burn) - } else { - // this chain is the source - // escrow the amount of the token - let src = if data.denom == token.to_string() { - token::balance_key(&token, &source_addr) - } else { - let key_prefix = storage::ibc_token_prefix(&data.denom) - .map_err(Error::IbcStorage)?; - token::multitoken_balance_key(&key_prefix, &source_addr) - }; - - let key_prefix = storage::ibc_account_prefix( - &msg.source_port, - &msg.source_channel, - &token, - ); - let escrow = token::multitoken_balance_key( - &key_prefix, - &Address::Internal(InternalAddress::IbcEscrow), - ); - (src, escrow) - }; - self.transfer_token(&source, &target, amount)?; - - // send a packet - let port_channel_id = - port_channel_id(msg.source_port.clone(), msg.source_channel); - let packet_data = serde_json::to_vec(&data) - .expect("encoding the packet data shouldn't fail"); - self.send_packet( - port_channel_id, - packet_data, - msg.timeout_height, - msg.timeout_timestamp, - ) - } - - /// Receive the specified token by unescrowing or minting - fn receive_token( - &mut self, - packet: &Packet, - data: &FungibleTokenPacketData, - ) -> std::result::Result<(), Self::Error> { - let token = storage::token(&data.denom).map_err(Error::IbcStorage)?; - let amount = Amount::from_str(&data.amount, 0).map_err(|e| { - Error::ReceivingToken(format!( - "Invalid amount: amount {}, error {}", - data.amount, e - )) - })?; - // The receiver should be an address because the origin-specific account - // key should be assigned internally - let dest_addr = Address::decode(&data.receiver).map_err(|e| { - Error::ReceivingToken(format!( - "Invalid receiver address: receiver {}, error {}", - data.receiver, e - )) - })?; - - let prefix = format!( - "{}/{}/", - packet.source_port.clone(), - packet.source_channel.clone() - ); - let (source, target) = match data.denom.strip_prefix(&prefix) { - Some(denom) => { - // unescrow the token because this chain was the source - let escrow_prefix = storage::ibc_account_prefix( - &packet.destination_port, - &packet.destination_channel, - &token, - ); - let escrow = token::multitoken_balance_key( - &escrow_prefix, - &Address::Internal(InternalAddress::IbcEscrow), - ); - let dest = if denom == token.to_string() { - token::balance_key(&token, &dest_addr) - } else { - let key_prefix = storage::ibc_token_prefix(denom) - .map_err(Error::IbcStorage)?; - token::multitoken_balance_key(&key_prefix, &dest_addr) - }; - (escrow, dest) - } - None => { - // mint the token because the sender chain is the source - let key_prefix = storage::ibc_account_prefix( - &packet.destination_port, - &packet.destination_channel, - &token, - ); - let mint = token::multitoken_balance_key( - &key_prefix, - &Address::Internal(InternalAddress::IbcMint), - ); - - // prefix the denom with the this chain port and channel - let denom = format!( - "{}/{}/{}", - &packet.destination_port, - &packet.destination_channel, - &data.denom - ); - let key_prefix = storage::ibc_token_prefix(&denom) - .map_err(Error::IbcStorage)?; - let dest = - token::multitoken_balance_key(&key_prefix, &dest_addr); - - // store the prefixed denom - let token_hash = storage::calc_hash(&denom); - let denom_key = storage::ibc_denom_key(token_hash); - self.write_ibc_data(&denom_key, denom.as_bytes())?; - - (mint, dest) - } - }; - self.transfer_token(&source, &target, amount)?; - - Ok(()) - } - - /// Refund the specified token by unescrowing or minting - fn refund_token( - &mut self, - packet: &Packet, - data: &FungibleTokenPacketData, - ) -> std::result::Result<(), Self::Error> { - let token = storage::token(&data.denom).map_err(Error::IbcStorage)?; - let amount = Amount::from_str(&data.amount, 0).map_err(|e| { - Error::ReceivingToken(format!( - "Invalid amount: amount {}, error {}", - data.amount, e - )) - })?; - - let dest_addr = Address::decode(&data.sender).map_err(|e| { - Error::SendingToken(format!( - "Invalid sender address: sender {}, error {}", - data.sender, e - )) - })?; - - let prefix = format!( - "{}/{}/", - packet.source_port.clone(), - packet.source_channel.clone() - ); - let (source, target) = if data.denom.starts_with(&prefix) { - // mint the token because the amount was burned - let key_prefix = storage::ibc_account_prefix( - &packet.source_port, - &packet.source_channel, - &token, - ); - let mint = token::multitoken_balance_key( - &key_prefix, - &Address::Internal(InternalAddress::IbcMint), - ); - let key_prefix = storage::ibc_token_prefix(&data.denom) - .map_err(Error::IbcStorage)?; - let dest = token::multitoken_balance_key(&key_prefix, &dest_addr); - (mint, dest) - } else { - // unescrow the token because the acount was escrowed - let dest = if data.denom == token.to_string() { - token::balance_key(&token, &dest_addr) - } else { - let key_prefix = storage::ibc_token_prefix(&data.denom) - .map_err(Error::IbcStorage)?; - token::multitoken_balance_key(&key_prefix, &dest_addr) - }; - - let key_prefix = storage::ibc_account_prefix( - &packet.source_port, - &packet.source_channel, - &token, - ); - let escrow = token::multitoken_balance_key( - &key_prefix, - &Address::Internal(InternalAddress::IbcEscrow), - ); - (escrow, dest) - }; - self.transfer_token(&source, &target, amount)?; - - Ok(()) - } -} - -/// Update a client with the given state and headers -pub fn update_client( - client_state: AnyClientState, - header: AnyHeader, -) -> Result<(AnyClientState, AnyConsensusState)> { - match client_state { - AnyClientState::Tendermint(cs) => match header { - AnyHeader::Tendermint(h) => { - let new_client_state = cs.with_header(h.clone()).wrap_any(); - let new_consensus_state = TmConsensusState::from(h).wrap_any(); - Ok((new_client_state, new_consensus_state)) - } - #[cfg(any(feature = "ibc-mocks-abcipp", feature = "ibc-mocks"))] - _ => Err(Error::ClientUpdate( - "The header type is mismatched".to_owned(), - )), - }, - #[cfg(any(feature = "ibc-mocks-abcipp", feature = "ibc-mocks"))] - AnyClientState::Mock(_) => match header { - AnyHeader::Mock(h) => Ok(( - MockClientState::new(h).wrap_any(), - MockConsensusState::new(h).wrap_any(), - )), - _ => Err(Error::ClientUpdate( - "The header type is mismatched".to_owned(), - )), - }, - } -} - -/// Returns a new client ID -pub fn client_id(client_type: ClientType, counter: u64) -> Result { - ClientId::new(client_type, counter).map_err(Error::ClientId) -} - -/// Returns a new connection ID -pub fn connection_id(counter: u64) -> ConnectionId { - ConnectionId::new(counter) -} - -/// Make a connection end from the init message -pub fn init_connection(msg: &MsgConnectionOpenInit) -> ConnectionEnd { - ConnectionEnd::new( - ConnState::Init, - msg.client_id.clone(), - msg.counterparty.clone(), - vec![msg.version.clone().unwrap_or_default()], - msg.delay_period, - ) -} - -/// Make a connection end from the try message -pub fn try_connection(msg: &MsgConnectionOpenTry) -> ConnectionEnd { - ConnectionEnd::new( - ConnState::TryOpen, - msg.client_id.clone(), - msg.counterparty.clone(), - msg.counterparty_versions.clone(), - msg.delay_period, - ) -} - -/// Open the connection -pub fn open_connection(conn: &mut ConnectionEnd) { - conn.set_state(ConnState::Open); -} - -/// Returns a new channel ID -pub fn channel_id(counter: u64) -> ChannelId { - ChannelId::new(counter) -} - -/// Open the channel -pub fn open_channel(channel: &mut ChannelEnd) { - channel.set_state(ChanState::Open); -} - -/// Close the channel -pub fn close_channel(channel: &mut ChannelEnd) { - channel.set_state(ChanState::Closed); -} - -/// Returns a port ID -pub fn port_id(id: &str) -> Result { - PortId::from_str(id).map_err(Error::PortId) -} - -/// Returns a pair of port ID and channel ID -pub fn port_channel_id( - port_id: PortId, - channel_id: ChannelId, -) -> PortChannelId { - PortChannelId { - port_id, - channel_id, - } -} - -/// Returns a sequence -pub fn sequence(index: u64) -> Sequence { - Sequence::from(index) -} - -/// Make a packet from MsgTransfer -pub fn packet_from_message( - msg: &MsgTransfer, - sequence: Sequence, - counterparty: &ChanCounterparty, -) -> Packet { - Packet { - sequence, - source_port: msg.source_port.clone(), - source_channel: msg.source_channel, - destination_port: counterparty.port_id.clone(), - destination_channel: *counterparty - .channel_id() - .expect("the counterparty channel should exist"), - data: serde_json::to_vec(&FungibleTokenPacketData::from(msg.clone())) - .expect("encoding the packet data shouldn't fail"), - timeout_height: msg.timeout_height, - timeout_timestamp: msg.timeout_timestamp, - } -} - -/// Returns a commitment from the given packet -pub fn commitment(packet: &Packet) -> PacketCommitment { - let timeout = packet.timeout_timestamp.nanoseconds().to_be_bytes(); - let revision_number = packet.timeout_height.revision_number.to_be_bytes(); - let revision_height = packet.timeout_height.revision_height.to_be_bytes(); - let data = sha2::Sha256::digest(&packet.data); - let input = [ - &timeout, - &revision_number, - &revision_height, - data.as_slice(), - ] - .concat(); - sha2::Sha256::digest(&input).to_vec().into() -} - -/// Returns a counterparty of a connection -pub fn connection_counterparty( - client_id: ClientId, - conn_id: ConnectionId, -) -> ConnCounterparty { - ConnCounterparty::new(client_id, Some(conn_id), commitment_prefix()) -} - -/// Returns a counterparty of a channel -pub fn channel_counterparty( - port_id: PortId, - channel_id: ChannelId, -) -> ChanCounterparty { - ChanCounterparty::new(port_id, Some(channel_id)) -} - -/// Returns Namada commitment prefix -pub fn commitment_prefix() -> CommitmentPrefix { - CommitmentPrefix::try_from(COMMITMENT_PREFIX.to_vec()) - .expect("the conversion shouldn't fail") -} - -/// Makes CreateClient event -pub fn make_create_client_event( - client_id: &ClientId, - msg: &MsgCreateAnyClient, -) -> IbcEvent { - let attributes = ClientAttributes { - client_id: client_id.clone(), - client_type: msg.client_state.client_type(), - consensus_height: msg.client_state.latest_height(), - ..Default::default() - }; - IbcEvent::CreateClient(CreateClient::from(attributes)) -} - -/// Makes UpdateClient event -pub fn make_update_client_event( - client_id: &ClientId, - msg: &MsgUpdateAnyClient, -) -> IbcEvent { - let attributes = ClientAttributes { - client_id: client_id.clone(), - client_type: msg.header.client_type(), - consensus_height: msg.header.height(), - ..Default::default() - }; - IbcEvent::UpdateClient(UpdateClient::from(attributes)) -} - -/// Makes UpgradeClient event -pub fn make_upgrade_client_event( - client_id: &ClientId, - msg: &MsgUpgradeAnyClient, -) -> IbcEvent { - let attributes = ClientAttributes { - client_id: client_id.clone(), - client_type: msg.client_state.client_type(), - consensus_height: msg.client_state.latest_height(), - ..Default::default() - }; - IbcEvent::UpgradeClient(UpgradeClient::from(attributes)) -} - -/// Makes OpenInitConnection event -pub fn make_open_init_connection_event( - conn_id: &ConnectionId, - msg: &MsgConnectionOpenInit, -) -> IbcEvent { - let attributes = ConnectionAttributes { - connection_id: Some(conn_id.clone()), - client_id: msg.client_id.clone(), - counterparty_connection_id: msg.counterparty.connection_id().cloned(), - counterparty_client_id: msg.counterparty.client_id().clone(), - ..Default::default() - }; - ConnOpenInit::from(attributes).into() -} - -/// Makes OpenTryConnection event -pub fn make_open_try_connection_event( - conn_id: &ConnectionId, - msg: &MsgConnectionOpenTry, -) -> IbcEvent { - let attributes = ConnectionAttributes { - connection_id: Some(conn_id.clone()), - client_id: msg.client_id.clone(), - counterparty_connection_id: msg.counterparty.connection_id().cloned(), - counterparty_client_id: msg.counterparty.client_id().clone(), - ..Default::default() - }; - ConnOpenTry::from(attributes).into() -} - -/// Makes OpenAckConnection event -pub fn make_open_ack_connection_event(msg: &MsgConnectionOpenAck) -> IbcEvent { - let attributes = ConnectionAttributes { - connection_id: Some(msg.connection_id.clone()), - counterparty_connection_id: Some( - msg.counterparty_connection_id.clone(), - ), - ..Default::default() - }; - ConnOpenAck::from(attributes).into() -} - -/// Makes OpenConfirmConnection event -pub fn make_open_confirm_connection_event( - msg: &MsgConnectionOpenConfirm, -) -> IbcEvent { - let attributes = ConnectionAttributes { - connection_id: Some(msg.connection_id.clone()), - ..Default::default() - }; - ConnOpenConfirm::from(attributes).into() -} - -/// Makes OpenInitChannel event -pub fn make_open_init_channel_event( - channel_id: &ChannelId, - msg: &MsgChannelOpenInit, -) -> IbcEvent { - let connection_id = match msg.channel.connection_hops().get(0) { - Some(c) => c.clone(), - None => ConnectionId::default(), - }; - let attributes = ChanOpenInit { - height: Height::default(), - port_id: msg.port_id.clone(), - channel_id: Some(*channel_id), - connection_id, - counterparty_port_id: msg.channel.counterparty().port_id().clone(), - counterparty_channel_id: msg - .channel - .counterparty() - .channel_id() - .cloned(), - }; - attributes.into() -} - -/// Makes OpenTryChannel event -pub fn make_open_try_channel_event( - channel_id: &ChannelId, - msg: &MsgChannelOpenTry, -) -> IbcEvent { - let connection_id = match msg.channel.connection_hops().get(0) { - Some(c) => c.clone(), - None => ConnectionId::default(), - }; - let attributes = ChanOpenTry { - height: Height::default(), - port_id: msg.port_id.clone(), - channel_id: Some(*channel_id), - connection_id, - counterparty_port_id: msg.channel.counterparty().port_id().clone(), - counterparty_channel_id: msg - .channel - .counterparty() - .channel_id() - .cloned(), - }; - attributes.into() -} - -/// Makes OpenAckChannel event -pub fn make_open_ack_channel_event( - msg: &MsgChannelOpenAck, - channel: &ChannelEnd, -) -> Result { - let conn_id = get_connection_id_from_channel(channel)?; - let counterparty = channel.counterparty(); - let attributes = ChanOpenAck { - height: Height::default(), - port_id: msg.port_id.clone(), - channel_id: Some(msg.channel_id), - counterparty_channel_id: Some(msg.counterparty_channel_id), - connection_id: conn_id.clone(), - counterparty_port_id: counterparty.port_id().clone(), - }; - Ok(attributes.into()) -} - -/// Makes OpenConfirmChannel event -pub fn make_open_confirm_channel_event( - msg: &MsgChannelOpenConfirm, - channel: &ChannelEnd, -) -> Result { - let conn_id = get_connection_id_from_channel(channel)?; - let counterparty = channel.counterparty(); - let attributes = ChanOpenConfirm { - height: Height::default(), - port_id: msg.port_id.clone(), - channel_id: Some(msg.channel_id), - connection_id: conn_id.clone(), - counterparty_port_id: counterparty.port_id().clone(), - counterparty_channel_id: counterparty.channel_id().cloned(), - }; - Ok(attributes.into()) -} - -/// Makes CloseInitChannel event -pub fn make_close_init_channel_event( - msg: &MsgChannelCloseInit, - channel: &ChannelEnd, -) -> Result { - let conn_id = get_connection_id_from_channel(channel)?; - let counterparty = channel.counterparty(); - let attributes = ChanCloseInit { - height: Height::default(), - port_id: msg.port_id.clone(), - channel_id: msg.channel_id, - connection_id: conn_id.clone(), - counterparty_port_id: counterparty.port_id().clone(), - counterparty_channel_id: counterparty.channel_id().cloned(), - }; - Ok(attributes.into()) -} - -/// Makes CloseConfirmChannel event -pub fn make_close_confirm_channel_event( - msg: &MsgChannelCloseConfirm, - channel: &ChannelEnd, -) -> Result { - let conn_id = get_connection_id_from_channel(channel)?; - let counterparty = channel.counterparty(); - let attributes = ChanCloseConfirm { - height: Height::default(), - port_id: msg.port_id.clone(), - channel_id: Some(msg.channel_id), - connection_id: conn_id.clone(), - counterparty_port_id: counterparty.port_id.clone(), - counterparty_channel_id: counterparty.channel_id().cloned(), - }; - Ok(attributes.into()) -} - -fn get_connection_id_from_channel( - channel: &ChannelEnd, -) -> Result<&ConnectionId> { - channel.connection_hops().get(0).ok_or_else(|| { - Error::Channel("No connection for the channel".to_owned()) - }) -} - -/// Makes SendPacket event -pub fn make_send_packet_event(packet: Packet) -> IbcEvent { - IbcEvent::SendPacket(SendPacket { - height: packet.timeout_height, - packet, - }) -} - -/// Makes WriteAcknowledgement event -pub fn make_write_ack_event(packet: Packet, ack: Vec) -> IbcEvent { - IbcEvent::WriteAcknowledgement(WriteAcknowledgement { - // this height is not used - height: Height::default(), - packet, - ack, - }) -} - -/// Makes AcknowledgePacket event -pub fn make_ack_event(packet: Packet) -> IbcEvent { - IbcEvent::AcknowledgePacket(AcknowledgePacket { - // this height is not used - height: Height::default(), - packet, - }) -} - -/// Makes TimeoutPacket event -pub fn make_timeout_event(packet: Packet) -> IbcEvent { - IbcEvent::TimeoutPacket(TimeoutPacket { - // this height is not used - height: Height::default(), - packet, - }) -} diff --git a/core/src/ledger/ibc/context/common.rs b/core/src/ledger/ibc/context/common.rs index 1d03ffbeec..4c96c034b5 100644 --- a/core/src/ledger/ibc/context/common.rs +++ b/core/src/ledger/ibc/context/common.rs @@ -1,5 +1,6 @@ //! IbcCommonContext implementation for IBC +use borsh::BorshSerialize; use prost::Message; use sha2::Digest; @@ -363,7 +364,14 @@ pub trait IbcCommonContext: IbcStorageContext { denom: PrefixedDenom, ) -> Result<(), ContextError> { let key = storage::ibc_denom_key(trace_hash); - let bytes = denom.to_string().as_bytes().to_vec(); + let bytes = denom.to_string().try_to_vec().map_err(|e| { + ContextError::ChannelError(ChannelError::Other { + description: format!( + "Encoding the denom failed: Denom {}, error {}", + denom, e + ), + }) + })?; self.write(&key, bytes).map_err(|_| { ContextError::ChannelError(ChannelError::Other { description: format!("Writing the denom failed: Key {}", key), diff --git a/core/src/ledger/ibc/context/execution.rs b/core/src/ledger/ibc/context/execution.rs index ddf289ce08..ae84afa614 100644 --- a/core/src/ledger/ibc/context/execution.rs +++ b/core/src/ledger/ibc/context/execution.rs @@ -1,5 +1,7 @@ //! ExecutionContext implementation for IBC +use borsh::{BorshDeserialize, BorshSerialize}; + use super::super::{IbcActions, IbcCommonContext}; use crate::ibc::core::ics02_client::client_state::ClientState; use crate::ibc::core::ics02_client::client_type::ClientType; @@ -180,7 +182,7 @@ where .expect("Creating a key for the client state shouldn't fail"); let list = match self.ctx.borrow().read(&key) { Ok(Some(value)) => { - let list = String::from_utf8(value).map_err(|e| { + let list = String::try_from_slice(&value).map_err(|e| { ContextError::ConnectionError(ConnectionError::Other { description: format!( "Decoding the connection list failed: Key {}, \ @@ -201,7 +203,7 @@ where }))? } }; - let bytes = list.as_bytes().to_vec(); + let bytes = list.try_to_vec().expect("encoding shouldn't fail"); self.ctx.borrow_mut().write(&key, bytes).map_err(|_| { ContextError::ConnectionError(ConnectionError::Other { description: format!( diff --git a/core/src/ledger/ibc/context/storage.rs b/core/src/ledger/ibc/context/storage.rs index a99a659187..87555990a4 100644 --- a/core/src/ledger/ibc/context/storage.rs +++ b/core/src/ledger/ibc/context/storage.rs @@ -6,6 +6,7 @@ pub use ics23::ProofSpec; use super::super::Error; use crate::ledger::storage_api; +use crate::types::address::Address; use crate::types::ibc::IbcEvent; use crate::types::storage::{BlockHeight, Header, Key}; use crate::types::token::Amount; @@ -54,8 +55,25 @@ pub trait IbcStorageContext { /// Transfer token fn transfer_token( &mut self, - src: &Key, - dest: &Key, + src: &Address, + dest: &Address, + token: &Address, + amount: Amount, + ) -> Result<(), Self::Error>; + + /// Mint token + fn mint_token( + &mut self, + target: &Address, + token: &Address, + amount: Amount, + ) -> Result<(), Self::Error>; + + /// Burn token + fn burn_token( + &mut self, + target: &Address, + token: &Address, amount: Amount, ) -> Result<(), Self::Error>; diff --git a/core/src/ledger/ibc/context/transfer_mod.rs b/core/src/ledger/ibc/context/transfer_mod.rs index ad0aa75800..8280f7c36b 100644 --- a/core/src/ledger/ibc/context/transfer_mod.rs +++ b/core/src/ledger/ibc/context/transfer_mod.rs @@ -388,7 +388,7 @@ where _port_id: &PortId, _channel_id: &ChannelId, ) -> Result { - Ok(Address::Internal(InternalAddress::IbcEscrow)) + Ok(Address::Internal(InternalAddress::Ibc)) } fn can_send_coins(&self) -> Result<(), TokenTransferError> { @@ -444,44 +444,18 @@ where ) -> Result<(), TokenTransferError> { // Assumes that the coin denom is prefixed with "port-id/channel-id" or // has no prefix - let (token, amount) = get_token_amount(coin)?; - - let src = if coin.denom.trace_path.is_empty() - || *from == Address::Internal(InternalAddress::IbcEscrow) - || *from == Address::Internal(InternalAddress::IbcMint) - { - token::balance_key(&token, from) - } else { - let prefix = storage::ibc_token_prefix(coin.denom.to_string()) - .map_err(|_| TokenTransferError::InvalidCoin { - coin: coin.to_string(), - })?; - token::multitoken_balance_key(&prefix, from) - }; - - let dest = if coin.denom.trace_path.is_empty() - || *to == Address::Internal(InternalAddress::IbcEscrow) - || *to == Address::Internal(InternalAddress::IbcBurn) - { - token::balance_key(&token, to) - } else { - let prefix = storage::ibc_token_prefix(coin.denom.to_string()) - .map_err(|_| TokenTransferError::InvalidCoin { - coin: coin.to_string(), - })?; - token::multitoken_balance_key(&prefix, to) - }; + let (ibc_token, amount) = get_token_amount(coin)?; self.ctx .borrow_mut() - .transfer_token(&src, &dest, amount) + .transfer_token(from, to, &ibc_token, amount) .map_err(|_| { TokenTransferError::ContextError(ContextError::ChannelError( ChannelError::Other { description: format!( "Sending a coin failed: from {}, to {}, amount {}", - src, - dest, + from, + to, amount.to_string_native() ), }, @@ -494,33 +468,18 @@ where account: &Self::AccountId, coin: &PrefixedCoin, ) -> Result<(), TokenTransferError> { - let (token, amount) = get_token_amount(coin)?; - - let src = token::balance_key( - &token, - &Address::Internal(InternalAddress::IbcMint), - ); - - let dest = if coin.denom.trace_path.is_empty() { - token::balance_key(&token, account) - } else { - let prefix = storage::ibc_token_prefix(coin.denom.to_string()) - .map_err(|_| TokenTransferError::InvalidCoin { - coin: coin.to_string(), - })?; - token::multitoken_balance_key(&prefix, account) - }; + // The trace path of the denom is already updated if receiving the token + let (ibc_token, amount) = get_token_amount(coin)?; self.ctx .borrow_mut() - .transfer_token(&src, &dest, amount) + .mint_token(account, &ibc_token, amount) .map_err(|_| { TokenTransferError::ContextError(ContextError::ChannelError( ChannelError::Other { description: format!( - "Sending a coin failed: from {}, to {}, amount {}", - src, - dest, + "Minting a coin failed: account {}, amount {}", + account, amount.to_string_native() ), }, @@ -533,33 +492,18 @@ where account: &Self::AccountId, coin: &PrefixedCoin, ) -> Result<(), TokenTransferError> { - let (token, amount) = get_token_amount(coin)?; - - let src = if coin.denom.trace_path.is_empty() { - token::balance_key(&token, account) - } else { - let prefix = storage::ibc_token_prefix(coin.denom.to_string()) - .map_err(|_| TokenTransferError::InvalidCoin { - coin: coin.to_string(), - })?; - token::multitoken_balance_key(&prefix, account) - }; - - let dest = token::balance_key( - &token, - &Address::Internal(InternalAddress::IbcBurn), - ); + let (ibc_token, amount) = get_token_amount(coin)?; + // The burn is "unminting" from the minted balance self.ctx .borrow_mut() - .transfer_token(&src, &dest, amount) + .burn_token(account, &ibc_token, amount) .map_err(|_| { TokenTransferError::ContextError(ContextError::ChannelError( ChannelError::Other { description: format!( - "Sending a coin failed: from {}, to {}, amount {}", - src, - dest, + "Burning a coin failed: account {}, amount {}", + account, amount.to_string_native() ), }, @@ -605,16 +549,15 @@ where } } -/// Get the token address and the amount from PrefixedCoin +/// Get the token address and the amount from PrefixedCoin. If the base denom is +/// not an address, it returns `IbcToken` fn get_token_amount( coin: &PrefixedCoin, ) -> Result<(Address, token::Amount), TokenTransferError> { - let token = - Address::decode(coin.denom.base_denom.as_str()).map_err(|_| { - TokenTransferError::InvalidCoin { - coin: coin.denom.base_denom.to_string(), - } - })?; + let token = match Address::decode(coin.denom.base_denom.as_str()) { + Ok(token_addr) if coin.denom.trace_path.is_empty() => token_addr, + _ => storage::ibc_token(coin.denom.to_string()), + }; let amount = coin.amount.try_into().map_err(|_| { TokenTransferError::InvalidCoin { diff --git a/core/src/ledger/ibc/mod.rs b/core/src/ledger/ibc/mod.rs index 9a184ee51e..b56e8ce54a 100644 --- a/core/src/ledger/ibc/mod.rs +++ b/core/src/ledger/ibc/mod.rs @@ -9,6 +9,7 @@ use std::fmt::Debug; use std::rc::Rc; use std::time::Duration; +use borsh::BorshDeserialize; pub use context::common::IbcCommonContext; pub use context::storage::{IbcStorageContext, ProofSpec}; pub use context::transfer_mod::{ModuleWrapper, TransferModule}; @@ -140,7 +141,7 @@ where } } - /// Restore the denom when it is hashed, i.e. the denom is `ibc/{hash}`. + /// Restore the denom when it is hashed fn restore_denom(&self, msg: MsgTransfer) -> Result { let mut msg = msg; // lookup the original denom with the IBC token hash @@ -151,7 +152,7 @@ where { let denom_key = storage::ibc_denom_key(token_hash); let denom = match self.ctx.borrow().read(&denom_key) { - Ok(Some(v)) => String::from_utf8(v).map_err(|e| { + Ok(Some(v)) => String::try_from_slice(&v[..]).map_err(|e| { Error::Denom(format!( "Decoding the denom string failed: {}", e diff --git a/core/src/ledger/ibc/storage.rs b/core/src/ledger/ibc/storage.rs index 3f0eb94d2a..1a47680f00 100644 --- a/core/src/ledger/ibc/storage.rs +++ b/core/src/ledger/ibc/storage.rs @@ -23,8 +23,6 @@ const CLIENTS_COUNTER: &str = "clients/counter"; const CONNECTIONS_COUNTER: &str = "connections/counter"; const CHANNELS_COUNTER: &str = "channelEnds/counter"; const DENOM: &str = "ibc_denom"; -/// Key segment for a multitoken related to IBC -pub const MULTITOKEN_STORAGE_KEY: &str = "ibc"; #[allow(missing_docs)] #[derive(Error, Debug)] @@ -44,67 +42,6 @@ pub enum Error { /// IBC storage functions result pub type Result = std::result::Result; -/// IBC prefix -#[allow(missing_docs)] -pub enum IbcPrefix { - Client, - Connection, - Channel, - Port, - Capability, - SeqSend, - SeqRecv, - SeqAck, - Commitment, - Receipt, - Ack, - Event, - Denom, - Unknown, -} - -/// Returns the prefix from the given key -pub fn ibc_prefix(key: &Key) -> Option { - match &key.segments[..] { - [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(prefix), ..] - if addr == &Address::Internal(InternalAddress::Ibc) => - { - Some(match &*prefix.raw() { - "clients" => IbcPrefix::Client, - "connections" => IbcPrefix::Connection, - "channelEnds" => IbcPrefix::Channel, - "ports" => IbcPrefix::Port, - "capabilities" => IbcPrefix::Capability, - "nextSequenceSend" => IbcPrefix::SeqSend, - "nextSequenceRecv" => IbcPrefix::SeqRecv, - "nextSequenceAck" => IbcPrefix::SeqAck, - "commitments" => IbcPrefix::Commitment, - "receipts" => IbcPrefix::Receipt, - "acks" => IbcPrefix::Ack, - "event" => IbcPrefix::Event, - "ibc_denom" => IbcPrefix::Denom, - _ => IbcPrefix::Unknown, - }) - } - _ => None, - } -} - -/// Check if the given key is a key of the client counter -pub fn is_client_counter_key(key: &Key) -> bool { - *key == client_counter_key() -} - -/// Check if the given key is a key of the connection counter -pub fn is_connection_counter_key(key: &Key) -> bool { - *key == connection_counter_key() -} - -/// Check if the given key is a key of the channel counter -pub fn is_channel_counter_key(key: &Key) -> bool { - *key == channel_counter_key() -} - /// Returns a key of the IBC-related data pub fn ibc_key(path: impl AsRef) -> Result { let path = Key::parse(path).map_err(Error::StorageKey)?; @@ -473,26 +410,20 @@ pub fn token(denom: impl AsRef) -> Result
{ /// Get the hash of IBC token address from the denom string pub fn token_hash_from_denom(denom: impl AsRef) -> Result> { - match denom - .as_ref() - .strip_prefix(&format!("{}/", MULTITOKEN_STORAGE_KEY)) - { - Some(addr_str) => { - let addr = Address::decode(addr_str).map_err(|e| { - Error::Denom(format!( - "Decoding the denom failed: ibc_token {}, error {}", - addr_str, e - )) - })?; - match addr { - Address::Internal(InternalAddress::IbcToken(h)) => Ok(Some(h)), - _ => Err(Error::Denom(format!( - "Unexpected address was given: {}", - addr - ))), - } - } - None => Ok(None), + let addr = Address::decode(denom.as_ref()).map_err(|e| { + Error::Denom(format!( + "Decoding the denom failed: denom {}, error {}", + denom.as_ref(), + e + )) + })?; + match addr { + Address::Established(_) => Ok(None), + Address::Internal(InternalAddress::IbcToken(h)) => Ok(Some(h)), + _ => Err(Error::Denom(format!( + "Unexpected address was given: {}", + addr + ))), } } @@ -503,17 +434,10 @@ pub fn calc_hash(denom: impl AsRef) -> String { format!("{:.width$x}", hasher.finalize(), width = HASH_HEX_LEN) } -/// Key's prefix of the received token over IBC -pub fn ibc_token_prefix(denom: impl AsRef) -> Result { - let token = token(&denom)?; +/// Obtain the IbcToken with the hash from the given denom +pub fn ibc_token(denom: impl AsRef) -> Address { let hash = calc_hash(&denom); - let ibc_token = Address::Internal(InternalAddress::IbcToken(hash)); - let prefix = Key::from(token.to_db_key()) - .push(&MULTITOKEN_STORAGE_KEY.to_owned()) - .expect("Cannot obtain a storage key") - .push(&ibc_token.to_db_key()) - .expect("Cannot obtain a storage key"); - Ok(prefix) + Address::Internal(InternalAddress::IbcToken(hash)) } /// Returns true if the given key is for IBC @@ -522,18 +446,24 @@ pub fn is_ibc_key(key: &Key) -> bool { DbKeySeg::AddressSeg(addr) if *addr == Address::Internal(InternalAddress::Ibc)) } -/// Returns true if the sub prefix is for IBC -pub fn is_ibc_sub_prefix(sub_prefix: &Key) -> bool { - matches!(&sub_prefix.segments[0], - DbKeySeg::StringSeg(s) if s == MULTITOKEN_STORAGE_KEY) -} - -/// Returns true if the given key is the denom key -pub fn is_ibc_denom_key(key: &Key) -> bool { +/// Returns the token hash if the given key is the denom key +pub fn is_ibc_denom_key(key: &Key) -> Option { match &key.segments[..] { - [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(prefix), ..] => { - addr == &Address::Internal(InternalAddress::Ibc) && prefix == DENOM + [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(prefix), + DbKeySeg::AddressSeg(Address::Internal(InternalAddress::IbcToken( + hash, + ))), + ] => { + if addr == &Address::Internal(InternalAddress::Ibc) + && prefix == DENOM + { + Some(hash.clone()) + } else { + None + } } - _ => false, + _ => None, } } diff --git a/core/src/ledger/parameters/mod.rs b/core/src/ledger/parameters/mod.rs index c507e55d49..03d27fa2da 100644 --- a/core/src/ledger/parameters/mod.rs +++ b/core/src/ledger/parameters/mod.rs @@ -46,6 +46,8 @@ pub struct Parameters { pub implicit_vp_code_hash: Hash, /// Expected number of epochs per year (read only) pub epochs_per_year: u64, + /// Maximum number of signature per transaction + pub max_signatures_per_transaction: u8, /// PoS gain p (read only) pub pos_gain_p: Dec, /// PoS gain d (read only) @@ -117,6 +119,7 @@ impl Parameters { tx_whitelist, implicit_vp_code_hash, epochs_per_year, + max_signatures_per_transaction, pos_gain_p, pos_gain_d, staked_ratio, @@ -168,6 +171,13 @@ impl Parameters { let epochs_per_year_key = storage::get_epochs_per_year_key(); storage.write(&epochs_per_year_key, epochs_per_year)?; + let max_signatures_per_transaction_key = + storage::get_max_signatures_per_transaction_key(); + storage.write( + &max_signatures_per_transaction_key, + max_signatures_per_transaction, + )?; + let pos_gain_p_key = storage::get_pos_gain_p_key(); storage.write(&pos_gain_p_key, pos_gain_p)?; @@ -197,6 +207,17 @@ impl Parameters { } } +/// Get the max signatures per transactio parameter +pub fn max_signatures_per_transaction( + storage: &S, +) -> storage_api::Result> +where + S: StorageRead, +{ + let key = storage::get_max_signatures_per_transaction_key(); + storage.read(&key) +} + /// Update the max_expected_time_per_block parameter in storage. Returns the /// parameters and gas cost. pub fn update_max_expected_time_per_block_parameter( @@ -340,6 +361,20 @@ where storage.write_bytes(&key, implicit_vp) } +/// Update the max signatures per transaction storage parameter +pub fn update_max_signature_per_tx( + storage: &mut S, + value: u8, +) -> storage_api::Result<()> +where + S: StorageRead + StorageWrite, +{ + let key = storage::get_max_signatures_per_transaction_key(); + // Using `fn write_bytes` here, because implicit_vp doesn't need to be + // encoded, it's bytes already. + storage.write(&key, value) +} + /// Read the the epoch duration parameter from store pub fn read_epoch_duration_parameter( storage: &S, @@ -434,6 +469,15 @@ where .ok_or(ReadError::ParametersMissing) .into_storage_result()?; + // read the maximum signatures per transaction + let max_signatures_per_transaction_key = + storage::get_max_signatures_per_transaction_key(); + let value: Option = + storage.read(&max_signatures_per_transaction_key)?; + let max_signatures_per_transaction: u8 = value + .ok_or(ReadError::ParametersMissing) + .into_storage_result()?; + // read PoS gain P let pos_gain_p_key = storage::get_pos_gain_p_key(); let value = storage.read(&pos_gain_p_key)?; @@ -478,6 +522,7 @@ where tx_whitelist, implicit_vp_code_hash, epochs_per_year, + max_signatures_per_transaction, pos_gain_p, pos_gain_d, staked_ratio, diff --git a/core/src/ledger/parameters/storage.rs b/core/src/ledger/parameters/storage.rs index 94498e3578..cdb484dd32 100644 --- a/core/src/ledger/parameters/storage.rs +++ b/core/src/ledger/parameters/storage.rs @@ -44,6 +44,7 @@ struct Keys { max_proposal_bytes: &'static str, faucet_account: &'static str, wrapper_tx_fees: &'static str, + max_signatures_per_transaction: &'static str, } /// Returns if the key is a parameter key. @@ -119,6 +120,14 @@ pub fn is_max_proposal_bytes_key(key: &Key) -> bool { is_max_proposal_bytes_key_at_addr(key, &ADDRESS) } +/// Returns if the key is the max signature per transacton key +pub fn is_max_signatures_per_transaction_key(key: &Key) -> bool { + matches!(&key.segments[..], [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(max_signatures_per_transaction), + ] if addr == &ADDRESS && max_signatures_per_transaction == Keys::VALUES.max_signatures_per_transaction) +} + /// Storage key used for epoch parameter. pub fn get_epoch_duration_storage_key() -> Key { get_epoch_duration_key_at_addr(ADDRESS) @@ -183,3 +192,15 @@ pub fn get_faucet_account_key() -> Key { pub fn get_wrapper_tx_fees_key() -> Key { get_wrapper_tx_fees_key_at_addr(ADDRESS) } + +/// Storage key used for the max signatures per transaction key +pub fn get_max_signatures_per_transaction_key() -> Key { + Key { + segments: vec![ + DbKeySeg::AddressSeg(ADDRESS), + DbKeySeg::StringSeg( + Keys::VALUES.max_signatures_per_transaction.to_string(), + ), + ], + } +} diff --git a/core/src/ledger/storage/masp_conversions.rs b/core/src/ledger/storage/masp_conversions.rs index 4dec02d4f5..bbb61ff50d 100644 --- a/core/src/ledger/storage/masp_conversions.rs +++ b/core/src/ledger/storage/masp_conversions.rs @@ -9,7 +9,7 @@ use masp_primitives::merkle_tree::FrozenCommitmentTree; use masp_primitives::sapling::Node; use crate::types::address::Address; -use crate::types::storage::{Epoch, Key}; +use crate::types::storage::Epoch; use crate::types::token::MaspDenom; /// A representation of the conversion state @@ -23,12 +23,7 @@ pub struct ConversionState { #[allow(clippy::type_complexity)] pub assets: BTreeMap< AssetType, - ( - (Address, Option, MaspDenom), - Epoch, - AllowedConversion, - usize, - ), + ((Address, MaspDenom), Epoch, AllowedConversion, usize), >, } @@ -66,24 +61,16 @@ where // have to use. This trick works under the assumption that reward tokens // from different epochs are exactly equivalent. let reward_asset = - encode_asset_type(address::nam(), &None, MaspDenom::Zero, Epoch(0)); + encode_asset_type(address::nam(), MaspDenom::Zero, Epoch(0)); // Conversions from the previous to current asset for each address let mut current_convs = - BTreeMap::<(Address, Option, MaspDenom), AllowedConversion>::new(); + BTreeMap::<(Address, MaspDenom), AllowedConversion>::new(); // Reward all tokens according to above reward rates - for ((addr, sub_prefix), reward) in &masp_rewards { + for (addr, reward) in &masp_rewards { // Dispense a transparent reward in parallel to the shielded rewards - let addr_bal: token::Amount = match sub_prefix { - None => wl_storage - .read(&token::balance_key(addr, &masp_addr))? - .unwrap_or_default(), - Some(sub) => wl_storage - .read(&token::multitoken_balance_key( - &token::multitoken_balance_prefix(addr, sub), - &masp_addr, - ))? - .unwrap_or_default(), - }; + let addr_bal: token::Amount = wl_storage + .read(&token::balance_key(addr, &masp_addr))? + .unwrap_or_default(); // The reward for each reward.1 units of the current asset is // reward.0 units of the reward token // Since floor(a) + floor(b) <= floor(a+b), there will always be @@ -95,18 +82,16 @@ where // cancelled out/replaced with the new asset let old_asset = encode_asset_type( addr.clone(), - sub_prefix, denom, wl_storage.storage.last_epoch, ); let new_asset = encode_asset_type( addr.clone(), - sub_prefix, denom, wl_storage.storage.block.epoch, ); current_convs.insert( - (addr.clone(), sub_prefix.clone(), denom), + (addr.clone(), denom), (MaspAmount::from_pair(old_asset, -(reward.1 as i64)).unwrap() + MaspAmount::from_pair(new_asset, reward.1).unwrap() + MaspAmount::from_pair(reward_asset, reward.0).unwrap()) @@ -116,7 +101,7 @@ where wl_storage.storage.conversion_state.assets.insert( old_asset, ( - (addr.clone(), sub_prefix.clone(), denom), + (addr.clone(), denom), wl_storage.storage.last_epoch, MaspAmount::zero().into(), 0, @@ -188,20 +173,19 @@ where // Add purely decoding entries to the assets map. These will be // overwritten before the creation of the next commitment tree - for (addr, sub_prefix) in masp_rewards.keys() { + for addr in masp_rewards.keys() { for denom in token::MaspDenom::iter() { // Add the decoding entry for the new asset type. An uncommited // node position is used since this is not a conversion. let new_asset = encode_asset_type( addr.clone(), - sub_prefix, denom, wl_storage.storage.block.epoch, ); wl_storage.storage.conversion_state.assets.insert( new_asset, ( - (addr.clone(), sub_prefix.clone(), denom), + (addr.clone(), denom), wl_storage.storage.block.epoch, MaspAmount::zero().into(), wl_storage.storage.conversion_state.tree.size(), @@ -229,19 +213,10 @@ where /// Construct MASP asset type with given epoch for given token pub fn encode_asset_type( addr: Address, - sub_prefix: &Option, denom: MaspDenom, epoch: Epoch, ) -> AssetType { - let new_asset_bytes = ( - addr, - sub_prefix - .as_ref() - .map(|k| k.to_string()) - .unwrap_or_default(), - denom, - epoch.0, - ) + let new_asset_bytes = (addr, denom, epoch.0) .try_to_vec() .expect("unable to serialize address and epoch"); AssetType::new(new_asset_bytes.as_ref()) diff --git a/core/src/ledger/storage/mockdb.rs b/core/src/ledger/storage/mockdb.rs index 24ffd0e59b..971584e742 100644 --- a/core/src/ledger/storage/mockdb.rs +++ b/core/src/ledger/storage/mockdb.rs @@ -53,7 +53,7 @@ impl DB for MockDB { Ok(()) } - fn read_last_block(&mut self) -> Result> { + fn read_last_block(&self) -> Result> { // Block height let height: BlockHeight = match self.0.borrow().get("height") { Some(bytes) => types::decode(bytes).map_err(Error::CodingError)?, @@ -211,8 +211,8 @@ impl DB for MockDB { } } - fn write_block( - &mut self, + fn add_block_to_batch( + &self, state: BlockStateWrite, _batch: &mut Self::WriteBatch, _is_full_commit: bool, diff --git a/core/src/ledger/storage/mod.rs b/core/src/ledger/storage/mod.rs index 5aa3d0cf80..94669a34f4 100644 --- a/core/src/ledger/storage/mod.rs +++ b/core/src/ledger/storage/mod.rs @@ -256,12 +256,12 @@ pub trait DB: std::fmt::Debug { fn flush(&self, wait: bool) -> Result<()>; /// Read the last committed block's metadata - fn read_last_block(&mut self) -> Result>; + fn read_last_block(&self) -> Result>; /// Write block's metadata. Merkle tree sub-stores are committed only when /// `is_full_commit` is `true` (typically on a beginning of a new epoch). - fn write_block( - &mut self, + fn add_block_to_batch( + &self, state: BlockStateWrite, batch: &mut Self::WriteBatch, is_full_commit: bool, @@ -532,7 +532,8 @@ where ethereum_height: self.ethereum_height.as_ref(), eth_events_queue: &self.eth_events_queue, }; - self.db.write_block(state, &mut batch, is_full_commit)?; + self.db + .add_block_to_batch(state, &mut batch, is_full_commit)?; let header = self .header .take() @@ -1224,6 +1225,7 @@ mod tests { tx_whitelist: vec![], implicit_vp_code_hash: Hash::zero(), epochs_per_year: 100, + max_signatures_per_transaction: 15, pos_gain_p: Dec::new(1,1).expect("Cannot fail"), pos_gain_d: Dec::new(1,1).expect("Cannot fail"), staked_ratio: Dec::new(1,1).expect("Cannot fail"), diff --git a/core/src/ledger/storage/wl_storage.rs b/core/src/ledger/storage/wl_storage.rs index 1cb7e56a27..4fb2490ab9 100644 --- a/core/src/ledger/storage/wl_storage.rs +++ b/core/src/ledger/storage/wl_storage.rs @@ -143,6 +143,12 @@ where /// Commit the current block's write log to the storage and commit the block /// to DB. Starts a new block write log. pub fn commit_block(&mut self) -> storage_api::Result<()> { + if self.storage.last_epoch != self.storage.block.epoch { + self.storage + .update_epoch_in_merkle_tree() + .into_storage_result()?; + } + let mut batch = D::batch(); self.write_log .commit_block(&mut self.storage, &mut batch) @@ -205,7 +211,6 @@ where .new_epoch(height, evidence_max_age_num_blocks); tracing::info!("Began a new epoch {}", self.storage.block.epoch); } - self.storage.update_epoch_in_merkle_tree()?; Ok(new_epoch) } } diff --git a/core/src/ledger/storage/write_log.rs b/core/src/ledger/storage/write_log.rs index 4b4c055c50..641fa7fc19 100644 --- a/core/src/ledger/storage/write_log.rs +++ b/core/src/ledger/storage/write_log.rs @@ -9,10 +9,13 @@ use thiserror::Error; use crate::ledger; use crate::ledger::storage::traits::StorageHasher; use crate::ledger::storage::Storage; -use crate::types::address::{Address, EstablishedAddressGen}; +use crate::types::address::{Address, EstablishedAddressGen, InternalAddress}; use crate::types::hash::Hash; use crate::types::ibc::IbcEvent; use crate::types::storage; +use crate::types::token::{ + is_any_minted_balance_key, is_any_minter_key, is_any_token_balance_key, +}; #[allow(missing_docs)] #[derive(Error, Debug)] @@ -470,21 +473,33 @@ impl WriteLog { // get changed keys grouped by the address for key in changed_keys.iter() { - for addr in &key.find_addresses() { - if verifiers_from_tx.contains(addr) - || initialized_accounts.contains(addr) - { - // We can skip this when the address has been added from the - // Tx above. - // Also skip if it's an address of a newly initialized - // account, because anything can be written into an - // account's storage in the same tx in which it's - // initialized (there is no VP in the state prior to tx - // execution). - continue; + // for token keys, trigger Multitoken VP and the owner's VP + if let Some([_, owner]) = is_any_token_balance_key(key) { + verifiers + .insert(Address::Internal(InternalAddress::Multitoken)); + verifiers.insert(owner.clone()); + } else if is_any_minted_balance_key(key).is_some() + || is_any_minter_key(key).is_some() + { + verifiers + .insert(Address::Internal(InternalAddress::Multitoken)); + } else { + for addr in &key.find_addresses() { + if verifiers_from_tx.contains(addr) + || initialized_accounts.contains(addr) + { + // We can skip this when the address has been added from + // the Tx above. + // Also skip if it's an address of a newly initialized + // account, because anything can be written into an + // account's storage in the same tx in which it's + // initialized (there is no VP in the state prior to tx + // execution). + continue; + } + // Add the address as a verifier + verifiers.insert(addr.clone()); } - // Add the address as a verifier - verifiers.insert(addr.clone()); } } (verifiers, changed_keys) diff --git a/core/src/ledger/storage_api/account.rs b/core/src/ledger/storage_api/account.rs new file mode 100644 index 0000000000..69d348710d --- /dev/null +++ b/core/src/ledger/storage_api/account.rs @@ -0,0 +1,101 @@ +//! Cryptographic signature keys storage API + +use super::*; +use crate::types::account::AccountPublicKeysMap; +use crate::types::address::Address; +use crate::types::key::*; +use crate::types::storage::Key; + +/// Init the subspace of a new account +pub fn init_account_storage( + storage: &mut S, + owner: &Address, + public_keys: &[common::PublicKey], + threshold: u8, +) -> Result<()> +where + S: StorageWrite + StorageRead, +{ + for (index, public_key) in public_keys.iter().enumerate() { + let index = index as u8; + pks_handle(owner).insert(storage, index, public_key.clone())?; + } + let threshold_key = threshold_key(owner); + storage.write(&threshold_key, threshold) +} + +/// Get the threshold associated with an account +pub fn threshold(storage: &S, owner: &Address) -> Result> +where + S: StorageRead, +{ + let threshold_key = threshold_key(owner); + storage.read(&threshold_key) +} + +/// Get the public keys index map associated with an account +pub fn public_keys( + storage: &S, + owner: &Address, +) -> Result> +where + S: StorageRead, +{ + let public_keys = pks_handle(owner) + .iter(storage)? + .filter_map(|data| match data { + Ok((_index, public_key)) => Some(public_key), + Err(_) => None, + }) + .collect::>(); + + Ok(public_keys) +} + +/// Get the public key index map associated with an account +pub fn public_keys_index_map( + storage: &S, + owner: &Address, +) -> Result +where + S: StorageRead, +{ + let public_keys = public_keys(storage, owner)?; + + Ok(AccountPublicKeysMap::from_iter(public_keys)) +} + +/// Check if an account exists in storage +pub fn exists(storage: &S, owner: &Address) -> Result +where + S: StorageRead, +{ + let vp_key = Key::validity_predicate(owner); + storage.has_key(&vp_key) +} + +/// Set public key at specific index +pub fn set_public_key_at( + storage: &mut S, + owner: &Address, + public_key: &common::PublicKey, + index: u8, +) -> Result<()> +where + S: StorageWrite + StorageRead, +{ + pks_handle(owner).insert(storage, index, public_key.clone())?; + Ok(()) +} + +/// Clear the public keys account subtorage space +pub fn clear_public_keys(storage: &mut S, owner: &Address) -> Result<()> +where + S: StorageWrite + StorageRead, +{ + let total_pks = pks_handle(owner).len(storage)?; + for index in 0..total_pks as u8 { + pks_handle(owner).remove(storage, &index)?; + } + Ok(()) +} diff --git a/core/src/ledger/storage_api/key.rs b/core/src/ledger/storage_api/key.rs index 6e3eba64aa..69bdd50720 100644 --- a/core/src/ledger/storage_api/key.rs +++ b/core/src/ledger/storage_api/key.rs @@ -4,23 +4,17 @@ use super::*; use crate::types::address::Address; use crate::types::key::*; -/// Get the public key associated with the given address. Returns `Ok(None)` if -/// not found. -pub fn get(storage: &S, owner: &Address) -> Result> -where - S: StorageRead, -{ - let key = pk_key(owner); - storage.read(&key) -} - /// Reveal a PK of an implicit account - the PK is written into the storage /// of the address derived from the PK. -pub fn reveal_pk(storage: &mut S, pk: &common::PublicKey) -> Result<()> +pub fn reveal_pk( + storage: &mut S, + public_key: &common::PublicKey, +) -> Result<()> where - S: StorageWrite, + S: StorageWrite + StorageRead, { - let addr: Address = pk.into(); - let key = pk_key(&addr); - storage.write(&key, pk) + let owner: Address = public_key.into(); + pks_handle(&owner).insert(storage, 0, public_key.clone())?; + + Ok(()) } diff --git a/core/src/ledger/storage_api/mod.rs b/core/src/ledger/storage_api/mod.rs index 451996d0e5..f97478e0d6 100644 --- a/core/src/ledger/storage_api/mod.rs +++ b/core/src/ledger/storage_api/mod.rs @@ -1,6 +1,7 @@ //! The common storage read trait is implemented in the storage, client RPC, tx //! and VPs (both native and WASM). +pub mod account; pub mod collections; mod error; pub mod governance; diff --git a/core/src/ledger/storage_api/token.rs b/core/src/ledger/storage_api/token.rs index 880d748274..1985d8325c 100644 --- a/core/src/ledger/storage_api/token.rs +++ b/core/src/ledger/storage_api/token.rs @@ -3,12 +3,10 @@ use super::{StorageRead, StorageWrite}; use crate::ledger::storage_api; use crate::types::address::Address; -use crate::types::storage::DbKeySeg::StringSeg; -use crate::types::storage::Key; use crate::types::token; pub use crate::types::token::{ - balance_key, is_balance_key, is_total_supply_key, total_supply_key, Amount, - Change, + balance_key, is_any_minted_balance_key, is_balance_key, minted_balance_key, + minter_key, Amount, Change, }; /// Read the balance of a given token and owner. @@ -33,7 +31,7 @@ pub fn read_total_supply( where S: StorageRead, { - let key = token::total_supply_key(token); + let key = token::minted_balance_key(token); let balance = storage.read::(&key)?.unwrap_or_default(); Ok(balance) } @@ -44,17 +42,11 @@ where pub fn read_denom( storage: &S, token: &Address, - sub_prefix: Option<&Key>, ) -> storage_api::Result> where S: StorageRead, { - if let Some(sub_prefix) = sub_prefix { - if sub_prefix.segments.contains(&StringSeg("ibc".to_string())) { - return Ok(Some(token::NATIVE_MAX_DECIMAL_PLACES.into())); - } - } - let key = token::denom_key(token, sub_prefix); + let key = token::denom_key(token); storage.read(&key).map(|opt_denom| { Some( opt_denom @@ -67,13 +59,12 @@ where pub fn write_denom( storage: &mut S, token: &Address, - sub_prefix: Option<&Key>, denom: token::Denomination, ) -> storage_api::Result<()> where S: StorageRead + StorageWrite, { - let key = token::denom_key(token, sub_prefix); + let key = token::denom_key(token); storage.write(&key, denom) } @@ -132,7 +123,7 @@ where storage_api::Error::new_const("Token balance overflow") })?; - let total_supply_key = token::total_supply_key(token); + let total_supply_key = token::minted_balance_key(token); let cur_supply = storage .read::(&total_supply_key)? .unwrap_or_default(); diff --git a/core/src/ledger/testnet_pow.rs b/core/src/ledger/testnet_pow.rs index aa1257a886..dcb7c9feb2 100644 --- a/core/src/ledger/testnet_pow.rs +++ b/core/src/ledger/testnet_pow.rs @@ -12,7 +12,7 @@ use crate::ledger::storage_api::collections::LazyMap; use crate::types::address::Address; use crate::types::hash::Hash; use crate::types::storage::{self, DbKeySeg, Key}; -use crate::types::token; +use crate::types::uint::Uint; /// Initialize faucet's storage. This must be called at genesis if faucet /// account is being used. @@ -20,7 +20,7 @@ pub fn init_faucet_storage( storage: &mut S, address: &Address, difficulty: Difficulty, - withdrawal_limit: token::Amount, + withdrawal_limit: Uint, ) -> storage_api::Result<()> where S: StorageWrite, @@ -457,7 +457,7 @@ where pub fn read_withdrawal_limit( storage: &S, address: &Address, -) -> storage_api::Result +) -> storage_api::Result where S: StorageRead, { @@ -471,7 +471,7 @@ where pub fn write_withdrawal_limit( storage: &mut S, address: &Address, - withdrawal_limit: token::Amount, + withdrawal_limit: Uint, ) -> Result<(), storage_api::Error> where S: StorageWrite, diff --git a/core/src/proto/mod.rs b/core/src/proto/mod.rs index 27ae37ff69..6eca982087 100644 --- a/core/src/proto/mod.rs +++ b/core/src/proto/mod.rs @@ -4,8 +4,8 @@ pub mod generated; mod types; pub use types::{ - Code, Commitment, Data, Dkg, Error, Header, MaspBuilder, Section, Signable, - SignableEthMessage, Signature, Signed, Tx, TxError, + Code, Commitment, Data, Dkg, Error, Header, MaspBuilder, MultiSignature, + Section, Signable, SignableEthMessage, Signature, Signed, Tx, TxError, }; #[cfg(test)] diff --git a/core/src/proto/types.rs b/core/src/proto/types.rs index 69da19f58d..981a86873b 100644 --- a/core/src/proto/types.rs +++ b/core/src/proto/types.rs @@ -1,8 +1,12 @@ use std::borrow::Cow; -use std::collections::{HashMap, HashSet}; +use std::cmp::Ordering; +use std::collections::{BTreeSet, HashMap, HashSet}; use std::convert::TryFrom; +use std::env; +use std::fs::File; use std::hash::{Hash, Hasher}; use std::marker::PhantomData; +use std::path::PathBuf; #[cfg(feature = "ferveo-tpke")] use ark_ec::AffineCurve; @@ -24,11 +28,12 @@ use super::generated::types; use crate::ledger::storage::{KeccakHasher, Sha256Hasher, StorageHasher}; #[cfg(any(feature = "tendermint", feature = "tendermint-abcipp"))] use crate::tendermint_proto::abci::ResponseDeliverTx; +use crate::types::account::AccountPublicKeysMap; use crate::types::address::Address; use crate::types::chain::ChainId; use crate::types::keccak::{keccak_hash, KeccakHash}; use crate::types::key::{self, *}; -use crate::types::storage::{Epoch, Key}; +use crate::types::storage::Epoch; use crate::types::time::DateTimeUtc; use crate::types::token::MaspDenom; #[cfg(feature = "ferveo-tpke")] @@ -57,6 +62,12 @@ pub enum Error { NoTimestampError, #[error("Timestamp is invalid: {0}")] InvalidTimestamp(prost_types::TimestampError), + #[error("The section signature is invalid: {0}")] + InvalidSectionSignature(String), + #[error("Couldn't serialize transaction from JSON at {0}")] + InvalidJSONDeserialization(String), + #[error("The wrapper signature is invalid.")] + InvalidWrapperSignature, } pub type Result = std::result::Result; @@ -360,6 +371,146 @@ impl Code { } } +#[derive( + Clone, + Debug, + BorshSerialize, + BorshDeserialize, + BorshSchema, + Serialize, + Deserialize, + Eq, + PartialEq, +)] +pub struct SignatureIndex { + pub signature: common::Signature, + pub index: u8, +} + +impl SignatureIndex { + pub fn from_single_signature(signature: common::Signature) -> Self { + Self { + signature, + index: 0, + } + } + + pub fn to_vec(&self) -> Vec { + vec![self.clone()] + } + + pub fn verify( + &self, + public_key_index_map: &AccountPublicKeysMap, + data: &impl SignableBytes, + ) -> std::result::Result<(), VerifySigError> { + let public_key = + public_key_index_map.get_public_key_from_index(self.index); + if let Some(public_key) = public_key { + common::SigScheme::verify_signature( + &public_key, + data, + &self.signature, + ) + } else { + Err(VerifySigError::MissingData) + } + } +} + +impl Ord for SignatureIndex { + fn cmp(&self, other: &Self) -> Ordering { + self.index.cmp(&other.index) + } +} + +impl PartialOrd for SignatureIndex { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +/// A section representing a multisig over another section +#[derive( + Clone, + Debug, + BorshSerialize, + BorshDeserialize, + BorshSchema, + Serialize, + Deserialize, +)] +pub struct MultiSignature { + /// The hash of the section being signed + targets: Vec, + /// The signature over the above hash + pub signatures: BTreeSet, +} + +impl MultiSignature { + /// Sign the given section hash with the given key and return a section + pub fn new( + targets: Vec, + secret_keys: &[common::SecretKey], + public_keys_index_map: &AccountPublicKeysMap, + ) -> Self { + let target = Self { + targets: targets.clone(), + signatures: BTreeSet::new(), + } + .get_hash(); + + let signatures_public_keys_map = + secret_keys.iter().map(|secret_key: &common::SecretKey| { + let signature = common::SigScheme::sign(secret_key, target); + let public_key = secret_key.ref_to(); + (public_key, signature) + }); + + let signatures = signatures_public_keys_map + .filter_map(|(public_key, signature)| { + let public_key_index = public_keys_index_map + .get_index_from_public_key(&public_key); + public_key_index + .map(|index| SignatureIndex { signature, index }) + }) + .collect::>(); + + Self { + targets, + signatures, + } + } + + pub fn total_signatures(&self) -> u8 { + self.signatures.len() as u8 + } + + /// Hash this signature section + pub fn hash<'a>(&self, hasher: &'a mut Sha256) -> &'a mut Sha256 { + hasher.update( + self.try_to_vec() + .expect("unable to serialize multisignature section"), + ); + hasher + } + + /// Get the hash of this section + pub fn get_hash(&self) -> crate::types::hash::Hash { + crate::types::hash::Hash( + self.hash(&mut Sha256::new()).finalize_reset().into(), + ) + } + + pub fn get_raw_hash(&self) -> crate::types::hash::Hash { + Self { + signatures: BTreeSet::new(), + ..self.clone() + } + .get_hash() + } +} + /// A section representing the signature over another section #[derive( Clone, @@ -371,26 +522,19 @@ impl Code { Deserialize, )] pub struct Signature { - /// Additional random data - salt: [u8; 8], /// The hash of the section being signed targets: Vec, - /// The public key to verify the below signature - pub_key: common::PublicKey, /// The signature over the above hashes pub signature: Option, } impl Signature { - /// Sign the given section hash with the given key and return a section pub fn new( targets: Vec, sec_key: &common::SecretKey, ) -> Self { let mut sec = Self { - salt: DateTimeUtc::now().0.timestamp_millis().to_le_bytes(), targets, - pub_key: sec_key.ref_to(), signature: None, }; sec.signature = Some(common::SigScheme::sign(sec_key, sec.get_hash())); @@ -414,11 +558,14 @@ impl Signature { } /// Verify that the signature contained in this section is valid - pub fn verify_signature(&self) -> std::result::Result<(), VerifySigError> { + pub fn verify_signature( + &self, + public_key: &common::PublicKey, + ) -> std::result::Result<(), VerifySigError> { let signature = self.signature.as_ref().ok_or(VerifySigError::MissingData)?; common::SigScheme::verify_signature( - &self.pub_key, + public_key, &Self { signature: None, ..self.clone() @@ -671,7 +818,7 @@ pub struct MaspBuilder { pub target: crate::types::hash::Hash, /// The decoded set of asset types used by the transaction. Useful for /// offline wallets trying to display AssetTypes. - pub asset_types: HashSet<(Address, Option, MaspDenom, Epoch)>, + pub asset_types: HashSet<(Address, MaspDenom, Epoch)>, /// Track how Info objects map to descriptors and outputs #[serde( serialize_with = "borsh_serde::", @@ -722,6 +869,7 @@ impl borsh::BorshSchema for MaspBuilder { Serialize, Deserialize, )] +#[allow(clippy::large_enum_variant)] pub enum Section { /// Transaction data that needs to be sent to hardware wallets Data(Data), @@ -730,6 +878,8 @@ pub enum Section { /// Transaction code. Sending to hardware wallets optional Code(Code), /// A transaction signature. Often produced by hardware wallets + SectionSignature(MultiSignature), + /// A transaction header/protocol signature Signature(Signature), /// Ciphertext obtained by encrypting arbitrary transaction sections Ciphertext(Ciphertext), @@ -759,7 +909,8 @@ impl Section { Self::Data(data) => data.hash(hasher), Self::ExtraData(extra) => extra.hash(hasher), Self::Code(code) => code.hash(hasher), - Self::Signature(sig) => sig.hash(hasher), + Self::Signature(signature) => signature.hash(hasher), + Self::SectionSignature(signatures) => signatures.hash(hasher), Self::Ciphertext(ct) => ct.hash(hasher), Self::MaspBuilder(mb) => mb.hash(hasher), Self::MaspTx(tx) => { @@ -831,6 +982,15 @@ impl Section { } } + /// Extract the section signature from this section if possible + pub fn section_signature(&self) -> Option { + if let Self::SectionSignature(data) = self { + Some(data.clone()) + } else { + None + } + } + /// Extract the ciphertext from this section if possible pub fn ciphertext(&self) -> Option { if let Self::Ciphertext(data) = self { @@ -987,6 +1147,24 @@ impl Tx { } } + /// Dump to file + // TODO: refactor + pub fn dump(&self, path: Option) -> Result { + let path = path + .unwrap_or(env::current_dir().expect( + "Should be able to get the current working directory.", + )); + let tx_filename = format!("{}.tx", self.header_hash()); + let tx_filename_clone_error = tx_filename.clone(); + let tx_filename_clone_ok = tx_filename.clone(); + let out = File::create(path.join(tx_filename)).unwrap(); + serde_json::to_writer_pretty(out, &self) + .map_err(|_| { + Error::InvalidJSONDeserialization(tx_filename_clone_error) + }) + .map(|_| path.join(tx_filename_clone_ok)) + } + /// Get the transaction header pub fn header(&self) -> Header { self.header.clone() @@ -1105,36 +1283,102 @@ impl Tx { bytes } + /// Verify that the section with the given hash has been signed by the given + /// public key + pub fn verify_section_signatures( + &self, + hashes: &[crate::types::hash::Hash], + public_keys_index_map: AccountPublicKeysMap, + threshold: u8, + max_signatures: Option, + ) -> std::result::Result<(), Error> { + let max_signatures = max_signatures.unwrap_or(u8::MAX); + let mut valid_signatures = 0; + + for section in &self.sections { + if let Section::SectionSignature(signatures) = section { + if !hashes.iter().all(|x| { + signatures.targets.contains(x) || section.get_hash() == *x + }) { + return Err(Error::InvalidSectionSignature( + "missing target hash.".to_string(), + )); + } + + for target in &signatures.targets { + if self.get_section(target).is_none() { + return Err(Error::InvalidSectionSignature( + "Missing target section.".to_string(), + )); + } + } + + if signatures.total_signatures() > max_signatures { + return Err(Error::InvalidSectionSignature( + "too many signatures.".to_string(), + )); + } + + if signatures.total_signatures() < threshold { + return Err(Error::InvalidSectionSignature( + "too few signatures.".to_string(), + )); + } + + for signature_index in &signatures.signatures { + let is_valid_signature = signature_index + .verify( + &public_keys_index_map, + &signatures.get_raw_hash(), + ) + .is_ok(); + if is_valid_signature { + valid_signatures += 1; + } + if valid_signatures >= threshold { + return Ok(()); + } + } + } + } + Err(Error::InvalidSectionSignature( + "invalid signatures.".to_string(), + )) + } + /// Verify that the sections with the given hashes have been signed together /// by the given public key. I.e. this function looks for one signature that /// covers over the given slice of hashes. pub fn verify_signature( &self, - pk: &common::PublicKey, + public_key: &common::PublicKey, hashes: &[crate::types::hash::Hash], - ) -> std::result::Result<&Signature, VerifySigError> { + ) -> Result<&Signature> { for section in &self.sections { - if let Section::Signature(sig_sec) = section { - // Check that the signer is matched and that the hashes being + if let Section::Signature(signature) = section { + // Check that the hashes being // checked are a subset of those in this section - if sig_sec.pub_key == *pk - && hashes.iter().all(|x| { - sig_sec.targets.contains(x) || section.get_hash() == *x - }) - { + if hashes.iter().all(|x| { + signature.targets.contains(x) || section.get_hash() == *x + }) { // Ensure that all the sections the signature signs over are // present - for target in &sig_sec.targets { + for target in &signature.targets { if self.get_section(target).is_none() { - return Err(VerifySigError::MissingData); + return Err(Error::InvalidSectionSignature( + "Target section is missing.".to_string(), + )); } } // Finally verify that the signature itself is valid - return sig_sec.verify_signature().map(|_| sig_sec); + return signature + .verify_signature(public_key) + .map(|_| signature) + .map_err(|_| Error::InvalidWrapperSignature); } } } - Err(VerifySigError::MissingData) + Err(Error::InvalidWrapperSignature) } /// Validate any and all ciphertexts stored in this transaction diff --git a/core/src/types/account.rs b/core/src/types/account.rs new file mode 100644 index 0000000000..95b8b6e2d3 --- /dev/null +++ b/core/src/types/account.rs @@ -0,0 +1,94 @@ +//! Helper structures to manage accounts + +use std::collections::HashMap; + +use borsh::{BorshDeserialize, BorshSerialize}; +use serde::{Deserialize, Serialize}; + +use super::address::Address; +use super::key::common; + +#[derive( + Debug, Clone, BorshSerialize, BorshDeserialize, Serialize, Deserialize, +)] +/// Account data +pub struct Account { + /// The map between indexes and public keys for an account + pub public_keys_map: AccountPublicKeysMap, + /// The account signature threshold + pub threshold: u8, + /// The address corresponding to the account owner + pub address: Address, +} + +impl Account { + /// Retrive a public key from the index + pub fn get_public_key_from_index( + &self, + index: u8, + ) -> Option { + self.public_keys_map.get_public_key_from_index(index) + } + + /// Retrive the index of a public key + pub fn get_index_from_public_key( + &self, + public_key: &common::PublicKey, + ) -> Option { + self.public_keys_map.get_index_from_public_key(public_key) + } +} + +#[derive( + Debug, Clone, BorshSerialize, BorshDeserialize, Serialize, Deserialize, +)] +/// Holds the public key map data as a bimap for efficient quering +pub struct AccountPublicKeysMap { + /// Hashmap from public key to index + pub pk_to_idx: HashMap, + /// Hashmap from index key to public key + pub idx_to_pk: HashMap, +} + +impl FromIterator for AccountPublicKeysMap { + fn from_iter>(iter: T) -> Self { + let mut pk_to_idx = HashMap::new(); + let mut idx_to_pk = HashMap::new(); + + for (index, public_key) in iter.into_iter().enumerate() { + pk_to_idx.insert(public_key.to_owned(), index as u8); + idx_to_pk.insert(index as u8, public_key.to_owned()); + } + + Self { + pk_to_idx, + idx_to_pk, + } + } +} + +impl AccountPublicKeysMap { + /// Retrive a public key from the index + pub fn get_public_key_from_index( + &self, + index: u8, + ) -> Option { + self.idx_to_pk.get(&index).cloned() + } + + /// Retrive the index of a public key + pub fn get_index_from_public_key( + &self, + public_key: &common::PublicKey, + ) -> Option { + self.pk_to_idx.get(public_key).cloned() + } + + /// Return an empty AccountPublicKeysMap + pub fn empty() -> Self { + AccountPublicKeysMap { + pk_to_idx: HashMap::new(), + idx_to_pk: HashMap::new(), + } + } +} diff --git a/core/src/types/address.rs b/core/src/types/address.rs index eca6c87251..1a6611a2f5 100644 --- a/core/src/types/address.rs +++ b/core/src/types/address.rs @@ -17,7 +17,6 @@ use crate::ibc::signer::Signer; use crate::types::ethereum_events::EthAddress; use crate::types::key; use crate::types::key::PublicKeyHash; -use crate::types::storage::Key; use crate::types::token::Denomination; /// The length of an established [`Address`] encoded with Borsh. @@ -54,10 +53,6 @@ pub const FIXED_LEN_STRING_BYTES: usize = 45; /// Internal IBC address pub const IBC: Address = Address::Internal(InternalAddress::Ibc); -/// Internal IBC token burn address -pub const IBC_BURN: Address = Address::Internal(InternalAddress::IbcBurn); -/// Internal IBC token mint address -pub const IBC_MINT: Address = Address::Internal(InternalAddress::IbcMint); /// Internal ledger parameters address pub const PARAMETERS: Address = Address::Internal(InternalAddress::Parameters); /// Internal PoS address @@ -84,18 +79,14 @@ mod internal { "ano::Slash Fund "; pub const IBC: &str = "ibc::Inter-Blockchain Communication "; - pub const IBC_ESCROW: &str = - "ibc::IBC Escrow Address "; - pub const IBC_BURN: &str = - "ibc::IBC Burn Address "; - pub const IBC_MINT: &str = - "ibc::IBC Mint Address "; pub const ETH_BRIDGE: &str = "ano::ETH Bridge Address "; pub const ETH_BRIDGE_POOL: &str = "ano::ETH Bridge Pool Address "; pub const REPLAY_PROTECTION: &str = "ano::Replay Protection "; + pub const MULTITOKEN: &str = + "ano::Multitoken "; } /// Fixed-length address strings prefix for established addresses. @@ -106,6 +97,8 @@ const PREFIX_IMPLICIT: &str = "imp"; const PREFIX_INTERNAL: &str = "ano"; /// Fixed-length address strings prefix for IBC addresses. const PREFIX_IBC: &str = "ibc"; +/// Fixed-length address strings prefix for Ethereum addresses. +const PREFIX_ETH: &str = "eth"; #[allow(missing_docs)] #[derive(Error, Debug)] @@ -233,20 +226,23 @@ impl Address { InternalAddress::IbcToken(hash) => { format!("{}::{}", PREFIX_IBC, hash) } - InternalAddress::IbcEscrow => { - internal::IBC_ESCROW.to_string() - } - InternalAddress::IbcBurn => internal::IBC_BURN.to_string(), - InternalAddress::IbcMint => internal::IBC_MINT.to_string(), InternalAddress::EthBridge => { internal::ETH_BRIDGE.to_string() } InternalAddress::EthBridgePool => { internal::ETH_BRIDGE_POOL.to_string() } + InternalAddress::Erc20(eth_addr) => { + let eth_addr = + eth_addr.to_canonical().replace("0x", ""); + format!("{}::{}", PREFIX_ETH, eth_addr) + } InternalAddress::ReplayProtection => { internal::REPLAY_PROTECTION.to_string() } + InternalAddress::Multitoken => { + internal::MULTITOKEN.to_string() + } }; debug_assert_eq!(string.len(), FIXED_LEN_STRING_BYTES); string @@ -320,6 +316,9 @@ impl Address { internal::REPLAY_PROTECTION => { Ok(Address::Internal(InternalAddress::ReplayProtection)) } + internal::MULTITOKEN => { + Ok(Address::Internal(InternalAddress::Multitoken)) + } _ => Err(Error::new( ErrorKind::InvalidData, "Invalid internal address", @@ -327,15 +326,6 @@ impl Address { }, Some((PREFIX_IBC, raw)) => match string { internal::IBC => Ok(Address::Internal(InternalAddress::Ibc)), - internal::IBC_ESCROW => { - Ok(Address::Internal(InternalAddress::IbcEscrow)) - } - internal::IBC_BURN => { - Ok(Address::Internal(InternalAddress::IbcBurn)) - } - internal::IBC_MINT => { - Ok(Address::Internal(InternalAddress::IbcMint)) - } _ if raw.len() == HASH_HEX_LEN => Ok(Address::Internal( InternalAddress::IbcToken(raw.to_string()), )), @@ -344,6 +334,23 @@ impl Address { "Invalid IBC internal address", )), }, + Some((PREFIX_ETH, raw)) => match string { + _ if raw.len() == HASH_HEX_LEN => { + match EthAddress::from_str(&format!("0x{}", raw)) { + Ok(eth_addr) => Ok(Address::Internal( + InternalAddress::Erc20(eth_addr), + )), + Err(e) => Err(Error::new( + ErrorKind::InvalidData, + e.to_string(), + )), + } + } + _ => Err(Error::new( + ErrorKind::InvalidData, + "Invalid ERC20 internal address", + )), + }, _ => Err(Error::new( ErrorKind::InvalidData, "Invalid address prefix", @@ -532,12 +539,6 @@ pub enum InternalAddress { Ibc, /// IBC-related token IbcToken(String), - /// Escrow for IBC token transfer - IbcEscrow, - /// Burn tokens with IBC token transfer - IbcBurn, - /// Mint tokens from this address with IBC token transfer - IbcMint, /// Governance address Governance, /// SlashFund address for governance @@ -546,24 +547,12 @@ pub enum InternalAddress { EthBridge, /// The pool of transactions to be relayed to Ethereum EthBridgePool, + /// ERC20 token for Ethereum bridge + Erc20(EthAddress), /// Replay protection contains transactions' hash ReplayProtection, -} - -impl InternalAddress { - /// Get an IBC token address from the port ID and channel ID - pub fn ibc_token_address( - port_id: String, - channel_id: String, - token: &Address, - ) -> Self { - let mut hasher = Sha256::new(); - let s = format!("{}/{}/{}", port_id, channel_id, token); - hasher.update(&s); - let hash = - format!("{:.width$x}", hasher.finalize(), width = HASH_HEX_LEN); - InternalAddress::IbcToken(hash) - } + /// Multitoken + Multitoken, } impl Display for InternalAddress { @@ -579,12 +568,11 @@ impl Display for InternalAddress { Self::SlashFund => "SlashFund".to_string(), Self::Ibc => "IBC".to_string(), Self::IbcToken(hash) => format!("IbcToken: {}", hash), - Self::IbcEscrow => "IbcEscrow".to_string(), - Self::IbcBurn => "IbcBurn".to_string(), - Self::IbcMint => "IbcMint".to_string(), Self::EthBridge => "EthBridge".to_string(), Self::EthBridgePool => "EthBridgePool".to_string(), + Self::Erc20(eth_addr) => format!("Erc20: {}", eth_addr), Self::ReplayProtection => "ReplayProtection".to_string(), + Self::Multitoken => "Multitoken".to_string(), } ) } @@ -670,15 +658,15 @@ pub fn tokens() -> HashMap { /// Temporary helper for testing, a hash map of tokens addresses with their /// MASP XAN incentive schedules. If the reward is (a, b) then a rewarded tokens /// are dispensed for every b possessed tokens. -pub fn masp_rewards() -> HashMap<(Address, Option), (u64, u64)> { +pub fn masp_rewards() -> HashMap { vec![ - ((nam(), None), (0, 100)), - ((btc(), None), (1, 100)), - ((eth(), None), (2, 100)), - ((dot(), None), (3, 100)), - ((schnitzel(), None), (4, 100)), - ((apfel(), None), (5, 100)), - ((kartoffel(), None), (6, 100)), + (nam(), (0, 100)), + (btc(), (1, 100)), + (eth(), (2, 100)), + (dot(), (3, 100)), + (schnitzel(), (4, 100)), + (apfel(), (5, 100)), + (kartoffel(), (6, 100)), ] .into_iter() .collect() @@ -875,35 +863,30 @@ pub mod testing { InternalAddress::Parameters => {} InternalAddress::Ibc => {} InternalAddress::IbcToken(_) => {} - InternalAddress::IbcEscrow => {} - InternalAddress::IbcBurn => {} - InternalAddress::IbcMint => {} InternalAddress::EthBridge => {} InternalAddress::EthBridgePool => {} - InternalAddress::ReplayProtection => {} /* Add new addresses in - * the - * `prop_oneof` below. */ + InternalAddress::Erc20(_) => {} + InternalAddress::ReplayProtection => {} + InternalAddress::Multitoken => {} /* Add new addresses in the + * `prop_oneof` below. */ }; prop_oneof![ Just(InternalAddress::PoS), Just(InternalAddress::PosSlashPool), Just(InternalAddress::Ibc), Just(InternalAddress::Parameters), - Just(InternalAddress::Ibc), arb_ibc_token(), - Just(InternalAddress::IbcEscrow), - Just(InternalAddress::IbcBurn), - Just(InternalAddress::IbcMint), Just(InternalAddress::Governance), Just(InternalAddress::SlashFund), Just(InternalAddress::EthBridge), Just(InternalAddress::EthBridgePool), - Just(InternalAddress::ReplayProtection) + Just(arb_erc20()), + Just(InternalAddress::ReplayProtection), + Just(InternalAddress::Multitoken), ] } fn arb_ibc_token() -> impl Strategy { - // use sha2::{Digest, Sha256}; ("[a-zA-Z0-9_]{2,128}", any::()).prop_map(|(id, counter)| { let mut hasher = sha2::Sha256::new(); let s = format!( @@ -918,4 +901,9 @@ pub mod testing { InternalAddress::IbcToken(hash) }) } + + fn arb_erc20() -> InternalAddress { + use crate::types::ethereum_events::testing::arbitrary_eth_address; + InternalAddress::Erc20(arbitrary_eth_address()) + } } diff --git a/core/src/types/eth_abi.rs b/core/src/types/eth_abi.rs index 5beaa6fe8f..adda87e6c9 100644 --- a/core/src/types/eth_abi.rs +++ b/core/src/types/eth_abi.rs @@ -198,7 +198,7 @@ mod tests { ) .expect("Test failed"), ], - voting_powers: vec![8828299.into()], + voting_powers: vec![8828299.try_into().unwrap()], epoch: 0.into(), }; let encoded = valset_update.encode().into_inner(); diff --git a/core/src/types/key/mod.rs b/core/src/types/key/mod.rs index 8c59262f72..685e3464f7 100644 --- a/core/src/types/key/mod.rs +++ b/core/src/types/key/mod.rs @@ -12,6 +12,8 @@ use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use data_encoding::HEXUPPER; +use lazy_map::LazyMap; +use namada_macros::StorageKeys; #[cfg(feature = "rand")] use rand::{CryptoRng, RngCore}; use serde::Serialize; @@ -19,25 +21,45 @@ use sha2::{Digest, Sha256}; use thiserror::Error; use super::address::Address; -use super::storage::{self, DbKeySeg, Key, KeySeg}; +use super::storage::{self, DbKeySeg, Key}; use crate::ledger::storage::{Sha256Hasher, StorageHasher}; +use crate::ledger::storage_api::collections::{lazy_map, LazyCollection}; use crate::types::address; -const PK_STORAGE_KEY: &str = "public_key"; -const PROTOCOL_PK_STORAGE_KEY: &str = "protocol_public_key"; +/// Storage keys for account. +#[derive(StorageKeys)] +struct Keys { + public_keys: &'static str, + threshold: &'static str, + protocol_public_keys: &'static str, +} /// Obtain a storage key for user's public key. -pub fn pk_key(owner: &Address) -> storage::Key { - Key::from(owner.to_db_key()) - .push(&PK_STORAGE_KEY.to_owned()) - .expect("Cannot obtain a storage key") +pub fn pks_key_prefix(owner: &Address) -> storage::Key { + Key { + segments: vec![ + DbKeySeg::AddressSeg(owner.to_owned()), + DbKeySeg::StringSeg(Keys::VALUES.public_keys.to_string()), + ], + } +} + +/// Object that LazyMap handler for the user's public key subspace +pub fn pks_handle(owner: &Address) -> LazyMap { + LazyMap::open(pks_key_prefix(owner)) } /// Check if the given storage key is a public key. If it is, returns the owner. -pub fn is_pk_key(key: &Key) -> Option<&Address> { +pub fn is_pks_key(key: &Key) -> Option<&Address> { match &key.segments[..] { - [DbKeySeg::AddressSeg(owner), DbKeySeg::StringSeg(key)] - if key == PK_STORAGE_KEY => + [ + DbKeySeg::AddressSeg(owner), + DbKeySeg::StringSeg(prefix), + DbKeySeg::StringSeg(data), + DbKeySeg::StringSeg(index), + ] if prefix.as_str() == Keys::VALUES.public_keys + && data.as_str() == lazy_map::DATA_SUBKEY + && index.parse::().is_ok() => { Some(owner) } @@ -45,18 +67,43 @@ pub fn is_pk_key(key: &Key) -> Option<&Address> { } } +/// Check if the given storage key is a threshol key. +pub fn is_threshold_key(key: &Key) -> Option<&Address> { + match &key.segments[..] { + [DbKeySeg::AddressSeg(owner), DbKeySeg::StringSeg(prefix)] + if prefix.as_str() == Keys::VALUES.threshold => + { + Some(owner) + } + _ => None, + } +} + +/// Obtain the storage key for a user threshold +pub fn threshold_key(owner: &Address) -> storage::Key { + Key { + segments: vec![ + DbKeySeg::AddressSeg(owner.to_owned()), + DbKeySeg::StringSeg(Keys::VALUES.threshold.to_string()), + ], + } +} + /// Obtain a storage key for user's protocol public key. pub fn protocol_pk_key(owner: &Address) -> storage::Key { - Key::from(owner.to_db_key()) - .push(&PROTOCOL_PK_STORAGE_KEY.to_owned()) - .expect("Cannot obtain a storage key") + Key { + segments: vec![ + DbKeySeg::AddressSeg(owner.to_owned()), + DbKeySeg::StringSeg(Keys::VALUES.protocol_public_keys.to_string()), + ], + } } /// Check if the given storage key is a public key. If it is, returns the owner. pub fn is_protocol_pk_key(key: &Key) -> Option<&Address> { match &key.segments[..] { [DbKeySeg::AddressSeg(owner), DbKeySeg::StringSeg(key)] - if key == PROTOCOL_PK_STORAGE_KEY => + if key.as_str() == Keys::VALUES.protocol_public_keys => { Some(owner) } diff --git a/core/src/types/mod.rs b/core/src/types/mod.rs index c35b314296..8303e75742 100644 --- a/core/src/types/mod.rs +++ b/core/src/types/mod.rs @@ -1,5 +1,6 @@ //! Types definitions. +pub mod account; pub mod address; pub mod chain; pub mod dec; diff --git a/core/src/types/token.rs b/core/src/types/token.rs index b7c731e0e1..bf4e23cbe3 100644 --- a/core/src/types/token.rs +++ b/core/src/types/token.rs @@ -1,6 +1,6 @@ //! A basic fungible token -use std::fmt::{Display, Formatter}; +use std::fmt::Display; use std::iter::Sum; use std::ops::{Add, AddAssign, Div, Mul, Sub, SubAssign}; use std::str::FromStr; @@ -15,7 +15,9 @@ use super::dec::POS_DECIMAL_PRECISION; use crate::ibc::applications::transfer::Amount as IbcAmount; use crate::ledger::storage_api::token::read_denom; use crate::ledger::storage_api::{self, StorageRead}; -use crate::types::address::{masp, Address, DecodeError as AddressError}; +use crate::types::address::{ + masp, Address, DecodeError as AddressError, InternalAddress, +}; use crate::types::dec::Dec; use crate::types::hash::Hash; use crate::types::storage; @@ -199,15 +201,13 @@ impl Amount { pub fn denominated( &self, token: &Address, - sub_prefix: Option<&Key>, storage: &impl StorageRead, ) -> storage_api::Result { - let denom = - read_denom(storage, token, sub_prefix)?.ok_or_else(|| { - storage_api::Error::SimpleMessage( - "No denomination found in storage for the given token", - ) - })?; + let denom = read_denom(storage, token)?.ok_or_else(|| { + storage_api::Error::SimpleMessage( + "No denomination found in storage for the given token", + ) + })?; Ok(DenominatedAmount { amount: *self, denom, @@ -767,6 +767,10 @@ impl TryFrom for Amount { pub const BALANCE_STORAGE_KEY: &str = "balance"; /// Key segment for a denomination key pub const DENOM_STORAGE_KEY: &str = "denomination"; +/// Key segment for multitoken minter +pub const MINTER_STORAGE_KEY: &str = "minter"; +/// Key segment for minted balance +pub const MINTED_STORAGE_KEY: &str = "minted"; /// Key segment for head shielded transaction pointer keys pub const HEAD_TX_KEY: &str = "head-tx"; /// Key segment prefix for shielded transaction key @@ -775,91 +779,41 @@ pub const TX_KEY_PREFIX: &str = "tx-"; pub const CONVERSION_KEY_PREFIX: &str = "conv"; /// Key segment prefix for pinned shielded transactions pub const PIN_KEY_PREFIX: &str = "pin-"; -const TOTAL_SUPPLY_STORAGE_KEY: &str = "total_supply"; - -/// A fully qualified (multi-) token address. -#[derive( - Clone, - PartialEq, - Eq, - PartialOrd, - Ord, - Debug, - Hash, - BorshSerialize, - BorshDeserialize, -)] -pub struct TokenAddress { - /// The address of the (multi-) token - pub address: Address, - /// If it is a mutli-token, this indicates the sub-token. - pub sub_prefix: Option, -} - -impl TokenAddress { - /// A function for displaying a [`TokenAddress`]. Takes a - /// human readable name of the token as input. - pub fn format_with_alias(&self, alias: &str) -> String { - format!( - "{}{}", - alias, - self.sub_prefix - .as_ref() - .map(|k| format!("/{}", k)) - .unwrap_or_default() - ) - } -} - -impl Display for TokenAddress { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - let formatted = format!( - "{}{}", - self.address, - self.sub_prefix - .as_ref() - .map(|k| format!("/{}", k)) - .unwrap_or_default() - ); - f.write_str(&formatted) - } -} /// Obtain a storage key for user's balance. pub fn balance_key(token_addr: &Address, owner: &Address) -> Key { - Key::from(token_addr.to_db_key()) - .push(&BALANCE_STORAGE_KEY.to_owned()) - .expect("Cannot obtain a storage key") + balance_prefix(token_addr) .push(&owner.to_db_key()) .expect("Cannot obtain a storage key") } /// Obtain a storage key prefix for all users' balances. pub fn balance_prefix(token_addr: &Address) -> Key { - Key::from(token_addr.to_db_key()) + Key::from(Address::Internal(InternalAddress::Multitoken).to_db_key()) + .push(&token_addr.to_db_key()) + .expect("Cannot obtain a storage key") .push(&BALANCE_STORAGE_KEY.to_owned()) .expect("Cannot obtain a storage key") } -/// Obtain a storage key prefix for multitoken balances. -pub fn multitoken_balance_prefix( - token_addr: &Address, - sub_prefix: &Key, -) -> Key { - Key::from(token_addr.to_db_key()).join(sub_prefix) +/// Obtain a storage key for the multitoken minter. +pub fn minter_key(token_addr: &Address) -> Key { + Key::from(Address::Internal(InternalAddress::Multitoken).to_db_key()) + .push(&token_addr.to_db_key()) + .expect("Cannot obtain a storage key") + .push(&MINTER_STORAGE_KEY.to_owned()) + .expect("Cannot obtain a storage key") } -/// Obtain a storage key for user's multitoken balance. -pub fn multitoken_balance_key(prefix: &Key, owner: &Address) -> Key { - prefix - .push(&BALANCE_STORAGE_KEY.to_owned()) - .expect("Cannot obtain a storage key") - .push(&owner.to_db_key()) +/// Obtain a storage key for the minted multitoken balance. +pub fn minted_balance_key(token_addr: &Address) -> Key { + balance_prefix(token_addr) + .push(&MINTED_STORAGE_KEY.to_owned()) .expect("Cannot obtain a storage key") } /// Check if the given storage key is balance key for the given token. If it is, -/// returns the owner. +/// returns the owner. For minted balances, use [`is_any_minted_balance_key()`]. pub fn is_balance_key<'a>( token_addr: &Address, key: &'a Key, @@ -867,9 +821,15 @@ pub fn is_balance_key<'a>( match &key.segments[..] { [ DbKeySeg::AddressSeg(addr), - DbKeySeg::StringSeg(key), + DbKeySeg::AddressSeg(token), + DbKeySeg::StringSeg(balance), DbKeySeg::AddressSeg(owner), - ] if key == BALANCE_STORAGE_KEY && addr == token_addr => Some(owner), + ] if *addr == Address::Internal(InternalAddress::Multitoken) + && token == token_addr + && balance == BALANCE_STORAGE_KEY => + { + Some(owner) + } _ => None, } } @@ -879,25 +839,24 @@ pub fn is_balance_key<'a>( pub fn is_any_token_balance_key(key: &Key) -> Option<[&Address; 2]> { match &key.segments[..] { [ + DbKeySeg::AddressSeg(addr), DbKeySeg::AddressSeg(token), - DbKeySeg::StringSeg(key), + DbKeySeg::StringSeg(balance), DbKeySeg::AddressSeg(owner), - ] if key == BALANCE_STORAGE_KEY => Some([token, owner]), + ] if *addr == Address::Internal(InternalAddress::Multitoken) + && balance == BALANCE_STORAGE_KEY => + { + Some([token, owner]) + } _ => None, } } /// Obtain a storage key denomination of a token. -pub fn denom_key(token_addr: &Address, sub_prefix: Option<&Key>) -> Key { - match sub_prefix { - Some(sub) => Key::from(token_addr.to_db_key()) - .join(sub) - .push(&DENOM_STORAGE_KEY.to_owned()) - .expect("Cannot obtain a storage key"), - None => Key::from(token_addr.to_db_key()) - .push(&DENOM_STORAGE_KEY.to_owned()) - .expect("Cannot obtain a storage key"), - } +pub fn denom_key(token_addr: &Address) -> Key { + Key::from(token_addr.to_db_key()) + .push(&DENOM_STORAGE_KEY.to_owned()) + .expect("Cannot obtain a storage key") } /// Check if the given storage key is a denomination key for the given token. @@ -920,71 +879,37 @@ pub fn is_masp_key(key: &Key) -> bool { || key.starts_with(PIN_KEY_PREFIX))) } -/// Storage key for total supply of a token -pub fn total_supply_key(token_address: &Address) -> Key { - Key::from(token_address.to_db_key()) - .push(&TOTAL_SUPPLY_STORAGE_KEY.to_owned()) - .expect("Cannot obtain a storage key") -} - -/// Is storage key for total supply of a specific token? -pub fn is_total_supply_key(key: &Key, token_address: &Address) -> bool { - matches!(&key.segments[..], [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(key)] if addr == token_address && key == TOTAL_SUPPLY_STORAGE_KEY) -} - -/// Check if the given storage key is multitoken balance key for the given -/// token. If it is, returns the sub prefix and the owner. -pub fn is_multitoken_balance_key<'a>( - token_addr: &Address, - key: &'a Key, -) -> Option<(Key, &'a Address)> { - match key.segments.first() { - Some(DbKeySeg::AddressSeg(addr)) if addr == token_addr => { - multitoken_balance_owner(key) +/// Check if the given storage key is for a minter of a unspecified token. +/// If it is, returns the token. +pub fn is_any_minter_key(key: &Key) -> Option<&Address> { + match &key.segments[..] { + [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::AddressSeg(token), + DbKeySeg::StringSeg(minter), + ] if *addr == Address::Internal(InternalAddress::Multitoken) + && minter == MINTER_STORAGE_KEY => + { + Some(token) } _ => None, } } -/// Check if the given storage key is multitoken balance key for unspecified -/// token. If it is, returns the sub prefix and the token and owner addresses. -pub fn is_any_multitoken_balance_key( - key: &Key, -) -> Option<(Key, [&Address; 2])> { - match key.segments.first() { - Some(DbKeySeg::AddressSeg(token)) => multitoken_balance_owner(key) - .map(|(sub, owner)| (sub, [token, owner])), - _ => None, - } -} - -/// Check if the given storage key is token or multitoken balance key for -/// unspecified token. If it is, returns the token and owner addresses. -pub fn is_any_token_or_multitoken_balance_key( - key: &Key, -) -> Option<[&Address; 2]> { - is_any_multitoken_balance_key(key) - .map(|a| a.1) - .or_else(|| is_any_token_balance_key(key)) -} - -fn multitoken_balance_owner(key: &Key) -> Option<(Key, &Address)> { - let len = key.segments.len(); - if len < 4 { - // the key of a multitoken should have 1 or more segments other than - // token, balance, owner - return None; - } +/// Check if the given storage key is for total supply of a unspecified token. +/// If it is, returns the token. +pub fn is_any_minted_balance_key(key: &Key) -> Option<&Address> { match &key.segments[..] { [ - .., + DbKeySeg::AddressSeg(addr), + DbKeySeg::AddressSeg(token), DbKeySeg::StringSeg(balance), - DbKeySeg::AddressSeg(owner), - ] if balance == BALANCE_STORAGE_KEY => { - let sub_prefix = Key { - segments: key.segments[1..(len - 2)].to_vec(), - }; - Some((sub_prefix, owner)) + DbKeySeg::StringSeg(owner), + ] if *addr == Address::Internal(InternalAddress::Multitoken) + && balance == BALANCE_STORAGE_KEY + && owner == MINTED_STORAGE_KEY => + { + Some(token) } _ => None, } @@ -1011,8 +936,6 @@ pub struct Transfer { pub target: Address, /// Token's address pub token: Address, - /// Source token's sub prefix - pub sub_prefix: Option, /// The amount of tokens pub amount: DenominatedAmount, /// The unused storage location at which to place TxId diff --git a/core/src/types/transaction/account.rs b/core/src/types/transaction/account.rs new file mode 100644 index 0000000000..f2eaafe7ef --- /dev/null +++ b/core/src/types/transaction/account.rs @@ -0,0 +1,52 @@ +use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use serde::{Deserialize, Serialize}; + +use crate::types::address::Address; +use crate::types::hash::Hash; +use crate::types::key::common; + +/// A tx data type to initialize a new established account +#[derive( + Debug, + Clone, + PartialEq, + BorshSerialize, + BorshDeserialize, + BorshSchema, + Serialize, + Deserialize, +)] +pub struct InitAccount { + /// Public keys to be written into the account's storage. This can be used + /// for signature verification of transactions for the newly created + /// account. + pub public_keys: Vec, + /// The VP code hash + pub vp_code_hash: Hash, + /// The account signature threshold + pub threshold: u8, +} + +/// A tx data type to update an account's validity predicate +#[derive( + Debug, + Clone, + PartialEq, + BorshSerialize, + BorshDeserialize, + BorshSchema, + Serialize, + Deserialize, +)] +pub struct UpdateAccount { + /// An address of the account + pub addr: Address, + /// The new VP code hash + pub vp_code_hash: Option, + /// Public keys to be written into the account's storage. This can be used + /// for signature verification of transactions for the newly created + /// account. + pub public_keys: Vec, + /// The account signature threshold + pub threshold: Option, +} diff --git a/core/src/types/transaction/mod.rs b/core/src/types/transaction/mod.rs index c6cc23dffa..88ded1970d 100644 --- a/core/src/types/transaction/mod.rs +++ b/core/src/types/transaction/mod.rs @@ -1,5 +1,7 @@ //! Types that are used in transactions. +/// txs to manage accounts +pub mod account; /// txs that contain decrypted payloads or assertions of /// non-decryptability pub mod decrypted; @@ -7,6 +9,7 @@ pub mod decrypted; pub mod encrypted; /// txs to manage governance pub mod governance; +/// txs to manage pos pub mod pos; /// transaction protocols made by validators pub mod protocol; @@ -27,10 +30,8 @@ pub use wrapper::*; use crate::ledger::gas::VpsGas; use crate::types::address::Address; -use crate::types::dec::Dec; use crate::types::hash::Hash; use crate::types::ibc::IbcEvent; -use crate::types::key::*; use crate::types::storage; #[cfg(feature = "ferveo-tpke")] use crate::types::transaction::protocol::ProtocolTx; @@ -131,80 +132,6 @@ fn iterable_to_string( } } -/// A tx data type to update an account's validity predicate -#[derive( - Debug, - Clone, - PartialEq, - BorshSerialize, - BorshDeserialize, - BorshSchema, - Serialize, - Deserialize, -)] -pub struct UpdateVp { - /// An address of the account - pub addr: Address, - /// The new VP code hash - pub vp_code_hash: Hash, -} - -/// A tx data type to initialize a new established account -#[derive( - Debug, - Clone, - PartialEq, - BorshSerialize, - BorshDeserialize, - BorshSchema, - Serialize, - Deserialize, -)] -pub struct InitAccount { - /// Public key to be written into the account's storage. This can be used - /// for signature verification of transactions for the newly created - /// account. - pub public_key: common::PublicKey, - /// The VP code hash - pub vp_code_hash: Hash, -} - -/// A tx data type to initialize a new validator account. -#[derive( - Debug, - Clone, - PartialEq, - BorshSerialize, - BorshDeserialize, - BorshSchema, - Serialize, - Deserialize, -)] -pub struct InitValidator { - /// Public key to be written into the account's storage. This can be used - /// for signature verification of transactions for the newly created - /// account. - pub account_key: common::PublicKey, - /// A key to be used for signing blocks and votes on blocks. - pub consensus_key: common::PublicKey, - /// An Eth bridge governance public key - pub eth_cold_key: secp256k1::PublicKey, - /// An Eth bridge hot signing public key used for validator set updates and - /// cross-chain transactions - pub eth_hot_key: secp256k1::PublicKey, - /// Public key used to sign protocol transactions - pub protocol_key: common::PublicKey, - /// Serialization of the public session key used in the DKG - pub dkg_key: crate::types::key::dkg_session_keys::DkgPublicKey, - /// The initial commission rate charged for delegation rewards - pub commission_rate: Dec, - /// The maximum change allowed per epoch to the commission rate. This is - /// immutable once set here. - pub max_commission_rate_change: Dec, - /// The VP code for validator account - pub validator_vp_code_hash: Hash, -} - /// Struct that classifies that kind of Tx /// based on the contents of its data. #[derive( @@ -241,6 +168,7 @@ mod test_process_tx { use super::*; use crate::proto::{Code, Data, Section, Signature, Tx, TxError}; use crate::types::address::nam; + use crate::types::key::*; use crate::types::storage::Epoch; use crate::types::token::Amount; @@ -411,6 +339,8 @@ fn test_process_tx_decrypted_unsigned() { #[test] fn test_process_tx_decrypted_signed() { use crate::proto::{Code, Data, Section, Signature, Tx}; + use crate::types::key::*; + fn gen_keypair() -> common::SecretKey { use rand::prelude::ThreadRng; use rand::thread_rng; diff --git a/core/src/types/transaction/pos.rs b/core/src/types/transaction/pos.rs index d334047ffe..fa0e3d0891 100644 --- a/core/src/types/transaction/pos.rs +++ b/core/src/types/transaction/pos.rs @@ -5,8 +5,48 @@ use serde::{Deserialize, Serialize}; use crate::types::address::Address; use crate::types::dec::Dec; +use crate::types::hash::Hash; +use crate::types::key::{common, secp256k1}; use crate::types::token; +/// A tx data type to initialize a new validator account. +#[derive( + Debug, + Clone, + PartialEq, + BorshSerialize, + BorshDeserialize, + BorshSchema, + Serialize, + Deserialize, +)] +pub struct InitValidator { + /// Public key to be written into the account's storage. This can be used + /// for signature verification of transactions for the newly created + /// account. + pub account_keys: Vec, + /// The minimum number of signatures needed + pub threshold: u8, + /// A key to be used for signing blocks and votes on blocks. + pub consensus_key: common::PublicKey, + /// An Eth bridge governance public key + pub eth_cold_key: secp256k1::PublicKey, + /// An Eth bridge hot signing public key used for validator set updates and + /// cross-chain transactions + pub eth_hot_key: secp256k1::PublicKey, + /// Public key used to sign protocol transactions + pub protocol_key: common::PublicKey, + /// Serialization of the public session key used in the DKG + pub dkg_key: crate::types::key::dkg_session_keys::DkgPublicKey, + /// The initial commission rate charged for delegation rewards + pub commission_rate: Dec, + /// The maximum change allowed per epoch to the commission rate. This is + /// immutable once set here. + pub max_commission_rate_change: Dec, + /// The VP code for validator account + pub validator_vp_code_hash: Hash, +} + /// A bond is a validator's self-bond or a delegation from non-validator to a /// validator. #[derive( diff --git a/core/src/types/uint.rs b/core/src/types/uint.rs index 637694a29c..ea935c1cc1 100644 --- a/core/src/types/uint.rs +++ b/core/src/types/uint.rs @@ -8,7 +8,6 @@ use std::ops::{Add, AddAssign, BitAnd, Div, Mul, Neg, Rem, Sub, SubAssign}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use impl_num_traits::impl_uint_num_traits; use num_integer::Integer; -use serde::{Deserialize, Serialize}; use uint::construct_uint; use crate::types::token; @@ -31,8 +30,6 @@ construct_uint! { /// Namada native type to replace for unsigned 256 bit /// integers. #[derive( - Serialize, - Deserialize, BorshSerialize, BorshDeserialize, BorshSchema, @@ -41,6 +38,62 @@ construct_uint! { pub struct Uint(4); } +impl serde::Serialize for Uint { + fn serialize( + &self, + serializer: S, + ) -> std::result::Result + where + S: serde::Serializer, + { + let amount_string = self.to_string(); + serde::Serialize::serialize(&amount_string, serializer) + } +} + +impl<'de> serde::Deserialize<'de> for Uint { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + use serde::de::Error as serdeError; + let amount_string: String = + serde::Deserialize::deserialize(deserializer)?; + + let digits = amount_string + .chars() + .filter_map(|c| { + if c.is_ascii_digit() { + c.to_digit(10).map(Uint::from) + } else { + None + } + }) + .rev() + .collect::>(); + if digits.len() != amount_string.len() { + return Err(D::Error::custom(AmountParseError::FromString)); + } + if digits.len() > 77 { + return Err(D::Error::custom(AmountParseError::ScaleTooLarge( + digits.len() as u32, + 77, + ))); + } + let mut value = Uint::default(); + let ten = Uint::from(10); + for (pow, digit) in digits.into_iter().enumerate() { + value = ten + .checked_pow(Uint::from(pow)) + .and_then(|scaling| scaling.checked_mul(digit)) + .and_then(|scaled| value.checked_add(scaled)) + .ok_or(AmountParseError::PrecisionOverflow) + .map_err(D::Error::custom)?; + } + Ok(value) + } +} + impl_uint_num_traits!(Uint, 4); impl Integer for Uint { @@ -624,4 +677,15 @@ mod test_uint { assert!(-that <= -this); assert!(-that <= this); } + + #[test] + fn test_serialization_roundtrip() { + let amount: Uint = serde_json::from_str(r#""1000000000""#).unwrap(); + assert_eq!(amount, Uint::from(1000000000)); + let serialized = serde_json::to_string(&amount).unwrap(); + assert_eq!(serialized, r#""1000000000""#); + + let amount: Result = serde_json::from_str(r#""1000000000.2""#); + assert!(amount.is_err()); + } } diff --git a/core/src/types/voting_power.rs b/core/src/types/voting_power.rs index 96a9579279..946e08b834 100644 --- a/core/src/types/voting_power.rs +++ b/core/src/types/voting_power.rs @@ -31,9 +31,22 @@ use crate::types::uint::Uint; )] pub struct EthBridgeVotingPower(u64); -impl From for EthBridgeVotingPower { - fn from(val: u64) -> Self { - Self(val) +impl EthBridgeVotingPower { + /// Maximum value that can be represented for the voting power + /// stored in an Ethereum bridge smart contract. + pub const MAX: Self = Self(1 << 32); +} + +impl TryFrom for EthBridgeVotingPower { + type Error = (); + + #[inline] + fn try_from(val: u64) -> Result { + if val <= Self::MAX.0 { + Ok(Self(val)) + } else { + Err(()) + } } } @@ -41,7 +54,8 @@ impl From<&FractionalVotingPower> for EthBridgeVotingPower { fn from(ratio: &FractionalVotingPower) -> Self { // normalize the voting power // https://github.com/anoma/ethereum-bridge/blob/fe93d2e95ddb193a759811a79c8464ad4d709c12/test/utils/utilities.js#L29 - const NORMALIZED_VOTING_POWER: Uint = Uint::from_u64(1 << 32); + const NORMALIZED_VOTING_POWER: Uint = + Uint::from_u64(EthBridgeVotingPower::MAX.0); let voting_power = ratio.0 * NORMALIZED_VOTING_POWER; let voting_power = voting_power.round().to_integer().low_u64(); diff --git a/documentation/dev/src/specs/ledger/default-transactions.md b/documentation/dev/src/specs/ledger/default-transactions.md index fb254f0cd3..d1e36b923a 100644 --- a/documentation/dev/src/specs/ledger/default-transactions.md +++ b/documentation/dev/src/specs/ledger/default-transactions.md @@ -28,7 +28,7 @@ Transparently transfer `amount` of fungible `token` from the `source` to the `ta Attach [Transfer](../encoding.md#transfer) to the `data`. -### tx_update_vp +### tx_update_account Update a validity predicate of an established account. diff --git a/encoding_spec/src/main.rs b/encoding_spec/src/main.rs index b9e2b034ef..5889b03b8d 100644 --- a/encoding_spec/src/main.rs +++ b/encoding_spec/src/main.rs @@ -70,10 +70,13 @@ fn main() -> Result<(), Box> { let public_key_schema = PublicKey::schema_container(); // TODO update after let signature_schema = Signature::schema_container(); - let init_account_schema = transaction::InitAccount::schema_container(); - let init_validator_schema = transaction::InitValidator::schema_container(); + let init_account_schema = + transaction::account::InitAccount::schema_container(); + let init_validator_schema = + transaction::pos::InitValidator::schema_container(); let token_transfer_schema = token::Transfer::schema_container(); - let update_vp_schema = transaction::UpdateVp::schema_container(); + let update_account = + transaction::account::UpdateAccount::schema_container(); let pos_bond_schema = pos::Bond::schema_container(); let pos_withdraw_schema = pos::Withdraw::schema_container(); let wrapper_tx_schema = transaction::WrapperTx::schema_container(); @@ -98,7 +101,7 @@ fn main() -> Result<(), Box> { definitions.extend(init_account_schema.definitions); definitions.extend(init_validator_schema.definitions); definitions.extend(token_transfer_schema.definitions); - definitions.extend(update_vp_schema.definitions); + definitions.extend(update_account.definitions); definitions.extend(pos_bond_schema.definitions); definitions.extend(pos_withdraw_schema.definitions); definitions.extend(wrapper_tx_schema.definitions); @@ -179,11 +182,11 @@ fn main() -> Result<(), Box> { ).with_rust_doc_link("https://dev.namada.net/master/rustdoc/namada/types/token/struct.Transfer.html"); tables.push(token_transfer_table); - let update_vp_definition = - definitions.remove(&update_vp_schema.declaration).unwrap(); - let update_vp_table = - definition_to_table(update_vp_schema.declaration, update_vp_definition).with_rust_doc_link("https://dev.namada.net/master/rustdoc/namada/types/transaction/struct.UpdateVp.html"); - tables.push(update_vp_table); + let update_account_definition = + definitions.remove(&update_account.declaration).unwrap(); + let update_accoun_table = + definition_to_table(update_account.declaration, update_account_definition).with_rust_doc_link("https://dev.namada.net/master/rustdoc/namada/types/transaction/struct.UpdateVp.html"); + tables.push(update_accoun_table); let pos_bond_definition = definitions.remove(&pos_bond_schema.declaration).unwrap(); diff --git a/ethereum_bridge/src/protocol/transactions/ethereum_events/events.rs b/ethereum_bridge/src/protocol/transactions/ethereum_events/events.rs index d4cd0370aa..0052fb01b1 100644 --- a/ethereum_bridge/src/protocol/transactions/ethereum_events/events.rs +++ b/ethereum_bridge/src/protocol/transactions/ethereum_events/events.rs @@ -26,9 +26,7 @@ use namada_core::types::ethereum_events::{ }; use namada_core::types::storage::{BlockHeight, Key, KeySeg}; use namada_core::types::token; -use namada_core::types::token::{ - balance_key, multitoken_balance_key, multitoken_balance_prefix, -}; +use namada_core::types::token::{balance_key, minted_balance_key}; use crate::parameters::read_native_erc20_address; use crate::protocol::transactions::update; @@ -234,8 +232,8 @@ where H: 'static + StorageHasher + Sync, { let mut changed_keys = BTreeSet::default(); - let keys: wrapped_erc20s::Keys = asset.into(); - let balance_key = keys.balance(receiver); + let token = wrapped_erc20s::token(asset); + let balance_key = balance_key(&token, receiver); update::amount(wl_storage, &balance_key, |balance| { tracing::debug!( %balance_key, @@ -251,7 +249,7 @@ where })?; _ = changed_keys.insert(balance_key); - let supply_key = keys.supply(); + let supply_key = minted_balance_key(&token); update::amount(wl_storage, &supply_key, |supply| { tracing::debug!( %supply_key, @@ -266,6 +264,10 @@ where ); })?; _ = changed_keys.insert(supply_key); + + // mint the token without a minter because a protocol tx doesn't need to + // trigger a VP + Ok(changed_keys) } @@ -477,12 +479,9 @@ where ); (escrow_balance_key, sender_balance_key) } else { - let sub_prefix = wrapped_erc20s::sub_prefix(&transfer.transfer.asset); - let prefix = multitoken_balance_prefix(&BRIDGE_ADDRESS, &sub_prefix); - let escrow_balance_key = - multitoken_balance_key(&prefix, &BRIDGE_POOL_ADDRESS); - let sender_balance_key = - multitoken_balance_key(&prefix, &transfer.transfer.sender); + let token = wrapped_erc20s::token(&transfer.transfer.asset); + let escrow_balance_key = balance_key(&token, &BRIDGE_POOL_ADDRESS); + let sender_balance_key = balance_key(&token, &transfer.transfer.sender); (escrow_balance_key, sender_balance_key) }; update::amount(wl_storage, &source, |balance| { @@ -518,15 +517,15 @@ where return Ok(changed_keys); } - let keys: wrapped_erc20s::Keys = (&transfer.transfer.asset).into(); + let token = wrapped_erc20s::token(&transfer.transfer.asset); - let escrow_balance_key = keys.balance(&BRIDGE_POOL_ADDRESS); + let escrow_balance_key = balance_key(&token, &BRIDGE_POOL_ADDRESS); update::amount(wl_storage, &escrow_balance_key, |balance| { balance.spend(&transfer.transfer.amount); })?; _ = changed_keys.insert(escrow_balance_key); - let supply_key = keys.supply(); + let supply_key = minted_balance_key(&token); update::amount(wl_storage, &supply_key, |supply| { supply.spend(&transfer.transfer.amount); })?; @@ -659,12 +658,8 @@ mod tests { ) .expect("Test failed"); } else { - let sub_prefix = - wrapped_erc20s::sub_prefix(&transfer.transfer.asset); - let prefix = - multitoken_balance_prefix(&BRIDGE_ADDRESS, &sub_prefix); - let sender_key = - multitoken_balance_key(&prefix, &transfer.transfer.sender); + let token = wrapped_erc20s::token(&transfer.transfer.asset); + let sender_key = balance_key(&token, &transfer.transfer.sender); let sender_balance = Amount::from(0); wl_storage .write_bytes( @@ -672,8 +667,7 @@ mod tests { sender_balance.try_to_vec().expect("Test failed"), ) .expect("Test failed"); - let escrow_key = - multitoken_balance_key(&prefix, &BRIDGE_POOL_ADDRESS); + let escrow_key = balance_key(&token, &BRIDGE_POOL_ADDRESS); let escrow_balance = Amount::from(10); wl_storage .write_bytes( @@ -681,11 +675,13 @@ mod tests { escrow_balance.try_to_vec().expect("Test failed"), ) .expect("Test failed"); - let asset_keys: wrapped_erc20s::Keys = - (&transfer.transfer.asset).into(); - update::amount(wl_storage, &asset_keys.supply(), |supply| { - supply.receive(&transfer.transfer.amount); - }) + update::amount( + wl_storage, + &minted_balance_key(&token), + |supply| { + supply.receive(&transfer.transfer.amount); + }, + ) .expect("Test failed"); }; let gas_fee = Amount::from(1); @@ -786,9 +782,9 @@ mod tests { ) .unwrap(); - let wdai: wrapped_erc20s::Keys = (&DAI_ERC20_ETH_ADDRESS).into(); - let receiver_balance_key = wdai.balance(&receiver); - let wdai_supply_key = wdai.supply(); + let wdai = wrapped_erc20s::token(&DAI_ERC20_ETH_ADDRESS); + let receiver_balance_key = balance_key(&wdai, &receiver); + let wdai_supply_key = minted_balance_key(&wdai); assert_eq!( stored_keys_count(&wl_storage), @@ -814,7 +810,7 @@ mod tests { let native_erc20 = read_native_erc20_address(&wl_storage).expect("Test failed"); let random_erc20 = EthAddress([0xff; 20]); - let random_erc20_keys: wrapped_erc20s::Keys = (&random_erc20).into(); + let random_erc20_token = wrapped_erc20s::token(&random_erc20); let pending_transfers = init_bridge_pool_transfers( &mut wl_storage, [native_erc20, random_erc20], @@ -853,10 +849,12 @@ mod tests { let mut changed_keys = act_on(&mut wl_storage, event).unwrap(); assert!( - changed_keys - .remove(&random_erc20_keys.balance(&BRIDGE_POOL_ADDRESS)) + changed_keys.remove(&balance_key( + &random_erc20_token, + &BRIDGE_POOL_ADDRESS + )) ); - assert!(changed_keys.remove(&random_erc20_keys.supply())); + assert!(changed_keys.remove(&minted_balance_key(&random_erc20_token))); assert!(changed_keys.remove(&payer_balance_key)); assert!(changed_keys.remove(&pool_balance_key)); assert!(changed_keys.remove(&get_nonce_key())); @@ -987,20 +985,15 @@ mod tests { .expect("Test failed"); assert_eq!(escrow_balance, Amount::from(0)); } else { - let sub_prefix = - wrapped_erc20s::sub_prefix(&transfer.transfer.asset); - let prefix = - multitoken_balance_prefix(&BRIDGE_ADDRESS, &sub_prefix); - let sender_key = - multitoken_balance_key(&prefix, &transfer.transfer.sender); + let token = wrapped_erc20s::token(&transfer.transfer.asset); + let sender_key = balance_key(&token, &transfer.transfer.sender); let value = wl_storage.read_bytes(&sender_key).expect("Test failed"); let sender_balance = Amount::try_from_slice(&value.expect("Test failed")) .expect("Test failed"); assert_eq!(sender_balance, transfer.transfer.amount); - let escrow_key = - multitoken_balance_key(&prefix, &BRIDGE_POOL_ADDRESS); + let escrow_key = balance_key(&token, &BRIDGE_POOL_ADDRESS); let value = wl_storage.read_bytes(&escrow_key).expect("Test failed"); let escrow_balance = @@ -1129,12 +1122,12 @@ mod tests { if asset == &native_erc20 { return None; } - let asset_keys: wrapped_erc20s::Keys = asset.into(); + let erc20_token = wrapped_erc20s::token(asset); let prev_balance = wl_storage - .read(&asset_keys.balance(&BRIDGE_POOL_ADDRESS)) + .read(&balance_key(&erc20_token, &BRIDGE_POOL_ADDRESS)) .expect("Test failed"); let prev_supply = wl_storage - .read(&asset_keys.supply()) + .read(&minted_balance_key(&erc20_token)) .expect("Test failed"); Some(Delta { asset: *asset, @@ -1163,14 +1156,14 @@ mod tests { .checked_sub(sent_amount) .expect("Test failed"); - let asset_keys: wrapped_erc20s::Keys = asset.into(); + let erc20_token = wrapped_erc20s::token(asset); let balance: token::Amount = wl_storage - .read(&asset_keys.balance(&BRIDGE_POOL_ADDRESS)) + .read(&balance_key(&erc20_token, &BRIDGE_POOL_ADDRESS)) .expect("Read must succeed") .expect("Balance must exist"); let supply: token::Amount = wl_storage - .read(&asset_keys.supply()) + .read(&minted_balance_key(&erc20_token)) .expect("Read must succeed") .expect("Balance must exist"); @@ -1189,19 +1182,19 @@ mod tests { test_wrapped_erc20s_aux(|wl_storage, event| { let native_erc20 = read_native_erc20_address(wl_storage).expect("Test failed"); - let wnam_keys: wrapped_erc20s::Keys = (&native_erc20).into(); + let wnam = wrapped_erc20s::token(&native_erc20); let escrow_balance_key = balance_key(&nam(), &BRIDGE_ADDRESS); // check pre supply assert!( wl_storage - .read_bytes(&wnam_keys.balance(&BRIDGE_POOL_ADDRESS)) + .read_bytes(&balance_key(&wnam, &BRIDGE_POOL_ADDRESS)) .expect("Test failed") .is_none() ); assert!( wl_storage - .read_bytes(&wnam_keys.supply()) + .read_bytes(&minted_balance_key(&wnam)) .expect("Test failed") .is_none() ); @@ -1217,13 +1210,13 @@ mod tests { // check post supply assert!( wl_storage - .read_bytes(&wnam_keys.balance(&BRIDGE_POOL_ADDRESS)) + .read_bytes(&balance_key(&wnam, &BRIDGE_POOL_ADDRESS)) .expect("Test failed") .is_none() ); assert!( wl_storage - .read_bytes(&wnam_keys.supply()) + .read_bytes(&minted_balance_key(&wnam)) .expect("Test failed") .is_none() ); diff --git a/ethereum_bridge/src/protocol/transactions/ethereum_events/mod.rs b/ethereum_bridge/src/protocol/transactions/ethereum_events/mod.rs index bb7b614187..d3cd32972d 100644 --- a/ethereum_bridge/src/protocol/transactions/ethereum_events/mod.rs +++ b/ethereum_bridge/src/protocol/transactions/ethereum_events/mod.rs @@ -284,7 +284,7 @@ mod tests { use namada_core::types::ethereum_events::{ EthereumEvent, TransferToNamada, }; - use namada_core::types::token::Amount; + use namada_core::types::token::{balance_key, minted_balance_key, Amount}; use super::*; use crate::protocol::transactions::utils::GetVoters; @@ -335,7 +335,7 @@ mod tests { apply_updates(&mut wl_storage, updates, voting_powers)?; let eth_msg_keys: vote_tallies::Keys = (&body).into(); - let wrapped_erc20_keys: wrapped_erc20s::Keys = (&asset).into(); + let wrapped_erc20_token = wrapped_erc20s::token(&asset); assert_eq!( BTreeSet::from_iter(vec![ eth_msg_keys.body(), @@ -343,8 +343,8 @@ mod tests { eth_msg_keys.seen_by(), eth_msg_keys.voting_power(), eth_msg_keys.voting_started_epoch(), - wrapped_erc20_keys.balance(&receiver), - wrapped_erc20_keys.supply(), + balance_key(&wrapped_erc20_token, &receiver), + minted_balance_key(&wrapped_erc20_token), ]), changed_keys ); @@ -375,8 +375,8 @@ mod tests { let epoch_bytes = epoch_bytes.unwrap(); assert_eq!(Epoch::try_from_slice(&epoch_bytes)?, Epoch(0)); - let wrapped_erc20_balance_bytes = - wl_storage.read_bytes(&wrapped_erc20_keys.balance(&receiver))?; + let wrapped_erc20_balance_bytes = wl_storage + .read_bytes(&balance_key(&wrapped_erc20_token, &receiver))?; let wrapped_erc20_balance_bytes = wrapped_erc20_balance_bytes.unwrap(); assert_eq!( Amount::try_from_slice(&wrapped_erc20_balance_bytes)?, @@ -384,7 +384,7 @@ mod tests { ); let wrapped_erc20_supply_bytes = - wl_storage.read_bytes(&wrapped_erc20_keys.supply())?; + wl_storage.read_bytes(&minted_balance_key(&wrapped_erc20_token))?; let wrapped_erc20_supply_bytes = wrapped_erc20_supply_bytes.unwrap(); assert_eq!( Amount::try_from_slice(&wrapped_erc20_supply_bytes)?, @@ -435,7 +435,7 @@ mod tests { "No gas should be used for a derived transaction" ); let eth_msg_keys = vote_tallies::Keys::from(&event); - let dai_keys = wrapped_erc20s::Keys::from(&DAI_ERC20_ETH_ADDRESS); + let dai_token = wrapped_erc20s::token(&DAI_ERC20_ETH_ADDRESS); assert_eq!( tx_result.changed_keys, BTreeSet::from_iter(vec![ @@ -444,8 +444,8 @@ mod tests { eth_msg_keys.seen_by(), eth_msg_keys.voting_power(), eth_msg_keys.voting_started_epoch(), - dai_keys.balance(&receiver), - dai_keys.supply(), + balance_key(&dai_token, &receiver), + minted_balance_key(&dai_token), ]) ); assert!(tx_result.vps_result.accepted_vps.is_empty()); diff --git a/genesis/dev.toml b/genesis/dev.toml index b6eb070b42..19985a3b9e 100644 --- a/genesis/dev.toml +++ b/genesis/dev.toml @@ -28,7 +28,6 @@ net_address = "127.0.0.1:26656" [token.NAM] address = "atest1v4ehgw36x3prswzxggunzv6pxqmnvdj9xvcyzvpsggeyvs3cg9qnywf589qnwvfsg5erg3fkl09rg5" denom = 8 -vp = "vp_token" [token.NAM.balances] # In token balances, we can use: # 1. An address any account @@ -45,7 +44,6 @@ Bertha = "1000000" [token.BTC] address = "atest1v4ehgw36xdzryve5gsc52veeg5cnsv2yx5eygvp38qcrvd29xy6rys6p8yc5xvp4xfpy2v694wgwcp" denom = 8 -vp = "vp_token" [token.BTC.balances] atest1v4ehgw368ycryv2z8qcnxv3cxgmrgvjpxs6yg333gym5vv2zxepnj334g4rryvj9xucrgve4x3xvr4 = 1000000 atest1v4ehgw36x3qng3jzggu5yvpsxgcngv2xgguy2dpkgvu5x33kx3pr2w2zgep5xwfkxscrxs2pj8075p = 1000000 @@ -55,7 +53,6 @@ a1qyqzsqqqqqcyvvf5xcu5vd6rg4z5233hg9pn23pjgdryzdjy8pz52wzxxscnvvjxx3rryvzz8y5p6m [token.ETH] address = "atest1v4ehgw36xqmr2d3nx3ryvd2xxgmrq33j8qcns33sxezrgv6zxdzrydjrxveygd2yxumrsdpsf9jc2p" denom = 18 -vp = "vp_token" [token.ETH.balances] atest1v4ehgw368ycryv2z8qcnxv3cxgmrgvjpxs6yg333gym5vv2zxepnj334g4rryvj9xucrgve4x3xvr4 = 1000000 atest1v4ehgw36x3qng3jzggu5yvpsxgcngv2xgguy2dpkgvu5x33kx3pr2w2zgep5xwfkxscrxs2pj8075p = 1000000 @@ -65,7 +62,6 @@ a1qyqzsqqqqqcyvvf5xcu5vd6rg4z5233hg9pn23pjgdryzdjy8pz52wzxxscnvvjxx3rryvzz8y5p6m [token.DOT] address = "atest1v4ehgw36gg6nvs2zgfpyxsfjgc65yv6pxy6nwwfsxgungdzrggeyzv35gveyxsjyxymyz335hur2jn" denom = 10 -vp = "vp_token" [token.DOT.balances] atest1v4ehgw368ycryv2z8qcnxv3cxgmrgvjpxs6yg333gym5vv2zxepnj334g4rryvj9xucrgve4x3xvr4 = 1000000 atest1v4ehgw36x3qng3jzggu5yvpsxgcngv2xgguy2dpkgvu5x33kx3pr2w2zgep5xwfkxscrxs2pj8075p = 1000000 @@ -75,7 +71,6 @@ a1qyqzsqqqqqcyvvf5xcu5vd6rg4z5233hg9pn23pjgdryzdjy8pz52wzxxscnvvjxx3rryvzz8y5p6m [token.schnitzel] address = "atest1v4ehgw36xue5xvf5xvuyzvpjx5un2v3k8qeyvd3cxdqns32p89rrxd6xx9zngvpegccnzs699rdnnt" denom = 6 -vp = "vp_token" [token.schnitzel.balances] atest1v4ehgw368ycryv2z8qcnxv3cxgmrgvjpxs6yg333gym5vv2zxepnj334g4rryvj9xucrgve4x3xvr4 = 1000000 atest1v4ehgw36x3qng3jzggu5yvpsxgcngv2xgguy2dpkgvu5x33kx3pr2w2zgep5xwfkxscrxs2pj8075p = 1000000 @@ -85,7 +80,6 @@ a1qyqzsqqqqqcyvvf5xcu5vd6rg4z5233hg9pn23pjgdryzdjy8pz52wzxxscnvvjxx3rryvzz8y5p6m [token.apfel] address = "atest1v4ehgw36gfryydj9g3p5zv3kg9znyd358ycnzsfcggc5gvecgc6ygs2rxv6ry3zpg4zrwdfeumqcz9" denom = 6 -vp = "vp_token" [token.apfel.balances] atest1v4ehgw368ycryv2z8qcnxv3cxgmrgvjpxs6yg333gym5vv2zxepnj334g4rryvj9xucrgve4x3xvr4 = 1000000 atest1v4ehgw36x3qng3jzggu5yvpsxgcngv2xgguy2dpkgvu5x33kx3pr2w2zgep5xwfkxscrxs2pj8075p = 1000000 @@ -96,7 +90,6 @@ a1qyqzsqqqqqcyvvf5xcu5vd6rg4z5233hg9pn23pjgdryzdjy8pz52wzxxscnvvjxx3rryvzz8y5p6m address = "atest1v4ehgw36gep5ysecxq6nyv3jg3zygv3e89qn2vp48pryxsf4xpznvve5gvmy23fs89pryvf5a6ht90" denom = 6 public_key = "" -vp = "vp_token" [token.kartoffel.balances] atest1v4ehgw368ycryv2z8qcnxv3cxgmrgvjpxs6yg333gym5vv2zxepnj334g4rryvj9xucrgve4x3xvr4 = 1000000 atest1v4ehgw36x3qng3jzggu5yvpsxgcngv2xgguy2dpkgvu5x33kx3pr2w2zgep5xwfkxscrxs2pj8075p = 1000000 @@ -140,11 +133,6 @@ sha256 = "dc7b97f0448f2369bd2401c3c1d8898f53cac8c464a8c1b1f7f81415a658625d" # filename (relative to wasm path used by the node) filename = "vp_validator.wasm" -# Token VP -[wasm.vp_token] -filename = "vp_token.wasm" -sha256 = "e428a11f570d21dd3c871f5d35de6fe18098eb8ee0456b3e11a72ccdd8685cd0" - # General protocol parameters. [parameters] # Minimum number of blocks in an epoch. diff --git a/genesis/e2e-tests-single-node.toml b/genesis/e2e-tests-single-node.toml index beab8edc2f..fcb109d034 100644 --- a/genesis/e2e-tests-single-node.toml +++ b/genesis/e2e-tests-single-node.toml @@ -5,7 +5,7 @@ genesis_time = "2021-09-30T10:00:00Z" native_token = "NAM" faucet_pow_difficulty = 1 -faucet_withdrawal_limit = "1000000000" +faucet_withdrawal_limit = "1000" [validator.validator-0] # Validator's staked NAM at genesis. @@ -28,7 +28,6 @@ net_address = "127.0.0.1:27656" [token.NAM] address = "atest1v4ehgw36x3prswzxggunzv6pxqmnvdj9xvcyzvpsggeyvs3cg9qnywf589qnwvfsg5erg3fkl09rg5" denom = 6 -vp = "vp_token" [token.NAM.balances] Albert = "1000000" "Albert.public_key" = "100" @@ -45,7 +44,6 @@ faucet = "9223372036854" [token.BTC] address = "atest1v4ehgw36xdzryve5gsc52veeg5cnsv2yx5eygvp38qcrvd29xy6rys6p8yc5xvp4xfpy2v694wgwcp" denom = 8 -vp = "vp_token" [token.BTC.balances] Albert = "1000000" Bertha = "1000000" @@ -57,7 +55,6 @@ faucet = "9223372036854" [token.ETH] address = "atest1v4ehgw36xqmr2d3nx3ryvd2xxgmrq33j8qcns33sxezrgv6zxdzrydjrxveygd2yxumrsdpsf9jc2p" denom = 18 -vp = "vp_token" [token.ETH.balances] Albert = "1000000" Bertha = "1000000" @@ -69,7 +66,6 @@ faucet = "9223372036854" [token.DOT] address = "atest1v4ehgw36gg6nvs2zgfpyxsfjgc65yv6pxy6nwwfsxgungdzrggeyzv35gveyxsjyxymyz335hur2jn" denom = 10 -vp = "vp_token" [token.DOT.balances] Albert = "1000000" Bertha = "1000000" @@ -81,7 +77,6 @@ faucet = "9223372036854" [token.Schnitzel] address = "atest1v4ehgw36xue5xvf5xvuyzvpjx5un2v3k8qeyvd3cxdqns32p89rrxd6xx9zngvpegccnzs699rdnnt" denom = 6 -vp = "vp_token" [token.Schnitzel.balances] Albert = "1000000" Bertha = "1000000" @@ -93,7 +88,6 @@ faucet = "9223372036854" [token.Apfel] address = "atest1v4ehgw36gfryydj9g3p5zv3kg9znyd358ycnzsfcggc5gvecgc6ygs2rxv6ry3zpg4zrwdfeumqcz9" denom = 6 -vp = "vp_token" [token.Apfel.balances] Albert = "1000000" Bertha = "1000000" @@ -106,7 +100,6 @@ faucet = "9223372036854" address = "atest1v4ehgw36gep5ysecxq6nyv3jg3zygv3e89qn2vp48pryxsf4xpznvve5gvmy23fs89pryvf5a6ht90" public_key = "" denom = 6 -vp = "vp_token" [token.Kartoffel.balances] Albert = "1000000" Bertha = "1000000" @@ -151,10 +144,6 @@ filename = "vp_user.wasm" # filename (relative to wasm path used by the node) filename = "vp_validator.wasm" -# Token VP -[wasm.vp_token] -filename = "vp_token.wasm" - # Faucet VP [wasm.vp_testnet_faucet] filename = "vp_testnet_faucet.wasm" @@ -183,6 +172,8 @@ epochs_per_year = 31_536_000 pos_gain_p = "0.1" # The D gain factor in the Proof of Stake rewards controller pos_gain_d = "0.1" +# The maximum number of signatures allowed per transaction +max_signatures_per_transaction = 15 # Proof of stake parameters. [pos_params] diff --git a/proof_of_stake/src/epoched.rs b/proof_of_stake/src/epoched.rs index 4899ae1e1d..d5a567fc94 100644 --- a/proof_of_stake/src/epoched.rs +++ b/proof_of_stake/src/epoched.rs @@ -24,11 +24,14 @@ pub const LAST_UPDATE_SUB_KEY: &str = "last_update"; /// Sub-key for an epoched data structure's oldest epoch with some data pub const OLDEST_EPOCH_SUB_KEY: &str = "oldest_epoch"; +/// Default number of past epochs to keep. +const DEFAULT_NUM_PAST_EPOCHS: u64 = 2; + /// Discrete epoched data handle pub struct Epoched< Data, FutureEpochs, - const NUM_PAST_EPOCHS: u64 = 0, + const NUM_PAST_EPOCHS: u64 = DEFAULT_NUM_PAST_EPOCHS, SON = collections::Simple, > { storage_prefix: storage::Key, @@ -38,8 +41,11 @@ pub struct Epoched< } /// Discrete epoched data handle with nested lazy structure -pub type NestedEpoched = - Epoched; +pub type NestedEpoched< + Data, + FutureEpochs, + const NUM_PAST_EPOCHS: u64 = DEFAULT_NUM_PAST_EPOCHS, +> = Epoched; /// Delta epoched data handle pub struct EpochedDelta { @@ -659,6 +665,29 @@ where } } +/// Zero offset +#[derive( + Debug, + Clone, + BorshDeserialize, + BorshSerialize, + BorshSchema, + PartialEq, + Eq, + PartialOrd, + Ord, +)] +pub struct OffsetZero; +impl EpochOffset for OffsetZero { + fn value(_paras: &PosParams) -> u64 { + 0 + } + + fn dyn_offset() -> DynEpochOffset { + DynEpochOffset::Zero + } +} + /// Offset at pipeline length. #[derive( Debug, @@ -731,6 +760,8 @@ impl EpochOffset for OffsetPipelinePlusUnbondingLen { /// Offset length dynamic choice. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub enum DynEpochOffset { + /// Zero offset + Zero, /// Offset at pipeline length - 1 PipelineLenMinusOne, /// Offset at pipeline length. diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index 7f7265be91..4c087dbbd6 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -56,8 +56,9 @@ use storage::{ validator_address_raw_hash_key, validator_last_slash_key, validator_max_commission_rate_change_key, BondDetails, BondsAndUnbondsDetail, BondsAndUnbondsDetails, EpochedSlashes, - ReverseOrdTokenAmount, RewardsAccumulator, SlashedAmount, UnbondDetails, - ValidatorAddresses, ValidatorUnbondRecords, + ReverseOrdTokenAmount, RewardsAccumulator, SlashedAmount, + TotalConsensusStakes, UnbondDetails, ValidatorAddresses, + ValidatorUnbondRecords, }; use thiserror::Error; use types::{ @@ -84,6 +85,10 @@ pub fn staking_token_address(storage: &impl StorageRead) -> Address { .expect("Must be able to read native token address") } +/// Number of epochs below the current epoch for which full validator sets are +/// stored +const STORE_VALIDATOR_SETS_LEN: u64 = 2; + #[allow(missing_docs)] #[derive(Error, Debug)] pub enum GenesisError { @@ -96,6 +101,8 @@ pub enum GenesisError { pub enum InflationError { #[error("Error in calculating rewards: {0}")] Rewards(rewards::RewardsError), + #[error("Expected validator {0} to be in consensus set but got: {1:?}")] + ExpectedValidatorInConsensus(Address, Option), } #[allow(missing_docs)] @@ -274,6 +281,12 @@ pub fn validator_eth_cold_key_handle( ValidatorEthColdKeys::open(key) } +/// Get the storage handle to the total consensus validator stake +pub fn total_consensus_stake_key_handle() -> TotalConsensusStakes { + let key = storage::total_consensus_stake_key(); + TotalConsensusStakes::open(key) +} + /// Get the storage handle to a PoS validator's state pub fn validator_state_handle(validator: &Address) -> ValidatorStates { let key = storage::validator_state_key(validator); @@ -476,6 +489,9 @@ where )?; } + // Store the total consensus validator stake to storage + store_total_consensus_stake(storage, current_epoch)?; + // Write total deltas to storage total_deltas_handle().init_at_genesis( storage, @@ -488,13 +504,7 @@ where credit_tokens(storage, &staking_token, &ADDRESS, total_bonded)?; // Copy the genesis validator set into the pipeline epoch as well for epoch in (current_epoch.next()).iter_range(params.pipeline_len) { - copy_validator_sets_and_positions( - storage, - current_epoch, - epoch, - &consensus_validator_set_handle(), - &below_capacity_validator_set_handle(), - )?; + copy_validator_sets_and_positions(storage, current_epoch, epoch)?; } tracing::debug!("Genesis initialized"); @@ -748,7 +758,7 @@ where pub fn read_consensus_validator_set_addresses_with_stake( storage: &S, epoch: namada_core::types::storage::Epoch, -) -> storage_api::Result> +) -> storage_api::Result> where S: StorageRead, { @@ -792,7 +802,7 @@ where pub fn read_below_capacity_validator_set_addresses_with_stake( storage: &S, epoch: namada_core::types::storage::Epoch, -) -> storage_api::Result> +) -> storage_api::Result> where S: StorageRead, { @@ -1528,14 +1538,15 @@ pub fn copy_validator_sets_and_positions( storage: &mut S, current_epoch: Epoch, target_epoch: Epoch, - consensus_validator_set: &ConsensusValidatorSets, - below_capacity_validator_set: &BelowCapacityValidatorSets, ) -> storage_api::Result<()> where S: StorageRead + StorageWrite, { let prev_epoch = target_epoch.prev(); + let consensus_validator_set = consensus_validator_set_handle(); + let below_capacity_validator_set = below_capacity_validator_set_handle(); + let (consensus, below_capacity) = ( consensus_validator_set.at(&prev_epoch), below_capacity_validator_set.at(&prev_epoch), @@ -1597,28 +1608,31 @@ where // Copy validator positions let mut positions = HashMap::::default(); - let positions_handle = validator_set_positions_handle().at(&prev_epoch); + let validator_set_positions_handle = validator_set_positions_handle(); + let positions_handle = validator_set_positions_handle.at(&prev_epoch); + for result in positions_handle.iter(storage)? { let (validator, position) = result?; positions.insert(validator, position); } - let new_positions_handle = - validator_set_positions_handle().at(&target_epoch); + + let new_positions_handle = validator_set_positions_handle.at(&target_epoch); for (validator, position) in positions { let prev = new_positions_handle.insert(storage, validator, position)?; debug_assert!(prev.is_none()); } - validator_set_positions_handle().set_last_update(storage, current_epoch)?; + validator_set_positions_handle.set_last_update(storage, current_epoch)?; // Copy set of all validator addresses let mut all_validators = HashSet::
::default(); - let all_validators_handle = validator_addresses_handle().at(&prev_epoch); + let validator_addresses_handle = validator_addresses_handle(); + let all_validators_handle = validator_addresses_handle.at(&prev_epoch); for result in all_validators_handle.iter(storage)? { let validator = result?; all_validators.insert(validator); } let new_all_validators_handle = - validator_addresses_handle().at(&target_epoch); + validator_addresses_handle.at(&target_epoch); for validator in all_validators { let was_in = new_all_validators_handle.insert(storage, validator)?; debug_assert!(!was_in); @@ -1627,6 +1641,68 @@ where Ok(()) } +/// Compute total validator stake for the current epoch +fn compute_total_consensus_stake( + storage: &S, + epoch: Epoch, +) -> storage_api::Result +where + S: StorageRead, +{ + consensus_validator_set_handle() + .at(&epoch) + .iter(storage)? + .fold(Ok(token::Amount::zero()), |acc, entry| { + let acc = acc?; + let ( + NestedSubKey::Data { + key: amount, + nested_sub_key: _, + }, + _validator, + ) = entry?; + Ok(acc + amount) + }) +} + +/// Store total consensus stake +pub fn store_total_consensus_stake( + storage: &mut S, + epoch: Epoch, +) -> storage_api::Result<()> +where + S: StorageRead + StorageWrite, +{ + let total = compute_total_consensus_stake(storage, epoch)?; + tracing::debug!( + "Computed total consensus stake for epoch {}: {}", + epoch, + total.to_string_native() + ); + total_consensus_stake_key_handle().set(storage, total, epoch, 0) +} + +/// Purge the validator sets from the epochs older than the current epoch minus +/// `STORE_VALIDATOR_SETS_LEN` +pub fn purge_validator_sets_for_old_epoch( + storage: &mut S, + epoch: Epoch, +) -> storage_api::Result<()> +where + S: StorageRead + StorageWrite, +{ + if Epoch(STORE_VALIDATOR_SETS_LEN) < epoch { + let old_epoch = epoch - STORE_VALIDATOR_SETS_LEN - 1; + consensus_validator_set_handle() + .get_data_handler() + .remove_all(storage, &old_epoch)?; + below_capacity_validator_set_handle() + .get_data_handler() + .remove_all(storage, &old_epoch)?; + } + Ok(()) +} + /// Read the position of the validator in the subset of validators that have the /// same bonded stake. This information is held in its own epoched structure in /// addition to being inside the validator sets. @@ -2455,6 +2531,41 @@ where Ok((total, total_active)) } +/// Get the genesis consensus validators stake and consensus key for Tendermint, +/// converted from [`ValidatorSetUpdate`]s using the given function. +pub fn genesis_validator_set_tendermint( + storage: &S, + params: &PosParams, + current_epoch: Epoch, + mut f: impl FnMut(ValidatorSetUpdate) -> T, +) -> storage_api::Result> +where + S: StorageRead, +{ + let consensus_validator_handle = + consensus_validator_set_handle().at(¤t_epoch); + let iter = consensus_validator_handle.iter(storage)?; + + iter.map(|validator| { + let ( + NestedSubKey::Data { + key: new_stake, + nested_sub_key: _, + }, + address, + ) = validator?; + let consensus_key = validator_consensus_key_handle(&address) + .get(storage, current_epoch, params)? + .unwrap(); + let converted = f(ValidatorSetUpdate::Consensus(ConsensusValidator { + consensus_key, + bonded_stake: new_stake, + })); + Ok(converted) + }) + .collect() +} + /// Communicate imminent validator set updates to Tendermint. This function is /// called two blocks before the start of a new epoch because Tendermint /// validator updates become active two blocks after the updates are submitted. @@ -2753,6 +2864,42 @@ where } } +/// Collect the details of all of the enqueued slashes to be processed in future +/// epochs into a nested map +pub fn find_all_enqueued_slashes( + storage: &S, + epoch: Epoch, +) -> storage_api::Result>>> +where + S: StorageRead, +{ + let mut enqueued = HashMap::>>::new(); + for res in enqueued_slashes_handle().get_data_handler().iter(storage)? { + let ( + NestedSubKey::Data { + key: processing_epoch, + nested_sub_key: + NestedSubKey::Data { + key: address, + nested_sub_key: _, + }, + }, + slash, + ) = res?; + if processing_epoch <= epoch { + continue; + } + + let slashes = enqueued + .entry(address) + .or_default() + .entry(processing_epoch) + .or_default(); + slashes.push(slash); + } + Ok(enqueued) +} + /// Find all slashes and the associated validators in the PoS system pub fn find_all_slashes( storage: &S, @@ -3136,7 +3283,11 @@ where let state = validator_state_handle(&validator_address) .get(storage, epoch, ¶ms)?; if state != Some(ValidatorState::Consensus) { - continue; + return Err(InflationError::ExpectedValidatorInConsensus( + validator_address, + state, + )) + .into_storage_result(); } let stake_from_deltas = @@ -3252,9 +3403,9 @@ where for epoch in Epoch::iter_bounds_inclusive(start_epoch, end_epoch) { let consensus_stake = - Dec::from(get_total_consensus_stake(storage, epoch)?); + Dec::from(get_total_consensus_stake(storage, epoch, params)?); tracing::debug!( - "Consensus stake in epoch {}: {}", + "Total consensus stake in epoch {}: {}", epoch, consensus_stake ); @@ -3263,6 +3414,7 @@ where let infracting_stake = slashes.iter(storage)?.fold( Ok(Dec::zero()), |acc: storage_api::Result, res| { + let acc = acc?; let ( NestedSubKey::Data { key: validator, @@ -3276,11 +3428,7 @@ where .unwrap_or_default(); // println!("Val {} stake: {}", &validator, validator_stake); - if let Ok(inner) = acc { - Ok(inner + Dec::from(validator_stake)) - } else { - acc - } + Ok(acc + Dec::from(validator_stake)) // TODO: does something more complex need to be done // here in the event some of these slashes correspond to // the same validator? @@ -3438,8 +3586,10 @@ where } } } + // Safe sub cause `validator_set_update_epoch > current_epoch` + let start_offset = validator_set_update_epoch.0 - current_epoch.0; // Set the validator state as `Jailed` thru the pipeline epoch - for offset in 1..=params.pipeline_len { + for offset in start_offset..=params.pipeline_len { validator_state_handle(validator).set( storage, ValidatorState::Jailed, @@ -3856,22 +4006,14 @@ where fn get_total_consensus_stake( storage: &S, epoch: Epoch, + params: &PosParams, ) -> storage_api::Result where S: StorageRead, { - let mut total = token::Amount::default(); - for res in consensus_validator_set_handle().at(&epoch).iter(storage)? { - let ( - NestedSubKey::Data { - key: bonded_stake, - nested_sub_key: _, - }, - _validator, - ) = res?; - total += bonded_stake; - } - Ok(total) + total_consensus_stake_key_handle() + .get(storage, epoch, params) + .map(|o| o.expect("Total consensus stake could not be retrieved.")) } /// Find slashes applicable to a validator with inclusive `start` and exclusive diff --git a/proof_of_stake/src/storage.rs b/proof_of_stake/src/storage.rs index 79124fe3bb..54bd7cfe6b 100644 --- a/proof_of_stake/src/storage.rs +++ b/proof_of_stake/src/storage.rs @@ -36,6 +36,7 @@ const VALIDATOR_TOTAL_UNBONDED_STORAGE_KEY: &str = "total_unbonded"; const VALIDATOR_SETS_STORAGE_PREFIX: &str = "validator_sets"; const CONSENSUS_VALIDATOR_SET_STORAGE_KEY: &str = "consensus"; const BELOW_CAPACITY_VALIDATOR_SET_STORAGE_KEY: &str = "below_capacity"; +const TOTAL_CONSENSUS_STAKE_STORAGE_KEY: &str = "total_consensus_stake"; const TOTAL_DELTAS_STORAGE_KEY: &str = "total_deltas"; const VALIDATOR_SET_POSITIONS_KEY: &str = "validator_set_positions"; const CONSENSUS_KEYS: &str = "consensus_keys"; @@ -584,6 +585,21 @@ pub fn is_below_capacity_validator_set_key(key: &Key) -> bool { matches!(&key.segments[..], [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(key), DbKeySeg::StringSeg(set_type), DbKeySeg::StringSeg(lazy_map), DbKeySeg::StringSeg(data), DbKeySeg::StringSeg(_epoch), DbKeySeg::StringSeg(_), DbKeySeg::StringSeg(_amount), DbKeySeg::StringSeg(_), DbKeySeg::StringSeg(_position)] if addr == &ADDRESS && key == VALIDATOR_SETS_STORAGE_PREFIX && set_type == BELOW_CAPACITY_VALIDATOR_SET_STORAGE_KEY && lazy_map == LAZY_MAP_SUB_KEY && data == lazy_map::DATA_SUBKEY) } +/// Storage key for total consensus stake +pub fn total_consensus_stake_key() -> Key { + Key::from(ADDRESS.to_db_key()) + .push(&TOTAL_CONSENSUS_STAKE_STORAGE_KEY.to_owned()) + .expect("Cannot obtain a total consensus stake key") +} + +/// Is storage key for the total consensus stake? +pub fn is_total_consensus_stake_key(key: &Key) -> bool { + matches!(&key.segments[..], [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(key) + ] if addr == &ADDRESS && key == TOTAL_CONSENSUS_STAKE_STORAGE_KEY) +} + /// Storage key for total deltas of all validators. pub fn total_deltas_key() -> Key { Key::from(ADDRESS.to_db_key()) diff --git a/proof_of_stake/src/tests.rs b/proof_of_stake/src/tests.rs index 451a6880fe..6476827417 100644 --- a/proof_of_stake/src/tests.rs +++ b/proof_of_stake/src/tests.rs @@ -43,15 +43,17 @@ use crate::{ copy_validator_sets_and_positions, find_validator_by_raw_hash, get_num_consensus_validators, init_genesis, insert_validator_into_validator_set, is_validator, process_slashes, + purge_validator_sets_for_old_epoch, read_below_capacity_validator_set_addresses_with_stake, read_below_threshold_validator_set_addresses, read_consensus_validator_set_addresses_with_stake, read_total_stake, read_validator_delta_value, read_validator_stake, slash, - staking_token_address, total_deltas_handle, unbond_handle, unbond_tokens, - unjail_validator, update_validator_deltas, update_validator_set, - validator_consensus_key_handle, validator_set_update_tendermint, - validator_slashes_handle, validator_state_handle, withdraw_tokens, - write_validator_address_raw_hash, BecomeValidator, + staking_token_address, store_total_consensus_stake, total_deltas_handle, + unbond_handle, unbond_tokens, unjail_validator, update_validator_deltas, + update_validator_set, validator_consensus_key_handle, + validator_set_update_tendermint, validator_slashes_handle, + validator_state_handle, withdraw_tokens, write_validator_address_raw_hash, + BecomeValidator, STORE_VALIDATOR_SETS_LEN, }; proptest! { @@ -1159,8 +1161,7 @@ fn test_validator_sets() { .unwrap(); }; - // Start with two genesis validators with 1 NAM stake - let epoch = Epoch::default(); + // Create genesis validators let ((val1, pk1), stake1) = (gen_validator(), token::Amount::native_whole(1)); let ((val2, pk2), stake2) = @@ -1183,6 +1184,9 @@ fn test_validator_sets() { println!("val6: {val6}, {pk6}, {}", stake6.to_string_native()); println!("val7: {val7}, {pk7}, {}", stake7.to_string_native()); + let start_epoch = Epoch::default(); + let epoch = start_epoch; + init_genesis( &mut s, ¶ms, @@ -1749,6 +1753,28 @@ fn test_validator_sets() { }) ); assert_eq!(tm_updates[1], ValidatorSetUpdate::Deactivated(pk4)); + + // Check that the validator sets were purged for the old epochs + let last_epoch = epoch; + for e in Epoch::iter_bounds_inclusive( + start_epoch, + last_epoch + .sub_or_default(Epoch(STORE_VALIDATOR_SETS_LEN)) + .sub_or_default(Epoch(1)), + ) { + assert!( + consensus_validator_set_handle() + .at(&e) + .is_empty(&s) + .unwrap() + ); + assert!( + below_capacity_validator_set_handle() + .at(&e) + .is_empty(&s) + .unwrap() + ); + } } /// When a consensus set validator with 0 voting power adds a bond in the same @@ -2003,14 +2029,14 @@ fn get_tendermint_set_updates( fn advance_epoch(s: &mut TestWlStorage, params: &PosParams) -> Epoch { s.storage.block.epoch = s.storage.block.epoch.next(); let current_epoch = s.storage.block.epoch; + store_total_consensus_stake(s, current_epoch).unwrap(); copy_validator_sets_and_positions( s, current_epoch, current_epoch + params.pipeline_len, - &consensus_validator_set_handle(), - &below_capacity_validator_set_handle(), ) .unwrap(); + purge_validator_sets_for_old_epoch(s, current_epoch).unwrap(); // process_slashes(s, current_epoch).unwrap(); // dbg!(current_epoch); current_epoch diff --git a/proof_of_stake/src/types.rs b/proof_of_stake/src/types.rs index caf705fce3..736ffe7a46 100644 --- a/proof_of_stake/src/types.rs +++ b/proof_of_stake/src/types.rs @@ -29,6 +29,10 @@ use crate::parameters::PosParams; // core::types::token::NATIVE_MAX_DECIMAL_PLACES?? const U64_MAX: u64 = u64::MAX; +/// Number of epochs below the current epoch for which validator deltas and +/// slashes are stored +const VALIDATOR_DELTAS_SLASHES_LEN: u64 = 23; + // TODO: add this to the spec /// Stored positions of validators in validator sets pub type ValidatorSetPositions = crate::epoched::NestedEpoched< @@ -121,18 +125,22 @@ pub type BelowCapacityValidatorSets = crate::epoched::NestedEpoched< crate::epoched::OffsetPipelineLen, >; +/// Epoched total consensus validator stake +pub type TotalConsensusStakes = + crate::epoched::Epoched; + /// Epoched validator's deltas. pub type ValidatorDeltas = crate::epoched::EpochedDelta< token::Change, crate::epoched::OffsetUnbondingLen, - 23, + VALIDATOR_DELTAS_SLASHES_LEN, >; /// Epoched total deltas. pub type TotalDeltas = crate::epoched::EpochedDelta< token::Change, crate::epoched::OffsetUnbondingLen, - 23, + VALIDATOR_DELTAS_SLASHES_LEN, >; /// Epoched validator commission rate @@ -164,7 +172,7 @@ pub type ValidatorSlashes = NestedMap; pub type EpochedSlashes = crate::epoched::NestedEpoched< ValidatorSlashes, crate::epoched::OffsetUnbondingLen, - 23, + VALIDATOR_DELTAS_SLASHES_LEN, >; /// Epoched validator's unbonds diff --git a/shared/src/ledger/args.rs b/shared/src/ledger/args.rs index 35081d3283..2665678def 100644 --- a/shared/src/ledger/args.rs +++ b/shared/src/ledger/args.rs @@ -102,6 +102,8 @@ pub struct TxCustom { pub code_path: C::Data, /// Path to the data file pub data_path: Option, + /// The address that correspond to the signatures/signing-keys + pub owner: C::Address, } /// Transfer transaction arguments @@ -115,8 +117,6 @@ pub struct TxTransfer { pub target: C::TransferTarget, /// Transferred token address pub token: C::Address, - /// Transferred token address - pub sub_prefix: Option, /// Transferred token amount pub amount: InputAmount, /// Native token address @@ -147,8 +147,6 @@ pub struct TxIbcTransfer { pub receiver: String, /// Transferred token addres s pub token: C::Address, - /// Transferred token address - pub sub_prefix: Option, /// Transferred token amount pub amount: token::Amount, /// Port ID @@ -168,14 +166,14 @@ pub struct TxIbcTransfer { pub struct TxInitAccount { /// Common tx arguments pub tx: Tx, - /// Address of the source account - pub source: C::Address, /// 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_key: C::PublicKey, + pub public_keys: Vec, + /// The account multisignature threshold + pub threshold: Option, } /// Transaction to initialize a new account @@ -183,12 +181,12 @@ pub struct TxInitAccount { pub struct TxInitValidator { /// Common tx arguments pub tx: Tx, - /// Source - pub source: C::Address, /// Signature scheme pub scheme: SchemeType, - /// Account key - pub account_key: Option, + /// Account keys + pub account_keys: Vec, + /// The account multisignature threshold + pub threshold: Option, /// Consensus key pub consensus_key: Option, /// Ethereum cold key @@ -211,15 +209,19 @@ pub struct TxInitValidator { /// Transaction to update a VP arguments #[derive(Clone, Debug)] -pub struct TxUpdateVp { +pub struct TxUpdateAccount { /// Common tx arguments pub tx: Tx, /// Path to the VP WASM code file - pub vp_code_path: PathBuf, + 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 @@ -306,6 +308,15 @@ pub struct QueryConversions { 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 { @@ -317,8 +328,6 @@ pub struct QueryBalance { pub token: Option, /// Whether not to convert balances pub no_conversions: bool, - /// Sub prefix of an account - pub sub_prefix: Option, } /// Query historical transfer(s) @@ -330,8 +339,6 @@ pub struct QueryTransfers { pub owner: Option, /// Address of a token pub token: Option, - /// sub-prefix if querying a multi-token - pub sub_prefix: Option, } /// Query PoS bond(s) @@ -356,6 +363,17 @@ pub struct QueryBondedStake { 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 { @@ -377,7 +395,7 @@ pub struct TxUnjailValidator { /// Validator address (should be self) pub validator: C::Address, /// Path to the TX WASM code file - pub tx_code_path: C::Data, + pub tx_code_path: PathBuf, } /// Query PoS commission rate @@ -432,8 +450,10 @@ pub struct QueryRawBytes { pub struct Tx { /// Simulate applying the transaction pub dry_run: bool, - /// Dump the transaction bytes + /// Dump the transaction bytes to file pub dump_tx: bool, + /// The output directory path to where serialize the transaction + 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 @@ -446,6 +466,8 @@ pub struct Tx { /// Whether to force overwrite the above alias, if it is provided, in the /// wallet. pub wallet_alias_force: bool, + /// The fee payer signing key + pub fee_payer: Option, /// The amount being payed to include the transaction pub fee_amount: InputAmount, /// The token in which the fee is being paid @@ -457,9 +479,7 @@ pub struct Tx { /// 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_key: Option, - /// Sign the tx with the keypair of the public key of the given address - pub signer: Option, + pub signing_keys: 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 diff --git a/shared/src/ledger/eth_bridge/bridge_pool.rs b/shared/src/ledger/eth_bridge/bridge_pool.rs index a95d56d475..6f0fe024a4 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool.rs @@ -8,7 +8,6 @@ use std::sync::Arc; use borsh::BorshSerialize; use ethbridge_bridge_contract::Bridge; use ethers::providers::Middleware; -use namada_core::ledger::eth_bridge::storage::wrapped_erc20s; use namada_core::ledger::eth_bridge::ADDRESS as BRIDGE_ADDRESS; use namada_core::types::key::common; use owo_colors::OwoColorize; @@ -46,7 +45,7 @@ pub async fn build_bridge_pool_tx< client: &C, wallet: &mut Wallet, args: args::EthereumBridgePool, -) -> Result<(Tx, Option
, common::PublicKey), Error> { +) -> Result<(Tx, Option
, Vec), Error> { let args::EthereumBridgePool { ref tx, asset, @@ -57,17 +56,15 @@ pub async fn build_bridge_pool_tx< gas_payer, code_path: wasm_code, } = args; - - let sub_prefix = Some(wrapped_erc20s::sub_prefix(&asset)); let DenominatedAmount { amount, .. } = - validate_amount(client, amount, &BRIDGE_ADDRESS, &sub_prefix, tx.force) + validate_amount(client, amount, &BRIDGE_ADDRESS, tx.force) .await .expect("Failed to validate amount"); let transfer = PendingTransfer { transfer: TransferToEthereum { asset, recipient, - sender, + sender: sender.clone(), amount, }, gas_fee: GasFee { @@ -92,6 +89,7 @@ pub async fn build_bridge_pool_tx< wallet, tx, transfer_tx, + Some(sender), TxSigningKey::None, #[cfg(not(feature = "mainnet"))] false, diff --git a/shared/src/ledger/ibc/vp/context.rs b/shared/src/ledger/ibc/vp/context.rs index 0da78bba53..daf2246cbe 100644 --- a/shared/src/ledger/ibc/vp/context.rs +++ b/shared/src/ledger/ibc/vp/context.rs @@ -2,7 +2,7 @@ use std::collections::{BTreeSet, HashMap, HashSet}; -use borsh::{BorshDeserialize, BorshSerialize}; +use borsh::BorshSerialize; use namada_core::ledger::ibc::storage::is_ibc_key; use namada_core::ledger::ibc::{IbcCommonContext, IbcStorageContext}; use namada_core::ledger::storage::write_log::StorageModification; @@ -11,9 +11,7 @@ use namada_core::ledger::storage_api::StorageRead; use namada_core::types::address::{Address, InternalAddress}; use namada_core::types::ibc::IbcEvent; use namada_core::types::storage::{BlockHeight, Header, Key}; -use namada_core::types::token::{ - is_any_token_balance_key, is_any_token_or_multitoken_balance_key, Amount, -}; +use namada_core::types::token::{self, Amount}; use super::Error; use crate::ledger::native_vp::CtxPreStorageRead; @@ -117,50 +115,104 @@ where fn transfer_token( &mut self, - src: &Key, - dest: &Key, + src: &Address, + dest: &Address, + token: &Address, amount: Amount, ) -> Result<(), Self::Error> { - let src_owner = is_any_token_or_multitoken_balance_key(src); - let mut src_bal = match src_owner { - Some([_, Address::Internal(InternalAddress::IbcMint)]) => { - Amount::max() - } - Some([_, Address::Internal(InternalAddress::IbcBurn)]) => { - unreachable!("Invalid transfer from IBC burn address") - } - _ => match self.read(src)? { - Some(v) => { - Amount::try_from_slice(&v[..]).map_err(Error::Decoding)? - } - None => unreachable!("The source has no balance"), - }, - }; + let src_key = token::balance_key(token, src); + let dest_key = token::balance_key(token, dest); + let src_bal: Option = + self.ctx.read(&src_key).map_err(Error::NativeVpError)?; + let mut src_bal = src_bal.expect("The source has no balance"); src_bal.spend(&amount); - let dest_owner = is_any_token_balance_key(dest); - let mut dest_bal = match dest_owner { - Some([_, Address::Internal(InternalAddress::IbcMint)]) => { - unreachable!("Invalid transfer to IBC mint address") - } - _ => match self.read(dest)? { - Some(v) => { - Amount::try_from_slice(&v[..]).map_err(Error::Decoding)? - } - None => Amount::default(), - }, - }; + let mut dest_bal: Amount = self + .ctx + .read(&dest_key) + .map_err(Error::NativeVpError)? + .unwrap_or_default(); dest_bal.receive(&amount); self.write( - src, + &src_key, src_bal.try_to_vec().expect("encoding shouldn't failed"), )?; self.write( - dest, + &dest_key, dest_bal.try_to_vec().expect("encoding shouldn't failed"), + ) + } + + fn mint_token( + &mut self, + target: &Address, + token: &Address, + amount: Amount, + ) -> Result<(), Self::Error> { + let target_key = token::balance_key(token, target); + let mut target_bal: Amount = self + .ctx + .read(&target_key) + .map_err(Error::NativeVpError)? + .unwrap_or_default(); + target_bal.receive(&amount); + + let minted_key = token::minted_balance_key(token); + let mut minted_bal: Amount = self + .ctx + .read(&minted_key) + .map_err(Error::NativeVpError)? + .unwrap_or_default(); + minted_bal.receive(&amount); + + self.write( + &target_key, + target_bal.try_to_vec().expect("encoding shouldn't failed"), + )?; + self.write( + &minted_key, + minted_bal.try_to_vec().expect("encoding shouldn't failed"), )?; - Ok(()) + let minter_key = token::minter_key(token); + self.write( + &minter_key, + Address::Internal(InternalAddress::Ibc) + .try_to_vec() + .expect("encoding shouldn't failed"), + ) + } + + fn burn_token( + &mut self, + target: &Address, + token: &Address, + amount: Amount, + ) -> Result<(), Self::Error> { + let target_key = token::balance_key(token, target); + let mut target_bal: Amount = self + .ctx + .read(&target_key) + .map_err(Error::NativeVpError)? + .unwrap_or_default(); + target_bal.spend(&amount); + + let minted_key = token::minted_balance_key(token); + let mut minted_bal: Amount = self + .ctx + .read(&minted_key) + .map_err(Error::NativeVpError)? + .unwrap_or_default(); + minted_bal.spend(&amount); + + self.write( + &target_key, + target_bal.try_to_vec().expect("encoding shouldn't failed"), + )?; + self.write( + &minted_key, + minted_bal.try_to_vec().expect("encoding shouldn't failed"), + ) } /// Get the current height of this chain @@ -254,16 +306,34 @@ where unimplemented!("Validation doesn't emit an event") } - /// Transfer token fn transfer_token( &mut self, - _src: &Key, - _dest: &Key, + _src: &Address, + _dest: &Address, + _token: &Address, _amount: Amount, ) -> Result<(), Self::Error> { unimplemented!("Validation doesn't transfer") } + fn mint_token( + &mut self, + _target: &Address, + _token: &Address, + _amount: Amount, + ) -> Result<(), Self::Error> { + unimplemented!("Validation doesn't mint") + } + + fn burn_token( + &mut self, + _target: &Address, + _token: &Address, + _amount: Amount, + ) -> Result<(), Self::Error> { + unimplemented!("Validation doesn't burn") + } + fn get_height(&self) -> Result { self.ctx.get_block_height().map_err(Error::NativeVpError) } diff --git a/shared/src/ledger/ibc/vp/denom.rs b/shared/src/ledger/ibc/vp/denom.rs deleted file mode 100644 index d58edfdc33..0000000000 --- a/shared/src/ledger/ibc/vp/denom.rs +++ /dev/null @@ -1,85 +0,0 @@ -//! IBC validity predicate for denom - -use prost::Message; -use thiserror::Error; - -use super::Ibc; -use crate::ibc::applications::transfer::packet::PacketData; -use crate::ibc::core::ics04_channel::msgs::PacketMsg; -use crate::ibc::core::ics26_routing::msgs::MsgEnvelope; -use crate::ibc_proto::google::protobuf::Any; -use crate::ledger::ibc::storage; -use crate::ledger::native_vp::VpEnv; -use crate::ledger::storage::{self as ledger_storage, StorageHasher}; -use crate::types::storage::KeySeg; -use crate::vm::WasmCacheAccess; - -#[allow(missing_docs)] -#[derive(Error, Debug)] -pub enum Error { - #[error("Decoding IBC data error: {0}")] - DecodingData(prost::DecodeError), - #[error("Invalid message: {0}")] - IbcMessage(String), - #[error("Decoding PacketData error: {0}")] - DecodingPacketData(serde_json::Error), - #[error("Denom error: {0}")] - Denom(String), -} - -/// IBC channel functions result -pub type Result = std::result::Result; - -impl<'a, DB, H, CA> Ibc<'a, DB, H, CA> -where - DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: 'static + StorageHasher, - CA: 'static + WasmCacheAccess, -{ - pub(super) fn validate_denom(&self, tx_data: &[u8]) -> Result<()> { - let ibc_msg = Any::decode(tx_data).map_err(Error::DecodingData)?; - let envelope: MsgEnvelope = ibc_msg.try_into().map_err(|e| { - Error::IbcMessage(format!( - "Decoding a MsgRecvPacket failed: Error {}", - e - )) - })?; - // A transaction only with MsgRecvPacket can update the denom store - let msg = match envelope { - MsgEnvelope::Packet(PacketMsg::Recv(msg)) => msg, - _ => { - return Err(Error::IbcMessage( - "Non-MsgRecvPacket message updated the denom store" - .to_string(), - )); - } - }; - let data = serde_json::from_slice::(&msg.packet.data) - .map_err(Error::DecodingPacketData)?; - let denom = format!( - "{}/{}/{}", - &msg.packet.port_id_on_b, - &msg.packet.chan_id_on_b, - &data.token.denom, - ); - let token_hash = storage::calc_hash(&denom); - let denom_key = storage::ibc_denom_key(token_hash.raw()); - match self.ctx.read_bytes_post(&denom_key) { - Ok(Some(v)) => match std::str::from_utf8(&v) { - Ok(d) if d == denom => Ok(()), - Ok(d) => Err(Error::Denom(format!( - "Mismatch the denom: original {}, denom {}", - denom, d - ))), - Err(e) => Err(Error::Denom(format!( - "Decoding the denom failed: key {}, error {}", - denom_key, e - ))), - }, - _ => Err(Error::Denom(format!( - "Looking up the denom failed: Key {}", - denom_key - ))), - } - } -} diff --git a/shared/src/ledger/ibc/vp/mod.rs b/shared/src/ledger/ibc/vp/mod.rs index 5986a01771..bab039212a 100644 --- a/shared/src/ledger/ibc/vp/mod.rs +++ b/shared/src/ledger/ibc/vp/mod.rs @@ -1,8 +1,6 @@ //! IBC integration as a native validity predicate mod context; -mod denom; -mod token; use std::cell::RefCell; use std::collections::{BTreeSet, HashSet}; @@ -10,19 +8,18 @@ use std::rc::Rc; use std::time::Duration; use context::{PseudoExecutionContext, VpValidationContext}; -use namada_core::ledger::ibc::storage::{is_ibc_denom_key, is_ibc_key}; use namada_core::ledger::ibc::{ Error as ActionError, IbcActions, TransferModule, ValidationParams, }; use namada_core::ledger::storage::write_log::StorageModification; use namada_core::ledger::storage::{self as ledger_storage, StorageHasher}; use namada_core::proto::Tx; -use namada_core::types::address::{Address, InternalAddress}; +use namada_core::types::address::Address; use namada_core::types::storage::Key; use namada_proof_of_stake::read_pos_params; use thiserror::Error; -pub use token::{Error as IbcTokenError, IbcToken}; +use crate::ledger::ibc::storage::{calc_hash, is_ibc_denom_key, is_ibc_key}; use crate::ledger::native_vp::{self, Ctx, NativeVp, VpEnv}; use crate::ledger::parameters::read_epoch_duration_parameter; use crate::vm::WasmCacheAccess; @@ -40,8 +37,8 @@ pub enum Error { IbcAction(ActionError), #[error("State change error: {0}")] StateChange(String), - #[error("Denom store error: {0}")] - Denom(denom::Error), + #[error("Denom error: {0}")] + Denom(String), #[error("IBC event error: {0}")] IbcEvent(String), } @@ -68,8 +65,6 @@ where { type Error = Error; - const ADDR: InternalAddress = InternalAddress::Ibc; - fn validate_tx( &self, tx_data: &Tx, @@ -86,9 +81,7 @@ where self.validate_with_msg(&tx_data)?; // Validate the denom store if a denom key has been changed - if keys_changed.iter().any(is_ibc_denom_key) { - self.validate_denom(&tx_data).map_err(Error::Denom)?; - } + self.validate_denom(keys_changed)?; Ok(true) } @@ -173,6 +166,35 @@ where upgrade_path: Vec::new(), }) } + + fn validate_denom(&self, keys_changed: &BTreeSet) -> VpResult<()> { + for key in keys_changed { + if let Some(hash) = is_ibc_denom_key(key) { + match self.ctx.read_post::(key).map_err(|e| { + Error::Denom(format!( + "Getting the denom failed: Key {}, Error {}", + key, e + )) + })? { + Some(denom) => { + if calc_hash(&denom) != hash { + return Err(Error::Denom(format!( + "The denom is invalid: Key {}, Denom {}", + key, denom + ))); + } + } + None => { + return Err(Error::Denom(format!( + "The corresponding denom wasn't stored: Key {}", + key + ))); + } + } + } + } + Ok(()) + } } fn match_value( @@ -283,8 +305,8 @@ mod tests { }; use super::{get_dummy_header, *}; use crate::core::ledger::storage::testing::TestWlStorage; - use crate::core::types::address::nam; use crate::core::types::address::testing::established_address_1; + use crate::core::types::address::{nam, InternalAddress}; use crate::core::types::storage::Epoch; use crate::ibc::applications::transfer::acknowledgement::TokenTransferAcknowledgement; use crate::ibc::applications::transfer::coin::PrefixedCoin; @@ -1023,7 +1045,7 @@ mod tests { // client connection list let client_conn_key = client_connections_key(&msg.client_id_on_a); let conn_list = conn_id.to_string(); - let bytes = conn_list.as_bytes().to_vec(); + let bytes = conn_list.try_to_vec().expect("encoding failed"); wl_storage .write_log .write(&client_conn_key, bytes) @@ -1130,7 +1152,7 @@ mod tests { // client connection list let client_conn_key = client_connections_key(&msg.client_id_on_a); let conn_list = conn_id.to_string(); - let bytes = conn_list.as_bytes().to_vec(); + let bytes = conn_list.try_to_vec().expect("encoding failed"); wl_storage .write_log .write(&client_conn_key, bytes) @@ -1246,7 +1268,7 @@ mod tests { // client connection list let client_conn_key = client_connections_key(&msg.client_id_on_b); let conn_list = conn_id.to_string(); - let bytes = conn_list.as_bytes().to_vec(); + let bytes = conn_list.try_to_vec().expect("encoding failed"); wl_storage .write_log .write(&client_conn_key, bytes) @@ -2187,7 +2209,7 @@ mod tests { )); let trace_hash = calc_hash(coin.denom.to_string()); let denom_key = ibc_denom_key(&trace_hash); - let bytes = coin.denom.to_string().as_bytes().to_vec(); + let bytes = coin.denom.to_string().try_to_vec().unwrap(); wl_storage .write_log .write(&denom_key, bytes) @@ -2432,7 +2454,7 @@ mod tests { .expect("write failed"); // init the escrow balance let balance_key = - balance_key(&nam(), &Address::Internal(InternalAddress::IbcEscrow)); + balance_key(&nam(), &Address::Internal(InternalAddress::Ibc)); let amount = Amount::native_whole(100); wl_storage .write_log @@ -2581,7 +2603,7 @@ mod tests { .expect("write failed"); // init the escrow balance let balance_key = - balance_key(&nam(), &Address::Internal(InternalAddress::IbcEscrow)); + balance_key(&nam(), &Address::Internal(InternalAddress::Ibc)); let amount = Amount::native_whole(100); wl_storage .write_log diff --git a/shared/src/ledger/ibc/vp/token.rs b/shared/src/ledger/ibc/vp/token.rs index 18234abd35..1c9cc5bc1d 100644 --- a/shared/src/ledger/ibc/vp/token.rs +++ b/shared/src/ledger/ibc/vp/token.rs @@ -80,8 +80,6 @@ where { type Error = Error; - const ADDR: InternalAddress = InternalAddress::IbcBurn; - fn validate_tx( &self, tx_data: &Tx, diff --git a/shared/src/ledger/masp.rs b/shared/src/ledger/masp.rs index b6ccd8672b..a843e050cf 100644 --- a/shared/src/ledger/masp.rs +++ b/shared/src/ledger/masp.rs @@ -50,7 +50,7 @@ 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, TokenAddress}; +use namada_core::types::token::{Change, MaspDenom}; use namada_core::types::transaction::AffineCurve; #[cfg(feature = "masp-tx-gen")] use rand_core::{CryptoRng, OsRng, RngCore}; @@ -345,9 +345,6 @@ impl pub trait ShieldedUtils: Sized + BorshDeserialize + BorshSerialize + Default + Clone { - /// The type of the Tendermint client to make queries with - type C: crate::ledger::queries::Client + std::marker::Sync; - /// Get a MASP transaction prover fn local_tx_prover(&self) -> LocalTxProver; @@ -407,7 +404,7 @@ pub enum PinnedBalanceError { // #[derive(BorshSerialize, BorshDeserialize, Debug, Clone)] // pub struct MaspAmount { -// pub asset: TokenAddress, +// pub asset: Address, // pub amount: token::Amount, // } @@ -415,7 +412,7 @@ pub enum PinnedBalanceError { #[derive(BorshSerialize, BorshDeserialize, Debug, Clone)] pub struct MaspChange { /// the token address - pub asset: TokenAddress, + pub asset: Address, /// the change in the token pub change: token::Change, } @@ -424,10 +421,10 @@ pub struct MaspChange { #[derive( BorshSerialize, BorshDeserialize, Debug, Clone, Default, PartialEq, Eq, )] -pub struct MaspAmount(HashMap<(Epoch, TokenAddress), token::Change>); +pub struct MaspAmount(HashMap<(Epoch, Address), token::Change>); impl std::ops::Deref for MaspAmount { - type Target = HashMap<(Epoch, TokenAddress), token::Change>; + type Target = HashMap<(Epoch, Address), token::Change>; fn deref(&self) -> &Self::Target { &self.0 @@ -495,14 +492,9 @@ impl std::ops::Mul for MaspAmount { impl<'a> From<&'a MaspAmount> for Amount { fn from(masp_amount: &'a MaspAmount) -> Amount { let mut res = Amount::zero(); - for ((epoch, key), val) in masp_amount.iter() { + for ((epoch, token), val) in masp_amount.iter() { for denom in MaspDenom::iter() { - let asset = make_asset_type( - Some(*epoch), - &key.address, - &key.sub_prefix, - denom, - ); + let asset = make_asset_type(Some(*epoch), token, denom); res += Amount::from_pair(asset, denom.denominate_i128(val)) .unwrap(); } @@ -558,8 +550,7 @@ pub struct ShieldedContext { /// The set of note positions that have been spent pub spents: HashSet, /// Maps asset types to their decodings - pub asset_types: - HashMap, MaspDenom, Epoch)>, + pub asset_types: HashMap, /// Maps note positions to their corresponding viewing keys pub vk_map: HashMap, } @@ -631,9 +622,9 @@ impl ShieldedContext { /// Fetch the current state of the multi-asset shielded pool into a /// ShieldedContext - pub async fn fetch( + pub async fn fetch( &mut self, - client: &U::C, + client: &C, sks: &[ExtendedSpendingKey], fvks: &[ViewingKey], ) { @@ -699,8 +690,8 @@ impl ShieldedContext { /// transactions as a vector. More concretely, the HEAD_TX_KEY location /// stores the index of the last accepted transaction and each transaction /// is stored at a key derived from its index. - pub async fn fetch_shielded_transfers( - client: &U::C, + pub async fn fetch_shielded_transfers( + client: &C, last_txidx: u64, ) -> BTreeMap<(BlockHeight, TxIndex), (Epoch, Transfer, Transaction)> { // The address of the MASP account @@ -710,7 +701,7 @@ impl ShieldedContext { .push(&HEAD_TX_KEY.to_owned()) .expect("Cannot obtain a storage key"); // Query for the index of the last accepted transaction - let head_txidx = query_storage_value::(client, &head_tx_key) + let head_txidx = query_storage_value::(client, &head_tx_key) .await .unwrap_or(0); let mut shielded_txs = BTreeMap::new(); @@ -723,7 +714,7 @@ impl ShieldedContext { // Obtain the current transaction let (tx_epoch, tx_height, tx_index, current_tx, current_stx) = query_storage_value::< - U::C, + C, (Epoch, BlockHeight, TxIndex, Transfer, Transaction), >(client, ¤t_tx_key) .await @@ -744,10 +735,10 @@ impl ShieldedContext { /// associated to notes, memos, and diversifiers. And the set of notes that /// we have spent are updated. The witness map is maintained to make it /// easier to construct note merkle paths in other code. See - /// . - pub async fn scan_tx( + /// + pub async fn scan_tx( &mut self, - client: &U::C, + client: &C, height: BlockHeight, index: TxIndex, epoch: Epoch, @@ -850,10 +841,7 @@ impl ShieldedContext { } // Record the changes to the transparent accounts let mut transfer_delta = TransferDelta::new(); - let token_addr = TokenAddress { - address: tx.token.clone(), - sub_prefix: tx.sub_prefix.clone(), - }; + let token_addr = tx.token.clone(); transfer_delta.insert( tx.source.clone(), MaspChange { @@ -883,9 +871,9 @@ impl ShieldedContext { /// Compute the total unspent notes associated with the viewing key in the /// context. If the key is not in the context, then we do not know the /// balance and hence we return None. - pub async fn compute_shielded_balance( + pub async fn compute_shielded_balance( &mut self, - client: &U::C, + client: &C, vk: &ViewingKey, ) -> Option { // Cannot query the balance of a key that's not in the map @@ -913,44 +901,42 @@ impl ShieldedContext { /// Query the ledger for the decoding of the given asset type and cache it /// if it is found. - pub async fn decode_asset_type( + pub async fn decode_asset_type( &mut self, - client: &U::C, + client: &C, asset_type: AssetType, - ) -> Option<(Address, Option, MaspDenom, Epoch)> { + ) -> Option<(Address, MaspDenom, Epoch)> { // Try to find the decoding in the cache if let decoded @ Some(_) = self.asset_types.get(&asset_type) { return decoded.cloned(); } // Query for the ID of the last accepted transaction - let (addr, sub_prefix, denom, ep, _conv, _path): ( + let (addr, denom, ep, _conv, _path): ( Address, - Option, MaspDenom, _, Amount, MerklePath, ) = rpc::query_conversion(client, asset_type).await?; self.asset_types - .insert(asset_type, (addr.clone(), sub_prefix.clone(), denom, ep)); - Some((addr, sub_prefix, denom, ep)) + .insert(asset_type, (addr.clone(), denom, ep)); + Some((addr, denom, ep)) } /// Query the ledger for the conversion that is allowed for the given asset /// type and cache it. - async fn query_allowed_conversion<'a>( + async fn query_allowed_conversion<'a, C: Client + Sync>( &'a mut self, - client: &U::C, + client: &C, asset_type: AssetType, conversions: &'a mut Conversions, ) { if let Entry::Vacant(conv_entry) = conversions.entry(asset_type) { // Query for the ID of the last accepted transaction - if let Some((addr, sub_prefix, denom, ep, conv, path)) = + if let Some((addr, denom, ep, conv, path)) = query_conversion(client, asset_type).await { - self.asset_types - .insert(asset_type, (addr, sub_prefix, denom, ep)); + self.asset_types.insert(asset_type, (addr, denom, ep)); // If the conversion is 0, then we just have a pure decoding if conv != Amount::zero() { conv_entry.insert((conv.into(), path, 0)); @@ -963,9 +949,9 @@ 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: &U::C, + client: &C, vk: &ViewingKey, target_epoch: Epoch, ) -> Option { @@ -993,11 +979,11 @@ 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: &U::C, + client: &C, conv: AllowedConversion, - asset_type: (Epoch, TokenAddress, MaspDenom), + asset_type: (Epoch, Address, MaspDenom), value: i128, usage: &mut i128, input: &mut MaspAmount, @@ -1010,12 +996,8 @@ impl ShieldedContext { // If conversion if possible, accumulate the exchanged amount let conv: Amount = conv.into(); // The amount required of current asset to qualify for conversion - let masp_asset = make_asset_type( - Some(asset_type.0), - &asset_type.1.address, - &asset_type.1.sub_prefix, - asset_type.2, - ); + let masp_asset = + make_asset_type(Some(asset_type.0), &asset_type.1, asset_type.2); let threshold = -conv[&masp_asset]; if threshold == 0 { eprintln!( @@ -1047,9 +1029,9 @@ 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: &U::C, + client: &C, mut input: MaspAmount, target_epoch: Epoch, mut conversions: Conversions, @@ -1063,18 +1045,10 @@ impl ShieldedContext { let asset_epoch = *asset_epoch; let token_addr = token_addr.clone(); for denom in MaspDenom::iter() { - let target_asset_type = make_asset_type( - Some(target_epoch), - &token_addr.address, - &token_addr.sub_prefix, - denom, - ); - let asset_type = make_asset_type( - Some(asset_epoch), - &token_addr.address, - &token_addr.sub_prefix, - denom, - ); + let target_asset_type = + make_asset_type(Some(target_epoch), &token_addr, denom); + let asset_type = + make_asset_type(Some(asset_epoch), &token_addr, denom); let at_target_asset_type = target_epoch == asset_epoch; let denom_value = denom.denominate_i128(&value); @@ -1139,9 +1113,9 @@ impl ShieldedContext { (asset_epoch, token_addr.clone()), denom_value.into(), ); - for ((e, key), val) in input.iter() { - if *key == token_addr && *e == asset_epoch { - comp.insert((*e, key.clone()), *val); + for ((e, token), val) in input.iter() { + if *token == token_addr && *e == asset_epoch { + comp.insert((*e, token.clone()), *val); } } output += comp.clone(); @@ -1156,9 +1130,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( &mut self, - client: &U::C, + client: &C, vk: &ViewingKey, target: Amount, target_epoch: Epoch, @@ -1226,8 +1200,8 @@ impl ShieldedContext { /// keys to try to decrypt the output notes. If no transaction is pinned at /// the given payment address fails with /// `PinnedBalanceError::NoTransactionPinned`. - pub async fn compute_pinned_balance( - client: &U::C, + pub async fn compute_pinned_balance( + client: &C, owner: PaymentAddress, viewing_key: &ViewingKey, ) -> Result<(Amount, Epoch), PinnedBalanceError> { @@ -1249,7 +1223,7 @@ impl ShieldedContext { .push(&(PIN_KEY_PREFIX.to_owned() + &owner.hash())) .expect("Cannot obtain a storage key"); // Obtain the transaction pointer at the key - let txidx = rpc::query_storage_value::(client, &pin_key) + let txidx = rpc::query_storage_value::(client, &pin_key) .await .ok_or(PinnedBalanceError::NoTransactionPinned)?; // Construct the key for where the pinned transaction is stored @@ -1259,7 +1233,7 @@ impl ShieldedContext { // Obtain the pointed to transaction let (tx_epoch, _tx_height, _tx_index, _tx, shielded) = rpc::query_storage_value::< - U::C, + C, (Epoch, BlockHeight, TxIndex, Transfer, Transaction), >(client, &tx_key) .await @@ -1297,9 +1271,9 @@ 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( &mut self, - client: &U::C, + client: &C, owner: PaymentAddress, viewing_key: &ViewingKey, ) -> Result<(MaspAmount, Epoch), PinnedBalanceError> { @@ -1322,29 +1296,24 @@ impl ShieldedContext { /// Convert an amount whose units are AssetTypes to one whose units are /// Addresses that they decode to. All asset types not corresponding to /// the given epoch are ignored. - pub async fn decode_amount( + pub async fn decode_amount( &mut self, - client: &U::C, + client: &C, amt: Amount, target_epoch: Epoch, - ) -> HashMap { + ) -> HashMap { let mut res = HashMap::new(); for (asset_type, val) in amt.components() { // Decode the asset type let decoded = self.decode_asset_type(client, *asset_type).await; // Only assets with the target timestamp count match decoded { - Some(asset_type @ (_, _, _, epoch)) - if epoch == target_epoch => - { + Some(asset_type @ (_, _, epoch)) if epoch == target_epoch => { decode_component( asset_type, *val, &mut res, - |address, sub_prefix, _| TokenAddress { - address, - sub_prefix, - }, + |address, _| address, ); } _ => {} @@ -1355,32 +1324,20 @@ impl ShieldedContext { /// Convert an amount whose units are AssetTypes to one whose units are /// Addresses that they decode to. - pub async fn decode_all_amounts( + pub async fn decode_all_amounts( &mut self, - client: &U::C, + client: &C, amt: Amount, ) -> MaspAmount { - let mut res: HashMap<(Epoch, TokenAddress), Change> = - HashMap::default(); + let mut res: HashMap<(Epoch, Address), Change> = HashMap::default(); for (asset_type, val) in amt.components() { // Decode the asset type if let Some(decoded) = self.decode_asset_type(client, *asset_type).await { - decode_component( - decoded, - *val, - &mut res, - |address, sub_prefix, epoch| { - ( - epoch, - TokenAddress { - address, - sub_prefix, - }, - ) - }, - ) + decode_component(decoded, *val, &mut res, |address, epoch| { + (epoch, address) + }) } } MaspAmount(res) @@ -1394,9 +1351,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( + pub async fn gen_shielded_transfer( &mut self, - client: &U::C, + client: &C, args: &args::TxTransfer, shielded_gas: bool, ) -> Result< @@ -1440,12 +1397,8 @@ impl ShieldedContext { unreachable!("The function `gen_shielded_transfer` is only called by `submit_tx` which validates amounts.") }; // Convert transaction amount into MASP types - let (asset_types, amount) = convert_amount( - epoch, - &args.token, - &args.sub_prefix.as_ref(), - amt.amount, - ); + let (asset_types, amount) = + convert_amount(epoch, &args.token, amt.amount); let tx_fee = // If there are shielded inputs @@ -1456,7 +1409,7 @@ impl ShieldedContext { // Transaction fees need to match the amount in the wrapper Transfer // when MASP source is used let (_, shielded_fee) = - convert_amount(epoch, &args.tx.fee_token, &None, fee.amount); + convert_amount(epoch, &args.tx.fee_token, fee.amount); let required_amt = if shielded_gas { amount + shielded_fee.clone() } else { @@ -1611,9 +1564,9 @@ impl ShieldedContext { /// transactions. If an owner is specified, then restrict the set to only /// transactions crediting/debiting the given owner. If token is specified, /// then restrict set to only transactions involving the given token. - pub async fn query_tx_deltas( + pub async fn query_tx_deltas( &mut self, - client: &U::C, + client: &C, query_owner: &Either>, query_token: &Option
, viewing_keys: &HashMap, @@ -1701,10 +1654,7 @@ impl ShieldedContext { let delta = TransferDelta::from([( transfer.source.clone(), MaspChange { - asset: TokenAddress { - address: transfer.token.clone(), - sub_prefix: transfer.sub_prefix.clone(), - }, + asset: transfer.token.clone(), change: -transfer.amount.amount.change(), }, )]); @@ -1744,33 +1694,15 @@ fn extract_payload( } /// Make asset type corresponding to given address and epoch -pub fn make_asset_type( +pub fn make_asset_type( epoch: Option, token: &Address, - sub_prefix: &Option, denom: MaspDenom, ) -> AssetType { // Typestamp the chosen token with the current epoch let token_bytes = match epoch { - None => ( - token, - sub_prefix - .as_ref() - .map(|k| k.to_string()) - .unwrap_or_default(), - denom, - ) - .try_to_vec() - .expect("token should serialize"), - Some(epoch) => ( - token, - sub_prefix - .as_ref() - .map(|k| k.to_string()) - .unwrap_or_default(), - denom, - epoch.0, - ) + None => (token, denom).try_to_vec().expect("token should serialize"), + Some(epoch) => (token, denom, epoch.0) .try_to_vec() .expect("token should serialize"), }; @@ -1782,14 +1714,12 @@ pub fn make_asset_type( fn convert_amount( epoch: Epoch, token: &Address, - sub_prefix: &Option<&String>, val: token::Amount, ) -> ([AssetType; 4], Amount) { let mut amount = Amount::zero(); let asset_types: [AssetType; 4] = MaspDenom::iter() .map(|denom| { - let asset_type = - make_asset_type(Some(epoch), token, sub_prefix, denom); + let asset_type = make_asset_type(Some(epoch), token, denom); // Combine the value and unit into one amount amount += Amount::from_nonnegative(asset_type, denom.denominate(&val)) diff --git a/shared/src/ledger/native_vp/ethereum_bridge/authorize.rs b/shared/src/ledger/native_vp/ethereum_bridge/authorize.rs deleted file mode 100644 index 8c998ad50b..0000000000 --- a/shared/src/ledger/native_vp/ethereum_bridge/authorize.rs +++ /dev/null @@ -1,56 +0,0 @@ -//! Functionality to do with checking whether a transaction is authorized by the -//! "owner" of some key under this account -use std::collections::BTreeSet; - -use namada_core::types::address::Address; - -/// For wrapped ERC20 transfers, checks that `verifiers` contains the `sender`'s -/// address - we delegate to the sender's VP to authorize the transfer (for -/// regular Namada accounts, this will be `vp_implicit` or `vp_user`). -pub(super) fn is_authorized( - verifiers: &BTreeSet
, - sender: &Address, - receiver: &Address, -) -> bool { - verifiers.contains(sender) && verifiers.contains(receiver) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::types::address; - - #[test] - fn test_is_authorized_passes() { - let sender = address::testing::established_address_1(); - let receiver = address::testing::established_address_2(); - let verifiers = BTreeSet::from([sender.clone(), receiver.clone()]); - - let authorized = is_authorized(&verifiers, &sender, &receiver); - - assert!(authorized); - } - - #[test] - fn test_is_authorized_fails() { - let sender = address::testing::established_address_1(); - let receiver = address::testing::established_address_2(); - let verifiers = BTreeSet::default(); - - let authorized = is_authorized(&verifiers, &sender, &receiver); - - assert!(!authorized); - - let verifiers = BTreeSet::from([sender.clone()]); - - let authorized = is_authorized(&verifiers, &sender, &receiver); - - assert!(!authorized); - - let verifiers = BTreeSet::from([receiver.clone()]); - - let authorized = is_authorized(&verifiers, &sender, &receiver); - - assert!(!authorized); - } -} diff --git a/shared/src/ledger/native_vp/ethereum_bridge/bridge_pool_vp.rs b/shared/src/ledger/native_vp/ethereum_bridge/bridge_pool_vp.rs index 283cf52c58..a63b252955 100644 --- a/shared/src/ledger/native_vp/ethereum_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/native_vp/ethereum_bridge/bridge_pool_vp.rs @@ -92,30 +92,20 @@ where transfer: &PendingTransfer, ) -> Result { // check that the assets to be transferred were escrowed - let asset_key = wrapped_erc20s::Keys::from(&transfer.transfer.asset); - let owner_key = asset_key.balance(&transfer.transfer.sender); - let escrow_key = asset_key.balance(&BRIDGE_POOL_ADDRESS); + let token = wrapped_erc20s::token(&transfer.transfer.asset); + let owner_key = balance_key(&token, &transfer.transfer.sender); + let escrow_key = balance_key(&token, &BRIDGE_POOL_ADDRESS); if keys_changed.contains(&owner_key) && keys_changed.contains(&escrow_key) { - match check_balance_changes( - &self.ctx, - (&self.ctx.storage.native_token, &escrow_key) - .try_into() - .expect("This should not fail"), - (&self.ctx.storage.native_token, &owner_key) - .try_into() - .expect("This should not fail"), - ) { - Ok(Some((sender, _, amount))) - if check_delta(&sender, &amount, transfer) => {} - other => { + match check_balance_changes(&self.ctx, &owner_key, &escrow_key)? { + Some(amount) if amount == transfer.transfer.amount => Ok(true), + _ => { tracing::debug!( "The assets of the transfer were not properly \ - escrowed into the Ethereum bridge pool: {:?}", - other + escrowed into the Ethereum bridge pool" ); - return Ok(false); + Ok(false) } } } else { @@ -123,14 +113,8 @@ where "The assets of the transfer were not properly escrowed into \ the Ethereum bridge pool." ); - return Ok(false); + Ok(false) } - - tracing::info!( - "The Ethereum bridge pool VP accepted the transfer {:?}.", - transfer - ); - Ok(true) } /// Check that the correct amount of Nam was sent @@ -235,15 +219,6 @@ where } } -/// Check if a delta matches the delta given by a transfer -fn check_delta( - sender: &Address, - amount: &Amount, - transfer: &PendingTransfer, -) -> bool { - *sender == transfer.transfer.sender && *amount == transfer.transfer.amount -} - /// Helper struct for handling the different escrow /// checking scenarios. struct EscrowDelta<'a> { @@ -269,8 +244,6 @@ where { type Error = Error; - const ADDR: InternalAddress = InternalAddress::EthBridgePool; - fn validate_tx( &self, tx: &Tx, @@ -486,7 +459,7 @@ mod test_bridge_pool_vp { ) -> BTreeSet { // get the balance keys let token_key = - wrapped_erc20s::Keys::from(&ASSET).balance(&balance.owner); + balance_key(&wrapped_erc20s::token(&ASSET), &balance.owner); let account_key = balance_key(&nam(), &balance.owner); // update the balance of nam @@ -1028,12 +1001,14 @@ mod test_bridge_pool_vp { BTreeSet::from([get_pending_key(&transfer)]) }; // We escrow 0 tokens - keys_changed.insert( - wrapped_erc20s::Keys::from(&ASSET).balance(&bertha_address()), - ); - keys_changed.insert( - wrapped_erc20s::Keys::from(&ASSET).balance(&BRIDGE_POOL_ADDRESS), - ); + keys_changed.insert(balance_key( + &wrapped_erc20s::token(&ASSET), + &bertha_address(), + )); + keys_changed.insert(balance_key( + &wrapped_erc20s::token(&ASSET), + &BRIDGE_POOL_ADDRESS, + )); let verifiers = BTreeSet::default(); // create the data to be given to the vp diff --git a/shared/src/ledger/native_vp/ethereum_bridge/mod.rs b/shared/src/ledger/native_vp/ethereum_bridge/mod.rs index 7e5062a251..85df785e79 100644 --- a/shared/src/ledger/native_vp/ethereum_bridge/mod.rs +++ b/shared/src/ledger/native_vp/ethereum_bridge/mod.rs @@ -2,6 +2,5 @@ //! This includes both the bridge vp and the vp for the bridge //! pool. -mod authorize; pub mod bridge_pool_vp; pub mod vp; diff --git a/shared/src/ledger/native_vp/ethereum_bridge/vp.rs b/shared/src/ledger/native_vp/ethereum_bridge/vp.rs index 47eed29f3c..33a828938a 100644 --- a/shared/src/ledger/native_vp/ethereum_bridge/vp.rs +++ b/shared/src/ledger/native_vp/ethereum_bridge/vp.rs @@ -9,11 +9,10 @@ use namada_core::ledger::eth_bridge::storage::{ }; use namada_core::ledger::storage::traits::StorageHasher; use namada_core::ledger::{eth_bridge, storage as ledger_storage}; -use namada_core::types::address::{Address, InternalAddress}; +use namada_core::types::address::Address; use namada_core::types::storage::Key; use namada_core::types::token::{balance_key, Amount, Change}; -use crate::ledger::native_vp::ethereum_bridge::authorize; use crate::ledger::native_vp::{Ctx, NativeVp, StorageReader, VpEnv}; use crate::proto::Tx; use crate::vm::WasmCacheAccess; @@ -94,7 +93,7 @@ where #[derive(Debug)] enum CheckType { Escrow, - Erc20Transfer(wrapped_erc20s::Key, wrapped_erc20s::Key), + Erc20Transfer, } #[derive(thiserror::Error, Debug)] @@ -110,8 +109,6 @@ where { type Error = Error; - const ADDR: InternalAddress = eth_bridge::INTERNAL_ADDRESS; - /// Validate that a wasm transaction is permitted to change keys under this /// account. /// @@ -138,35 +135,14 @@ where "Ethereum Bridge VP triggered", ); - let (key_a, key_b) = match determine_check_type( + match determine_check_type( &self.ctx.storage.native_token, keys_changed, )? { - Some(CheckType::Erc20Transfer(key_a, key_b)) => (key_a, key_b), - Some(CheckType::Escrow) => return self.check_escrow(verifiers), - None => return Ok(false), - }; - let (sender, receiver, _) = - match check_balance_changes(&self.ctx, key_a, key_b)? { - Some(sender) => sender, - None => return Ok(false), - }; - if authorize::is_authorized(verifiers, &sender, &receiver) { - tracing::info!( - ?verifiers, - ?sender, - ?receiver, - "Ethereum Bridge VP authorized transfer" - ); - Ok(true) - } else { - tracing::info!( - ?verifiers, - ?sender, - ?receiver, - "Ethereum Bridge VP rejected unauthorized transfer" - ); - Ok(false) + // Multitoken VP checks the balance changes for the ERC20 transfer + Some(CheckType::Erc20Transfer) => Ok(true), + Some(CheckType::Escrow) => self.check_escrow(verifiers), + None => Ok(false), } } } @@ -245,155 +221,93 @@ fn determine_check_type( ); return Ok(None); } - Ok(Some(CheckType::Erc20Transfer(key_a, key_b))) + Ok(Some(CheckType::Erc20Transfer)) } -/// Checks that the balances at both `key_a` and `key_b` have changed by some -/// amount, and that the changes balance each other out. If the balance changes -/// are invalid, the reason is logged and a `None` is returned. Otherwise, -/// return: -/// - the `Address` of the sender i.e. the owner of the balance which is -/// decreasing -/// - the `Address` of the receiver i.e. the owner of the balance which is -/// increasing -/// - the `Amount` of the transfer i.e. by how much the sender's balance -/// decreased, or equivalently by how much the receiver's balance increased +/// Checks that the balances at both `sender` and `receiver` have changed by +/// some amount, and that the changes balance each other out. If the balance +/// changes are invalid, the reason is logged and a `None` is returned. +/// Otherwise, return the `Amount` of the transfer i.e. by how much the sender's +/// balance decreased, or equivalently by how much the receiver's balance +/// increased pub(super) fn check_balance_changes( reader: impl StorageReader, - key_a: wrapped_erc20s::Key, - key_b: wrapped_erc20s::Key, -) -> Result> { - let (balance_a, balance_b) = - match (key_a.suffix.clone(), key_b.suffix.clone()) { - ( - wrapped_erc20s::KeyType::Balance { .. }, - wrapped_erc20s::KeyType::Balance { .. }, - ) => (Key::from(&key_a), Key::from(&key_b)), - ( - wrapped_erc20s::KeyType::Balance { .. }, - wrapped_erc20s::KeyType::Supply, - ) - | ( - wrapped_erc20s::KeyType::Supply, - wrapped_erc20s::KeyType::Balance { .. }, - ) => { - tracing::debug!( - ?key_a, - ?key_b, - "Rejecting transaction that is attempting to change a \ - supply key" - ); - return Ok(None); - } - ( - wrapped_erc20s::KeyType::Supply, - wrapped_erc20s::KeyType::Supply, - ) => { - // in theory, this should be unreachable!() as we would have - // already rejected if both supply keys were for - // the same asset - tracing::debug!( - ?key_a, - ?key_b, - "Rejecting transaction that is attempting to change two \ - supply keys" - ); - return Ok(None); - } - }; - let balance_a_pre = reader - .read_pre_value::(&balance_a)? + sender: &Key, + receiver: &Key, +) -> Result> { + let sender_balance_pre = reader + .read_pre_value::(sender)? .unwrap_or_default() .change(); - let balance_a_post = match reader.read_post_value::(&balance_a)? { + let sender_balance_post = match reader.read_post_value::(sender)? { Some(value) => value, None => { - tracing::debug!( - ?balance_a, - "Rejecting transaction as could not read_post balance key" - ); - return Ok(None); + return Err(eyre!( + "Rejecting transaction as could not read_post balance key {}", + sender, + )); } } .change(); - let balance_b_pre = reader - .read_pre_value::(&balance_b)? + let receiver_balance_pre = reader + .read_pre_value::(receiver)? .unwrap_or_default() .change(); - let balance_b_post = match reader.read_post_value::(&balance_b)? { + let receiver_balance_post = match reader + .read_post_value::(receiver)? + { Some(value) => value, None => { - tracing::debug!( - ?balance_b, - "Rejecting transaction as could not read_post balance key" - ); - return Ok(None); + return Err(eyre!( + "Rejecting transaction as could not read_post balance key {}", + receiver, + )); } } .change(); - let balance_a_delta = calculate_delta(balance_a_pre, balance_a_post)?; - let balance_b_delta = calculate_delta(balance_b_pre, balance_b_post)?; - if balance_a_delta != -balance_b_delta { + let sender_balance_delta = + calculate_delta(sender_balance_pre, sender_balance_post)?; + let receiver_balance_delta = + calculate_delta(receiver_balance_pre, receiver_balance_post)?; + if receiver_balance_delta != -sender_balance_delta { tracing::debug!( - ?balance_a_pre, - ?balance_b_pre, - ?balance_a_post, - ?balance_b_post, - ?balance_a_delta, - ?balance_b_delta, + ?sender_balance_pre, + ?receiver_balance_pre, + ?sender_balance_post, + ?receiver_balance_post, + ?sender_balance_delta, + ?receiver_balance_delta, "Rejecting transaction as balance changes do not match" ); return Ok(None); } - if balance_a_delta.is_zero() { - assert_eq!(balance_b_delta, Change::zero()); - tracing::debug!("Rejecting transaction as no balance change"); + if sender_balance_delta.is_zero() || sender_balance_delta > Change::zero() { + assert!( + receiver_balance_delta.is_zero() + || receiver_balance_delta < Change::zero() + ); + tracing::debug!( + "Rejecting transaction as no balance change or invalid change" + ); return Ok(None); } - if balance_a_post < Change::zero() { + if sender_balance_post < Change::zero() { tracing::debug!( - ?balance_a_post, + ?sender_balance_post, "Rejecting transaction as balance is negative" ); return Ok(None); } - if balance_b_post < Change::zero() { + if receiver_balance_post < Change::zero() { tracing::debug!( - ?balance_b_post, + ?receiver_balance_post, "Rejecting transaction as balance is negative" ); return Ok(None); } - if balance_a_delta < Change::zero() { - if let wrapped_erc20s::KeyType::Balance { owner: sender } = key_a.suffix - { - let wrapped_erc20s::KeyType::Balance { owner: receiver } = - key_b.suffix else { unreachable!() }; - Ok(Some(( - sender, - receiver, - Amount::from_change(balance_b_delta), - ))) - } else { - unreachable!() - } - } else { - assert!(balance_b_delta < Change::zero()); - if let wrapped_erc20s::KeyType::Balance { owner: sender } = key_b.suffix - { - let wrapped_erc20s::KeyType::Balance { owner: receiver } = - key_a.suffix else { unreachable!() }; - Ok(Some(( - sender, - receiver, - Amount::from_change(balance_a_delta), - ))) - } else { - unreachable!() - } - } + Ok(Some(Amount::from_change(receiver_balance_delta))) } /// Return the delta between `balance_pre` and `balance_post`, erroring if there @@ -437,6 +351,7 @@ mod tests { use crate::types::ethereum_events; use crate::types::ethereum_events::EthAddress; use crate::types::storage::TxIndex; + use crate::types::token::minted_balance_key; use crate::types::transaction::TxType; use crate::vm::wasm::VpCache; use crate::vm::WasmCacheRwAccess; @@ -563,10 +478,9 @@ mod tests { { let keys_changed = BTreeSet::from_iter(vec![ arbitrary_key(), - wrapped_erc20s::Keys::from( + minted_balance_key(&wrapped_erc20s::token( ðereum_events::testing::DAI_ERC20_ETH_ADDRESS, - ) - .supply(), + )), ]); let result = determine_check_type(&nam(), &keys_changed); @@ -577,10 +491,10 @@ mod tests { { let keys_changed = BTreeSet::from_iter(vec![ arbitrary_key(), - wrapped_erc20s::Keys::from( - ðereum_events::testing::DAI_ERC20_ETH_ADDRESS, - ) - .balance( + balance_key( + &wrapped_erc20s::token( + ðereum_events::testing::DAI_ERC20_ETH_ADDRESS, + ), &Address::decode(ARBITRARY_OWNER_A_ADDRESS) .expect("Couldn't set up test"), ), @@ -596,17 +510,17 @@ mod tests { fn test_rejects_if_multitoken_keys_for_different_assets() { { let keys_changed = BTreeSet::from_iter(vec![ - wrapped_erc20s::Keys::from( - ðereum_events::testing::DAI_ERC20_ETH_ADDRESS, - ) - .balance( + balance_key( + &wrapped_erc20s::token( + ðereum_events::testing::DAI_ERC20_ETH_ADDRESS, + ), &Address::decode(ARBITRARY_OWNER_A_ADDRESS) .expect("Couldn't set up test"), ), - wrapped_erc20s::Keys::from( - ðereum_events::testing::USDC_ERC20_ETH_ADDRESS, - ) - .balance( + balance_key( + &wrapped_erc20s::token( + ðereum_events::testing::USDC_ERC20_ETH_ADDRESS, + ), &Address::decode(ARBITRARY_OWNER_B_ADDRESS) .expect("Couldn't set up test"), ), @@ -623,8 +537,9 @@ mod tests { let asset = ðereum_events::testing::DAI_ERC20_ETH_ADDRESS; { let keys_changed = BTreeSet::from_iter(vec![ - wrapped_erc20s::Keys::from(asset).supply(), - wrapped_erc20s::Keys::from(asset).balance( + minted_balance_key(&wrapped_erc20s::token(asset)), + balance_key( + &wrapped_erc20s::token(asset), &Address::decode(ARBITRARY_OWNER_B_ADDRESS) .expect("Couldn't set up test"), ), diff --git a/shared/src/ledger/native_vp/governance/mod.rs b/shared/src/ledger/native_vp/governance/mod.rs index 7d9323f256..d41242a477 100644 --- a/shared/src/ledger/native_vp/governance/mod.rs +++ b/shared/src/ledger/native_vp/governance/mod.rs @@ -16,7 +16,7 @@ use crate::ledger::native_vp::{Ctx, NativeVp}; use crate::ledger::storage_api::StorageRead; use crate::ledger::{native_vp, pos}; use crate::proto::Tx; -use crate::types::address::{Address, InternalAddress}; +use crate::types::address::Address; use crate::types::storage::{Epoch, Key}; use crate::types::token; use crate::vm::WasmCacheAccess; @@ -50,8 +50,6 @@ where { type Error = Error; - const ADDR: InternalAddress = InternalAddress::Governance; - fn validate_tx( &self, tx_data: &Tx, diff --git a/shared/src/ledger/native_vp/mod.rs b/shared/src/ledger/native_vp/mod.rs index dcc0432ea7..ed34545f16 100644 --- a/shared/src/ledger/native_vp/mod.rs +++ b/shared/src/ledger/native_vp/mod.rs @@ -3,6 +3,7 @@ pub mod ethereum_bridge; pub mod governance; +pub mod multitoken; pub mod parameters; pub mod replay_protection; pub mod slash_fund; @@ -21,7 +22,7 @@ use crate::ledger::storage; use crate::ledger::storage::write_log::WriteLog; use crate::ledger::storage::{Storage, StorageHasher}; use crate::proto::Tx; -use crate::types::address::{Address, InternalAddress}; +use crate::types::address::Address; use crate::types::hash::Hash; use crate::types::storage::{ BlockHash, BlockHeight, Epoch, Header, Key, TxIndex, @@ -36,9 +37,6 @@ pub type Error = storage_api::Error; /// A native VP module should implement its validation logic using this trait. pub trait NativeVp { - /// The address of this VP - const ADDR: InternalAddress; - /// Error type for the methods' results. type Error: std::error::Error; diff --git a/shared/src/ledger/native_vp/multitoken.rs b/shared/src/ledger/native_vp/multitoken.rs new file mode 100644 index 0000000000..5b88c0f152 --- /dev/null +++ b/shared/src/ledger/native_vp/multitoken.rs @@ -0,0 +1,595 @@ +//! Native VP for multitokens + +use std::collections::{BTreeSet, HashMap}; + +use thiserror::Error; + +use crate::ledger::native_vp::{self, Ctx, NativeVp}; +use crate::ledger::storage; +use crate::ledger::vp_env::VpEnv; +use crate::proto::Tx; +use crate::types::address::{Address, InternalAddress}; +use crate::types::storage::{Key, KeySeg}; +use crate::types::token::{ + is_any_minted_balance_key, is_any_minter_key, is_any_token_balance_key, + minter_key, Amount, Change, +}; +use crate::vm::WasmCacheAccess; + +#[allow(missing_docs)] +#[derive(Error, Debug)] +pub enum Error { + #[error("Native VP error: {0}")] + NativeVpError(#[from] native_vp::Error), +} + +/// Multitoken functions result +pub type Result = std::result::Result; + +/// Multitoken VP +pub struct MultitokenVp<'a, DB, H, CA> +where + DB: storage::DB + for<'iter> storage::DBIter<'iter>, + H: storage::StorageHasher, + CA: WasmCacheAccess, +{ + /// Context to interact with the host structures. + pub ctx: Ctx<'a, DB, H, CA>, +} + +impl<'a, DB, H, CA> NativeVp for MultitokenVp<'a, DB, H, CA> +where + DB: 'static + storage::DB + for<'iter> storage::DBIter<'iter>, + H: 'static + storage::StorageHasher, + CA: 'static + WasmCacheAccess, +{ + type Error = Error; + + fn validate_tx( + &self, + _tx: &Tx, + keys_changed: &BTreeSet, + verifiers: &BTreeSet
, + ) -> Result { + let mut changes = HashMap::new(); + let mut mints = HashMap::new(); + for key in keys_changed { + if let Some([token, _]) = is_any_token_balance_key(key) { + let pre: Amount = self.ctx.read_pre(key)?.unwrap_or_default(); + let post: Amount = self.ctx.read_post(key)?.unwrap_or_default(); + let diff = post.change() - pre.change(); + match changes.get_mut(token) { + Some(change) => *change += diff, + None => _ = changes.insert(token, diff), + } + } else if let Some(token) = is_any_minted_balance_key(key) { + let pre: Amount = self.ctx.read_pre(key)?.unwrap_or_default(); + let post: Amount = self.ctx.read_post(key)?.unwrap_or_default(); + let diff = post.change() - pre.change(); + match mints.get_mut(token) { + Some(mint) => *mint += diff, + None => _ = mints.insert(token, diff), + } + + // Check if the minter is set + if !self.is_valid_minter(token, verifiers)? { + return Ok(false); + } + } else if let Some(token) = is_any_minter_key(key) { + if !self.is_valid_minter(token, verifiers)? { + return Ok(false); + } + } else if key.segments.get(0) + == Some( + &Address::Internal(InternalAddress::Multitoken).to_db_key(), + ) + { + // Reject when trying to update an unexpected key under + // `#Multitoken/...` + return Ok(false); + } + } + + Ok(changes.iter().all(|(token, change)| { + let mint = match mints.get(token) { + Some(mint) => *mint, + None => Change::zero(), + }; + *change == mint + })) + } +} + +impl<'a, DB, H, CA> MultitokenVp<'a, DB, H, CA> +where + DB: 'static + storage::DB + for<'iter> storage::DBIter<'iter>, + H: 'static + storage::StorageHasher, + CA: 'static + WasmCacheAccess, +{ + /// Return the minter if the minter is valid and the minter VP exists + pub fn is_valid_minter( + &self, + token: &Address, + verifiers: &BTreeSet
, + ) -> Result { + match token { + Address::Internal(InternalAddress::IbcToken(_)) => { + // Check if the minter is set + let minter_key = minter_key(token); + match self.ctx.read_post::
(&minter_key)? { + Some(minter) + if minter + == Address::Internal(InternalAddress::Ibc) => + { + Ok(verifiers.contains(&minter)) + } + _ => Ok(false), + } + } + _ => { + // ERC20 and other tokens should not be minted by a wasm + // transaction + Ok(false) + } + } + } +} + +#[cfg(test)] +mod tests { + use std::collections::BTreeSet; + + use borsh::BorshSerialize; + + use super::*; + use crate::core::ledger::storage::testing::TestWlStorage; + use crate::core::types::address::nam; + use crate::core::types::address::testing::{ + established_address_1, established_address_2, + }; + use crate::ledger::gas::VpGasMeter; + use crate::ledger::ibc::storage::ibc_token; + use crate::proto::{Code, Data, Section, Signature, Tx}; + use crate::types::address::{Address, InternalAddress}; + use crate::types::key::testing::keypair_1; + use crate::types::storage::TxIndex; + use crate::types::token::{ + balance_key, minted_balance_key, minter_key, Amount, + }; + use crate::types::transaction::TxType; + use crate::vm::wasm::compilation_cache::common::testing::cache as wasm_cache; + + const ADDRESS: Address = Address::Internal(InternalAddress::Multitoken); + + fn dummy_tx(wl_storage: &TestWlStorage) -> Tx { + let tx_code = vec![]; + let tx_data = vec![]; + let mut tx = Tx::new(TxType::Raw); + tx.header.chain_id = wl_storage.storage.chain_id.clone(); + tx.set_code(Code::new(tx_code)); + tx.set_data(Data::new(tx_data)); + tx.add_section(Section::Signature(Signature::new( + tx.sechashes(), + &keypair_1(), + ))); + tx + } + + #[test] + fn test_valid_transfer() { + let mut wl_storage = TestWlStorage::default(); + let mut keys_changed = BTreeSet::new(); + + let sender = established_address_1(); + let sender_key = balance_key(&nam(), &sender); + let amount = Amount::native_whole(100); + wl_storage + .storage + .write(&sender_key, amount.try_to_vec().unwrap()) + .expect("write failed"); + + // transfer 10 + let amount = Amount::native_whole(90); + wl_storage + .write_log + .write(&sender_key, amount.try_to_vec().unwrap()) + .expect("write failed"); + keys_changed.insert(sender_key); + let receiver = established_address_2(); + let receiver_key = balance_key(&nam(), &receiver); + let amount = Amount::native_whole(10); + wl_storage + .write_log + .write(&receiver_key, amount.try_to_vec().unwrap()) + .expect("write failed"); + keys_changed.insert(receiver_key); + + let tx_index = TxIndex::default(); + let tx = dummy_tx(&wl_storage); + let gas_meter = VpGasMeter::new(0); + let (vp_wasm_cache, _vp_cache_dir) = wasm_cache(); + let mut verifiers = BTreeSet::new(); + verifiers.insert(sender); + let ctx = Ctx::new( + &ADDRESS, + &wl_storage.storage, + &wl_storage.write_log, + &tx, + &tx_index, + gas_meter, + &keys_changed, + &verifiers, + vp_wasm_cache, + ); + + let vp = MultitokenVp { ctx }; + assert!( + vp.validate_tx(&tx, &keys_changed, &verifiers) + .expect("validation failed") + ); + } + + #[test] + fn test_invalid_transfer() { + let mut wl_storage = TestWlStorage::default(); + let mut keys_changed = BTreeSet::new(); + + let sender = established_address_1(); + let sender_key = balance_key(&nam(), &sender); + let amount = Amount::native_whole(100); + wl_storage + .storage + .write(&sender_key, amount.try_to_vec().unwrap()) + .expect("write failed"); + + // transfer 10 + let amount = Amount::native_whole(90); + wl_storage + .write_log + .write(&sender_key, amount.try_to_vec().unwrap()) + .expect("write failed"); + keys_changed.insert(sender_key); + let receiver = established_address_2(); + let receiver_key = balance_key(&nam(), &receiver); + // receive more than 10 + let amount = Amount::native_whole(100); + wl_storage + .write_log + .write(&receiver_key, amount.try_to_vec().unwrap()) + .expect("write failed"); + keys_changed.insert(receiver_key); + + let tx_index = TxIndex::default(); + let tx = dummy_tx(&wl_storage); + let gas_meter = VpGasMeter::new(0); + let (vp_wasm_cache, _vp_cache_dir) = wasm_cache(); + let verifiers = BTreeSet::new(); + let ctx = Ctx::new( + &ADDRESS, + &wl_storage.storage, + &wl_storage.write_log, + &tx, + &tx_index, + gas_meter, + &keys_changed, + &verifiers, + vp_wasm_cache, + ); + + let vp = MultitokenVp { ctx }; + assert!( + !vp.validate_tx(&tx, &keys_changed, &verifiers) + .expect("validation failed") + ); + } + + #[test] + fn test_valid_mint() { + let mut wl_storage = TestWlStorage::default(); + let mut keys_changed = BTreeSet::new(); + + // IBC token + let token = ibc_token("/port-42/channel-42/denom"); + + // mint 100 + let target = established_address_1(); + let target_key = balance_key(&token, &target); + let amount = Amount::native_whole(100); + wl_storage + .write_log + .write(&target_key, amount.try_to_vec().unwrap()) + .expect("write failed"); + keys_changed.insert(target_key); + let minted_key = minted_balance_key(&token); + let amount = Amount::native_whole(100); + wl_storage + .write_log + .write(&minted_key, amount.try_to_vec().unwrap()) + .expect("write failed"); + keys_changed.insert(minted_key); + + // minter + let minter = Address::Internal(InternalAddress::Ibc); + let minter_key = minter_key(&token); + wl_storage + .write_log + .write(&minter_key, minter.try_to_vec().unwrap()) + .expect("write failed"); + keys_changed.insert(minter_key); + + let tx_index = TxIndex::default(); + let tx = dummy_tx(&wl_storage); + let gas_meter = VpGasMeter::new(0); + let (vp_wasm_cache, _vp_cache_dir) = wasm_cache(); + let mut verifiers = BTreeSet::new(); + // for the minter + verifiers.insert(minter); + let ctx = Ctx::new( + &ADDRESS, + &wl_storage.storage, + &wl_storage.write_log, + &tx, + &tx_index, + gas_meter, + &keys_changed, + &verifiers, + vp_wasm_cache, + ); + + let vp = MultitokenVp { ctx }; + assert!( + vp.validate_tx(&tx, &keys_changed, &verifiers) + .expect("validation failed") + ); + } + + #[test] + fn test_invalid_mint() { + let mut wl_storage = TestWlStorage::default(); + let mut keys_changed = BTreeSet::new(); + + // mint 100 + let target = established_address_1(); + let target_key = balance_key(&nam(), &target); + // mint more than 100 + let amount = Amount::native_whole(1000); + wl_storage + .write_log + .write(&target_key, amount.try_to_vec().unwrap()) + .expect("write failed"); + keys_changed.insert(target_key); + let minted_key = minted_balance_key(&nam()); + let amount = Amount::native_whole(100); + wl_storage + .write_log + .write(&minted_key, amount.try_to_vec().unwrap()) + .expect("write failed"); + keys_changed.insert(minted_key); + + // minter + let minter = nam(); + let minter_key = minter_key(&nam()); + wl_storage + .write_log + .write(&minter_key, minter.try_to_vec().unwrap()) + .expect("write failed"); + keys_changed.insert(minter_key); + + let tx_index = TxIndex::default(); + let tx = dummy_tx(&wl_storage); + let gas_meter = VpGasMeter::new(0); + let (vp_wasm_cache, _vp_cache_dir) = wasm_cache(); + let mut verifiers = BTreeSet::new(); + // for the minter + verifiers.insert(minter); + let ctx = Ctx::new( + &ADDRESS, + &wl_storage.storage, + &wl_storage.write_log, + &tx, + &tx_index, + gas_meter, + &keys_changed, + &verifiers, + vp_wasm_cache, + ); + + let vp = MultitokenVp { ctx }; + assert!( + !vp.validate_tx(&tx, &keys_changed, &verifiers) + .expect("validation failed") + ); + } + + #[test] + fn test_no_minter() { + let mut wl_storage = TestWlStorage::default(); + let mut keys_changed = BTreeSet::new(); + + // IBC token + let token = ibc_token("/port-42/channel-42/denom"); + + // mint 100 + let target = established_address_1(); + let target_key = balance_key(&token, &target); + let amount = Amount::native_whole(100); + wl_storage + .write_log + .write(&target_key, amount.try_to_vec().unwrap()) + .expect("write failed"); + keys_changed.insert(target_key); + let minted_key = minted_balance_key(&token); + let amount = Amount::native_whole(100); + wl_storage + .write_log + .write(&minted_key, amount.try_to_vec().unwrap()) + .expect("write failed"); + keys_changed.insert(minted_key); + + // no minter is set + + let tx_index = TxIndex::default(); + let tx = dummy_tx(&wl_storage); + let gas_meter = VpGasMeter::new(0); + let (vp_wasm_cache, _vp_cache_dir) = wasm_cache(); + let verifiers = BTreeSet::new(); + let ctx = Ctx::new( + &ADDRESS, + &wl_storage.storage, + &wl_storage.write_log, + &tx, + &tx_index, + gas_meter, + &keys_changed, + &verifiers, + vp_wasm_cache, + ); + + let vp = MultitokenVp { ctx }; + assert!( + !vp.validate_tx(&tx, &keys_changed, &verifiers) + .expect("validation failed") + ); + } + + #[test] + fn test_invalid_minter() { + let mut wl_storage = TestWlStorage::default(); + let mut keys_changed = BTreeSet::new(); + + // IBC token + let token = ibc_token("/port-42/channel-42/denom"); + + // mint 100 + let target = established_address_1(); + let target_key = balance_key(&token, &target); + let amount = Amount::native_whole(100); + wl_storage + .write_log + .write(&target_key, amount.try_to_vec().unwrap()) + .expect("write failed"); + keys_changed.insert(target_key); + let minted_key = minted_balance_key(&token); + let amount = Amount::native_whole(100); + wl_storage + .write_log + .write(&minted_key, amount.try_to_vec().unwrap()) + .expect("write failed"); + keys_changed.insert(minted_key); + + // invalid minter + let minter = established_address_1(); + let minter_key = minter_key(&token); + wl_storage + .write_log + .write(&minter_key, minter.try_to_vec().unwrap()) + .expect("write failed"); + keys_changed.insert(minter_key); + + let tx_index = TxIndex::default(); + let tx = dummy_tx(&wl_storage); + let gas_meter = VpGasMeter::new(0); + let (vp_wasm_cache, _vp_cache_dir) = wasm_cache(); + let mut verifiers = BTreeSet::new(); + // for the minter + verifiers.insert(minter); + let ctx = Ctx::new( + &ADDRESS, + &wl_storage.storage, + &wl_storage.write_log, + &tx, + &tx_index, + gas_meter, + &keys_changed, + &verifiers, + vp_wasm_cache, + ); + + let vp = MultitokenVp { ctx }; + assert!( + !vp.validate_tx(&tx, &keys_changed, &verifiers) + .expect("validation failed") + ); + } + + #[test] + fn test_invalid_minter_update() { + let mut wl_storage = TestWlStorage::default(); + let mut keys_changed = BTreeSet::new(); + + let minter_key = minter_key(&nam()); + let minter = established_address_1(); + wl_storage + .write_log + .write(&minter_key, minter.try_to_vec().unwrap()) + .expect("write failed"); + + keys_changed.insert(minter_key); + + let tx_index = TxIndex::default(); + let tx = dummy_tx(&wl_storage); + let gas_meter = VpGasMeter::new(0); + let (vp_wasm_cache, _vp_cache_dir) = wasm_cache(); + let mut verifiers = BTreeSet::new(); + // for the minter + verifiers.insert(minter); + let ctx = Ctx::new( + &ADDRESS, + &wl_storage.storage, + &wl_storage.write_log, + &tx, + &tx_index, + gas_meter, + &keys_changed, + &verifiers, + vp_wasm_cache, + ); + + let vp = MultitokenVp { ctx }; + assert!( + !vp.validate_tx(&tx, &keys_changed, &verifiers) + .expect("validation failed") + ); + } + + #[test] + fn test_invalid_key_update() { + let mut wl_storage = TestWlStorage::default(); + let mut keys_changed = BTreeSet::new(); + + let key = Key::from( + Address::Internal(InternalAddress::Multitoken).to_db_key(), + ) + .push(&"invalid_segment".to_string()) + .unwrap(); + wl_storage + .write_log + .write(&key, 0.try_to_vec().unwrap()) + .expect("write failed"); + + keys_changed.insert(key); + + let tx_index = TxIndex::default(); + let tx = dummy_tx(&wl_storage); + let gas_meter = VpGasMeter::new(0); + let (vp_wasm_cache, _vp_cache_dir) = wasm_cache(); + let verifiers = BTreeSet::new(); + let ctx = Ctx::new( + &ADDRESS, + &wl_storage.storage, + &wl_storage.write_log, + &tx, + &tx_index, + gas_meter, + &keys_changed, + &verifiers, + vp_wasm_cache, + ); + + let vp = MultitokenVp { ctx }; + assert!( + !vp.validate_tx(&tx, &keys_changed, &verifiers) + .expect("validation failed") + ); + } +} diff --git a/shared/src/ledger/native_vp/parameters.rs b/shared/src/ledger/native_vp/parameters.rs index d367c16698..bb1db0ab30 100644 --- a/shared/src/ledger/native_vp/parameters.rs +++ b/shared/src/ledger/native_vp/parameters.rs @@ -4,7 +4,7 @@ use std::collections::BTreeSet; use namada_core::ledger::storage; use namada_core::proto::Tx; -use namada_core::types::address::{Address, InternalAddress}; +use namada_core::types::address::Address; use namada_core::types::storage::Key; use thiserror::Error; @@ -41,8 +41,6 @@ where { type Error = Error; - const ADDR: InternalAddress = InternalAddress::Parameters; - fn validate_tx( &self, tx_data: &Tx, diff --git a/shared/src/ledger/native_vp/replay_protection.rs b/shared/src/ledger/native_vp/replay_protection.rs index 9b300e376b..a2a2a66f36 100644 --- a/shared/src/ledger/native_vp/replay_protection.rs +++ b/shared/src/ledger/native_vp/replay_protection.rs @@ -3,7 +3,7 @@ use std::collections::BTreeSet; use namada_core::ledger::storage; -use namada_core::types::address::{Address, InternalAddress}; +use namada_core::types::address::Address; use namada_core::types::storage::Key; use thiserror::Error; @@ -40,8 +40,6 @@ where { type Error = Error; - const ADDR: InternalAddress = InternalAddress::ReplayProtection; - fn validate_tx( &self, _tx_data: &Tx, diff --git a/shared/src/ledger/native_vp/slash_fund.rs b/shared/src/ledger/native_vp/slash_fund.rs index bed71d3bd9..8f2ab54400 100644 --- a/shared/src/ledger/native_vp/slash_fund.rs +++ b/shared/src/ledger/native_vp/slash_fund.rs @@ -11,7 +11,7 @@ use crate::ledger::native_vp::{self, governance, Ctx, NativeVp}; use crate::ledger::storage::{self as ledger_storage, StorageHasher}; use crate::ledger::storage_api::StorageRead; use crate::proto::Tx; -use crate::types::address::{Address, InternalAddress}; +use crate::types::address::Address; use crate::types::storage::Key; use crate::types::token; use crate::vm::WasmCacheAccess; @@ -45,8 +45,6 @@ where { type Error = Error; - const ADDR: InternalAddress = InternalAddress::SlashFund; - fn validate_tx( &self, tx_data: &Tx, diff --git a/shared/src/ledger/pos/vp.rs b/shared/src/ledger/pos/vp.rs index dda3497027..18085c5c53 100644 --- a/shared/src/ledger/pos/vp.rs +++ b/shared/src/ledger/pos/vp.rs @@ -89,8 +89,6 @@ where { type Error = Error; - const ADDR: InternalAddress = InternalAddress::PoS; - fn validate_tx( &self, tx_data: &Tx, @@ -101,7 +99,7 @@ where // use validation::DataUpdate::{self, *}; // use validation::ValidatorUpdate::*; - let addr = Address::Internal(Self::ADDR); + let addr = Address::Internal(InternalAddress::PoS); // let mut changes: Vec = vec![]; let _current_epoch = self.ctx.pre().get_block_epoch()?; diff --git a/shared/src/ledger/protocol/mod.rs b/shared/src/ledger/protocol/mod.rs index 8d360a3bd5..6cab156d7a 100644 --- a/shared/src/ledger/protocol/mod.rs +++ b/shared/src/ledger/protocol/mod.rs @@ -8,10 +8,11 @@ use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; use thiserror::Error; use crate::ledger::gas::{self, BlockGasMeter, VpGasMeter}; -use crate::ledger::ibc::vp::{Ibc, IbcToken}; +use crate::ledger::ibc::vp::Ibc; use crate::ledger::native_vp::ethereum_bridge::bridge_pool_vp::BridgePoolVp; use crate::ledger::native_vp::ethereum_bridge::vp::EthBridge; use crate::ledger::native_vp::governance::GovernanceVp; +use crate::ledger::native_vp::multitoken::MultitokenVp; use crate::ledger::native_vp::parameters::{self, ParametersVp}; use crate::ledger::native_vp::replay_protection::ReplayProtectionVp; use crate::ledger::native_vp::slash_fund::SlashFundVp; @@ -58,7 +59,7 @@ pub enum Error { #[error("Parameters native VP: {0}")] ParametersNativeVpError(parameters::Error), #[error("IBC Token native VP: {0}")] - IbcTokenNativeVpError(crate::ledger::ibc::vp::IbcTokenError), + MultitokenNativeVpError(crate::ledger::native_vp::multitoken::Error), #[error("Governance native VP error: {0}")] GovernanceNativeVpError(crate::ledger::native_vp::governance::Error), #[error("SlashFund native VP error: {0}")] @@ -550,16 +551,12 @@ where gas_meter = slash_fund.ctx.gas_meter.into_inner(); result } - InternalAddress::IbcToken(_) - | InternalAddress::IbcEscrow - | InternalAddress::IbcBurn - | InternalAddress::IbcMint => { - // validate the transfer - let ibc_token = IbcToken { ctx }; - let result = ibc_token + InternalAddress::Multitoken => { + let multitoken = MultitokenVp { ctx }; + let result = multitoken .validate_tx(tx, &keys_changed, &verifiers) - .map_err(Error::IbcTokenNativeVpError); - gas_meter = ibc_token.ctx.gas_meter.into_inner(); + .map_err(Error::MultitokenNativeVpError); + gas_meter = multitoken.ctx.gas_meter.into_inner(); result } InternalAddress::EthBridge => { @@ -588,6 +585,14 @@ where replay_protection_vp.ctx.gas_meter.into_inner(); result } + InternalAddress::IbcToken(_) + | InternalAddress::Erc20(_) => { + // The address should be a part of a multitoken key + gas_meter = ctx.gas_meter.into_inner(); + Ok(verifiers.contains(&Address::Internal( + InternalAddress::Multitoken, + ))) + } }; accepted diff --git a/shared/src/ledger/queries/shell.rs b/shared/src/ledger/queries/shell.rs index 7583f8bcf2..38fb7ca61a 100644 --- a/shared/src/ledger/queries/shell.rs +++ b/shared/src/ledger/queries/shell.rs @@ -5,9 +5,10 @@ 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::types::account::{Account, AccountPublicKeysMap}; use namada_core::types::address::Address; use namada_core::types::hash::Hash; -use namada_core::types::storage::{BlockHeight, BlockResults, Key, KeySeg}; +use namada_core::types::storage::{BlockHeight, BlockResults, KeySeg}; use namada_core::types::token::MaspDenom; use self::eth_bridge::{EthBridge, ETH_BRIDGE}; @@ -27,7 +28,6 @@ use crate::types::transaction::TxResult; type Conversion = ( Address, - Option, MaspDenom, Epoch, masp_primitives::transaction::components::Amount, @@ -76,6 +76,12 @@ router! {SHELL, // was the transaction applied? ( "applied" / [tx_hash: Hash] ) -> Option = applied, + // Query account subspace + ( "account" / [owner: Address] ) -> Option = account, + + // Query public key revealad + ( "revealed" / [owner: Address] ) -> bool = revealed, + // IBC UpdateClient event ( "ibc_client_update" / [client_id: ClientId] / [consensus_height: BlockHeight] ) -> Option = ibc_client_update, @@ -163,7 +169,7 @@ where H: 'static + StorageHasher + Sync, { // Conversion values are constructed on request - if let Some(((addr, sub_prefix, denom), epoch, conv, pos)) = ctx + if let Some(((addr, denom), epoch, conv, pos)) = ctx .wl_storage .storage .conversion_state @@ -172,7 +178,6 @@ where { Ok(( addr.clone(), - sub_prefix.clone(), *denom, *epoch, Into::::into( @@ -440,6 +445,46 @@ where .cloned()) } +fn account( + ctx: RequestCtx<'_, D, H>, + owner: Address, +) -> storage_api::Result> +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ + let account_exists = storage_api::account::exists(ctx.wl_storage, &owner)?; + + if account_exists { + let public_keys = + storage_api::account::public_keys(ctx.wl_storage, &owner)?; + let threshold = + storage_api::account::threshold(ctx.wl_storage, &owner)?; + + Ok(Some(Account { + public_keys_map: AccountPublicKeysMap::from_iter(public_keys), + address: owner, + threshold: threshold.unwrap_or(1), + })) + } else { + Ok(None) + } +} + +fn revealed( + ctx: RequestCtx<'_, D, H>, + owner: Address, +) -> storage_api::Result +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ + let public_keys = + storage_api::account::public_keys(ctx.wl_storage, &owner)?; + + Ok(!public_keys.is_empty()) +} + #[cfg(test)] mod test { diff --git a/shared/src/ledger/queries/shell/eth_bridge.rs b/shared/src/ledger/queries/shell/eth_bridge.rs index 42d0207d5b..d7d0ed249e 100644 --- a/shared/src/ledger/queries/shell/eth_bridge.rs +++ b/shared/src/ledger/queries/shell/eth_bridge.rs @@ -17,7 +17,7 @@ use namada_core::types::ethereum_events::{ }; use namada_core::types::ethereum_structs::RelayProof; use namada_core::types::storage::{BlockHeight, DbKeySeg, Key}; -use namada_core::types::token::Amount; +use namada_core::types::token::{minted_balance_key, Amount}; use namada_core::types::vote_extensions::validator_set_update::{ ValidatorSetArgs, VotingPowersMap, }; @@ -123,13 +123,12 @@ where "The Ethereum bridge storage is not initialized", )); }; - if asset == native_erc20 { - return Err(storage_api::Error::SimpleMessage( - "Wrapped NAM's supply is not kept track of", - )); - } - let keys: wrapped_erc20s::Keys = (&asset).into(); - ctx.wl_storage.read(&keys.supply()) + let token = if asset == native_erc20 { + ctx.wl_storage.storage.native_token.clone() + } else { + wrapped_erc20s::token(&asset) + }; + ctx.wl_storage.read(&minted_balance_key(&token)) } /// Helper function to read a smart contract from storage. @@ -1365,15 +1364,26 @@ mod test_ethbridge_router { assert!(resp.is_err()); } - /// Test that reading the wrapped NAM supply fails. + /// Test reading the wrapped NAM supply #[tokio::test] - async fn test_read_wnam_supply_fails() { + async fn test_read_wnam_supply() { let mut client = TestClient::new(RPC); assert_eq!(client.wl_storage.storage.last_epoch.0, 0); // initialize storage test_utils::init_default_storage(&mut client.wl_storage); + let native_erc20 = + read_native_erc20_address(&client.wl_storage).expect("Test failed"); + + // write tokens to storage + let amount = Amount::native_whole(12345); + let token = &client.wl_storage.storage.native_token; + client + .wl_storage + .write(&minted_balance_key(token), amount) + .expect("Test failed"); + // commit the changes client .wl_storage @@ -1382,21 +1392,12 @@ mod test_ethbridge_router { .expect("Test failed"); // check that reading wrapped NAM fails - let native_erc20 = - read_native_erc20_address(&client.wl_storage).expect("Test failed"); let result = RPC .shell() .eth_bridge() .read_erc20_supply(&client, &native_erc20) .await; - let Err(err) = result else { - panic!("Test failed"); - }; - - assert_eq!( - err.to_string(), - "Wrapped NAM's supply is not kept track of" - ); + assert_matches!(result, Ok(Some(a)) if a == amount); } /// Test reading the supply of an ERC20 token. @@ -1420,10 +1421,10 @@ mod test_ethbridge_router { // write tokens to storage let amount = Amount::native_whole(12345); - let keys: wrapped_erc20s::Keys = (&ERC20_TOKEN).into(); + let token = wrapped_erc20s::token(&ERC20_TOKEN); client .wl_storage - .write(&keys.supply(), amount) + .write(&minted_balance_key(&token), amount) .expect("Test failed"); // check that the supply was updated diff --git a/shared/src/ledger/queries/vp/pos.rs b/shared/src/ledger/queries/vp/pos.rs index d872aa5002..075b936e25 100644 --- a/shared/src/ledger/queries/vp/pos.rs +++ b/shared/src/ledger/queries/vp/pos.rs @@ -1,21 +1,24 @@ //! Queries router and handlers for PoS validity predicate -use std::collections::{HashMap, HashSet}; +use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use namada_core::ledger::storage_api::collections::lazy_map; use namada_core::ledger::storage_api::OptionExt; +use namada_proof_of_stake::parameters::PosParams; use namada_proof_of_stake::types::{ BondId, BondsAndUnbondsDetail, BondsAndUnbondsDetails, CommissionPair, - Slash, WeightedValidator, + Slash, ValidatorState, WeightedValidator, }; use namada_proof_of_stake::{ - self, below_capacity_validator_set_handle, bond_amount, bond_handle, - consensus_validator_set_handle, find_all_slashes, - find_delegation_validators, find_delegations, read_all_validator_addresses, - read_pos_params, read_total_stake, - read_validator_max_commission_rate_change, read_validator_stake, - unbond_handle, validator_commission_rate_handle, validator_slashes_handle, + self, bond_amount, bond_handle, find_all_enqueued_slashes, + find_all_slashes, find_delegation_validators, find_delegations, + read_all_validator_addresses, + read_below_capacity_validator_set_addresses_with_stake, + read_consensus_validator_set_addresses_with_stake, read_pos_params, + read_total_stake, read_validator_max_commission_rate_change, + read_validator_stake, unbond_handle, validator_commission_rate_handle, + validator_slashes_handle, validator_state_handle, }; use crate::ledger::queries::types::RequestCtx; @@ -43,18 +46,23 @@ router! {POS, ( "commission" / [validator: Address] / [epoch: opt Epoch] ) -> Option = validator_commission, + + ( "state" / [validator: Address] / [epoch: opt Epoch] ) + -> Option = validator_state, }, ( "validator_set" ) = { ( "consensus" / [epoch: opt Epoch] ) - -> HashSet = consensus_validator_set, + -> BTreeSet = consensus_validator_set, ( "below_capacity" / [epoch: opt Epoch] ) - -> HashSet = below_capacity_validator_set, + -> BTreeSet = below_capacity_validator_set, // TODO: add "below_threshold" }, + ( "pos_params") -> PosParams = pos_params, + ( "total_stake" / [epoch: opt Epoch] ) -> token::Amount = total_stake, @@ -82,6 +90,9 @@ router! {POS, ( "bonds_and_unbonds" / [source: opt Address] / [validator: opt Address] ) -> BondsAndUnbondsDetails = bonds_and_unbonds, + ( "enqueued_slashes" ) + -> HashMap>> = enqueued_slashes, + ( "all_slashes" ) -> HashMap> = slashes, ( "is_delegator" / [addr: Address ] / [epoch: opt Epoch] ) -> bool = is_delegator, @@ -133,6 +144,15 @@ impl Enriched { // Handlers that implement the functions via `trait StorageRead`: +/// Get the PoS parameters +fn pos_params(ctx: RequestCtx<'_, D, H>) -> storage_api::Result +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ + read_pos_params(ctx.wl_storage) +} + /// Find if the given address belongs to a validator account. fn is_validator( ctx: RequestCtx<'_, D, H>, @@ -203,6 +223,26 @@ where } } +/// Get the validator state +fn validator_state( + ctx: RequestCtx<'_, D, H>, + validator: Address, + epoch: Option, +) -> storage_api::Result> +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ + let epoch = epoch.unwrap_or(ctx.wl_storage.storage.last_epoch); + let params = read_pos_params(ctx.wl_storage)?; + let state = validator_state_handle(&validator).get( + ctx.wl_storage, + epoch, + ¶ms, + )?; + Ok(state) +} + /// Get the total stake of a validator at the given epoch or current when /// `None`. The total stake is a sum of validator's self-bonds and delegations /// to their address. @@ -226,64 +266,29 @@ where fn consensus_validator_set( ctx: RequestCtx<'_, D, H>, epoch: Option, -) -> storage_api::Result> +) -> storage_api::Result> where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, { let epoch = epoch.unwrap_or(ctx.wl_storage.storage.last_epoch); - consensus_validator_set_handle() - .at(&epoch) - .iter(ctx.wl_storage)? - .map(|next_result| { - next_result.map( - |( - lazy_map::NestedSubKey::Data { - key: bonded_stake, - nested_sub_key: _position, - }, - address, - )| { - WeightedValidator { - bonded_stake, - address, - } - }, - ) - }) - .collect() + read_consensus_validator_set_addresses_with_stake(ctx.wl_storage, epoch) } /// Get all the validator in the below-capacity set with their bonded stake. fn below_capacity_validator_set( ctx: RequestCtx<'_, D, H>, epoch: Option, -) -> storage_api::Result> +) -> storage_api::Result> where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, { let epoch = epoch.unwrap_or(ctx.wl_storage.storage.last_epoch); - below_capacity_validator_set_handle() - .at(&epoch) - .iter(ctx.wl_storage)? - .map(|next_result| { - next_result.map( - |( - lazy_map::NestedSubKey::Data { - key: bonded_stake, - nested_sub_key: _position, - }, - address, - )| { - WeightedValidator { - bonded_stake: bonded_stake.into(), - address, - } - }, - ) - }) - .collect() + read_below_capacity_validator_set_addresses_with_stake( + ctx.wl_storage, + epoch, + ) } /// Get the total stake in PoS system at the given epoch or current when `None`. @@ -496,7 +501,19 @@ where find_all_slashes(ctx.wl_storage) } -/// All slashes +/// Enqueued slashes +fn enqueued_slashes( + ctx: RequestCtx<'_, D, H>, +) -> storage_api::Result>>> +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ + let current_epoch = ctx.wl_storage.storage.last_epoch; + find_all_enqueued_slashes(ctx.wl_storage, current_epoch) +} + +/// Native validator address by looking up the Tendermint address fn validator_by_tm_addr( ctx: RequestCtx<'_, D, H>, tm_addr: String, diff --git a/shared/src/ledger/queries/vp/token.rs b/shared/src/ledger/queries/vp/token.rs index cbad27005f..ffb1117c91 100644 --- a/shared/src/ledger/queries/vp/token.rs +++ b/shared/src/ledger/queries/vp/token.rs @@ -1,15 +1,15 @@ +//! Token validity predicate queries + use namada_core::ledger::storage::{DBIter, StorageHasher, DB}; use namada_core::ledger::storage_api; use namada_core::ledger::storage_api::token::read_denom; use namada_core::types::address::Address; -use namada_core::types::storage::Key; use namada_core::types::token; use crate::ledger::queries::RequestCtx; router! {TOKEN, - ( "denomination" / [addr: Address] / [sub_prefix: opt Key] ) -> Option = denomination, - ( "denomination" / [addr: Address] / "ibc" / [_ibc_junk: String] ) -> Option = denomination_ibc, + ( "denomination" / [addr: Address] ) -> Option = denomination, } /// Get the number of decimal places (in base 10) for a @@ -17,27 +17,47 @@ router! {TOKEN, fn denomination( ctx: RequestCtx<'_, D, H>, addr: Address, - sub_prefix: Option, ) -> storage_api::Result> where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, { - read_denom(ctx.wl_storage, &addr, sub_prefix.as_ref()) + read_denom(ctx.wl_storage, &addr) } -// TODO Please fix this +#[cfg(any(test, feature = "async-client"))] +pub mod client_only_methods { + use borsh::BorshDeserialize; -/// Get the number of decimal places (in base 10) for a -/// token specified by `addr`. -fn denomination_ibc( - ctx: RequestCtx<'_, D, H>, - addr: Address, - _ibc_junk: String, -) -> storage_api::Result> -where - D: 'static + DB + for<'iter> DBIter<'iter> + Sync, - H: 'static + StorageHasher + Sync, -{ - read_denom(ctx.wl_storage, &addr, None) + use super::Token; + use crate::ledger::queries::{Client, RPC}; + use crate::types::address::Address; + use crate::types::token; + + impl Token { + /// Get the balance of the given `token` belonging to the given `owner`. + pub async fn balance( + &self, + client: &CLIENT, + token: &Address, + owner: &Address, + ) -> Result::Error> + where + CLIENT: Client + Sync, + { + let balance_key = token::balance_key(token, owner); + let response = RPC + .shell() + .storage_value(client, None, None, false, &balance_key) + .await?; + + let balance = if response.data.is_empty() { + token::Amount::default() + } else { + token::Amount::try_from_slice(&response.data) + .unwrap_or_default() + }; + Ok(balance) + } + } } diff --git a/shared/src/ledger/rpc.rs b/shared/src/ledger/rpc.rs index 90d05ebd13..c9e458692a 100644 --- a/shared/src/ledger/rpc.rs +++ b/shared/src/ledger/rpc.rs @@ -9,13 +9,18 @@ use masp_primitives::asset_type::AssetType; use masp_primitives::merkle_tree::MerklePath; use masp_primitives::sapling::Node; use namada_core::ledger::storage::LastBlock; +#[cfg(not(feature = "mainnet"))] use namada_core::ledger::testnet_pow; +use namada_core::types::account::Account; use namada_core::types::address::Address; use namada_core::types::storage::Key; use namada_core::types::token::{ - Amount, DenominatedAmount, Denomination, MaspDenom, TokenAddress, + Amount, DenominatedAmount, Denomination, MaspDenom, +}; +use namada_proof_of_stake::parameters::PosParams; +use namada_proof_of_stake::types::{ + BondsAndUnbondsDetails, CommissionPair, ValidatorState, }; -use namada_proof_of_stake::types::{BondsAndUnbondsDetails, CommissionPair}; use serde::Serialize; use crate::ledger::args::InputAmount; @@ -34,9 +39,8 @@ use crate::tendermint_rpc::Order; use crate::types::control_flow::{time, Halt, TryHalt}; use crate::types::governance::{ProposalVote, VotePower}; use crate::types::hash::Hash; -use crate::types::key::*; +use crate::types::key::common; use crate::types::storage::{BlockHeight, BlockResults, Epoch, PrefixValue}; -use crate::types::token::balance_key; use crate::types::{storage, token}; /// Query the status of a given transaction. @@ -137,18 +141,10 @@ pub async fn get_token_balance( client: &C, token: &Address, owner: &Address, -) -> Option { - let balance_key = balance_key(token, owner); - query_storage_value(client, &balance_key).await -} - -/// Get account's public key stored in its storage sub-space -pub async fn get_public_key( - client: &C, - address: &Address, -) -> Option { - let key = pk_key(address); - query_storage_value(client, &key).await +) -> token::Amount { + unwrap_client_response::( + RPC.vp().token().balance(client, token, owner).await, + ) } /// Check if the given address is a known validator. @@ -241,7 +237,6 @@ pub async fn query_conversion( asset_type: AssetType, ) -> Option<( Address, - Option, MaspDenom, Epoch, masp_primitives::transaction::components::Amount, @@ -696,6 +691,13 @@ pub async fn get_proposal_votes( } } +/// Get the PoS parameters +pub async fn get_pos_params( + client: &C, +) -> PosParams { + unwrap_client_response::(RPC.vp().pos().pos_params(client).await) +} + /// Get all validators in the given epoch pub async fn get_all_validators( client: &C, @@ -736,6 +738,20 @@ pub async fn get_validator_stake( .unwrap_or_default() } +/// Query and return a validator's state +pub async fn get_validator_state( + client: &C, + validator: &Address, + epoch: Option, +) -> Option { + unwrap_client_response::>( + RPC.vp() + .pos() + .validator_state(client, validator, &epoch) + .await, + ) +} + /// Get the delegator's delegation pub async fn get_delegators_delegation< C: crate::ledger::queries::Client + Sync, @@ -775,6 +791,42 @@ pub async fn query_bond( ) } +/// Query the accunt substorage space of an address +pub async fn get_account_info( + client: &C, + owner: &Address, +) -> Option { + unwrap_client_response::>( + RPC.shell().account(client, owner).await, + ) +} + +/// Query if the public_key is revealed +pub async fn is_public_key_revealed< + C: crate::ledger::queries::Client + Sync, +>( + client: &C, + owner: &Address, +) -> bool { + unwrap_client_response::(RPC.shell().revealed(client, owner).await) +} + +/// Query an account substorage at a specific index +pub async fn get_public_key_at( + client: &C, + owner: &Address, + index: u8, +) -> Option { + let account = unwrap_client_response::>( + RPC.shell().account(client, owner).await, + ); + if let Some(account) = account { + account.get_public_key_from_index(index) + } else { + None + } +} + /// Query a validator's unbonds for a given epoch pub async fn query_and_print_unbonds< C: crate::ledger::queries::Client + Sync, @@ -954,7 +1006,6 @@ pub async fn validate_amount( client: &C, amount: InputAmount, token: &Address, - sub_prefix: &Option, force: bool, ) -> Option { let input_amount = match amount { @@ -962,10 +1013,7 @@ pub async fn validate_amount( InputAmount::Validated(amt) => return Some(amt), }; let denom = unwrap_client_response::>( - RPC.vp() - .token() - .denomination(client, token, sub_prefix) - .await, + RPC.vp().token().denomination(client, token).await, ) .or_else(|| { if force { @@ -1061,14 +1109,11 @@ pub async fn format_denominated_amount< C: crate::ledger::queries::Client + Sync, >( client: &C, - token: &TokenAddress, + token: &Address, amount: token::Amount, ) -> String { let denom = unwrap_client_response::>( - RPC.vp() - .token() - .denomination(client, &token.address, &token.sub_prefix) - .await, + RPC.vp().token().denomination(client, token).await, ) .unwrap_or_else(|| { println!( diff --git a/shared/src/ledger/signing.rs b/shared/src/ledger/signing.rs index b6b06f0dae..898596772a 100644 --- a/shared/src/ledger/signing.rs +++ b/shared/src/ledger/signing.rs @@ -15,14 +15,12 @@ use masp_primitives::asset_type::AssetType; use masp_primitives::transaction::components::sapling::fees::{ InputView, OutputView, }; +use namada_core::types::account::AccountPublicKeysMap; use namada_core::types::address::{ masp, masp_tx_key, Address, ImplicitAddress, }; -use namada_core::types::storage::Key; -use namada_core::types::token::{ - self, Amount, DenominatedAmount, MaspDenom, TokenAddress, -}; -use namada_core::types::transaction::{pos, MIN_FEE}; +use namada_core::types::token::{self, Amount, DenominatedAmount, MaspDenom}; +use namada_core::types::transaction::pos; use prost::Message; use serde::{Deserialize, Serialize}; use zeroize::Zeroizing; @@ -33,29 +31,27 @@ use crate::ibc::applications::transfer::msgs::transfer::{ use crate::ibc_proto::google::protobuf::Any; use crate::ledger::masp::make_asset_type; use crate::ledger::parameters::storage as parameter_storage; -use crate::ledger::rpc::{ - format_denominated_amount, query_wasm_code_hash, TxBroadcastData, -}; +use crate::ledger::rpc::{format_denominated_amount, query_wasm_code_hash}; use crate::ledger::tx::{ Error, 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_VP_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::ledger::wallet::store::AddressVpType; use crate::ledger::wallet::{Wallet, WalletUtils}; use crate::ledger::{args, rpc}; -use crate::proto::{MaspBuilder, Section, Signature, Tx}; +use crate::proto::{MaspBuilder, MultiSignature, Section, Signature, Tx}; 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::{ - Fee, InitAccount, InitValidator, TxType, UpdateVp, WrapperTx, -}; +use crate::types::transaction::pos::InitValidator; +use crate::types::transaction::{Fee, TxType, WrapperTx}; #[cfg(feature = "std")] /// Env. var specifying where to store signing test vectors @@ -83,12 +79,12 @@ pub async fn find_pk< "Looking-up public key of {} from the ledger...", addr.encode() ); - rpc::get_public_key(client, addr).await.ok_or(Error::Other( - format!( + rpc::get_public_key_at(client, addr, 0) + .await + .ok_or(Error::Other(format!( "No public key found for the address {}", addr.encode() - ), - )) + ))) } Address::Implicit(ImplicitAddress(pkh)) => Ok(wallet .find_key_by_pkh(pkh, password) @@ -114,28 +110,20 @@ pub async fn find_pk< pub fn find_key_by_pk( wallet: &mut Wallet, args: &args::Tx, - keypair: &common::PublicKey, + public_key: &common::PublicKey, ) -> Result { - if *keypair == masp_tx_key().ref_to() { + if *public_key == masp_tx_key().ref_to() { // We already know the secret key corresponding to the MASP sentinal key Ok(masp_tx_key()) - } else if args - .signing_key - .as_ref() - .map(|x| x.ref_to() == *keypair) - .unwrap_or(false) - { - // We can lookup the secret key from the CLI arguments in this case - Ok(args.signing_key.clone().unwrap()) } else { // Otherwise we need to search the wallet for the secret key wallet - .find_key_by_pk(keypair, args.password.clone()) + .find_key_by_pk(public_key, args.password.clone()) .map_err(|err| { Error::Other(format!( "Unable to load the keypair from the wallet for public \ key {}. Failed with: {}", - keypair, err + public_key, err )) }) } @@ -163,32 +151,27 @@ pub async fn tx_signer< wallet: &mut Wallet, args: &args::Tx, default: TxSigningKey, -) -> Result<(Option
, common::PublicKey), Error> { - let signer = if args.dry_run { - // We cannot override the signer if we're doing a dry run - default - } else if let Some(signing_key) = &args.signing_key { - // Otherwise use the signing key override provided by user - return Ok((None, signing_key.ref_to())); +) -> Result, Error> { + let signer = if !&args.signing_keys.is_empty() { + let public_keys = + args.signing_keys.iter().map(|key| key.ref_to()).collect(); + return Ok(public_keys); } else if let Some(verification_key) = &args.verification_key { - return Ok((None, verification_key.clone())); - } else if let Some(signer) = &args.signer { - // Otherwise use the signer address provided by user - TxSigningKey::WalletAddress(signer.clone()) + return Ok(vec![verification_key.clone()]); } else { // Otherwise use the signer determined by the caller default }; + // Now actually fetch the signing key and apply it match signer { TxSigningKey::WalletAddress(signer) if signer == masp() => { - Ok((None, masp_tx_key().ref_to())) + Ok(vec![masp_tx_key().ref_to()]) } - TxSigningKey::WalletAddress(signer) => Ok(( - Some(signer.clone()), + TxSigningKey::WalletAddress(signer) => Ok(vec![ find_pk::(client, wallet, &signer, args.password.clone()) .await?, - )), + ]), TxSigningKey::None => other_err( "All transactions must be signed; please either specify the key \ or the address from which to look up the signing key." @@ -209,24 +192,83 @@ pub async fn sign_tx( wallet: &mut Wallet, tx: &mut Tx, args: &args::Tx, - keypair: &common::PublicKey, + public_keys_index_map: &AccountPublicKeysMap, + public_keys: &[common::PublicKey], + threshold: u8, ) -> Result<(), Error> { - let keypair = find_key_by_pk(wallet, args, keypair)?; + let keypairs = public_keys + .iter() + .filter_map(|public_key| { + match find_key_by_pk(wallet, args, public_key) { + Ok(secret_key) => Some(secret_key), + Err(_) => None, + } + }) + .collect::>(); + // Sign over the transacttion data - tx.add_section(Section::Signature(Signature::new( + let multisignature_section = MultiSignature::new( vec![*tx.data_sechash(), *tx.code_sechash()], - &keypair, - ))); + &keypairs, + public_keys_index_map, + ); + + if (multisignature_section.total_signatures() < threshold) && !args.force { + return Err(Error::MissingSigningKeys( + threshold, + multisignature_section.total_signatures(), + )); + } + + // Sign over the transaction targets + tx.add_section(Section::SectionSignature(multisignature_section)); + // Remove all the sensitive sections tx.protocol_filter(); - // Then sign over the bound wrapper + + let fee_payer = match &args.fee_payer { + Some(keypair) => keypair, + None => { + if let Some(public_key) = keypairs.get(0) { + public_key + } else { + return Err(Error::InvalidFeePayer( + "Either --signing-keys or --fee-payer must be available." + .to_string(), + )); + } + } + }; + tx.add_section(Section::Signature(Signature::new( tx.sechashes(), - &keypair, + fee_payer, ))); + Ok(()) } +/// Return the necessary data regarding an account to be able to generate a +/// multisignature section +pub async fn aux_signing_data( + client: &C, + owner: Option
, + public_keys: Vec, +) -> (AccountPublicKeysMap, u8) { + if let Some(owner) = owner { + let account = rpc::get_account_info::(client, &owner).await; + let (public_keys_index_map, threshold) = if let Some(account) = account + { + (account.public_keys_map, account.threshold) + } else { + (AccountPublicKeysMap::from_iter(public_keys), 1u8) + }; + (public_keys_index_map, threshold) + } else { + (AccountPublicKeysMap::from_iter(public_keys), 0u8) + } +} + #[cfg(not(feature = "mainnet"))] /// Solve the PoW challenge if balance is insufficient to pay transaction fees /// or if solution is explicitly requested. @@ -252,10 +294,7 @@ pub async fn solve_pow_challenge( .unwrap_or_default(); let is_bal_sufficient = fee_amount <= balance; if !is_bal_sufficient { - let token_addr = TokenAddress { - address: args.fee_token.clone(), - sub_prefix: None, - }; + let token_addr = args.fee_token.clone(); let err_msg = format!( "The wrapper transaction source doesn't have enough balance to \ pay fee {}, got {}.", @@ -372,149 +411,6 @@ pub async fn wrap_tx< tx } -/// Create a wrapper tx from a normal tx. Get the hash of the -/// wrapper and its payload which is needed for monitoring its -/// progress on chain. -pub async fn sign_wrapper< - C: crate::ledger::queries::Client + Sync, - U: WalletUtils, ->( - client: &C, - #[allow(unused_variables)] wallet: &mut Wallet, - args: &args::Tx, - epoch: Epoch, - mut tx: Tx, - keypair: &common::SecretKey, - #[cfg(not(feature = "mainnet"))] requires_pow: bool, -) -> TxBroadcastData { - let fee_amount = if cfg!(feature = "mainnet") { - Amount::native_whole(MIN_FEE) - } else { - let wrapper_tx_fees_key = parameter_storage::get_wrapper_tx_fees_key(); - rpc::query_storage_value::( - client, - &wrapper_tx_fees_key, - ) - .await - .unwrap_or_default() - }; - let fee_token = &args.fee_token; - let source = Address::from(&keypair.ref_to()); - let balance_key = token::balance_key(fee_token, &source); - let balance = - rpc::query_storage_value::(client, &balance_key) - .await - .unwrap_or_default(); - let is_bal_sufficient = fee_amount <= balance; - if !is_bal_sufficient { - let token_addr = TokenAddress { - address: args.fee_token.clone(), - sub_prefix: None, - }; - let err_msg = format!( - "The wrapper transaction source doesn't have enough balance to \ - pay fee {}, got {}.", - format_denominated_amount(client, &token_addr, fee_amount).await, - format_denominated_amount(client, &token_addr, balance).await, - ); - eprintln!("{}", err_msg); - if !args.force && cfg!(feature = "mainnet") { - panic!("{}", err_msg); - } - } - - #[cfg(not(feature = "mainnet"))] - // A PoW solution can be used to allow zero-fee testnet transactions - let pow_solution: Option = { - // If the address derived from the keypair doesn't have enough balance - // to pay for the fee, allow to find a PoW solution instead. - if requires_pow || !is_bal_sufficient { - println!( - "The transaction requires the completion of a PoW challenge." - ); - // Obtain a PoW challenge for faucet withdrawal - let challenge = - rpc::get_testnet_pow_challenge(client, source).await; - - // Solve the solution, this blocks until a solution is found - let solution = challenge.solve(); - Some(solution) - } else { - None - } - }; - - // This object governs how the payload will be processed - tx.update_header(TxType::Wrapper(Box::new(WrapperTx::new( - Fee { - amount: fee_amount, - token: fee_token.clone(), - }, - keypair.ref_to(), - epoch, - args.gas_limit.clone(), - #[cfg(not(feature = "mainnet"))] - pow_solution, - )))); - tx.header.chain_id = args.chain_id.clone().unwrap(); - tx.header.expiration = args.expiration; - - #[cfg(feature = "std")] - // Attempt to decode the construction - if let Ok(path) = env::var(ENV_VAR_LEDGER_LOG_PATH) { - let mut tx = tx.clone(); - // Contract the large data blobs in the transaction - tx.wallet_filter(); - // Convert the transaction to Ledger format - let decoding = to_ledger_vector(client, wallet, &tx) - .await - .expect("unable to decode transaction"); - let output = serde_json::to_string(&decoding) - .expect("failed to serialize decoding"); - // Record the transaction at the identified path - let mut f = File::options() - .append(true) - .create(true) - .open(path) - .expect("failed to open test vector file"); - writeln!(f, "{},", output) - .expect("unable to write test vector to file"); - } - #[cfg(feature = "std")] - // Attempt to decode the construction - if let Ok(path) = env::var(ENV_VAR_TX_LOG_PATH) { - let mut tx = tx.clone(); - // Contract the large data blobs in the transaction - tx.wallet_filter(); - // Record the transaction at the identified path - let mut f = File::options() - .append(true) - .create(true) - .open(path) - .expect("failed to open test vector file"); - writeln!(f, "{:x?},", tx).expect("unable to write test vector to file"); - } - - // Remove all the sensitive sections - tx.protocol_filter(); - // Then sign over the bound wrapper committing to all other sections - tx.add_section(Section::Signature(Signature::new(tx.sechashes(), keypair))); - // We use this to determine when the wrapper tx makes it on-chain - let wrapper_hash = tx.header_hash().to_string(); - // We use this to determine when the decrypted inner tx makes it - // on-chain - let decrypted_hash = tx - .clone() - .update_header(TxType::Raw) - .header_hash() - .to_string(); - TxBroadcastData::Wrapper { - tx, - wrapper_hash, - decrypted_hash, - } -} - #[allow(clippy::result_large_err)] fn other_err(string: String) -> Result { Err(Error::Other(string)) @@ -537,23 +433,13 @@ fn make_ledger_amount_addr( output: &mut Vec, amount: DenominatedAmount, token: &Address, - sub_prefix: &Option, prefix: &str, ) { - let token_address = TokenAddress { - address: token.clone(), - sub_prefix: sub_prefix.clone(), - }; if let Some(token) = tokens.get(token) { - output.push(format!( - "{}Amount {}: {}", - prefix, - token_address.format_with_alias(token), - amount - )); + output.push(format!("{}Amount {}: {}", prefix, token, amount)); } else { output.extend(vec![ - format!("{}Token: {}", prefix, token_address), + format!("{}Token: {}", prefix, token), format!("{}Amount: {}", prefix, amount), ]); } @@ -567,27 +453,21 @@ async fn make_ledger_amount_asset( output: &mut Vec, amount: u64, token: &AssetType, - assets: &HashMap, MaspDenom, Epoch)>, + assets: &HashMap, prefix: &str, ) { - if let Some((token, sub_prefix, _, _epoch)) = assets.get(token) { + if let Some((token, _, _epoch)) = assets.get(token) { // If the AssetType can be decoded, then at least display Addressees - let token_addr = TokenAddress { - address: token.clone(), - sub_prefix: sub_prefix.clone(), - }; let formatted_amt = - format_denominated_amount(client, &token_addr, amount.into()).await; + format_denominated_amount(client, token, amount.into()).await; if let Some(token) = tokens.get(token) { - output.push(format!( - "{}Amount: {} {}", - prefix, - token_addr.format_with_alias(token), - formatted_amt, - )); + output + .push( + format!("{}Amount: {} {}", prefix, token, formatted_amt,), + ); } else { output.extend(vec![ - format!("{}Token: {}", prefix, token_addr), + format!("{}Token: {}", prefix, token), format!("{}Amount: {}", prefix, formatted_amt), ]); } @@ -671,7 +551,7 @@ pub async fn make_ledger_masp_endpoints< output: &mut Vec, transfer: &Transfer, builder: Option<&MaspBuilder>, - assets: &HashMap, MaspDenom, Epoch)>, + assets: &HashMap, ) { if transfer.source != masp() { output.push(format!("Sender : {}", transfer.source)); @@ -681,7 +561,6 @@ pub async fn make_ledger_masp_endpoints< output, transfer.amount, &transfer.token, - &transfer.sub_prefix, "Sending ", ); } @@ -709,7 +588,6 @@ pub async fn make_ledger_masp_endpoints< output, transfer.amount, &transfer.token, - &transfer.sub_prefix, "Receiving ", ); } @@ -735,7 +613,6 @@ pub async fn make_ledger_masp_endpoints< output, transfer.amount, &transfer.token, - &transfer.sub_prefix, "", ); } @@ -766,9 +643,10 @@ pub async fn to_ledger_vector< .unwrap(); let reveal_pk_hash = query_wasm_code_hash(client, TX_REVEAL_PK).await.unwrap(); - let update_vp_hash = query_wasm_code_hash(client, TX_UPDATE_VP_WASM) - .await - .unwrap(); + let update_account_hash = + query_wasm_code_hash(client, TX_UPDATE_ACCOUNT_WASM) + .await + .unwrap(); let transfer_hash = query_wasm_code_hash(client, TX_TRANSFER_WASM) .await .unwrap(); @@ -839,12 +717,12 @@ pub async fn to_ledger_vector< tv.output.extend(vec![ format!("Type : Init Account"), - format!("Public key : {}", init_account.public_key), + format!("Public key : {:?}", init_account.public_keys), format!("VP type : {}", vp_code), ]); tv.output_expert.extend(vec![ - format!("Public key : {}", init_account.public_key), + format!("Public key : {:?}", init_account.public_keys), format!("VP type : {}", HEXLOWER.encode(&extra.0)), ]); } else if code_hash == init_validator_hash { @@ -869,7 +747,7 @@ pub async fn to_ledger_vector< tv.output.extend(vec![ format!("Type : Init Validator"), - format!("Account key : {}", init_validator.account_key), + format!("Account key : {:?}", init_validator.account_keys), format!("Consensus key : {}", init_validator.consensus_key), format!("Protocol key : {}", init_validator.protocol_key), format!("DKG key : {}", init_validator.dkg_key), @@ -882,7 +760,7 @@ pub async fn to_ledger_vector< ]); tv.output_expert.extend(vec![ - format!("Account key : {}", init_validator.account_key), + format!("Account key : {:?}", init_validator.account_keys), format!("Consensus key : {}", init_validator.consensus_key), format!("Protocol key : {}", init_validator.protocol_key), format!("DKG key : {}", init_validator.dkg_key), @@ -980,36 +858,40 @@ pub async fn to_ledger_vector< tv.output_expert .extend(vec![format!("Public key : {}", public_key)]); - } else if code_hash == update_vp_hash { + } else if code_hash == update_account_hash { let transfer = - UpdateVp::try_from_slice(&tx.data().ok_or_else(|| { + UpdateAccount::try_from_slice(&tx.data().ok_or_else(|| { std::io::Error::from(ErrorKind::InvalidData) })?)?; tv.name = "Update VP 0".to_string(); - let extra = tx - .get_section(&transfer.vp_code_hash) - .and_then(|x| Section::extra_data_sec(x.as_ref())) - .expect("unable to load vp code") - .code - .hash(); - let vp_code = if extra == user_hash { - "User".to_string() - } else { - HEXLOWER.encode(&extra.0) - }; - - tv.output.extend(vec![ - format!("Type : Update VP"), - format!("Address : {}", transfer.addr), - format!("VP type : {}", vp_code), - ]); + match &transfer.vp_code_hash { + Some(hash) => { + let extra = tx + .get_section(hash) + .and_then(|x| Section::extra_data_sec(x.as_ref())) + .expect("unable to load vp code") + .code + .hash(); + let vp_code = if extra == user_hash { + "User".to_string() + } else { + HEXLOWER.encode(&extra.0) + }; + tv.output.extend(vec![ + format!("Type : Update VP"), + format!("Address : {}", transfer.addr), + format!("VP type : {}", vp_code), + ]); - tv.output_expert.extend(vec![ - format!("Address : {}", transfer.addr), - format!("VP type : {}", HEXLOWER.encode(&extra.0)), - ]); + tv.output_expert.extend(vec![ + format!("Address : {}", transfer.addr), + format!("VP type : {}", HEXLOWER.encode(&extra.0)), + ]); + } + None => (), + }; } else if code_hash == transfer_hash { let transfer = Transfer::try_from_slice(&tx.data().ok_or_else(|| { @@ -1022,16 +904,10 @@ pub async fn to_ledger_vector< Section::MaspBuilder(builder) if builder.target == shielded_hash => { - for (addr, sub_prefix, denom, epoch) in &builder.asset_types - { + for (addr, denom, epoch) in &builder.asset_types { asset_types.insert( - make_asset_type( - Some(*epoch), - addr, - sub_prefix, - *denom, - ), - (addr.clone(), sub_prefix.clone(), *denom, *epoch), + make_asset_type(Some(*epoch), addr, *denom), + (addr.clone(), *denom, *epoch), ); } Some(builder) @@ -1216,10 +1092,7 @@ pub async fn to_ledger_vector< } if let Some(wrapper) = tx.header.wrapper() { - let gas_token = TokenAddress { - address: wrapper.fee.token.clone(), - sub_prefix: None, - }; + let gas_token = wrapper.fee.token.clone(); let gas_limit = format_denominated_amount( client, &gas_token, diff --git a/shared/src/ledger/tx.rs b/shared/src/ledger/tx.rs index 36644a3e14..3045c01300 100644 --- a/shared/src/ledger/tx.rs +++ b/shared/src/ledger/tx.rs @@ -15,12 +15,11 @@ use masp_primitives::transaction::components::transparent::fees::{ InputView as TransparentInputView, OutputView as TransparentOutputView, }; use masp_primitives::transaction::components::Amount; -use namada_core::types::address::{masp, masp_tx_key, Address}; +use namada_core::types::address::{masp, Address}; use namada_core::types::dec::Dec; -use namada_core::types::storage::Key; use namada_core::types::token::MaspDenom; use namada_proof_of_stake::parameters::PosParams; -use namada_proof_of_stake::types::CommissionPair; +use namada_proof_of_stake::types::{CommissionPair, ValidatorState}; use prost::EncodeError; use thiserror::Error; @@ -44,9 +43,10 @@ use crate::tendermint_rpc::error::Error as RpcError; use crate::types::control_flow::{time, ProceedOrElse}; use crate::types::key::*; use crate::types::masp::TransferTarget; -use crate::types::storage::{Epoch, RESERVED_ADDRESS_PREFIX}; +use crate::types::storage::Epoch; use crate::types::time::DateTimeUtc; -use crate::types::transaction::{pos, InitAccount, TxType, UpdateVp}; +use crate::types::transaction::account::{InitAccount, UpdateAccount}; +use crate::types::transaction::{pos, TxType}; use crate::types::{storage, token}; use crate::vm; use crate::vm::WasmValidationError; @@ -62,7 +62,7 @@ pub const TX_VOTE_PROPOSAL: &str = "tx_vote_proposal.wasm"; /// Reveal public key transaction WASM path pub const TX_REVEAL_PK: &str = "tx_reveal_pk.wasm"; /// Update validity predicate WASM path -pub const TX_UPDATE_VP_WASM: &str = "tx_update_vp.wasm"; +pub const TX_UPDATE_ACCOUNT_WASM: &str = "tx_update_account.wasm"; /// Transfer transaction WASM path pub const TX_TRANSFER_WASM: &str = "tx_transfer.wasm"; /// IBC transaction WASM path @@ -110,6 +110,18 @@ pub enum Error { /// Invalid validator address #[error("The address {0} doesn't belong to any known validator account.")] InvalidValidatorAddress(Address), + /// Not jailed at pipeline epoch + #[error( + "The validator address {0} is not jailed at epoch when it would be \ + restored." + )] + ValidatorNotCurrentlyJailed(Address), + /// Validator still frozen and ineligible to be unjailed + #[error( + "The validator address {0} is currently frozen and ineligible to be \ + unjailed." + )] + ValidatorFrozenFromUnjailing(Address), /// Rate of epoch change too large for current epoch #[error( "New rate, {0}, is too large of a change with respect to the \ @@ -206,6 +218,15 @@ pub enum Error { /// Epoch not in storage #[error("Proposal end epoch is not in the storage.")] EpochNotInStorage, + /// Couldn't understand who the fee pair is + #[error("{0}")] + InvalidFeePayer(String), + /// Account threshold is not set + #[error("Account threshold must be set.")] + MissingAccountThreshold, + /// Not enough signature + #[error("Account threshold is {0} but the valid signatures are {1}.")] + MissingSigningKeys(u8, u8), /// Other Errors that may show up when using the interface #[error("{0}")] Other(String), @@ -219,6 +240,8 @@ pub enum ProcessTxResponse { Broadcast(Response), /// Result of dry running transaction DryRun, + /// Dump transaction to disk + Dump, } impl ProcessTxResponse { @@ -241,13 +264,29 @@ pub async fn prepare_tx< wallet: &mut Wallet, args: &args::Tx, tx: Tx, + owner: Option
, default_signer: TxSigningKey, #[cfg(not(feature = "mainnet"))] requires_pow: bool, -) -> Result<(Tx, Option
, common::PublicKey), Error> { - let (signer_addr, signer_pk) = +) -> Result<(Tx, Option
, Vec), Error> { + let signer_public_keys = tx_signer::(client, wallet, args, default_signer.clone()).await?; + + let fee_payer = match &args.fee_payer { + Some(keypair) => keypair.ref_to(), + None => { + if let Some(public_key) = signer_public_keys.get(0) { + public_key.clone() + } else { + return Err(Error::InvalidFeePayer( + "Either --signing-keys or --fee-payer must be available." + .to_string(), + )); + } + } + }; + if args.dry_run { - Ok((tx, signer_addr, signer_pk)) + Ok((tx, owner, signer_public_keys)) } else { let epoch = rpc::query_epoch(client).await; Ok(( @@ -257,13 +296,13 @@ pub async fn prepare_tx< args, epoch, tx.clone(), - &signer_pk, + &fee_payer, #[cfg(not(feature = "mainnet"))] requires_pow, ) .await, - signer_addr, - signer_pk, + owner, + signer_public_keys, )) } } @@ -339,7 +378,7 @@ pub async fn build_reveal_pk< client: &C, wallet: &mut Wallet, args: args::RevealPk, -) -> Result, common::PublicKey)>, Error> { +) -> Result, Vec)>, Error> { let args::RevealPk { tx: args, public_key, @@ -381,7 +420,7 @@ pub async fn has_revealed_pk( client: &C, addr: &Address, ) -> bool { - rpc::get_public_key(client, addr).await.is_some() + rpc::is_public_key_revealed(client, addr).await } /// Submit transaction to reveal the given public key @@ -393,7 +432,7 @@ pub async fn build_reveal_pk_aux< wallet: &mut Wallet, public_key: &common::PublicKey, args: &args::Tx, -) -> Result<(Tx, Option
, common::PublicKey), Error> { +) -> Result<(Tx, Option
, Vec), Error> { let addr: Address = public_key.into(); println!("Submitting a tx to reveal the public key for address {addr}..."); let tx_data = public_key.try_to_vec().map_err(Error::EncodeKeyFailure)?; @@ -411,11 +450,14 @@ pub async fn build_reveal_pk_aux< tx.set_data(Data::new(tx_data)); tx.set_code(Code::from_hash(tx_code_hash)); + let owner: Address = public_key.into(); + prepare_tx::( client, wallet, args, tx, + Some(owner), TxSigningKey::WalletAddress(addr), #[cfg(not(feature = "mainnet"))] false, @@ -548,18 +590,18 @@ where /// decode components of a masp note pub fn decode_component( - (addr, sub, denom, epoch): (Address, Option, MaspDenom, Epoch), + (addr, denom, epoch): (Address, MaspDenom, Epoch), val: i128, res: &mut HashMap, mk_key: F, ) where - F: FnOnce(Address, Option, Epoch) -> K, + F: FnOnce(Address, Epoch) -> K, K: Eq + std::hash::Hash, { let decoded_change = token::Change::from_masp_denominated(val, denom) .expect("expected this to fit"); - res.entry(mk_key(addr, sub, epoch)) + res.entry(mk_key(addr, epoch)) .and_modify(|val| *val += decoded_change) .or_insert(decoded_change); } @@ -623,7 +665,7 @@ pub async fn build_validator_commission_change< client: &C, wallet: &mut Wallet, args: args::CommissionRateChange, -) -> Result<(Tx, Option
, common::PublicKey), Error> { +) -> Result<(Tx, Option
, Vec), Error> { let epoch = rpc::query_epoch(client).await; let tx_code_hash = @@ -631,11 +673,7 @@ pub async fn build_validator_commission_change< .await .unwrap(); - // TODO: put following two let statements in its own function - let params_key = crate::ledger::pos::params_key(); - let params = rpc::query_storage_value::(client, ¶ms_key) - .await - .expect("Parameter should be defined."); + let params: PosParams = rpc::get_pos_params(client).await; let validator = args.validator.clone(); if rpc::is_validator(client, &validator).await { @@ -702,6 +740,7 @@ pub async fn build_validator_commission_change< wallet, &args.tx, tx, + Some(default_signer.clone()), TxSigningKey::WalletAddress(default_signer), #[cfg(not(feature = "mainnet"))] false, @@ -717,7 +756,7 @@ pub async fn build_unjail_validator< client: &C, wallet: &mut Wallet, args: args::TxUnjailValidator, -) -> Result<(Tx, Option
, common::PublicKey), Error> { +) -> Result<(Tx, Option
, Vec), Error> { if !rpc::is_validator(client, &args.validator).await { eprintln!("The given address {} is not a validator.", &args.validator); if !args.tx.force { @@ -725,9 +764,10 @@ pub async fn build_unjail_validator< } } - let tx_code_path = String::from_utf8(args.tx_code_path).unwrap(); let tx_code_hash = - query_wasm_code_hash(client, tx_code_path).await.unwrap(); + query_wasm_code_hash(client, args.tx_code_path.to_str().unwrap()) + .await + .unwrap(); let data = args .validator @@ -747,6 +787,7 @@ pub async fn build_unjail_validator< wallet, &args.tx, tx, + Some(default_signer.clone()), TxSigningKey::WalletAddress(default_signer), #[cfg(not(feature = "mainnet"))] false, @@ -770,9 +811,53 @@ pub async fn submit_unjail_validator< } } - let tx_code_path = String::from_utf8(args.tx_code_path).unwrap(); + let params: PosParams = rpc::get_pos_params(client).await; + let current_epoch = rpc::query_epoch(client).await; + let pipeline_epoch = current_epoch + params.pipeline_len; + + let validator_state_at_pipeline = + rpc::get_validator_state(client, &args.validator, Some(pipeline_epoch)) + .await + .expect("Validator state should be defined."); + if validator_state_at_pipeline != ValidatorState::Jailed { + eprintln!( + "The given validator address {} is not jailed at the pipeline \ + epoch when it would be restored to one of the validator sets.", + &args.validator + ); + if !args.tx.force { + return Err(Error::ValidatorNotCurrentlyJailed( + args.validator.clone(), + )); + } + } + + let last_slash_epoch_key = + crate::ledger::pos::validator_last_slash_key(&args.validator); + let last_slash_epoch = + rpc::query_storage_value::(client, &last_slash_epoch_key) + .await; + if let Some(last_slash_epoch) = last_slash_epoch { + let eligible_epoch = + last_slash_epoch + params.slash_processing_epoch_offset(); + if current_epoch < eligible_epoch { + eprintln!( + "The given validator address {} is currently frozen and not \ + yet eligible to be unjailed.", + &args.validator + ); + if !args.tx.force { + return Err(Error::ValidatorNotCurrentlyJailed( + args.validator.clone(), + )); + } + } + } + let tx_code_hash = - query_wasm_code_hash(client, tx_code_path).await.unwrap(); + query_wasm_code_hash(client, args.tx_code_path.to_str().unwrap()) + .await + .unwrap(); let data = args .validator @@ -792,6 +877,7 @@ pub async fn submit_unjail_validator< wallet, &args.tx, tx, + Some(default_signer.clone()), TxSigningKey::WalletAddress(default_signer), #[cfg(not(feature = "mainnet"))] false, @@ -808,7 +894,7 @@ pub async fn build_withdraw< client: &C, wallet: &mut Wallet, args: args::Withdraw, -) -> Result<(Tx, Option
, common::PublicKey), Error> { +) -> Result<(Tx, Option
, Vec), Error> { let epoch = rpc::query_epoch(client).await; let validator = @@ -864,6 +950,7 @@ pub async fn build_withdraw< wallet, &args.tx, tx, + Some(default_signer.clone()), TxSigningKey::WalletAddress(default_signer), #[cfg(not(feature = "mainnet"))] false, @@ -883,7 +970,7 @@ pub async fn build_unbond< ( Tx, Option
, - common::PublicKey, + Vec, Option<(Epoch, token::Amount)>, ), Error, @@ -952,18 +1039,19 @@ pub async fn build_unbond< tx.set_code(Code::from_hash(tx_code_hash)); let default_signer = args.source.unwrap_or_else(|| args.validator.clone()); - let (tx, signer_addr, default_signer) = prepare_tx::( + let (tx, signer_addr, public_keys) = prepare_tx::( client, wallet, &args.tx, tx, - TxSigningKey::WalletAddress(default_signer), + Some(default_signer.clone()), + TxSigningKey::WalletAddress(default_signer.clone()), #[cfg(not(feature = "mainnet"))] false, ) .await?; - Ok((tx, signer_addr, default_signer, latest_withdrawal_pre)) + Ok((tx, signer_addr, public_keys, latest_withdrawal_pre)) } /// Query the unbonds post-tx @@ -1036,7 +1124,7 @@ pub async fn build_bond< client: &C, wallet: &mut Wallet, args: args::Bond, -) -> Result<(Tx, Option
, common::PublicKey), Error> { +) -> Result<(Tx, Option
, Vec), Error> { let validator = known_validator_or_err(args.validator.clone(), args.tx.force, client) .await?; @@ -1089,6 +1177,7 @@ pub async fn build_bond< wallet, &args.tx, tx, + Some(default_signer.clone()), TxSigningKey::WalletAddress(default_signer), #[cfg(not(feature = "mainnet"))] false, @@ -1134,27 +1223,17 @@ pub async fn build_ibc_transfer< client: &C, wallet: &mut Wallet, args: args::TxIbcTransfer, -) -> Result<(Tx, Option
, common::PublicKey), Error> { +) -> Result<(Tx, Option
, Vec), Error> { // Check that the source address exists on chain let source = source_exists_or_err(args.source.clone(), args.tx.force, client) .await?; // We cannot check the receiver - let token = token_exists_or_err(args.token, args.tx.force, client).await?; + let token = args.token; // Check source balance - let (sub_prefix, balance_key) = match args.sub_prefix { - Some(sub_prefix) => { - let sub_prefix = storage::Key::parse(sub_prefix).unwrap(); - let prefix = token::multitoken_balance_prefix(&token, &sub_prefix); - ( - Some(sub_prefix), - token::multitoken_balance_key(&prefix, &source), - ) - } - None => (None, token::balance_key(&token, &source)), - }; + let balance_key = token::balance_key(&token, &source); check_balance_too_low_err( &token, @@ -1171,11 +1250,6 @@ pub async fn build_ibc_transfer< .await .unwrap(); - let denom = match sub_prefix { - // To parse IbcToken address, remove the address prefix - Some(sp) => sp.to_string().replace(RESERVED_ADDRESS_PREFIX, ""), - None => token.to_string(), - }; let amount = args .amount .to_string_native() @@ -1183,7 +1257,10 @@ pub async fn build_ibc_transfer< .next() .expect("invalid amount") .to_string(); - let token = Coin { denom, amount }; + let token = Coin { + denom: token.to_string(), + amount, + }; // this height should be that of the destination chain, not this chain let timeout_height = match args.timeout_height { @@ -1230,6 +1307,7 @@ pub async fn build_ibc_transfer< wallet, &args.tx, tx, + Some(args.source.clone()), TxSigningKey::WalletAddress(args.source), #[cfg(not(feature = "mainnet"))] false, @@ -1241,9 +1319,9 @@ pub async fn build_ibc_transfer< /// Returns true only if a new decoding has been added to the given set. async fn add_asset_type< C: crate::ledger::queries::Client + Sync, - U: ShieldedUtils, + U: ShieldedUtils, >( - asset_types: &mut HashSet<(Address, Option, MaspDenom, Epoch)>, + asset_types: &mut HashSet<(Address, MaspDenom, Epoch)>, shielded: &mut ShieldedContext, client: &C, asset_type: AssetType, @@ -1262,7 +1340,7 @@ async fn add_asset_type< /// type information. async fn used_asset_types< C: crate::ledger::queries::Client + Sync, - U: ShieldedUtils, + U: ShieldedUtils, P, R, K, @@ -1271,7 +1349,7 @@ async fn used_asset_types< shielded: &mut ShieldedContext, client: &C, builder: &Builder, -) -> Result, MaspDenom, Epoch)>, RpcError> { +) -> Result, RpcError> { let mut asset_types = HashSet::new(); // Collect all the asset types used in the Sapling inputs for input in builder.sapling_inputs() { @@ -1314,14 +1392,22 @@ async fn used_asset_types< pub async fn build_transfer< C: crate::ledger::queries::Client + Sync, V: WalletUtils, - U: ShieldedUtils, + U: ShieldedUtils, >( client: &C, wallet: &mut Wallet, shielded: &mut ShieldedContext, mut args: args::TxTransfer, -) -> Result<(Tx, Option
, common::PublicKey, Option, bool), Error> -{ +) -> Result< + ( + Tx, + Option
, + Vec, + Option, + bool, + ), + Error, +> { let source = args.source.effective_address(); let target = args.target.effective_address(); let token = args.token.clone(); @@ -1330,37 +1416,18 @@ pub async fn build_transfer< source_exists_or_err(source.clone(), args.tx.force, client).await?; // Check that the target address exists on chain target_exists_or_err(target.clone(), args.tx.force, client).await?; - // Check that the token address exists on chain - token_exists_or_err(token.clone(), args.tx.force, client).await?; // Check source balance - let (sub_prefix, balance_key) = match &args.sub_prefix { - Some(ref sub_prefix) => { - let sub_prefix = storage::Key::parse(sub_prefix).unwrap(); - let prefix = token::multitoken_balance_prefix(&token, &sub_prefix); - ( - Some(sub_prefix), - token::multitoken_balance_key(&prefix, &source), - ) - } - None => (None, token::balance_key(&token, &source)), - }; + let balance_key = token::balance_key(&token, &source); // validate the amount given - let validated_amount = validate_amount( - client, - args.amount, - &token, - &sub_prefix, - args.tx.force, - ) - .await - .expect("expected to validate amount"); + let validated_amount = + validate_amount(client, args.amount, &token, args.tx.force) + .await + .expect("expected to validate amount"); let validate_fee = validate_amount( client, args.tx.fee_amount, &args.tx.fee_token, - // TODO: Currently multi-tokens cannot be used to pay fees - &None, args.tx.force, ) .await @@ -1384,21 +1451,16 @@ pub async fn build_transfer< // signer. Also, if the transaction is shielded, redact the amount and token // types by setting the transparent value to 0 and token type to a constant. // This has no side-effect because transaction is to self. - let (_amount, token) = if source == masp_addr && target == masp_addr { - // TODO Refactor me, we shouldn't rely on any specific token here. - (token::Amount::default(), args.native_token.clone()) - } else { - (validated_amount.amount, token) - }; + let (_amount, token, shielded_gas) = + if source == masp_addr && target == masp_addr { + // TODO Refactor me, we shouldn't rely on any specific token here. + (token::Amount::default(), args.native_token.clone(), true) + } else { + (validated_amount.amount, token, false) + }; let default_signer = TxSigningKey::WalletAddress(args.source.effective_address()); - // If our chosen signer is the MASP sentinel key, then our shielded inputs - // will need to cover the gas fees. - let chosen_signer = - tx_signer::(client, wallet, &args.tx, default_signer.clone()) - .await? - .1; - let shielded_gas = masp_tx_key().ref_to() == chosen_signer; + // Determine whether to pin this transaction to a storage key let key = match &args.target { TransferTarget::PaymentAddress(pa) if pa.is_pinned() => Some(pa.hash()), @@ -1407,6 +1469,8 @@ pub async fn build_transfer< #[cfg(not(feature = "mainnet"))] let is_source_faucet = rpc::is_faucet_account(client, &source).await; + #[cfg(feature = "mainnet")] + let is_source_faucet = false; let tx_code_hash = query_wasm_code_hash(client, args.tx_code_path.to_str().unwrap()) @@ -1468,12 +1532,26 @@ pub async fn build_transfer< source: source.clone(), target: target.clone(), token: token.clone(), - sub_prefix: sub_prefix.clone(), amount: validated_amount, key: key.clone(), // Link the Transfer to the MASP Transaction by hash code shielded: masp_hash, }; + + #[cfg(not(feature = "mainnet"))] + let owner = if is_source_faucet || source == masp_addr { + None + } else { + Some(source.clone()) + }; + + #[cfg(feature = "mainnet")] + let owner = if source == masp_addr { + None + } else { + Some(source.clone()) + }; + tracing::debug!("Transfer data {:?}", transfer); // Encode the Transfer and store it beside the MASP transaction let data = transfer @@ -1484,20 +1562,22 @@ pub async fn build_transfer< tx.set_code(Code::from_hash(tx_code_hash)); // Dry-run/broadcast/submit the transaction - let (tx, signer_addr, def_key) = prepare_tx::( + let (tx, signer_addr, public_keys) = prepare_tx::( client, wallet, &args.tx, tx, + owner, default_signer.clone(), #[cfg(not(feature = "mainnet"))] is_source_faucet, ) .await?; + Ok(( tx, signer_addr, - def_key, + public_keys, shielded_tx_epoch, is_source_faucet, )) @@ -1511,8 +1591,8 @@ pub async fn build_init_account< client: &C, wallet: &mut Wallet, args: args::TxInitAccount, -) -> Result<(Tx, Option
, common::PublicKey), Error> { - let public_key = args.public_key; +) -> Result<(Tx, Option
, Vec), Error> { + let public_keys = args.public_keys; let vp_code_hash = query_wasm_code_hash(client, args.vp_code_path.to_str().unwrap()) @@ -1524,14 +1604,27 @@ pub async fn build_init_account< .await .unwrap(); + let threshold = match args.threshold { + Some(threshold) => threshold, + None => { + if public_keys.len() == 1 { + 1u8 + } else { + return Err(Error::MissingAccountThreshold); + } + } + }; + let mut tx = Tx::new(TxType::Raw); tx.header.chain_id = args.tx.chain_id.clone().unwrap(); tx.header.expiration = args.tx.expiration; let extra = tx.add_section(Section::ExtraData(Code::from_hash(vp_code_hash))); - let data = InitAccount { - public_key, + + let data: InitAccount = InitAccount { + public_keys, vp_code_hash: extra.get_hash(), + threshold, }; let data = data.try_to_vec().map_err(Error::EncodeTxFailure)?; tx.set_data(Data::new(data)); @@ -1542,7 +1635,8 @@ pub async fn build_init_account< wallet, &args.tx, tx, - TxSigningKey::WalletAddress(args.source), + None, + TxSigningKey::None, #[cfg(not(feature = "mainnet"))] false, ) @@ -1550,14 +1644,14 @@ pub async fn build_init_account< } /// Submit a transaction to update a VP -pub async fn build_update_vp< +pub async fn build_update_account< C: crate::ledger::queries::Client + Sync, U: WalletUtils, >( client: &C, wallet: &mut Wallet, - args: args::TxUpdateVp, -) -> Result<(Tx, Option
, common::PublicKey), Error> { + args: args::TxUpdateAccount, +) -> Result<(Tx, Option
, Vec), Error> { let addr = args.addr.clone(); // Check that the address is established and exists on chain @@ -1600,10 +1694,19 @@ pub async fn build_update_vp< } }?; - let vp_code_hash = - query_wasm_code_hash(client, args.vp_code_path.to_str().unwrap()) - .await - .unwrap(); + let public_keys = args.public_keys; + let threshold = args.threshold; + + let vp_code_hash = match args.vp_code_path { + Some(code_path) => { + let vp_hash = + query_wasm_code_hash(client, code_path.to_str().unwrap()) + .await + .unwrap(); + Some(vp_hash) + } + None => None, + }; let tx_code_hash = query_wasm_code_hash(client, args.tx_code_path.to_str().unwrap()) @@ -1613,12 +1716,23 @@ pub async fn build_update_vp< let mut tx = Tx::new(TxType::Raw); tx.header.chain_id = args.tx.chain_id.clone().unwrap(); tx.header.expiration = args.tx.expiration; - let extra = - tx.add_section(Section::ExtraData(Code::from_hash(vp_code_hash))); - let data = UpdateVp { + + let extra_hash = match vp_code_hash { + Some(vp_code_hash) => { + let tx_section = tx + .add_section(Section::ExtraData(Code::from_hash(vp_code_hash))); + Some(tx_section.get_hash()) + } + None => None, + }; + + let data = UpdateAccount { addr, - vp_code_hash: extra.get_hash(), + vp_code_hash: extra_hash, + public_keys, + threshold, }; + let data = data.try_to_vec().map_err(Error::EncodeTxFailure)?; tx.set_data(Data::new(data)); tx.set_code(Code::from_hash(tx_code_hash)); @@ -1628,6 +1742,7 @@ pub async fn build_update_vp< wallet, &args.tx, tx, + Some(args.addr.clone()), TxSigningKey::WalletAddress(args.addr), #[cfg(not(feature = "mainnet"))] false, @@ -1643,10 +1758,11 @@ pub async fn build_custom< client: &C, wallet: &mut Wallet, args: args::TxCustom, -) -> Result<(Tx, Option
, common::PublicKey), Error> { +) -> Result<(Tx, Option
, Vec), Error> { let mut tx = Tx::new(TxType::Raw); tx.header.chain_id = args.tx.chain_id.clone().unwrap(); tx.header.expiration = args.tx.expiration; + args.data_path.map(|data| tx.set_data(Data::new(data))); tx.set_code(Code::new(args.code_path)); @@ -1655,6 +1771,7 @@ pub async fn build_custom< wallet, &args.tx, tx, + Some(args.owner), TxSigningKey::None, #[cfg(not(feature = "mainnet"))] false, @@ -1735,26 +1852,6 @@ where } } -/// Returns the given token if the given address exists on chain -/// otherwise returns an error, force forces the address through even -/// if it isn't on chain -pub async fn token_exists_or_err( - token: Address, - force: bool, - client: &C, -) -> Result { - let message = - format!("The token address {} doesn't exist on chain.", token); - address_exists_or_err( - token, - force, - client, - message, - Error::TokenDoesNotExist, - ) - .await -} - /// 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 diff --git a/shared/src/vm/host_env.rs b/shared/src/vm/host_env.rs index 56cbc7d680..f290e484ff 100644 --- a/shared/src/vm/host_env.rs +++ b/shared/src/vm/host_env.rs @@ -23,6 +23,9 @@ use crate::types::hash::Hash; use crate::types::ibc::IbcEvent; use crate::types::internal::HostEnvResult; use crate::types::storage::{BlockHeight, Key, TxIndex}; +use crate::types::token::{ + is_any_minted_balance_key, is_any_minter_key, is_any_token_balance_key, +}; use crate::vm::memory::VmMemory; use crate::vm::prefix_iter::{PrefixIteratorId, PrefixIterators}; use crate::vm::{HostRef, MutHostRef}; @@ -902,9 +905,20 @@ where H: StorageHasher, CA: WasmCacheAccess, { + // Get the token if the key is a balance or minter key + let token = if let Some([token, _]) = is_any_token_balance_key(key) { + Some(token) + } else { + is_any_minted_balance_key(key).or_else(|| is_any_minter_key(key)) + }; + let write_log = unsafe { env.ctx.write_log.get() }; let storage = unsafe { env.ctx.storage.get() }; for addr in key.find_addresses() { + // skip if the address is a token address + if Some(&addr) == token { + continue; + } // skip the check for implicit and internal addresses if let Address::Implicit(_) | Address::Internal(_) = &addr { continue; diff --git a/test_utils/src/lib.rs b/test_utils/src/lib.rs index 6b0352bcc7..a12fcbff17 100644 --- a/test_utils/src/lib.rs +++ b/test_utils/src/lib.rs @@ -18,7 +18,6 @@ pub const WASM_FOR_TESTS_DIR: &str = "wasm_for_tests"; #[derive(Debug, Clone, Copy, EnumIter)] pub enum TestWasms { TxMemoryLimit, - TxMintTokens, TxNoOp, TxProposalCode, TxReadStorageKey, @@ -36,7 +35,6 @@ impl TestWasms { pub fn path(&self) -> PathBuf { let filename = match self { TestWasms::TxMemoryLimit => "tx_memory_limit.wasm", - TestWasms::TxMintTokens => "tx_mint_tokens.wasm", TestWasms::TxNoOp => "tx_no_op.wasm", TestWasms::TxProposalCode => "tx_proposal_code.wasm", TestWasms::TxReadStorageKey => "tx_read_storage_key.wasm", diff --git a/test_utils/src/tx_data.rs b/test_utils/src/tx_data.rs index 878217bc2c..a985479237 100644 --- a/test_utils/src/tx_data.rs +++ b/test_utils/src/tx_data.rs @@ -2,7 +2,9 @@ //! Namada transaction. use borsh::{BorshDeserialize, BorshSerialize}; +use namada_core::types::address::Address; use namada_core::types::storage; +use namada_core::types::token::Amount; /// Represents an arbitrary write to storage at the specified key. This should /// be used alongside the test `tx_write.wasm`. @@ -23,3 +25,27 @@ pub struct TxWriteData { /// The bytes to be written. pub value: Vec, } + +/// Represents minting of the specified token. This should +/// be used alongside the test `tx_mint_tokens.wasm`. +#[derive( + Clone, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + BorshSerialize, + BorshDeserialize, +)] +pub struct TxMintData { + /// The minter to mint the token + pub minter: Address, + /// The minted target + pub target: Address, + /// The minted token + pub token: Address, + /// The minted amount + pub amount: Amount, +} diff --git a/tests/src/e2e.rs b/tests/src/e2e.rs index ffe2ec0d86..10a6f69d6e 100644 --- a/tests/src/e2e.rs +++ b/tests/src/e2e.rs @@ -16,6 +16,5 @@ pub mod eth_bridge_tests; pub mod helpers; pub mod ibc_tests; pub mod ledger_tests; -pub mod multitoken_tests; pub mod setup; pub mod wallet_tests; diff --git a/tests/src/e2e/eth_bridge_tests.rs b/tests/src/e2e/eth_bridge_tests.rs index 1d15c5323a..e0cd8c5192 100644 --- a/tests/src/e2e/eth_bridge_tests.rs +++ b/tests/src/e2e/eth_bridge_tests.rs @@ -144,8 +144,8 @@ async fn test_roundtrip_eth_transfer() -> Result<()> { "add-erc20-transfer", "--address", BERTHA, - "--signer", - BERTHA, + "--signing-keys", + BERTHA_KEY, "--amount", &amount, "--erc20", @@ -335,8 +335,8 @@ async fn test_bridge_pool_e2e() { "add-erc20-transfer", "--address", BERTHA, - "--signer", - BERTHA, + "--signing-keys", + BERTHA_KEY, "--amount", "100", "--erc20", diff --git a/tests/src/e2e/eth_bridge_tests/helpers.rs b/tests/src/e2e/eth_bridge_tests/helpers.rs index 9fc7acd781..09df129e2b 100644 --- a/tests/src/e2e/eth_bridge_tests/helpers.rs +++ b/tests/src/e2e/eth_bridge_tests/helpers.rs @@ -14,7 +14,6 @@ use namada::ledger::eth_bridge::{ use namada::types::address::{wnam, Address}; use namada::types::ethereum_events::{EthAddress, Uint}; use namada_apps::config::ethereum_bridge; -use namada_core::ledger::eth_bridge; use namada_core::types::ethereum_events::{EthereumEvent, TransferToNamada}; use namada_core::types::token; @@ -173,16 +172,13 @@ pub fn attempt_wrapped_erc20_transfer( ) -> Result { let ledger_address = get_actor_rpc(test, node); - let eth_bridge_addr = eth_bridge::ADDRESS.to_string(); - let sub_prefix = wrapped_erc20s::sub_prefix(asset).to_string(); + let token = wrapped_erc20s::token(asset).to_string(); let amount = amount.to_string(); let transfer_args = vec![ "transfer", "--token", - ð_bridge_addr, - "--sub-prefix", - &sub_prefix, + &token, "--source", from, "--target", @@ -208,10 +204,8 @@ pub fn find_wrapped_erc20_balance( ) -> Result { let ledger_address = get_actor_rpc(test, node); - let sub_prefix = wrapped_erc20s::sub_prefix(asset); - let prefix = - token::multitoken_balance_prefix(ð_bridge::ADDRESS, &sub_prefix); - let balance_key = token::multitoken_balance_key(&prefix, owner); + let token = wrapped_erc20s::token(asset); + let balance_key = token::balance_key(&token, owner); let mut bytes = run!( test, Bin::Client, diff --git a/tests/src/e2e/helpers.rs b/tests/src/e2e/helpers.rs index 7c79fdfdb6..1fde997ef6 100644 --- a/tests/src/e2e/helpers.rs +++ b/tests/src/e2e/helpers.rs @@ -53,6 +53,7 @@ where /// and returns the [`Test`] handle and [`NamadaBgCmd`] for the validator node. /// It blocks until the node is ready to receive RPC requests from /// `namadac`. +#[allow(dead_code)] pub fn setup_single_node_test() -> Result<(Test, NamadaBgCmd)> { let test = setup::single_node_net()?; run_single_node_test_from(test) @@ -71,6 +72,7 @@ pub fn run_single_node_test_from(test: Test) -> Result<(Test, NamadaBgCmd)> { } /// Initialize an established account. +#[allow(dead_code)] pub fn init_established_account( test: &Test, rpc_addr: &str, @@ -424,20 +426,29 @@ pub fn epoch_sleep( ledger_address: &str, timeout_secs: u64, ) -> Result { - let old_epoch = get_epoch(test, ledger_address)?; - let start = Instant::now(); - let loop_timeout = Duration::new(timeout_secs, 0); - loop { - if Instant::now().duration_since(start) > loop_timeout { - panic!("Timed out waiting for the next epoch"); - } - let epoch = get_epoch(test, ledger_address)?; - if epoch > old_epoch { - break Ok(epoch); - } else { - sleep(10); - } - } + let mut find = run!( + test, + Bin::Client, + &["utils", "epoch-sleep", "--node", ledger_address], + Some(timeout_secs) + )?; + parse_reached_epoch(&mut find) +} + +pub fn parse_reached_epoch(find: &mut NamadaCmd) -> Result { + let (unread, matched) = find.exp_regex("Reached epoch .*")?; + let epoch_str = strip_trailing_newline(&matched) + .trim() + .rsplit_once(' ') + .unwrap() + .1; + let epoch = u64::from_str(epoch_str).map_err(|e| { + eyre!(format!( + "Epoch: {} parsed from {}, Error: {}\n\nOutput: {}", + epoch_str, matched, e, unread + )) + })?; + Ok(Epoch(epoch)) } /// Wait for txs and VPs WASM compilations to finish. This is useful to avoid a diff --git a/tests/src/e2e/ibc_tests.rs b/tests/src/e2e/ibc_tests.rs index e071c2bd13..52cb6ca9c0 100644 --- a/tests/src/e2e/ibc_tests.rs +++ b/tests/src/e2e/ibc_tests.rs @@ -72,7 +72,7 @@ use namada::ledger::storage::ics23_specs::ibc_proof_specs; use namada::ledger::storage::traits::Sha256Hasher; use namada::types::address::{Address, InternalAddress}; use namada::types::key::PublicKey; -use namada::types::storage::{BlockHeight, Key, RESERVED_ADDRESS_PREFIX}; +use namada::types::storage::{BlockHeight, Key}; use namada::types::token::Amount; use namada_apps::client::rpc::{ query_storage_value, query_storage_value_bytes, @@ -88,6 +88,7 @@ use namada_apps::facade::tendermint_rpc::{Client, HttpClient, Url}; use prost::Message; use setup::constants::*; +use super::helpers::wait_for_wasm_pre_compile; use super::setup::set_ethereum_bridge_mode; use crate::e2e::helpers::{find_address, get_actor_rpc, get_validator_pk}; use crate::e2e::setup::{self, sleep, Bin, NamadaCmd, Test, Who}; @@ -132,6 +133,9 @@ fn run_ledger_ibc() -> Result<()> { ledger_a.exp_string("This node is a validator")?; ledger_b.exp_string("This node is a validator")?; + wait_for_wasm_pre_compile(&mut ledger_a)?; + wait_for_wasm_pre_compile(&mut ledger_b)?; + // Wait for a first block ledger_a.exp_string("Committed block hash")?; ledger_b.exp_string("Committed block hash")?; @@ -216,7 +220,7 @@ fn create_client(test_a: &Test, test_b: &Test) -> Result<(ClientId, ClientId)> { consensus_state: make_consensus_state(test_b, height)?.into(), signer: Signer::from_str("test_a").expect("invalid signer"), }; - let height_a = submit_ibc_tx(test_a, message, ALBERT)?; + let height_a = submit_ibc_tx(test_a, message, ALBERT, ALBERT_KEY, false)?; let height = query_height(test_a)?; let client_state = make_client_state(test_a, height); @@ -226,7 +230,7 @@ fn create_client(test_a: &Test, test_b: &Test) -> Result<(ClientId, ClientId)> { consensus_state: make_consensus_state(test_a, height)?.into(), signer: Signer::from_str("test_b").expect("invalid signer"), }; - let height_b = submit_ibc_tx(test_b, message, ALBERT)?; + let height_b = submit_ibc_tx(test_b, message, ALBERT, ALBERT_KEY, false)?; // convert the client IDs from `ibc_relayer_type` to `ibc` let client_id_a = match get_event(test_a, height_a)? { @@ -356,7 +360,7 @@ fn update_client( client_id: client_id.clone(), signer: Signer::from_str("test").expect("invalid signer"), }; - submit_ibc_tx(target_test, message, ALBERT)?; + submit_ibc_tx(target_test, message, ALBERT, ALBERT_KEY, false)?; } let message = MsgUpdateClient { @@ -364,7 +368,7 @@ fn update_client( client_id: client_id.clone(), signer: Signer::from_str("test").expect("invalid signer"), }; - submit_ibc_tx(target_test, message, ALBERT)?; + submit_ibc_tx(target_test, message, ALBERT, ALBERT_KEY, false)?; check_ibc_update_query( target_test, @@ -428,7 +432,7 @@ fn connection_handshake( signer: Signer::from_str("test_a").expect("invalid signer"), }; // OpenInitConnection on Chain A - let height = submit_ibc_tx(test_a, msg, ALBERT)?; + let height = submit_ibc_tx(test_a, msg, ALBERT, ALBERT_KEY, false)?; let conn_id_a = match get_event(test_a, height)? { Some(IbcEvent::OpenInitConnection(event)) => event .connection_id() @@ -459,7 +463,7 @@ fn connection_handshake( // Update the client state of Chain A on Chain B update_client_with_height(test_a, test_b, client_id_b, height_a)?; // OpenTryConnection on Chain B - let height = submit_ibc_tx(test_b, msg, ALBERT)?; + let height = submit_ibc_tx(test_b, msg, ALBERT, ALBERT_KEY, false)?; let conn_id_b = match get_event(test_b, height)? { Some(IbcEvent::OpenTryConnection(event)) => event .connection_id() @@ -483,7 +487,7 @@ fn connection_handshake( // Update the client state of Chain B on Chain A update_client_with_height(test_b, test_a, client_id_a, height_b)?; // OpenAckConnection on Chain A - submit_ibc_tx(test_a, msg, ALBERT)?; + submit_ibc_tx(test_a, msg, ALBERT, ALBERT_KEY, false)?; // get the proofs on Chain A let height_a = query_height(test_a)?; @@ -497,7 +501,7 @@ fn connection_handshake( // Update the client state of Chain A on Chain B update_client_with_height(test_a, test_b, client_id_b, height_a)?; // OpenConfirmConnection on Chain B - submit_ibc_tx(test_b, msg, ALBERT)?; + submit_ibc_tx(test_b, msg, ALBERT, ALBERT_KEY, false)?; Ok((conn_id_a, conn_id_b)) } @@ -553,7 +557,7 @@ fn channel_handshake( channel, signer: Signer::from_str("test_a").expect("invalid signer"), }; - let height = submit_ibc_tx(test_a, msg, ALBERT)?; + let height = submit_ibc_tx(test_a, msg, ALBERT, ALBERT_KEY, false)?; let channel_id_a = match get_event(test_a, height)? { Some(IbcEvent::OpenInitChannel(event)) => event @@ -589,7 +593,7 @@ fn channel_handshake( // Update the client state of Chain A on Chain B update_client_with_height(test_a, test_b, client_id_b, height_a)?; // OpenTryChannel on Chain B - let height = submit_ibc_tx(test_b, msg, ALBERT)?; + let height = submit_ibc_tx(test_b, msg, ALBERT, ALBERT_KEY, false)?; let channel_id_b = match get_event(test_b, height)? { Some(IbcEvent::OpenTryChannel(event)) => event .channel_id() @@ -615,7 +619,7 @@ fn channel_handshake( // Update the client state of Chain B on Chain A update_client_with_height(test_b, test_a, client_id_a, height_b)?; // OpenAckChannel on Chain A - submit_ibc_tx(test_a, msg, ALBERT)?; + submit_ibc_tx(test_a, msg, ALBERT, ALBERT_KEY, false)?; // get the proofs on Chain A let height_a = query_height(test_a)?; @@ -630,7 +634,7 @@ fn channel_handshake( // Update the client state of Chain A on Chain B update_client_with_height(test_a, test_b, client_id_b, height_a)?; // OpenConfirmChannel on Chain B - submit_ibc_tx(test_b, msg, ALBERT)?; + submit_ibc_tx(test_b, msg, ALBERT, ALBERT_KEY, false)?; Ok((port_channel_id_a, port_channel_id_b)) } @@ -716,9 +720,10 @@ fn transfer_token( &receiver, NAM, &Amount::native_whole(100000), + ALBERT_KEY, port_channel_id_a, None, - None, + false, )?; let packet = match get_event(test_a, height)? { Some(IbcEvent::SendPacket(event)) => event.packet, @@ -736,7 +741,7 @@ fn transfer_token( // Update the client state of Chain A on Chain B update_client_with_height(test_a, test_b, client_id_b, height_a)?; // Receive the token on Chain B - let height = submit_ibc_tx(test_b, msg, ALBERT)?; + let height = submit_ibc_tx(test_b, msg, ALBERT, ALBERT_KEY, false)?; let (acknowledgement, packet) = match get_event(test_b, height)? { Some(IbcEvent::WriteAcknowledgement(event)) => { (event.ack, event.packet) @@ -761,7 +766,7 @@ fn transfer_token( // Update the client state of Chain B on Chain A update_client_with_height(test_b, test_a, client_id_a, height_b)?; // Acknowledge on Chain A - submit_ibc_tx(test_a, msg, ALBERT)?; + submit_ibc_tx(test_a, msg, ALBERT, ALBERT_KEY, false)?; Ok(()) } @@ -776,11 +781,7 @@ fn transfer_received_token( "{}/{}/{}", port_channel_id.port_id, port_channel_id.channel_id, xan ); - let sub_prefix = ibc_token_prefix(denom) - .unwrap() - .sub_key() - .unwrap() - .to_string(); + let ibc_token = ibc_token(denom).to_string(); let rpc = get_actor_rpc(test, &Who::Validator(0)); let amount = Amount::native_whole(50000).to_string_native(); @@ -791,9 +792,7 @@ fn transfer_received_token( "--target", ALBERT, "--token", - NAM, - "--sub-prefix", - &sub_prefix, + &ibc_token, "--amount", &amount, "--gas-amount", @@ -828,23 +827,18 @@ fn transfer_back( "{}/{}/{}", port_channel_id_b.port_id, port_channel_id_b.channel_id, xan ); - let hash = calc_hash(denom_raw); - let ibc_token = Address::Internal(InternalAddress::IbcToken(hash)); - // Need the address prefix for ibc-transfer command - let sub_prefix = format!( - "{}/{}{}", - MULTITOKEN_STORAGE_KEY, RESERVED_ADDRESS_PREFIX, ibc_token - ); + let ibc_token = ibc_token(denom_raw).to_string(); // Send a token from Chain B let height = transfer( test_b, BERTHA, &receiver, - NAM, + ibc_token, &Amount::native_whole(50000), + BERTHA_KEY, port_channel_id_b, - Some(sub_prefix), None, + false, )?; let packet = match get_event(test_b, height)? { Some(IbcEvent::SendPacket(event)) => event.packet, @@ -861,7 +855,7 @@ fn transfer_back( // Update the client state of Chain B on Chain A update_client_with_height(test_b, test_a, client_id_a, height_b)?; // Receive the token on Chain A - let height = submit_ibc_tx(test_a, msg, ALBERT)?; + let height = submit_ibc_tx(test_a, msg, ALBERT, ALBERT_KEY, false)?; let (acknowledgement, packet) = match get_event(test_a, height)? { Some(IbcEvent::WriteAcknowledgement(event)) => { (event.ack, event.packet) @@ -881,7 +875,7 @@ fn transfer_back( // Update the client state of Chain A on Chain B update_client_with_height(test_a, test_b, client_id_b, height_a)?; // Acknowledge on Chain B - submit_ibc_tx(test_b, msg, ALBERT)?; + submit_ibc_tx(test_b, msg, ALBERT, ALBERT_KEY, false)?; Ok(()) } @@ -901,9 +895,10 @@ fn transfer_timeout( &receiver, NAM, &Amount::native_whole(100000), + ALBERT_KEY, port_channel_id_a, - None, Some(Duration::new(5, 0)), + false, )?; let packet = match get_event(test_a, height)? { Some(IbcEvent::SendPacket(event)) => event.packet, @@ -924,7 +919,7 @@ fn transfer_timeout( // Update the client state of Chain B on Chain A update_client_with_height(test_b, test_a, client_id_a, height_b)?; // Timeout on Chain A - submit_ibc_tx(test_a, msg, ALBERT)?; + submit_ibc_tx(test_a, msg, ALBERT, ALBERT_KEY, false)?; Ok(()) } @@ -994,7 +989,9 @@ fn commitment_prefix() -> CommitmentPrefix { fn submit_ibc_tx( test: &Test, message: impl Msg + std::fmt::Debug, + owner: &str, signer: &str, + wait_reveal_pk: bool, ) -> Result { let data_path = test.test_dir.path().join("tx.data"); let data = make_ibc_data(message); @@ -1011,7 +1008,9 @@ fn submit_ibc_tx( TX_IBC_WASM, "--data-path", &data_path, - "--signer", + "--owner", + owner, + "--signing-keys", signer, "--gas-amount", "0", @@ -1025,6 +1024,9 @@ fn submit_ibc_tx( Some(40) )?; client.exp_string("Transaction applied")?; + if wait_reveal_pk { + client.exp_string("Transaction applied")?; + } check_tx_height(test, &mut client) } @@ -1035,9 +1037,10 @@ fn transfer( receiver: &Address, token: impl AsRef, amount: &Amount, + signer: impl AsRef, port_channel_id: &PortChannelId, - sub_prefix: Option, timeout_sec: Option, + wait_reveal_pk: bool, ) -> Result { let rpc = get_actor_rpc(test, &Who::Validator(0)); @@ -1051,8 +1054,8 @@ fn transfer( sender.as_ref(), "--receiver", &receiver, - "--signer", - sender.as_ref(), + "--signing-keys", + signer.as_ref(), "--token", token.as_ref(), "--amount", @@ -1064,11 +1067,7 @@ fn transfer( "--node", &rpc, ]; - let sp = sub_prefix.clone().unwrap_or_default(); - if sub_prefix.is_some() { - tx_args.push("--sub-prefix"); - tx_args.push(&sp); - } + let timeout = timeout_sec.unwrap_or_default().as_secs().to_string(); if timeout_sec.is_some() { tx_args.push("--timeout-sec-offset"); @@ -1077,6 +1076,9 @@ fn transfer( let mut client = run!(test, Bin::Client, tx_args, Some(40))?; client.exp_string("Transaction applied")?; + if wait_reveal_pk { + client.exp_string("Transaction applied")?; + } check_tx_height(test, &mut client) } @@ -1272,7 +1274,7 @@ fn check_balances( // Check the escrowed balance let expected = format!( ": 100000, owned by {}", - Address::Internal(InternalAddress::IbcEscrow) + Address::Internal(InternalAddress::Ibc) ); client.exp_string(&expected)?; // Check the source balance @@ -1285,21 +1287,12 @@ fn check_balances( "{}/{}/{}", &dest_port_channel_id.port_id, &dest_port_channel_id.channel_id, &token, ); - let key_prefix = ibc_token_prefix(denom)?; - let sub_prefix = key_prefix.sub_key().unwrap().to_string(); + let ibc_token = ibc_token(denom).to_string(); let rpc_b = get_actor_rpc(test_b, &Who::Validator(0)); let query_args = vec![ - "balance", - "--owner", - BERTHA, - "--token", - NAM, - "--sub-prefix", - &sub_prefix, - "--node", - &rpc_b, + "balance", "--owner", BERTHA, "--token", &ibc_token, "--node", &rpc_b, ]; - let expected = format!("nam with {}: 100000", sub_prefix); + let expected = format!("{}: 100000", ibc_token); let mut client = run!(test_b, Bin::Client, query_args, Some(40))?; client.exp_string(&expected)?; client.assert_success(); @@ -1317,40 +1310,23 @@ fn check_balances_after_non_ibc( "{}/{}/{}", port_channel_id.port_id, port_channel_id.channel_id, token ); - let key_prefix = ibc_token_prefix(denom)?; - let sub_prefix = key_prefix.sub_key().unwrap().to_string(); + let ibc_token = ibc_token(denom).to_string(); // Check the source let rpc = get_actor_rpc(test, &Who::Validator(0)); let query_args = vec![ - "balance", - "--owner", - BERTHA, - "--token", - NAM, - "--sub-prefix", - &sub_prefix, - "--node", - &rpc, + "balance", "--owner", BERTHA, "--token", &ibc_token, "--node", &rpc, ]; - let expected = format!("nam with {}: 50000", sub_prefix); + let expected = format!("{}: 50000", ibc_token); let mut client = run!(test, Bin::Client, query_args, Some(40))?; client.exp_string(&expected)?; client.assert_success(); // Check the traget let query_args = vec![ - "balance", - "--owner", - ALBERT, - "--token", - NAM, - "--sub-prefix", - &sub_prefix, - "--node", - &rpc, + "balance", "--owner", ALBERT, "--token", &ibc_token, "--node", &rpc, ]; - let expected = format!("nam with {}: 50000", sub_prefix); + let expected = format!("{}: 50000", ibc_token); let mut client = run!(test, Bin::Client, query_args, Some(40))?; client.exp_string(&expected)?; client.assert_success(); @@ -1373,7 +1349,7 @@ fn check_balances_after_back( // Check the escrowed balance let expected = format!( ": 50000, owned by {}", - Address::Internal(InternalAddress::IbcEscrow) + Address::Internal(InternalAddress::Ibc) ); client.exp_string(&expected)?; // Check the source balance @@ -1386,21 +1362,12 @@ fn check_balances_after_back( "{}/{}/{}", &dest_port_channel_id.port_id, &dest_port_channel_id.channel_id, &token, ); - let key_prefix = ibc_token_prefix(denom)?; - let sub_prefix = key_prefix.sub_key().unwrap().to_string(); + let ibc_token = ibc_token(denom).to_string(); let rpc_b = get_actor_rpc(test_b, &Who::Validator(0)); let query_args = vec![ - "balance", - "--owner", - BERTHA, - "--token", - NAM, - "--sub-prefix", - &sub_prefix, - "--node", - &rpc_b, + "balance", "--owner", BERTHA, "--token", &ibc_token, "--node", &rpc_b, ]; - let expected = format!("nam with {}: 0", sub_prefix); + let expected = format!("{}: 0", ibc_token); let mut client = run!(test_b, Bin::Client, query_args, Some(40))?; client.exp_string(&expected)?; client.assert_success(); diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 9b579bef92..5399eecb10 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -42,6 +42,7 @@ use super::helpers::{ use super::setup::{get_all_wasms_hashes, set_ethereum_bridge_mode, NamadaCmd}; use crate::e2e::helpers::{ epoch_sleep, find_address, find_bonded_stake, get_actor_rpc, get_epoch, + parse_reached_epoch, }; use crate::e2e::setup::{self, default_port_offset, sleep, Bin, Who}; use crate::{run, run_as}; @@ -171,6 +172,8 @@ fn test_node_connectivity_and_consensus() -> Result<()> { "0", "--gas-token", NAM, + "--signing-keys", + BERTHA_KEY, "--node", &validator_one_rpc, ]; @@ -427,7 +430,6 @@ fn ledger_txs_and_queries() -> Result<()> { source: find_address(&test, BERTHA).unwrap(), target: find_address(&test, ALBERT).unwrap(), token: find_address(&test, NAM).unwrap(), - sub_prefix: None, amount: token::DenominatedAmount { amount: token::Amount::native_whole(10), denom: token::NATIVE_MAX_DECIMAL_PLACES.into(), @@ -461,6 +463,8 @@ fn ledger_txs_and_queries() -> Result<()> { "0", "--gas-token", NAM, + "--signing-keys", + BERTHA_KEY, "--node", &validator_one_rpc, ], @@ -481,6 +485,8 @@ fn ledger_txs_and_queries() -> Result<()> { "0", "--gas-token", NAM, + "--signing-keys", + DAEWON, "--node", &validator_one_rpc, ], @@ -507,44 +513,46 @@ fn ledger_txs_and_queries() -> Result<()> { // 3. Submit a transaction to update an account's validity // predicate vec![ - "update", - "--address", - BERTHA, - "--code-path", - VP_USER_WASM, - "--gas-amount", - "0", - "--gas-limit", - "0", - "--gas-token", - NAM, + "update-account", + "--address", + BERTHA, + "--code-path", + VP_USER_WASM, + "--gas-amount", + "0", + "--gas-limit", + "0", + "--gas-token", + NAM, + "--signing-keys", + BERTHA_KEY, "--node", &validator_one_rpc, ], // 4. Submit a custom tx vec![ "tx", - "--signer", - BERTHA, "--code-path", TX_TRANSFER_WASM, "--data-path", &tx_data_path, + "--owner", + BERTHA, "--gas-amount", "0", "--gas-limit", "0", "--gas-token", NAM, + "--signing-keys", + BERTHA_KEY, "--node", &validator_one_rpc ], // 5. Submit a tx to initialize a new account vec![ "init-account", - "--source", - BERTHA, - "--public-key", + "--public-keys", // Value obtained from `namada::types::key::ed25519::tests::gen_keypair` "001be519a321e29020fa3cbfbfd01bd5e92db134305609270b71dace25b5a21168", "--code-path", @@ -557,11 +565,13 @@ fn ledger_txs_and_queries() -> Result<()> { "0", "--gas-token", NAM, + "--signing-keys", + BERTHA_KEY, "--node", &validator_one_rpc, ], - // 6. Submit a tx to withdraw from faucet account (requires PoW challenge - // solution) + // 6. Submit a tx to withdraw from faucet account (requires PoW challenge + // solution) vec![ "transfer", "--source", @@ -573,8 +583,8 @@ fn ledger_txs_and_queries() -> Result<()> { "--amount", "10.1", // Faucet withdrawal requires an explicit signer - "--signer", - ALBERT, + "--signing-keys", + ALBERT_KEY, "--node", &validator_one_rpc, ], @@ -677,7 +687,7 @@ fn masp_txs_and_queries() -> Result<()> { |genesis| { let parameters = ParametersConfig { epochs_per_year: epochs_per_year_from_min_duration( - if is_debug_mode() { 3600 } else { 360 }, + if is_debug_mode() { 1440 } else { 360 }, ), min_num_of_blocks: 1, ..genesis.parameters @@ -704,8 +714,7 @@ fn masp_txs_and_queries() -> Result<()> { let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); - let _ep1 = epoch_sleep(&test, &validator_one_rpc, 720)?; - + // the extra argument in the tuple is for flagging extra checks below let txs_args = vec![ // 2. Attempt to spend 10 BTC at SK(A) to PA(B) ( @@ -719,10 +728,12 @@ fn masp_txs_and_queries() -> Result<()> { BTC, "--amount", "10", + "--signing-keys", + ALBERT_KEY, "--node", &validator_one_rpc, ], - "No balance found", + ("No balance found", false), ), // 3. Attempt to spend 15 BTC at SK(A) to Bertha ( @@ -736,10 +747,12 @@ fn masp_txs_and_queries() -> Result<()> { BTC, "--amount", "15", + "--signing-keys", + ALBERT_KEY, "--node", &validator_one_rpc, ], - "No balance found", + ("No balance found", false), ), // 4. Send 20 BTC from Albert to PA(A) ( @@ -753,10 +766,12 @@ fn masp_txs_and_queries() -> Result<()> { BTC, "--amount", "20", + "--signing-keys", + ALBERT_KEY, "--node", &validator_one_rpc, ], - "Transaction is valid", + ("Transaction is valid", false), ), // 5. Attempt to spend 10 ETH at SK(A) to PA(B) ( @@ -770,10 +785,25 @@ fn masp_txs_and_queries() -> Result<()> { ETH, "--amount", "10", + "--signing-keys", + ALBERT_KEY, + "--node", + &validator_one_rpc, + ], + ("No balance found", false), + ), + // 10. Assert BTC balance at VK(A) is 0 + ( + vec![ + "balance", + "--owner", + AA_VIEWING_KEY, + "--token", + BTC, "--node", &validator_one_rpc, ], - "No balance found", + ("btc: 20", false), ), // 6. Spend 7 BTC at SK(A) to PA(B) ( @@ -787,10 +817,12 @@ fn masp_txs_and_queries() -> Result<()> { BTC, "--amount", "7", + "--signing-keys", + ALBERT_KEY, "--node", &validator_one_rpc, ], - "Transaction is valid", + ("Transaction is valid", false), ), // 7. Spend 7 BTC at SK(A) to PA(B) ( @@ -807,7 +839,7 @@ fn masp_txs_and_queries() -> Result<()> { "--node", &validator_one_rpc, ], - "Transaction is valid", + ("Transaction is valid", false), ), // 8. Attempt to spend 7 BTC at SK(A) to PA(B) ( @@ -824,7 +856,7 @@ fn masp_txs_and_queries() -> Result<()> { "--node", &validator_one_rpc, ], - "is lower than the amount to be transferred and fees", + ("is lower than the amount to be transferred and fees", false), ), // 9. Spend 6 BTC at SK(A) to PA(B) ( @@ -841,7 +873,7 @@ fn masp_txs_and_queries() -> Result<()> { "--node", &validator_one_rpc, ], - "Transaction is valid", + ("Transaction is valid", false), ), // 10. Assert BTC balance at VK(A) is 0 ( @@ -854,7 +886,7 @@ fn masp_txs_and_queries() -> Result<()> { "--node", &validator_one_rpc, ], - "No shielded btc balance found", + ("No shielded btc balance found", false), ), // 11. Assert ETH balance at VK(A) is 0 ( @@ -867,7 +899,7 @@ fn masp_txs_and_queries() -> Result<()> { "--node", &validator_one_rpc, ], - "No shielded eth balance found", + ("No shielded eth balance found", false), ), // 12. Assert balance at VK(B) is 10 BTC ( @@ -878,7 +910,7 @@ fn masp_txs_and_queries() -> Result<()> { "--node", &validator_one_rpc, ], - "btc : 20", + ("btc : 20", false), ), // 13. Send 10 BTC from SK(B) to Bertha ( @@ -895,11 +927,14 @@ fn masp_txs_and_queries() -> Result<()> { "--node", &validator_one_rpc, ], - "Transaction is valid", + ("Transaction is valid", false), ), ]; - for (tx_args, tx_result) in &txs_args { + // Wait till epoch boundary + let _ep0 = epoch_sleep(&test, &validator_one_rpc, 720)?; + + for (tx_args, (tx_result, wait_reveal_pk)) in &txs_args { for &dry_run in &[true, false] { let tx_args = if dry_run && tx_args[0] == "transfer" { vec![tx_args.clone(), vec!["--dry-run"]].concat() @@ -911,6 +946,9 @@ fn masp_txs_and_queries() -> Result<()> { if *tx_result == "Transaction is valid" && !dry_run { client.exp_string("Transaction accepted")?; client.exp_string("Transaction applied")?; + if *wait_reveal_pk { + client.exp_string("Transaction applied")?; + } } client.exp_string(tx_result)?; } @@ -1245,7 +1283,7 @@ fn masp_incentives() -> Result<()> { ], Some(60) )?; - let amt = (amt20 * masp_rewards[&(btc(), None)]).0 * (ep1.0 - ep0.0); + let amt = (amt20 * masp_rewards[&btc()]).0 * (ep1.0 - ep0.0); let denominated = DenominatedAmount { amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into(), @@ -1268,7 +1306,7 @@ fn masp_incentives() -> Result<()> { ], Some(60) )?; - let amt = (amt20 * masp_rewards[&(btc(), None)]).0 * (ep1.0 - ep0.0); + let amt = (amt20 * masp_rewards[&btc()]).0 * (ep1.0 - ep0.0); let denominated = DenominatedAmount { amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into(), @@ -1312,7 +1350,7 @@ fn masp_incentives() -> Result<()> { ], Some(60) )?; - let amt = (amt20 * masp_rewards[&(btc(), None)]).0 * (ep2.0 - ep0.0); + let amt = (amt20 * masp_rewards[&btc()]).0 * (ep2.0 - ep0.0); let denominated = DenominatedAmount { amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into(), @@ -1335,7 +1373,7 @@ fn masp_incentives() -> Result<()> { ], Some(60) )?; - let amt = (amt20 * masp_rewards[&(btc(), None)]).0 * (ep2.0 - ep0.0); + let amt = (amt20 * masp_rewards[&btc()]).0 * (ep2.0 - ep0.0); let denominated = DenominatedAmount { amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into(), @@ -1442,7 +1480,7 @@ fn masp_incentives() -> Result<()> { ], Some(60) )?; - let amt = (amt10 * masp_rewards[&(eth(), None)]).0 * (ep4.0 - ep3.0); + let amt = (amt10 * masp_rewards[ð()]).0 * (ep4.0 - ep3.0); let denominated = DenominatedAmount { amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into(), @@ -1466,8 +1504,8 @@ fn masp_incentives() -> Result<()> { ], Some(60) )?; - let amt = ((amt20 * masp_rewards[&(btc(), None)]).0 * (ep4.0 - ep0.0)) - + ((amt10 * masp_rewards[&(eth(), None)]).0 * (ep4.0 - ep3.0)); + let amt = ((amt20 * masp_rewards[&btc()]).0 * (ep4.0 - ep0.0)) + + ((amt10 * masp_rewards[ð()]).0 * (ep4.0 - ep3.0)); let denominated = DenominatedAmount { amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into(), @@ -1492,8 +1530,8 @@ fn masp_incentives() -> Result<()> { ETH, "--amount", "10", - "--signer", - BERTHA, + "--signing-keys", + BERTHA_KEY, "--node", &validator_one_rpc ], @@ -1539,7 +1577,7 @@ fn masp_incentives() -> Result<()> { ], Some(60) )?; - let amt = (amt10 * masp_rewards[&(eth(), None)]).0 * (ep5.0 - ep3.0); + let amt = (amt10 * masp_rewards[ð()]).0 * (ep5.0 - ep3.0); let denominated = DenominatedAmount { amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into(), @@ -1564,8 +1602,8 @@ fn masp_incentives() -> Result<()> { ], Some(60) )?; - let amt = ((amt20 * masp_rewards[&(btc(), None)]).0 * (ep5.0 - ep0.0)) - + ((amt10 * masp_rewards[&(eth(), None)]).0 * (ep5.0 - ep3.0)); + let amt = ((amt20 * masp_rewards[&btc()]).0 * (ep5.0 - ep0.0)) + + ((amt10 * masp_rewards[ð()]).0 * (ep5.0 - ep3.0)); let denominated = DenominatedAmount { amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into(), @@ -1590,8 +1628,8 @@ fn masp_incentives() -> Result<()> { BTC, "--amount", "20", - "--signer", - ALBERT, + "--signing-keys", + ALBERT_KEY, "--node", &validator_one_rpc ], @@ -1635,7 +1673,7 @@ fn masp_incentives() -> Result<()> { ], Some(60) )?; - let amt = (amt20 * masp_rewards[&(btc(), None)]).0 * (ep6.0 - ep0.0); + let amt = (amt20 * masp_rewards[&btc()]).0 * (ep6.0 - ep0.0); let denominated = DenominatedAmount { amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into(), @@ -1659,8 +1697,8 @@ fn masp_incentives() -> Result<()> { ], Some(60) )?; - let amt = ((amt20 * masp_rewards[&(btc(), None)]).0 * (ep6.0 - ep0.0)) - + ((amt10 * masp_rewards[&(eth(), None)]).0 * (ep5.0 - ep3.0)); + let amt = ((amt20 * masp_rewards[&btc()]).0 * (ep6.0 - ep0.0)) + + ((amt10 * masp_rewards[ð()]).0 * (ep5.0 - ep3.0)); let denominated = DenominatedAmount { amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into(), @@ -1686,7 +1724,7 @@ fn masp_incentives() -> Result<()> { ], Some(60) )?; - let amt = (amt20 * masp_rewards[&(btc(), None)]).0 * (ep6.0 - ep0.0); + let amt = (amt20 * masp_rewards[&btc()]).0 * (ep6.0 - ep0.0); let denominated = DenominatedAmount { amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into(), @@ -1709,7 +1747,7 @@ fn masp_incentives() -> Result<()> { ], Some(60) )?; - let amt = (amt10 * masp_rewards[&(eth(), None)]).0 * (ep5.0 - ep3.0); + let amt = (amt10 * masp_rewards[ð()]).0 * (ep5.0 - ep3.0); let denominated = DenominatedAmount { amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into(), @@ -1733,8 +1771,8 @@ fn masp_incentives() -> Result<()> { ], Some(60) )?; - let amt = ((amt20 * masp_rewards[&(btc(), None)]).0 * (ep6.0 - ep0.0)) - + ((amt10 * masp_rewards[&(eth(), None)]).0 * (ep5.0 - ep3.0)); + let amt = ((amt20 * masp_rewards[&btc()]).0 * (ep6.0 - ep0.0)) + + ((amt10 * masp_rewards[ð()]).0 * (ep5.0 - ep3.0)); let denominated = DenominatedAmount { amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into(), @@ -1759,10 +1797,10 @@ fn masp_incentives() -> Result<()> { "--token", NAM, "--amount", - &((amt10 * masp_rewards[&(eth(), None)]).0 * (ep5.0 - ep3.0)) + &((amt10 * masp_rewards[ð()]).0 * (ep5.0 - ep3.0)) .to_string_native(), - "--signer", - BERTHA, + "--signing-keys", + BERTHA_KEY, "--node", &validator_one_rpc ], @@ -1789,10 +1827,10 @@ fn masp_incentives() -> Result<()> { "--token", NAM, "--amount", - &((amt20 * masp_rewards[&(btc(), None)]).0 * (ep6.0 - ep0.0)) + &((amt20 * masp_rewards[&btc()]).0 * (ep6.0 - ep0.0)) .to_string_native(), - "--signer", - ALBERT, + "--signing-keys", + ALBERT_KEY, "--node", &validator_one_rpc ], @@ -1890,9 +1928,7 @@ fn invalid_transactions() -> Result<()> { let tx_args = vec![ "transfer", "--source", - DAEWON, - "--signing-key", - ALBERT_KEY, + BERTHA, "--target", ALBERT, "--token", @@ -1905,8 +1941,11 @@ fn invalid_transactions() -> Result<()> { "0", "--gas-token", NAM, + "--signing-keys", + ALBERT_KEY, "--node", &validator_one_rpc, + "--force", ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; @@ -1939,13 +1978,16 @@ fn invalid_transactions() -> Result<()> { ledger.exp_string("Committed block hash")?; let _bg_ledger = ledger.background(); + // we need to wait for the rpc endpoint to start + sleep(10); + // 5. Submit an invalid transactions (invalid token address) let daewon_lower = DAEWON.to_lowercase(); let tx_args = vec![ "transfer", "--source", DAEWON, - "--signing-key", + "--signing-keys", &daewon_lower, "--target", ALBERT, @@ -2042,6 +2084,8 @@ fn pos_bonds() -> Result<()> { "0", "--gas-token", NAM, + "--signing-keys", + "validator-0-account-key", "--node", &validator_one_rpc, ]; @@ -2066,6 +2110,8 @@ fn pos_bonds() -> Result<()> { "0", "--gas-token", NAM, + "--signing-keys", + BERTHA_KEY, "--node", &validator_one_rpc, ]; @@ -2087,6 +2133,8 @@ fn pos_bonds() -> Result<()> { "0", "--gas-token", NAM, + "--signing-keys", + "validator-0-account-key", "--node", &validator_one_rpc, ]; @@ -2111,6 +2159,8 @@ fn pos_bonds() -> Result<()> { "0", "--gas-token", NAM, + "--signing-keys", + BERTHA_KEY, "--node", &validator_one_rpc, ]; @@ -2130,7 +2180,7 @@ fn pos_bonds() -> Result<()> { epoch, delegation_withdrawable_epoch ); let start = Instant::now(); - let loop_timeout = Duration::new(60, 0); + let loop_timeout = Duration::new(120, 0); loop { if Instant::now().duration_since(start) > loop_timeout { panic!( @@ -2138,7 +2188,7 @@ fn pos_bonds() -> Result<()> { delegation_withdrawable_epoch ); } - let epoch = get_epoch(&test, &validator_one_rpc)?; + let epoch = epoch_sleep(&test, &validator_one_rpc, 40)?; if epoch >= delegation_withdrawable_epoch { break; } @@ -2155,6 +2205,8 @@ fn pos_bonds() -> Result<()> { "0", "--gas-token", NAM, + "--signing-keys", + "validator-0-account-key", "--node", &validator_one_rpc, ]; @@ -2177,6 +2229,8 @@ fn pos_bonds() -> Result<()> { "0", "--gas-token", NAM, + "--signing-keys", + BERTHA_KEY, "--node", &validator_one_rpc, ]; @@ -2253,6 +2307,8 @@ fn pos_rewards() -> Result<()> { "0", "--gas-token", NAM, + "--signing-keys", + BERTHA_KEY, "--ledger-address", &validator_zero_rpc, ]; @@ -2289,6 +2345,8 @@ fn pos_rewards() -> Result<()> { "0", "--gas-token", NAM, + "--signing-keys", + "validator-1-account-key", "--ledger-address", &validator_one_rpc, ]; @@ -2311,6 +2369,8 @@ fn pos_rewards() -> Result<()> { "0", "--gas-token", NAM, + "--signing-keys", + "validator-2-account-key", "--ledger-address", &validator_two_rpc, ]; @@ -2333,7 +2393,7 @@ fn pos_rewards() -> Result<()> { if Instant::now().duration_since(start) > loop_timeout { panic!("Timed out waiting for epoch: {}", wait_epoch); } - let epoch = get_epoch(&test, &validator_zero_rpc)?; + let epoch = epoch_sleep(&test, &validator_zero_rpc, 40)?; if dbg!(epoch) >= wait_epoch { break; } @@ -2399,6 +2459,8 @@ fn test_bond_queries() -> Result<()> { "0", "--gas-token", NAM, + "--signing-keys", + BERTHA_KEY, "--ledger-address", &validator_one_rpc, ]; @@ -2414,7 +2476,7 @@ fn test_bond_queries() -> Result<()> { if Instant::now().duration_since(start) > loop_timeout { panic!("Timed out waiting for epoch: {}", 1); } - let epoch = get_epoch(&test, &validator_one_rpc)?; + let epoch = epoch_sleep(&test, &validator_one_rpc, 40)?; if epoch >= Epoch(4) { break; } @@ -2435,6 +2497,8 @@ fn test_bond_queries() -> Result<()> { "0", "--gas-token", NAM, + "--signing-keys", + BERTHA_KEY, "--ledger-address", &validator_one_rpc, ]; @@ -2458,6 +2522,8 @@ fn test_bond_queries() -> Result<()> { "0", "--gas-token", NAM, + "--signing-keys", + BERTHA_KEY, "--ledger-address", &validator_one_rpc, ]; @@ -2551,14 +2617,13 @@ fn pos_init_validator() -> Result<()> { // 2. Initialize a new validator account with the non-validator node let new_validator = "new-validator"; - let new_validator_key = format!("{}-key", new_validator); + let _new_validator_key = format!("{}-key", new_validator); let tx_args = vec![ "init-validator", "--alias", new_validator, - "--source", - BERTHA, - "--unsafe-dont-encrypt", + "--account-keys", + "bertha-key", "--gas-amount", "0", "--gas-limit", @@ -2569,8 +2634,11 @@ fn pos_init_validator() -> Result<()> { "0.05", "--max-commission-rate-change", "0.01", + "--signing-keys", + "bertha-key", "--node", &non_validator_rpc, + "--unsafe-dont-encrypt", ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; client.exp_string("Transaction is valid.")?; @@ -2583,7 +2651,7 @@ fn pos_init_validator() -> Result<()> { "--source", BERTHA, "--target", - &new_validator_key, + new_validator, "--token", NAM, "--amount", @@ -2594,6 +2662,8 @@ fn pos_init_validator() -> Result<()> { "0", "--gas-token", NAM, + "--signing-keys", + BERTHA_KEY, "--node", &non_validator_rpc, ]; @@ -2617,6 +2687,8 @@ fn pos_init_validator() -> Result<()> { "0", "--gas-token", NAM, + "--signing-keys", + BERTHA_KEY, "--node", &non_validator_rpc, ]; @@ -2642,6 +2714,8 @@ fn pos_init_validator() -> Result<()> { "0", "--gas-token", NAM, + "--signing-keys", + BERTHA_KEY, "--node", &non_validator_rpc, ]; @@ -2674,6 +2748,13 @@ fn pos_init_validator() -> Result<()> { non_validator.interrupt()?; non_validator.exp_eof()?; + // it takes a bit before the node is shutdown. We dont want flasky test. + if is_debug_mode() { + sleep(10); + } else { + sleep(5); + } + let loc = format!("{}:{}", std::file!(), std::line!()); let validator_1_base_dir = test.get_base_dir(&Who::NonValidator); let mut validator_1 = setup::run_cmd( @@ -2704,7 +2785,7 @@ fn pos_init_validator() -> Result<()> { if Instant::now().duration_since(start) > loop_timeout { panic!("Timed out waiting for epoch: {}", earliest_update_epoch); } - let epoch = get_epoch(&test, &non_validator_rpc)?; + let epoch = epoch_sleep(&test, &non_validator_rpc, 40)?; if epoch >= earliest_update_epoch { break; } @@ -2765,6 +2846,8 @@ fn ledger_many_txs_in_a_block() -> Result<()> { "0", "--gas-token", NAM, + "--signing-keys", + BERTHA_KEY, "--node", ]); @@ -3056,7 +3139,7 @@ fn proposal_submission() -> Result<()> { "0", "--vote", "yay", - "--signer", + "--address", "validator-0", "--node", &validator_one_rpc, @@ -3078,7 +3161,7 @@ fn proposal_submission() -> Result<()> { "0", "--vote", "nay", - "--signer", + "--address", BERTHA, "--node", &validator_one_rpc, @@ -3096,7 +3179,7 @@ fn proposal_submission() -> Result<()> { "0", "--vote", "yay", - "--signer", + "--address", ALBERT, "--node", &validator_one_rpc, @@ -3322,7 +3405,7 @@ fn eth_governance_proposal() -> Result<()> { "yay", "--eth", &vote_arg, - "--signer", + "--address", BERTHA, "--ledger-address", &validator_one_rpc, @@ -3343,7 +3426,7 @@ fn eth_governance_proposal() -> Result<()> { "yay", "--eth", &vote_arg, - "--signer", + "--address", "validator-0", "--ledger-address", &validator_one_rpc, @@ -3575,7 +3658,7 @@ fn pgf_governance_proposal() -> Result<()> { "yay", "--pgf", &arg_vote, - "--signer", + "--address", "validator-0", "--ledger-address", &validator_one_rpc, @@ -3602,7 +3685,7 @@ fn pgf_governance_proposal() -> Result<()> { "yay", "--pgf", &different_vote, - "--signer", + "--address", BERTHA, "--ledger-address", &validator_one_rpc, @@ -3623,7 +3706,7 @@ fn pgf_governance_proposal() -> Result<()> { "yay", "--pgf", &different_vote, - "--signer", + "--address", BERTHA, "--ledger-address", &validator_one_rpc, @@ -3846,7 +3929,7 @@ fn proposal_offline() -> Result<()> { proposal_path.to_str().unwrap(), "--vote", "yay", - "--signer", + "--address", ALBERT, "--offline", "--node", @@ -4308,6 +4391,7 @@ fn test_genesis_validators() -> Result<()> { /// 4. Run it to get it to double vote and sign blocks /// 5. Submit a valid token transfer tx to validator 0 /// 6. Wait for double signing evidence +/// 7. Make sure the the first validator can proceed to the next epoch #[test] fn double_signing_gets_slashed() -> Result<()> { use std::net::SocketAddr; @@ -4317,9 +4401,24 @@ fn double_signing_gets_slashed() -> Result<()> { use namada_apps::client; use namada_apps::config::Config; + let mut pipeline_len = 0; + let mut unbonding_len = 0; + let mut cubic_offset = 0; + // Setup 2 genesis validator nodes let test = setup::network( - |genesis| setup::set_validators(2, genesis, default_port_offset), + |genesis| { + (pipeline_len, unbonding_len, cubic_offset) = ( + genesis.pos_params.pipeline_len, + genesis.pos_params.unbonding_len, + genesis.pos_params.cubic_slashing_window_length, + ); + let mut genesis = + setup::set_validators(4, genesis, default_port_offset); + // Make faster epochs to be more likely to discover boundary issues + genesis.parameters.min_num_of_blocks = 2; + genesis + }, None, )?; @@ -4337,6 +4436,7 @@ fn double_signing_gets_slashed() -> Result<()> { ethereum_bridge::ledger::Mode::Off, None, ); + println!("pipeline_len: {}", pipeline_len); // 1. Run 2 genesis validator ledger nodes let _bg_validator_0 = @@ -4346,6 +4446,18 @@ fn double_signing_gets_slashed() -> Result<()> { start_namada_ledger_node_wait_wasm(&test, Some(1), Some(40))? .background(); + let mut validator_2 = + run_as!(test, Who::Validator(2), Bin::Node, &["ledger"], Some(40))?; + validator_2.exp_string("Namada ledger node started")?; + validator_2.exp_string("This node is a validator")?; + let _bg_validator_2 = validator_2.background(); + + let mut validator_3 = + run_as!(test, Who::Validator(3), Bin::Node, &["ledger"], Some(40))?; + validator_3.exp_string("Namada ledger node started")?; + validator_3.exp_string("This node is a validator")?; + let _bg_validator_3 = validator_3.background(); + // 2. Copy the first genesis validator base-dir let validator_0_base_dir = test.get_base_dir(&Who::Validator(0)); let validator_0_base_dir_copy = test @@ -4372,7 +4484,7 @@ fn double_signing_gets_slashed() -> Result<()> { let net_address_port_0 = net_address_0.port(); let update_config = |ix: u8, mut config: Config| { - let first_port = net_address_port_0 + 6 * (ix as u16 + 1); + let first_port = net_address_port_0 + 26 * (ix as u16 + 1); let p2p_addr = convert_tm_addr_to_socket_addr(&config.ledger.cometbft.p2p.laddr) .ip() @@ -4467,7 +4579,128 @@ fn double_signing_gets_slashed() -> Result<()> { // 6. Wait for double signing evidence let mut validator_1 = bg_validator_1.foreground(); validator_1.exp_string("Processing evidence")?; - validator_1.exp_string("Slashing")?; + + println!("\nPARSING SLASH MESSAGE\n"); + let (_, res) = validator_1 + .exp_regex(r"Slashing [a-z0-9]+ for Duplicate vote in epoch [0-9]+") + .unwrap(); + println!("\n{res}\n"); + let bg_validator_1 = validator_1.background(); + + let exp_processing_epoch = Epoch::from_str(res.split(' ').last().unwrap()) + .unwrap() + + unbonding_len + + cubic_offset + + 1u64; + + // Query slashes + // let tx_args = ["slashes", "--node", &validator_one_rpc]; + // let client = run!(test, Bin::Client, tx_args, Some(40))?; + + let mut client = run!( + test, + Bin::Client, + &["slashes", "--node", &validator_one_rpc], + Some(40) + )?; + client.exp_string("No processed slashes found")?; + client.exp_string("Enqueued slashes for future processing")?; + let (_, res) = client + .exp_regex(r"To be processed in epoch [0-9]+") + .unwrap(); + let processing_epoch = + Epoch::from_str(res.split(' ').last().unwrap()).unwrap(); + + assert_eq!(processing_epoch, exp_processing_epoch); + + println!("\n{processing_epoch}\n"); + + // 6. Wait for processing epoch + loop { + let epoch = epoch_sleep(&test, &validator_one_rpc, 240)?; + println!("\nCurrent epoch: {}", epoch); + if epoch > processing_epoch { + break; + } + } + + let mut client = run!( + test, + Bin::Client, + &[ + "validator-state", + "--validator", + "validator-0", + "--node", + &validator_one_rpc + ], + Some(40) + )?; + let _ = client.exp_regex(r"Validator [a-z0-9]+ is jailed").unwrap(); + + let mut client = run!( + test, + Bin::Client, + &["slashes", "--node", &validator_one_rpc], + Some(40) + )?; + client.exp_string("Processed slashes:")?; + client.exp_string("No enqueued slashes found")?; + + let tx_args = vec![ + "unjail-validator", + "--validator", + "validator-0", + "--gas-amount", + "0", + "--gas-limit", + "0", + "--gas-token", + NAM, + "--node", + &validator_one_rpc, + ]; + let mut client = + run_as!(test, Who::Validator(0), Bin::Client, tx_args, Some(40))?; + client.exp_string("Transaction applied with result:")?; + client.exp_string("Transaction is valid.")?; + client.assert_success(); + + // Wait until pipeline epoch to see if the validator is back in consensus + let cur_epoch = epoch_sleep(&test, &validator_one_rpc, 240)?; + loop { + let epoch = epoch_sleep(&test, &validator_one_rpc, 240)?; + println!("\nCurrent epoch: {}", epoch); + if epoch > cur_epoch + pipeline_len + 1u64 { + break; + } + } + let mut client = run!( + test, + Bin::Client, + &[ + "validator-state", + "--validator", + "validator-0", + "--node", + &validator_one_rpc + ], + Some(40) + )?; + let _ = client + .exp_regex(r"Validator [a-z0-9]+ is in the .* set") + .unwrap(); + + // 7. Make sure the the first validator can proceed to the next epoch + epoch_sleep(&test, &validator_one_rpc, 120)?; + + // Make sure there are no errors + let mut validator_1 = bg_validator_1.foreground(); + validator_1.interrupt()?; + // Wait for the node to stop running to finish writing the state and tx + // queue + validator_1.exp_string("Namada ledger node has shut down.")?; + validator_1.assert_success(); Ok(()) } @@ -4505,6 +4738,8 @@ fn implicit_account_reveal_pk() -> Result<()> { NAM, "--amount", "10.1", + "--signing-keys", + source, "--node", &validator_0_rpc, ] @@ -4522,6 +4757,8 @@ fn implicit_account_reveal_pk() -> Result<()> { source, "--amount", "10.1", + "--signing-keys", + source, "--node", &validator_0_rpc, ] @@ -4532,16 +4769,18 @@ fn implicit_account_reveal_pk() -> Result<()> { // Submit proposal Box::new(|source| { // Gen data for proposal tx - let source = find_address(&test, source).unwrap(); + let author = find_address(&test, source).unwrap(); let valid_proposal_json_path = prepare_proposal_data( &test, - source, + author, ProposalType::Default(None), ); vec![ "init-proposal", "--data-path", valid_proposal_json_path.to_str().unwrap(), + "--signing-keys", + source, "--node", &validator_0_rpc, ] @@ -4577,6 +4816,8 @@ fn implicit_account_reveal_pk() -> Result<()> { NAM, "--amount", "1000", + "--signing-keys", + BERTHA_KEY, "--node", &validator_0_rpc, ]; @@ -4599,6 +4840,52 @@ fn implicit_account_reveal_pk() -> Result<()> { Ok(()) } +#[test] +fn test_epoch_sleep() -> Result<()> { + // Use slightly longer epochs to give us time to sleep + let test = setup::network( + |genesis| { + let parameters = ParametersConfig { + epochs_per_year: epochs_per_year_from_min_duration(30), + min_num_of_blocks: 1, + ..genesis.parameters + }; + GenesisConfig { + parameters, + ..genesis + } + }, + None, + )?; + + // 1. Run the ledger node + let mut ledger = + run_as!(test, Who::Validator(0), Bin::Node, &["ledger"], Some(40))?; + wait_for_wasm_pre_compile(&mut ledger)?; + + let _bg_ledger = ledger.background(); + + let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); + + // 2. Query the current epoch + let start_epoch = get_epoch(&test, &validator_one_rpc).unwrap(); + + // 3. Use epoch-sleep to sleep for an epoch + let args = ["utils", "epoch-sleep", "--node", &validator_one_rpc]; + let mut client = run!(test, Bin::Client, &args, None)?; + let reached_epoch = parse_reached_epoch(&mut client)?; + client.assert_success(); + + // 4. Confirm the current epoch is larger + // possibly badly, we assume we get here within 30 seconds of the last step + // should be fine haha (future debuggers: sorry) + let current_epoch = get_epoch(&test, &validator_one_rpc).unwrap(); + assert!(current_epoch > start_epoch); + assert_eq!(current_epoch, reached_epoch); + + Ok(()) +} + /// Prepare proposal data in the test's temp dir from the given source address. /// This can be submitted with "init-proposal" command. fn prepare_proposal_data( diff --git a/tests/src/e2e/multitoken_tests.rs b/tests/src/e2e/multitoken_tests.rs index 0f2b15d877..5d713b2aed 100644 --- a/tests/src/e2e/multitoken_tests.rs +++ b/tests/src/e2e/multitoken_tests.rs @@ -3,10 +3,10 @@ use color_eyre::eyre::Result; use namada_core::types::token; use super::helpers::get_actor_rpc; -use super::setup::constants::{ALBERT, BERTHA, CHRISTEL}; +use super::setup::constants::{ALBERT, BERTHA}; use super::setup::{self, Who}; use crate::e2e; -use crate::e2e::setup::constants::{ALBERT_KEY, BERTHA_KEY}; +use crate::e2e::setup::constants::{ALBERT_KEY, BERTHA_KEY, CHRISTEL_KEY}; mod helpers; @@ -45,7 +45,7 @@ fn test_multitoken_transfer_implicit_to_implicit() -> Result<()> { &multitoken_alias, ALBERT, BERTHA, - CHRISTEL, + CHRISTEL_KEY, &transfer_amount, )?; unauthorized_transfer.exp_string("Transaction applied with result")?; @@ -69,7 +69,7 @@ fn test_multitoken_transfer_implicit_to_implicit() -> Result<()> { &multitoken_alias, ALBERT, BERTHA, - ALBERT, + ALBERT_KEY, &token::Amount::native_whole(10_000_000), )?; authorized_transfer.exp_string("Transaction applied with result")?; @@ -131,7 +131,7 @@ fn test_multitoken_transfer_established_to_implicit() -> Result<()> { &multitoken_alias, established_alias, BERTHA, - CHRISTEL, + CHRISTEL_KEY, &transfer_amount, )?; unauthorized_transfer.exp_string("Transaction applied with result")?; @@ -155,7 +155,7 @@ fn test_multitoken_transfer_established_to_implicit() -> Result<()> { &multitoken_alias, established_alias, BERTHA, - ALBERT, + ALBERT_KEY, &transfer_amount, )?; authorized_transfer.exp_string("Transaction applied with result")?; @@ -216,7 +216,7 @@ fn test_multitoken_transfer_implicit_to_established() -> Result<()> { &multitoken_alias, ALBERT, established_alias, - CHRISTEL, + CHRISTEL_KEY, &transfer_amount, )?; unauthorized_transfer.exp_string("Transaction applied with result")?; @@ -239,7 +239,7 @@ fn test_multitoken_transfer_implicit_to_established() -> Result<()> { &multitoken_alias, ALBERT, established_alias, - ALBERT, + ALBERT_KEY, &transfer_amount, )?; authorized_transfer.exp_string("Transaction applied with result")?; @@ -326,7 +326,7 @@ fn test_multitoken_transfer_established_to_established() -> Result<()> { &multitoken_alias, established_alias, receiver_alias, - CHRISTEL, + CHRISTEL_KEY, &transfer_amount, )?; unauthorized_transfer.exp_string("Transaction applied with result")?; @@ -350,7 +350,7 @@ fn test_multitoken_transfer_established_to_established() -> Result<()> { &multitoken_alias, established_alias, receiver_alias, - ALBERT, + ALBERT_KEY, &transfer_amount, )?; authorized_transfer.exp_string("Transaction applied with result")?; diff --git a/tests/src/e2e/multitoken_tests/helpers.rs b/tests/src/e2e/multitoken_tests/helpers.rs index 27228cf266..176692c508 100644 --- a/tests/src/e2e/multitoken_tests/helpers.rs +++ b/tests/src/e2e/multitoken_tests/helpers.rs @@ -15,7 +15,7 @@ use regex::Regex; use super::setup::constants::NAM; use super::setup::{Bin, NamadaCmd, Test}; -use crate::e2e::setup::constants::ALBERT; +use crate::e2e::setup::constants::{ALBERT, ALBERT_KEY}; use crate::run; const MULTITOKEN_KEY_SEGMENT: &str = "tokens"; @@ -24,6 +24,7 @@ const RED_TOKEN_KEY_SEGMENT: &str = "red"; const MULTITOKEN_RED_TOKEN_SUB_PREFIX: &str = "tokens/red"; const ARBITRARY_SIGNER: &str = ALBERT; +const ARBITRARY_SIGNER_KEY: &str = ALBERT_KEY; /// Initializes a VP to represent a multitoken account. pub fn init_multitoken_vp(test: &Test, rpc_addr: &str) -> Result { @@ -113,8 +114,8 @@ pub fn mint_red_tokens( let tx_code_path = tx_code_path.to_string_lossy().to_string(); let tx_args = vec![ "tx", - "--signer", - ARBITRARY_SIGNER, + "--signing-keys", + ARBITRARY_SIGNER_KEY, "--code-path", &tx_code_path, "--data-path", @@ -135,7 +136,7 @@ pub fn attempt_red_tokens_transfer( multitoken: &str, from: &str, to: &str, - signer: &str, + signing_keys: &str, amount: &token::Amount, ) -> Result { let amount = amount.to_string_native(); @@ -149,8 +150,8 @@ pub fn attempt_red_tokens_transfer( from, "--target", to, - "--signer", - signer, + "--signing-keys", + signing_keys, "--amount", &amount, "--ledger-address", diff --git a/tests/src/e2e/setup.rs b/tests/src/e2e/setup.rs index 6d1a2496b0..6b2b01708f 100644 --- a/tests/src/e2e/setup.rs +++ b/tests/src/e2e/setup.rs @@ -841,6 +841,7 @@ pub mod constants { pub const CHRISTEL: &str = "Christel"; pub const CHRISTEL_KEY: &str = "Christel-key"; pub const DAEWON: &str = "Daewon"; + pub const DAEWON_KEY: &str = "Daewon-key"; pub const ESTER: &str = "Ester"; pub const MATCHMAKER_KEY: &str = "matchmaker-key"; pub const MASP: &str = "atest1v4ehgw36xaryysfsx5unvve4g5my2vjz89p52sjxxgenzd348yuyyv3hg3pnjs35g5unvde4ca36y5"; diff --git a/tests/src/native_vp/eth_bridge_pool.rs b/tests/src/native_vp/eth_bridge_pool.rs index 2829029abf..46478acf48 100644 --- a/tests/src/native_vp/eth_bridge_pool.rs +++ b/tests/src/native_vp/eth_bridge_pool.rs @@ -6,7 +6,6 @@ mod test_bridge_pool_vp { use namada::core::ledger::eth_bridge::storage::bridge_pool::BRIDGE_POOL_ADDRESS; use namada::ledger::eth_bridge::{ wrapped_erc20s, Contracts, EthereumBridgeConfig, UpgradeableContract, - ADDRESS, }; use namada::ledger::native_vp::ethereum_bridge::bridge_pool_vp::BridgePoolVp; use namada::proto::{Code, Data, Section, Signature, Tx}; @@ -84,27 +83,12 @@ mod test_bridge_pool_vp { // initialize Bertha's account env.spawn_accounts([&albert_address(), &bertha_address(), &nam()]); // enrich Albert - env.credit_tokens( - &albert_address(), - &nam(), - None, - BERTHA_WEALTH.into(), - ); + env.credit_tokens(&albert_address(), &nam(), BERTHA_WEALTH.into()); // enrich Bertha - env.credit_tokens( - &bertha_address(), - &nam(), - None, - BERTHA_WEALTH.into(), - ); + env.credit_tokens(&bertha_address(), &nam(), BERTHA_WEALTH.into()); // Bertha has ERC20 tokens too. - let sub_prefix = wrapped_erc20s::sub_prefix(&ASSET); - env.credit_tokens( - &bertha_address(), - &ADDRESS, - Some(sub_prefix), - BERTHA_TOKENS.into(), - ); + let token = wrapped_erc20s::token(&ASSET); + env.credit_tokens(&bertha_address(), &token, BERTHA_TOKENS.into()); env } diff --git a/tests/src/native_vp/pos.rs b/tests/src/native_vp/pos.rs index b76b4dd041..077385dc61 100644 --- a/tests/src/native_vp/pos.rs +++ b/tests/src/native_vp/pos.rs @@ -118,6 +118,11 @@ pub fn init_pos( tx_env.spawn_accounts([&native_token]); for validator in genesis_validators { tx_env.spawn_accounts([&validator.address]); + tx_env.init_account_storage( + &validator.address, + vec![validator.consensus_key.clone()], + 1, + ) } tx_env.wl_storage.storage.block.epoch = start_epoch; // Initialize PoS storage diff --git a/tests/src/storage_api/testnet_pow.rs b/tests/src/storage_api/testnet_pow.rs index 5e54188c1b..ab45bc99c8 100644 --- a/tests/src/storage_api/testnet_pow.rs +++ b/tests/src/storage_api/testnet_pow.rs @@ -11,7 +11,7 @@ use crate::vp; fn test_challenge_and_solution() -> storage_api::Result<()> { let faucet_address = address::testing::established_address_1(); let difficulty = Difficulty::try_new(1).unwrap(); - let withdrawal_limit = token::Amount::native_whole(1_000); + let withdrawal_limit = token::Amount::native_whole(1_000).into(); let mut tx_env = TestTxEnv::default(); diff --git a/tests/src/vm_host_env/ibc.rs b/tests/src/vm_host_env/ibc.rs index b73fef0a3d..5858abe7d3 100644 --- a/tests/src/vm_host_env/ibc.rs +++ b/tests/src/vm_host_env/ibc.rs @@ -60,13 +60,14 @@ pub use namada::ledger::ibc::storage::{ ack_key, channel_counter_key, channel_key, client_counter_key, client_state_key, client_type_key, client_update_height_key, client_update_timestamp_key, commitment_key, connection_counter_key, - connection_key, consensus_state_key, ibc_token_prefix, - next_sequence_ack_key, next_sequence_recv_key, next_sequence_send_key, - port_key, receipt_key, + connection_key, consensus_state_key, ibc_token, next_sequence_ack_key, + next_sequence_recv_key, next_sequence_send_key, port_key, receipt_key, }; use namada::ledger::ibc::vp::{ get_dummy_genesis_validator, get_dummy_header as tm_dummy_header, Ibc, - IbcToken, +}; +use namada::ledger::native_vp::multitoken::{ + Error as MultitokenVpError, MultitokenVp, }; use namada::ledger::native_vp::{Ctx, NativeVp}; use namada::ledger::parameters::storage::{ @@ -92,7 +93,7 @@ use namada::vm::{wasm, WasmCacheRwAccess}; use namada_test_utils::TestWasms; use namada_tx_prelude::BorshSerialize; -use crate::tx::{self, *}; +use crate::tx::*; const ADDRESS: Address = Address::Internal(InternalAddress::Ibc); @@ -115,19 +116,20 @@ impl<'a> TestIbcVp<'a> { } } -pub struct TestIbcTokenVp<'a> { - pub token: IbcToken<'a, MockDB, Sha256Hasher, WasmCacheRwAccess>, +pub struct TestMultitokenVp<'a> { + pub multitoken_vp: + MultitokenVp<'a, MockDB, Sha256Hasher, WasmCacheRwAccess>, } -impl<'a> TestIbcTokenVp<'a> { +impl<'a> TestMultitokenVp<'a> { pub fn validate( &self, - tx_data: &Tx, - ) -> std::result::Result { - self.token.validate_tx( - tx_data, - self.token.ctx.keys_changed, - self.token.ctx.verifiers, + tx: &Tx, + ) -> std::result::Result { + self.multitoken_vp.validate_tx( + tx, + self.multitoken_vp.ctx.keys_changed, + self.multitoken_vp.ctx.verifiers, ) } } @@ -168,11 +170,11 @@ pub fn validate_ibc_vp_from_tx<'a>( } /// Validate the native token VP for the given address -pub fn validate_token_vp_from_tx<'a>( +pub fn validate_multitoken_vp_from_tx<'a>( tx_env: &'a TestTxEnv, tx: &'a Tx, target: &Key, -) -> std::result::Result { +) -> std::result::Result { let (verifiers, keys_changed) = tx_env .wl_storage .write_log @@ -198,9 +200,9 @@ pub fn validate_token_vp_from_tx<'a>( &verifiers, vp_wasm_cache, ); - let token = IbcToken { ctx }; + let multitoken_vp = MultitokenVp { ctx }; - TestIbcTokenVp { token }.validate(tx) + TestMultitokenVp { multitoken_vp }.validate(tx) } /// Initialize the test storage. Requires initialized [`tx_host_env::ENV`]. @@ -233,10 +235,10 @@ pub fn init_storage() -> (Address, Address) { }); // initialize a token - let token = tx::ctx().init_account(code_hash).unwrap(); + let token = tx_host_env::ctx().init_account(code_hash).unwrap(); // initialize an account - let account = tx::ctx().init_account(code_hash).unwrap(); + let account = tx_host_env::ctx().init_account(code_hash).unwrap(); let key = token::balance_key(&token, &account); let init_bal = Amount::native_whole(100); let bytes = init_bal.try_to_vec().expect("encoding failed"); @@ -263,6 +265,22 @@ pub fn init_storage() -> (Address, Address) { env.wl_storage.storage.write(&key, &bytes).unwrap(); }); + // commit the initialized token and account + tx_host_env::with(|env| { + env.wl_storage.commit_tx(); + env.wl_storage.commit_block().unwrap(); + + // block header to check timeout timestamp + env.wl_storage + .storage + .set_header(tm_dummy_header()) + .unwrap(); + env.wl_storage + .storage + .begin_block(BlockHash::default(), BlockHeight(2)) + .unwrap(); + }); + (token, account) } @@ -762,6 +780,6 @@ pub fn packet_from_message( } pub fn balance_key_with_ibc_prefix(denom: String, owner: &Address) -> Key { - let prefix = ibc_token_prefix(denom).expect("invalid denom"); - token::multitoken_balance_key(&prefix, owner) + let ibc_token = ibc_token(denom); + token::balance_key(&ibc_token, owner) } diff --git a/tests/src/vm_host_env/mod.rs b/tests/src/vm_host_env/mod.rs index 1766d7bad3..5ad6850944 100644 --- a/tests/src/vm_host_env/mod.rs +++ b/tests/src/vm_host_env/mod.rs @@ -27,7 +27,8 @@ mod tests { get_dummy_header as tm_dummy_header, Error as IbcError, }; use namada::ledger::tx_env::TxEnv; - use namada::proto::{Code, Data, Section, Signature, Tx}; + use namada::proto::{Code, Data, Section, Tx}; + use namada::types::address::{Address, InternalAddress}; use namada::types::hash::Hash; use namada::types::key::*; use namada::types::storage::{self, BlockHash, BlockHeight, Key, KeySeg}; @@ -37,8 +38,10 @@ mod tests { use namada::types::{address, key}; use namada_core::ledger::ibc::context::transfer_mod::testing::DummyTransferModule; use namada_core::ledger::ibc::Error as IbcActionError; + use namada_core::proto::MultiSignature; use namada_test_utils::TestWasms; use namada_tx_prelude::{BorshSerialize, StorageRead, StorageWrite}; + use namada_vp_prelude::account::AccountPublicKeysMap; use namada_vp_prelude::VpEnv; use prost::Message; use test_log::test; @@ -436,12 +439,11 @@ mod tests { let addr = address::testing::established_address_1(); // Write the public key to storage - let pk_key = key::pk_key(&addr); let keypair = key::testing::keypair_1(); let pk = keypair.ref_to(); - env.wl_storage - .write(&pk_key, pk.try_to_vec().unwrap()) - .unwrap(); + + let _ = pks_handle(&addr).insert(&mut env.wl_storage, 0_u8, pk.clone()); + // Initialize the environment vp_host_env::set(env); @@ -454,15 +456,19 @@ mod tests { // Tx without any data vec![], ] { + let keypairs = vec![keypair.clone()]; + let pks_map = AccountPublicKeysMap::from_iter(vec![pk.clone()]); let signed_tx_data = vp_host_env::with(|env| { let mut tx = Tx::new(TxType::Raw); tx.header.chain_id = env.wl_storage.storage.chain_id.clone(); tx.header.expiration = expiration; tx.set_code(Code::new(code.clone())); tx.set_data(Data::new(data.clone())); - tx.add_section(Section::Signature(Signature::new( + + tx.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.code_sechash(), *tx.data_sechash()], - &keypair, + &keypairs, + &pks_map, ))); env.tx = tx; env.tx.clone() @@ -470,12 +476,14 @@ mod tests { assert_eq!(signed_tx_data.data().as_ref(), Some(data)); assert!( signed_tx_data - .verify_signature( - &pk, + .verify_section_signatures( &[ *signed_tx_data.data_sechash(), *signed_tx_data.code_sechash(), ], + pks_map, + 1, + None ) .is_ok() ); @@ -483,12 +491,16 @@ mod tests { let other_keypair = key::testing::keypair_2(); assert!( signed_tx_data - .verify_signature( - &other_keypair.ref_to(), + .verify_section_signatures( &[ *signed_tx_data.data_sechash(), *signed_tx_data.code_sechash(), ], + AccountPublicKeysMap::from_iter([ + other_keypair.ref_to() + ]), + 1, + None ) .is_err() ); @@ -545,9 +557,12 @@ mod tests { let mut tx = Tx::new(TxType::Raw); tx.set_code(Code::new(vec![])); tx.set_data(Data::new(input_data)); - tx.add_section(Section::Signature(Signature::new( + tx.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.code_sechash(), *tx.data_sechash()], - &key::testing::keypair_1(), + &[key::testing::keypair_1()], + &AccountPublicKeysMap::from_iter([ + key::testing::keypair_1().ref_to() + ]), ))); let result = vp::CTX.eval(empty_code, tx).unwrap(); assert!(!result); @@ -564,9 +579,12 @@ mod tests { let mut tx = Tx::new(TxType::Raw); tx.set_code(Code::new(vec![])); tx.set_data(Data::new(input_data)); - tx.add_section(Section::Signature(Signature::new( + tx.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.code_sechash(), *tx.data_sechash()], - &key::testing::keypair_1(), + &[key::testing::keypair_1()], + &AccountPublicKeysMap::from_iter([ + key::testing::keypair_1().ref_to() + ]), ))); let result = vp::CTX.eval(code_hash, tx).unwrap(); assert!(result); @@ -584,9 +602,12 @@ mod tests { let mut tx = Tx::new(TxType::Raw); tx.set_code(Code::new(vec![])); tx.set_data(Data::new(input_data)); - tx.add_section(Section::Signature(Signature::new( + tx.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.code_sechash(), *tx.data_sechash()], - &key::testing::keypair_1(), + &[key::testing::keypair_1()], + &AccountPublicKeysMap::from_iter([ + key::testing::keypair_1().ref_to() + ]), ))); let result = vp::CTX.eval(code_hash, tx).unwrap(); assert!(!result); @@ -606,9 +627,12 @@ mod tests { let mut tx = Tx::new(TxType::Raw); tx.set_code(Code::new(vec![])); tx.set_data(Data::new(tx_data.clone())); - tx.add_section(Section::Signature(Signature::new( + tx.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.code_sechash(), *tx.data_sechash()], - &key::testing::keypair_1(), + &[key::testing::keypair_1()], + &AccountPublicKeysMap::from_iter([ + key::testing::keypair_1().ref_to() + ]), ))); // create a client with the message @@ -642,9 +666,12 @@ mod tests { let mut tx = Tx::new(TxType::Raw); tx.set_code(Code::new(vec![])); tx.set_data(Data::new(tx_data.clone())); - tx.add_section(Section::Signature(Signature::new( + tx.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.code_sechash(), *tx.data_sechash()], - &key::testing::keypair_1(), + &[key::testing::keypair_1()], + &AccountPublicKeysMap::from_iter([ + key::testing::keypair_1().ref_to() + ]), ))); // update the client with the message tx_host_env::ibc::ibc_actions(tx::ctx()) @@ -681,9 +708,12 @@ mod tests { let mut tx = Tx::new(TxType::Raw); tx.set_code(Code::new(vec![])); tx.set_data(Data::new(tx_data.clone())); - tx.add_section(Section::Signature(Signature::new( + tx.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.code_sechash(), *tx.data_sechash()], - &key::testing::keypair_1(), + &[key::testing::keypair_1()], + &AccountPublicKeysMap::from_iter([ + key::testing::keypair_1().ref_to() + ]), ))); // init a connection with the message tx_host_env::ibc::ibc_actions(tx::ctx()) @@ -716,9 +746,12 @@ mod tests { let mut tx = Tx::new(TxType::Raw); tx.set_code(Code::new(vec![])); tx.set_data(Data::new(tx_data.clone())); - tx.add_section(Section::Signature(Signature::new( + tx.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.code_sechash(), *tx.data_sechash()], - &key::testing::keypair_1(), + &[key::testing::keypair_1()], + &AccountPublicKeysMap::from_iter([ + key::testing::keypair_1().ref_to() + ]), ))); // open the connection with the message tx_host_env::ibc::ibc_actions(tx::ctx()) @@ -756,9 +789,12 @@ mod tests { let mut tx = Tx::new(TxType::Raw); tx.set_code(Code::new(vec![])); tx.set_data(Data::new(tx_data.clone())); - tx.add_section(Section::Signature(Signature::new( + tx.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.code_sechash(), *tx.data_sechash()], - &key::testing::keypair_1(), + &[key::testing::keypair_1()], + &AccountPublicKeysMap::from_iter([ + key::testing::keypair_1().ref_to() + ]), ))); // open try a connection with the message tx_host_env::ibc::ibc_actions(tx::ctx()) @@ -791,9 +827,12 @@ mod tests { let mut tx = Tx::new(TxType::Raw); tx.set_code(Code::new(vec![])); tx.set_data(Data::new(tx_data.clone())); - tx.add_section(Section::Signature(Signature::new( + tx.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.code_sechash(), *tx.data_sechash()], - &key::testing::keypair_1(), + &[key::testing::keypair_1()], + &AccountPublicKeysMap::from_iter([ + key::testing::keypair_1().ref_to() + ]), ))); // open the connection with the mssage tx_host_env::ibc::ibc_actions(tx::ctx()) @@ -833,9 +872,12 @@ mod tests { let mut tx = Tx::new(TxType::Raw); tx.set_code(Code::new(vec![])); tx.set_data(Data::new(tx_data.clone())); - tx.add_section(Section::Signature(Signature::new( + tx.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.code_sechash(), *tx.data_sechash()], - &key::testing::keypair_1(), + &[key::testing::keypair_1()], + &AccountPublicKeysMap::from_iter([ + key::testing::keypair_1().ref_to() + ]), ))); // init a channel with the message tx_host_env::ibc::ibc_actions(tx::ctx()) @@ -868,9 +910,12 @@ mod tests { let mut tx = Tx::new(TxType::Raw); tx.set_code(Code::new(vec![])); tx.set_data(Data::new(tx_data.clone())); - tx.add_section(Section::Signature(Signature::new( + tx.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.code_sechash(), *tx.data_sechash()], - &key::testing::keypair_1(), + &[key::testing::keypair_1()], + &AccountPublicKeysMap::from_iter([ + key::testing::keypair_1().ref_to() + ]), ))); // open the channle with the message tx_host_env::ibc::ibc_actions(tx::ctx()) @@ -910,9 +955,12 @@ mod tests { let mut tx = Tx::new(TxType::Raw); tx.set_code(Code::new(vec![])); tx.set_data(Data::new(tx_data.clone())); - tx.add_section(Section::Signature(Signature::new( + tx.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.code_sechash(), *tx.data_sechash()], - &key::testing::keypair_1(), + &[key::testing::keypair_1()], + &AccountPublicKeysMap::from_iter([ + key::testing::keypair_1().ref_to() + ]), ))); // try open a channel with the message tx_host_env::ibc::ibc_actions(tx::ctx()) @@ -946,9 +994,12 @@ mod tests { let mut tx = Tx::new(TxType::Raw); tx.set_code(Code::new(vec![])); tx.set_data(Data::new(tx_data.clone())); - tx.add_section(Section::Signature(Signature::new( + tx.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.code_sechash(), *tx.data_sechash()], - &key::testing::keypair_1(), + &[key::testing::keypair_1()], + &AccountPublicKeysMap::from_iter([ + key::testing::keypair_1().ref_to() + ]), ))); // open a channel with the message tx_host_env::ibc::ibc_actions(tx::ctx()) @@ -991,9 +1042,12 @@ mod tests { let mut tx = Tx::new(TxType::Raw); tx.set_code(Code::new(vec![])); tx.set_data(Data::new(tx_data.clone())); - tx.add_section(Section::Signature(Signature::new( + tx.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.code_sechash(), *tx.data_sechash()], - &key::testing::keypair_1(), + &[key::testing::keypair_1()], + &AccountPublicKeysMap::from_iter([ + key::testing::keypair_1().ref_to() + ]), ))); // close the channel with the message let mut actions = tx_host_env::ibc::ibc_actions(tx::ctx()); @@ -1044,9 +1098,12 @@ mod tests { let mut tx = Tx::new(TxType::Raw); tx.set_code(Code::new(vec![])); tx.set_data(Data::new(tx_data.clone())); - tx.add_section(Section::Signature(Signature::new( + tx.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.code_sechash(), *tx.data_sechash()], - &key::testing::keypair_1(), + &[key::testing::keypair_1()], + &AccountPublicKeysMap::from_iter([ + key::testing::keypair_1().ref_to() + ]), ))); // close the channel with the message @@ -1094,9 +1151,12 @@ mod tests { let mut tx = Tx::new(TxType::Raw); tx.set_code(Code::new(vec![])); tx.set_data(Data::new(tx_data.clone())); - tx.add_section(Section::Signature(Signature::new( + tx.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.code_sechash(), *tx.data_sechash()], - &key::testing::keypair_1(), + &[key::testing::keypair_1()], + &AccountPublicKeysMap::from_iter([ + key::testing::keypair_1().ref_to() + ]), ))); // send the token and a packet with the data tx_host_env::ibc::ibc_actions(tx::ctx()) @@ -1110,10 +1170,10 @@ mod tests { // Check if the token was escrowed let escrow = token::balance_key( &token, - &address::Address::Internal(address::InternalAddress::IbcEscrow), + &address::Address::Internal(address::InternalAddress::Ibc), ); let token_vp_result = - ibc::validate_token_vp_from_tx(&env, &tx, &escrow); + ibc::validate_multitoken_vp_from_tx(&env, &tx, &escrow); assert!(token_vp_result.expect("token validation failed unexpectedly")); // Commit @@ -1143,9 +1203,12 @@ mod tests { let mut tx = Tx::new(TxType::Raw); tx.set_code(Code::new(vec![])); tx.set_data(Data::new(tx_data.clone())); - tx.add_section(Section::Signature(Signature::new( + tx.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.code_sechash(), *tx.data_sechash()], - &key::testing::keypair_1(), + &[key::testing::keypair_1()], + &AccountPublicKeysMap::from_iter([ + key::testing::keypair_1().ref_to() + ]), ))); // ack the packet with the message tx_host_env::ibc::ibc_actions(tx::ctx()) @@ -1165,7 +1228,7 @@ mod tests { assert_eq!(balance, Some(Amount::native_whole(0))); let escrow_key = token::balance_key( &token, - &address::Address::Internal(address::InternalAddress::IbcEscrow), + &address::Address::Internal(address::InternalAddress::Ibc), ); let escrow: Option = tx_host_env::with(|env| { env.wl_storage.read(&escrow_key).expect("read error") @@ -1188,14 +1251,23 @@ mod tests { writes.extend(channel_writes); // the origin-specific token let denom = format!("{}/{}/{}", port_id, channel_id, token); - let key_prefix = ibc_storage::ibc_token_prefix(&denom).unwrap(); - let balance_key = token::multitoken_balance_key(&key_prefix, &sender); + let ibc_token = ibc_storage::ibc_token(&denom); + let balance_key = token::balance_key(&ibc_token, &sender); let init_bal = Amount::native_whole(100); writes.insert(balance_key.clone(), init_bal.try_to_vec().unwrap()); + let minted_key = token::minted_balance_key(&ibc_token); + writes.insert(minted_key.clone(), init_bal.try_to_vec().unwrap()); + let minter_key = token::minter_key(&ibc_token); + writes.insert( + minter_key, + Address::Internal(InternalAddress::Ibc) + .try_to_vec() + .unwrap(), + ); // original denom let hash = ibc_storage::calc_hash(&denom); - let denom_key = ibc_storage::ibc_denom_key(&hash); - writes.insert(denom_key, denom.as_bytes().to_vec()); + let denom_key = ibc_storage::ibc_denom_key(hash); + writes.insert(denom_key, denom.try_to_vec().unwrap()); writes.into_iter().for_each(|(key, val)| { tx_host_env::with(|env| { env.wl_storage @@ -1207,11 +1279,7 @@ mod tests { // Start a transaction to send a packet // Set this chain is the sink zone - let ibc_token = address::Address::Internal( - address::InternalAddress::IbcToken(hash), - ); - let hashed_denom = - format!("{}/{}", ibc_storage::MULTITOKEN_STORAGE_KEY, ibc_token); + let hashed_denom = ibc_token.to_string(); let msg = ibc::msg_transfer(port_id, channel_id, hashed_denom, &sender); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); @@ -1219,9 +1287,12 @@ mod tests { let mut tx = Tx::new(TxType::Raw); tx.set_code(Code::new(vec![])); tx.set_data(Data::new(tx_data.clone())); - tx.add_section(Section::Signature(Signature::new( + tx.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.code_sechash(), *tx.data_sechash()], - &key::testing::keypair_1(), + &[key::testing::keypair_1()], + &AccountPublicKeysMap::from_iter([ + key::testing::keypair_1().ref_to() + ]), ))); // send the token and a packet with the data tx_host_env::ibc::ibc_actions(tx::ctx()) @@ -1233,11 +1304,8 @@ mod tests { let result = ibc::validate_ibc_vp_from_tx(&env, &tx); assert!(result.expect("validation failed unexpectedly")); // Check if the token was burned - let burn = token::balance_key( - &token, - &address::Address::Internal(address::InternalAddress::IbcBurn), - ); - let result = ibc::validate_token_vp_from_tx(&env, &tx, &burn); + let result = + ibc::validate_multitoken_vp_from_tx(&env, &tx, &minted_key); assert!(result.expect("token validation failed unexpectedly")); // Check the balance tx_host_env::set(env); @@ -1245,14 +1313,10 @@ mod tests { env.wl_storage.read(&balance_key).expect("read error") }); assert_eq!(balance, Some(Amount::native_whole(0))); - let burn_key = token::balance_key( - &token, - &address::Address::Internal(address::InternalAddress::IbcBurn), - ); - let burn: Option = tx_host_env::with(|env| { - env.wl_storage.read(&burn_key).expect("read error") + let minted: Option = tx_host_env::with(|env| { + env.wl_storage.read(&minted_key).expect("read error") }); - assert_eq!(burn, Some(Amount::native_whole(100))); + assert_eq!(minted, Some(Amount::native_whole(0))); } #[test] @@ -1295,9 +1359,12 @@ mod tests { let mut tx = Tx::new(TxType::Raw); tx.set_code(Code::new(vec![])); tx.set_data(Data::new(tx_data.clone())); - tx.add_section(Section::Signature(Signature::new( + tx.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.code_sechash(), *tx.data_sechash()], - &key::testing::keypair_1(), + &[key::testing::keypair_1()], + &AccountPublicKeysMap::from_iter([ + key::testing::keypair_1().ref_to() + ]), ))); // receive a packet with the message tx_host_env::ibc::ibc_actions(tx::ctx()) @@ -1309,20 +1376,23 @@ mod tests { let result = ibc::validate_ibc_vp_from_tx(&env, &tx); assert!(result.expect("validation failed unexpectedly")); // Check if the token was minted - let mint = token::balance_key( - &token, - &address::Address::Internal(address::InternalAddress::IbcMint), - ); - let result = ibc::validate_token_vp_from_tx(&env, &tx, &mint); + let denom = format!("{}/{}/{}", port_id, channel_id, token); + let ibc_token = ibc::ibc_token(&denom); + let minted_key = token::minted_balance_key(&ibc_token); + let result = + ibc::validate_multitoken_vp_from_tx(&env, &tx, &minted_key); assert!(result.expect("token validation failed unexpectedly")); // Check the balance tx_host_env::set(env); - let denom = format!("{}/{}/{}", port_id, channel_id, token); let key = ibc::balance_key_with_ibc_prefix(denom, &receiver); let balance: Option = tx_host_env::with(|env| { env.wl_storage.read(&key).expect("read error") }); assert_eq!(balance, Some(Amount::native_whole(100))); + let minted: Option = tx_host_env::with(|env| { + env.wl_storage.read(&minted_key).expect("read error") + }); + assert_eq!(minted, Some(Amount::native_whole(100))); } #[test] @@ -1349,7 +1419,7 @@ mod tests { // escrow in advance let escrow_key = token::balance_key( &token, - &address::Address::Internal(address::InternalAddress::IbcEscrow), + &address::Address::Internal(address::InternalAddress::Ibc), ); let val = Amount::native_whole(100).try_to_vec().unwrap(); tx_host_env::with(|env| { @@ -1383,9 +1453,12 @@ mod tests { let mut tx = Tx::new(TxType::Raw); tx.set_code(Code::new(vec![])); tx.set_data(Data::new(tx_data.clone())); - tx.add_section(Section::Signature(Signature::new( + tx.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.code_sechash(), *tx.data_sechash()], - &key::testing::keypair_1(), + &[key::testing::keypair_1()], + &AccountPublicKeysMap::from_iter([ + key::testing::keypair_1().ref_to() + ]), ))); // receive a packet with the message tx_host_env::ibc::ibc_actions(tx::ctx()) @@ -1397,7 +1470,8 @@ mod tests { let result = ibc::validate_ibc_vp_from_tx(&env, &tx); assert!(result.expect("validation failed unexpectedly")); // Check if the token was unescrowed - let result = ibc::validate_token_vp_from_tx(&env, &tx, &escrow_key); + let result = + ibc::validate_multitoken_vp_from_tx(&env, &tx, &escrow_key); assert!(result.expect("token validation failed unexpectedly")); // Check the balance tx_host_env::set(env); @@ -1434,9 +1508,13 @@ mod tests { }); }); // escrow in advance - let escrow_key = token::balance_key( - &token, - &address::Address::Internal(address::InternalAddress::IbcEscrow), + let dummy_src_port = "dummy_transfer"; + let dummy_src_channel = "channel_42"; + let denom = + format!("{}/{}/{}", dummy_src_port, dummy_src_channel, token); + let escrow_key = ibc::balance_key_with_ibc_prefix( + denom, + &address::Address::Internal(address::InternalAddress::Ibc), ); let val = Amount::native_whole(100).try_to_vec().unwrap(); tx_host_env::with(|env| { @@ -1448,8 +1526,6 @@ mod tests { // Set this chain as the source zone let counterparty = ibc::dummy_channel_counterparty(); - let dummy_src_port = "dummy_transfer"; - let dummy_src_channel = "channel_42"; let denom = format!( "{}/{}/{}/{}/{}", counterparty.port_id().clone(), @@ -1475,9 +1551,12 @@ mod tests { let mut tx = Tx::new(TxType::Raw); tx.set_code(Code::new(vec![])); tx.set_data(Data::new(tx_data.clone())); - tx.add_section(Section::Signature(Signature::new( + tx.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.code_sechash(), *tx.data_sechash()], - &key::testing::keypair_1(), + &[key::testing::keypair_1()], + &AccountPublicKeysMap::from_iter([ + key::testing::keypair_1().ref_to() + ]), ))); // receive a packet with the message tx_host_env::ibc::ibc_actions(tx::ctx()) @@ -1489,7 +1568,8 @@ mod tests { let result = ibc::validate_ibc_vp_from_tx(&env, &tx); assert!(result.expect("validation failed unexpectedly")); // Check if the token was unescrowed - let result = ibc::validate_token_vp_from_tx(&env, &tx, &escrow_key); + let result = + ibc::validate_multitoken_vp_from_tx(&env, &tx, &escrow_key); assert!(result.expect("token validation failed unexpectedly")); // Check the balance tx_host_env::set(env); @@ -1570,9 +1650,12 @@ mod tests { let mut tx = Tx::new(TxType::Raw); tx.set_code(Code::new(vec![])); tx.set_data(Data::new(tx_data.clone())); - tx.add_section(Section::Signature(Signature::new( + tx.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.code_sechash(), *tx.data_sechash()], - &key::testing::keypair_1(), + &[key::testing::keypair_1()], + &AccountPublicKeysMap::from_iter([ + key::testing::keypair_1().ref_to() + ]), ))); // timeout the packet @@ -1587,9 +1670,9 @@ mod tests { // Check if the token was refunded let escrow = token::balance_key( &token, - &address::Address::Internal(address::InternalAddress::IbcEscrow), + &address::Address::Internal(address::InternalAddress::Ibc), ); - let result = ibc::validate_token_vp_from_tx(&env, &tx, &escrow); + let result = ibc::validate_multitoken_vp_from_tx(&env, &tx, &escrow); assert!(result.expect("token validation failed unexpectedly")); } @@ -1655,9 +1738,12 @@ mod tests { let mut tx = Tx::new(TxType::Raw); tx.set_code(Code::new(vec![])); tx.set_data(Data::new(tx_data.clone())); - tx.add_section(Section::Signature(Signature::new( + tx.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.code_sechash(), *tx.data_sechash()], - &key::testing::keypair_1(), + &[key::testing::keypair_1()], + &AccountPublicKeysMap::from_iter([ + key::testing::keypair_1().ref_to() + ]), ))); // timeout the packet @@ -1672,9 +1758,9 @@ mod tests { // Check if the token was refunded let escrow = token::balance_key( &token, - &address::Address::Internal(address::InternalAddress::IbcEscrow), + &address::Address::Internal(address::InternalAddress::Ibc), ); - let result = ibc::validate_token_vp_from_tx(&env, &tx, &escrow); + let result = ibc::validate_multitoken_vp_from_tx(&env, &tx, &escrow); assert!(result.expect("token validation failed unexpectedly")); } } diff --git a/tests/src/vm_host_env/tx.rs b/tests/src/vm_host_env/tx.rs index c9ab18a08a..aad6510c89 100644 --- a/tests/src/vm_host_env/tx.rs +++ b/tests/src/vm_host_env/tx.rs @@ -18,7 +18,8 @@ use namada::vm::prefix_iter::PrefixIterators; use namada::vm::wasm::run::Error; use namada::vm::wasm::{self, TxCache, VpCache}; use namada::vm::{self, WasmCacheRwAccess}; -use namada_tx_prelude::{BorshSerialize, Ctx}; +use namada_tx_prelude::{storage_api, BorshSerialize, Ctx}; +use namada_vp_prelude::key::common; use tempfile::TempDir; use crate::vp::TestVpEnv; @@ -102,6 +103,7 @@ impl TestTxEnv { epoch_duration: Option, vp_whitelist: Option>, tx_whitelist: Option>, + max_signatures_per_transaction: Option, ) { parameters::update_epoch_parameter( &mut self.wl_storage, @@ -121,6 +123,11 @@ impl TestTxEnv { vp_whitelist.unwrap_or_default(), ) .unwrap(); + parameters::update_max_signature_per_tx( + &mut self.wl_storage, + max_signatures_per_transaction.unwrap_or(15), + ) + .unwrap(); } pub fn store_wasm_code(&mut self, code: Vec) { @@ -155,6 +162,34 @@ impl TestTxEnv { } } + pub fn init_account_storage( + &mut self, + owner: &Address, + public_keys: Vec, + threshold: u8, + ) { + storage_api::account::init_account_storage( + &mut self.wl_storage, + owner, + &public_keys, + threshold, + ) + .expect("Unable to write Account substorage."); + } + + /// Set public key for the address. + pub fn write_account_threshold( + &mut self, + address: &Address, + threshold: u8, + ) { + let storage_key = key::threshold_key(address); + self.wl_storage + .storage + .write(&storage_key, threshold.try_to_vec().unwrap()) + .unwrap(); + } + /// Commit the genesis state. Typically, you'll want to call this after /// setting up the initial state, before running a transaction. pub fn commit_genesis(&mut self) { @@ -177,36 +212,15 @@ impl TestTxEnv { &mut self, target: &Address, token: &Address, - sub_prefix: Option, amount: token::Amount, ) { - let storage_key = match &sub_prefix { - Some(sub_prefix) => { - let prefix = - token::multitoken_balance_prefix(token, sub_prefix); - token::multitoken_balance_key(&prefix, target) - } - None => token::balance_key(token, target), - }; + let storage_key = token::balance_key(token, target); self.wl_storage .storage .write(&storage_key, amount.try_to_vec().unwrap()) .unwrap(); } - /// Set public key for the address. - pub fn write_public_key( - &mut self, - address: &Address, - public_key: &key::common::PublicKey, - ) { - let storage_key = key::pk_key(address); - self.wl_storage - .storage - .write(&storage_key, public_key.try_to_vec().unwrap()) - .unwrap(); - } - /// Apply the tx changes to the write log. pub fn execute_tx(&mut self) -> Result<(), Error> { wasm::run::tx( diff --git a/tx_prelude/src/account.rs b/tx_prelude/src/account.rs new file mode 100644 index 0000000000..da6c213601 --- /dev/null +++ b/tx_prelude/src/account.rs @@ -0,0 +1,18 @@ +use namada_core::types::transaction::account::InitAccount; + +use super::*; + +pub fn init_account( + ctx: &mut Ctx, + owner: &Address, + data: InitAccount, +) -> EnvResult
{ + storage_api::account::init_account_storage( + ctx, + owner, + &data.public_keys, + data.threshold, + )?; + + Ok(owner.to_owned()) +} diff --git a/tx_prelude/src/ibc.rs b/tx_prelude/src/ibc.rs index 12654df964..d15a60a5af 100644 --- a/tx_prelude/src/ibc.rs +++ b/tx_prelude/src/ibc.rs @@ -9,11 +9,12 @@ pub use namada_core::ledger::ibc::{ }; use namada_core::ledger::storage_api::{StorageRead, StorageWrite}; use namada_core::ledger::tx_env::TxEnv; +use namada_core::types::address::{Address, InternalAddress}; pub use namada_core::types::ibc::IbcEvent; use namada_core::types::storage::{BlockHeight, Header, Key}; use namada_core::types::token::Amount; -use crate::token::transfer_with_keys; +use crate::token::{burn, mint, transfer}; use crate::{Ctx, KeyValIterator}; /// IBC actions to handle an IBC message @@ -72,11 +73,37 @@ impl IbcStorageContext for Ctx { fn transfer_token( &mut self, - src: &Key, - dest: &Key, + src: &Address, + dest: &Address, + token: &Address, amount: Amount, ) -> std::result::Result<(), Self::Error> { - transfer_with_keys(self, src, dest, amount) + let amount = amount.denominated(token, self)?; + transfer(self, src, dest, token, amount, &None, &None, &None) + } + + fn mint_token( + &mut self, + target: &Address, + token: &Address, + amount: Amount, + ) -> Result<(), Self::Error> { + mint( + self, + &Address::Internal(InternalAddress::Ibc), + target, + token, + amount, + ) + } + + fn burn_token( + &mut self, + target: &Address, + token: &Address, + amount: Amount, + ) -> Result<(), Self::Error> { + burn(self, target, token, amount) } fn get_height(&self) -> std::result::Result { diff --git a/tx_prelude/src/lib.rs b/tx_prelude/src/lib.rs index 6e5a0192c8..16103d6d34 100644 --- a/tx_prelude/src/lib.rs +++ b/tx_prelude/src/lib.rs @@ -6,6 +6,7 @@ #![deny(rustdoc::broken_intra_doc_links)] #![deny(rustdoc::private_intra_doc_links)] +pub mod account; pub mod ibc; pub mod key; pub mod proof_of_stake; diff --git a/tx_prelude/src/proof_of_stake.rs b/tx_prelude/src/proof_of_stake.rs index 48b0cb665f..cc8bcb7b63 100644 --- a/tx_prelude/src/proof_of_stake.rs +++ b/tx_prelude/src/proof_of_stake.rs @@ -2,7 +2,7 @@ use namada_core::types::dec::Dec; use namada_core::types::hash::Hash; -use namada_core::types::transaction::InitValidator; +use namada_core::types::transaction::pos::InitValidator; use namada_core::types::{key, token}; pub use namada_proof_of_stake::parameters::PosParams; use namada_proof_of_stake::{ @@ -74,7 +74,8 @@ impl Ctx { pub fn init_validator( &mut self, InitValidator { - account_key, + account_keys, + threshold, consensus_key, eth_cold_key, eth_hot_key, @@ -89,8 +90,12 @@ impl Ctx { let current_epoch = self.get_block_epoch()?; // Init validator account let validator_address = self.init_account(validator_vp_code_hash)?; - let pk_key = key::pk_key(&validator_address); - self.write(&pk_key, &account_key)?; + storage_api::account::init_account_storage( + self, + &validator_address, + &account_keys, + threshold, + )?; let protocol_pk_key = key::protocol_pk_key(&validator_address); self.write(&protocol_pk_key, &protocol_key)?; let dkg_pk_key = key::dkg_session_keys::dkg_pk_key(&validator_address); diff --git a/tx_prelude/src/token.rs b/tx_prelude/src/token.rs index 685a2e51a6..3cf1d8ead3 100644 --- a/tx_prelude/src/token.rs +++ b/tx_prelude/src/token.rs @@ -1,5 +1,5 @@ use masp_primitives::transaction::Transaction; -use namada_core::types::address::{Address, InternalAddress}; +use namada_core::types::address::Address; use namada_core::types::hash::Hash; use namada_core::types::storage::KeySeg; use namada_core::types::token; @@ -14,78 +14,25 @@ pub fn transfer( src: &Address, dest: &Address, token: &Address, - sub_prefix: Option, amount: DenominatedAmount, key: &Option, shielded_hash: &Option, shielded: &Option, ) -> TxResult { if amount.amount != Amount::default() { - let src_key = match &sub_prefix { - Some(sub_prefix) => { - let prefix = - token::multitoken_balance_prefix(token, sub_prefix); - token::multitoken_balance_key(&prefix, src) - } - None => token::balance_key(token, src), - }; - let dest_key = match &sub_prefix { - Some(sub_prefix) => { - let prefix = - token::multitoken_balance_prefix(token, sub_prefix); - token::multitoken_balance_key(&prefix, dest) - } - None => token::balance_key(token, dest), - }; + let src_key = token::balance_key(token, src); + let dest_key = token::balance_key(token, dest); + let src_bal: Option = ctx.read(&src_key)?; + let mut src_bal = src_bal.unwrap_or_else(|| { + log_string(format!("src {} has no balance", src_key)); + unreachable!() + }); + src_bal.spend(&amount.amount); + let mut dest_bal: Amount = ctx.read(&dest_key)?.unwrap_or_default(); + dest_bal.receive(&amount.amount); if src != dest { - let src_bal: Option = match src { - Address::Internal(InternalAddress::IbcMint) => { - Some(Amount::max_signed()) - } - Address::Internal(InternalAddress::IbcBurn) => { - log_string("invalid transfer from the burn address"); - unreachable!() - } - _ => ctx.read(&src_key)?, - }; - let mut src_bal = src_bal.unwrap_or_else(|| { - log_string(format!("src {} has no balance", src_key)); - unreachable!() - }); - src_bal.spend(&amount.amount); - let mut dest_bal: Amount = match dest { - Address::Internal(InternalAddress::IbcMint) => { - log_string("invalid transfer to the mint address"); - unreachable!() - } - _ => ctx.read(&dest_key)?.unwrap_or_default(), - }; - dest_bal.receive(&amount.amount); - - match src { - Address::Internal(InternalAddress::IbcMint) => { - ctx.write_temp(&src_key, src_bal)?; - } - Address::Internal(InternalAddress::IbcBurn) => { - log_string("invalid transfer from the burn address"); - unreachable!() - } - _ => { - ctx.write(&src_key, src_bal)?; - } - } - match dest { - Address::Internal(InternalAddress::IbcMint) => { - log_string("invalid transfer to the mint address"); - unreachable!() - } - Address::Internal(InternalAddress::IbcBurn) => { - ctx.write_temp(&dest_key, dest_bal)?; - } - _ => { - ctx.write(&dest_key, dest_bal)?; - } - } + ctx.write(&src_key, src_bal)?; + ctx.write(&dest_key, dest_bal)?; } } @@ -109,8 +56,6 @@ pub fn transfer( source: src.clone(), target: dest.clone(), token: token.clone(), - // todo: build asset types for multitokens - sub_prefix: None, amount, key: key.clone(), shielded: *shielded_hash, @@ -135,49 +80,49 @@ pub fn transfer( Ok(()) } -/// A token transfer with storage keys that can be used in a transaction. -pub fn transfer_with_keys( +/// Mint that can be used in a transaction. +pub fn mint( ctx: &mut Ctx, - src_key: &storage::Key, - dest_key: &storage::Key, + minter: &Address, + target: &Address, + token: &Address, amount: Amount, ) -> TxResult { - let src_owner = is_any_token_or_multitoken_balance_key(src_key); - let src_bal: Option = match src_owner { - Some([_, Address::Internal(InternalAddress::IbcMint)]) => { - Some(Amount::max_signed()) - } - Some([_, Address::Internal(InternalAddress::IbcBurn)]) => { - log_string("invalid transfer from the burn address"); - unreachable!() - } - _ => ctx.read(src_key)?, - }; - let mut src_bal = src_bal.unwrap_or_else(|| { - log_string(format!("src {} has no balance", src_key)); - unreachable!() - }); - src_bal.spend(&amount); - let dest_owner = is_any_token_balance_key(dest_key); - let mut dest_bal: Amount = match dest_owner { - Some([_, Address::Internal(InternalAddress::IbcMint)]) => { - log_string("invalid transfer to the mint address"); - unreachable!() - } - _ => ctx.read(dest_key)?.unwrap_or_default(), - }; - dest_bal.receive(&amount); - match src_owner { - Some([_, Address::Internal(InternalAddress::IbcMint)]) => { - ctx.write_temp(src_key, src_bal)?; - } - _ => ctx.write(src_key, src_bal)?, - } - match dest_owner { - Some([_, Address::Internal(InternalAddress::IbcBurn)]) => { - ctx.write_temp(dest_key, dest_bal)?; - } - _ => ctx.write(dest_key, dest_bal)?, - } + let target_key = token::balance_key(token, target); + let mut target_bal: Amount = ctx.read(&target_key)?.unwrap_or_default(); + target_bal.receive(&amount); + + let minted_key = token::minted_balance_key(token); + let mut minted_bal: Amount = ctx.read(&minted_key)?.unwrap_or_default(); + minted_bal.receive(&amount); + + ctx.write(&target_key, target_bal)?; + ctx.write(&minted_key, minted_bal)?; + + let minter_key = token::minter_key(token); + ctx.write(&minter_key, minter)?; + + Ok(()) +} + +/// Burn that can be used in a transaction. +pub fn burn( + ctx: &mut Ctx, + target: &Address, + token: &Address, + amount: Amount, +) -> TxResult { + let target_key = token::balance_key(token, target); + let mut target_bal: Amount = ctx.read(&target_key)?.unwrap_or_default(); + target_bal.spend(&amount); + + // burn the minted amount + let minted_key = token::minted_balance_key(token); + let mut minted_bal: Amount = ctx.read(&minted_key)?.unwrap_or_default(); + minted_bal.spend(&amount); + + ctx.write(&target_key, target_bal)?; + ctx.write(&minted_key, minted_bal)?; + Ok(()) } diff --git a/vp_prelude/src/lib.rs b/vp_prelude/src/lib.rs index 8ec55ef7cd..a7d19832e1 100644 --- a/vp_prelude/src/lib.rs +++ b/vp_prelude/src/lib.rs @@ -6,8 +6,6 @@ #![deny(rustdoc::broken_intra_doc_links)] #![deny(rustdoc::private_intra_doc_links)] -pub mod key; - // used in the VP input use core::convert::AsRef; use core::slice; @@ -79,6 +77,27 @@ pub fn is_proposal_accepted(ctx: &Ctx, proposal_id: u64) -> VpResult { ctx.has_key_pre(&proposal_execution_key) } +/// Verify section signatures +pub fn verify_signatures(ctx: &Ctx, tx: &Tx, owner: &Address) -> VpResult { + let max_signatures_per_transaction = + parameters::max_signatures_per_transaction(&ctx.pre())?; + + let public_keys_index_map = + storage_api::account::public_keys_index_map(&ctx.pre(), owner)?; + let threshold = + storage_api::account::threshold(&ctx.pre(), owner)?.unwrap_or(1); + + let targets = &[*tx.data_sechash(), *tx.code_sechash()]; + tx.verify_section_signatures( + targets, + public_keys_index_map, + threshold, + max_signatures_per_transaction, + ) + .map_err(|_e| Error::SimpleMessage("Invalid signatures")) + .map(|_| true) +} + /// Checks whether a transaction is valid, which happens in two cases: /// - tx is whitelisted, or /// - tx is executed by an approved governance proposal (no need to be diff --git a/wasm/checksums.json b/wasm/checksums.json index 8397ffb0d6..7a577cd9b9 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,22 +1,21 @@ { - "tx_bond.wasm": "tx_bond.e45489077b1f944d15197ecb432290be523189d8cf1f35b3c37211688ad582b2.wasm", - "tx_bridge_pool.wasm": "tx_bridge_pool.8c942b7e6a49562ff20770ac6e04df85188b49fabf4ca7f82fa3a5986a66a363.wasm", - "tx_change_validator_commission.wasm": "tx_change_validator_commission.37a8ec36194c2da2d9625a71a37dda7f5ef2530de562dacc29d70d9e1bd6d475.wasm", - "tx_ibc.wasm": "tx_ibc.a719260d45a15a3eeed5442abeda18be739face4ab509abcb00a6a10151ffc5c.wasm", - "tx_init_account.wasm": "tx_init_account.f979613d2b8b540ad471c663ec1aa3d9fad085ba7b1b059e2564c7a1eb5fa139.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.9a6c2aa5771fd08f26fb4d56ceb361c723d4b617da0ca2ab1a44c2b07f3b58b0.wasm", - "tx_init_validator.wasm": "tx_init_validator.01e521286a61e0a55e606319cadb330077824deb2d0d8462e180b27ec6a7567e.wasm", - "tx_reveal_pk.wasm": "tx_reveal_pk.01442c5045ff10d7da05b1843803db99d23a6992594b2c3eb83955f6af9a26fb.wasm", - "tx_transfer.wasm": "tx_transfer.f50a99b865d57c95ccfaec95963e87ba61f3a2d9f9fe7c0ab3cd9b09e0095d9c.wasm", - "tx_unbond.wasm": "tx_unbond.b081405d6a7bacf1aabf8f650014fda97b8cca9ae9d3a9d6746f601600f0d563.wasm", - "tx_unjail_validator.wasm": "tx_unjail_validator.87f90fb263cb9eee693bea861ed5a3b797b075c0164b7ac1f9a1611d661bffb4.wasm", - "tx_update_vp.wasm": "tx_update_vp.78e3064cd6f24b376ce7aa85611e9b9f77cee6d6629b4849d6a60cb12017e1f4.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.36047c640ca8a10a62811cc15ba6e054e639313adf69c70e80e428e152c972cb.wasm", - "tx_withdraw.wasm": "tx_withdraw.9ad086dbcee5bdbfc915730c58f5c9b897cdfdd7192ffc839c3c8fd3adbbbe88.wasm", - "vp_implicit.wasm": "vp_implicit.16bb18c3b7973747a6f9581b769fff007f9189ecde87d2aeeb2317fb0abd1acb.wasm", - "vp_masp.wasm": "vp_masp.70bcfc40b3d9e9f792f298ba2e0c5e60fb44b4d1e4152635b2236b4a59faf235.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.7b8ea312ee9820c6129a861067e881cbd9212275d48a9ffb9bffe2dcf6576b31.wasm", - "vp_token.wasm": "vp_token.ec4f3914d074168f7ecbd43d5587e014381d409b3df4db4f63eaf73d3a56cdd6.wasm", - "vp_user.wasm": "vp_user.fd9810232a2ec79ff3f9f8fc70a6427ce9d07a9ef51c1468a785247a9b08b337.wasm", - "vp_validator.wasm": "vp_validator.8ce4c52a53aa451459e37ec560aa56ac4083d8829b0c29168c448e1e9d764c22.wasm" + "tx_bond.wasm": "tx_bond.c4437ddc758308f06f9aad3cc0a49eda9729ea97d6b21409253d9e8a269a04c0.wasm", + "tx_bridge_pool.wasm": "tx_bridge_pool.4b56ec5d467bd64773d543e7cb440e4f3f9322c5f50279c6c7b82877efc926f8.wasm", + "tx_change_validator_commission.wasm": "tx_change_validator_commission.81a91f584ea801b1f92a95e9e2909ed5005a457d47a675abeebb854694015020.wasm", + "tx_ibc.wasm": "tx_ibc.feddf974a2513b30c2e42b186a3b9d34a543bb002a9d6517a452cd690f5cea13.wasm", + "tx_init_account.wasm": "tx_init_account.b612a90c4b81cbde7e2d7f762bf2a069a4b0e80cce3564963e5fb9177b5dbdfe.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.b6eafd6e12267968b5e55372f80ce7c58a77b3032cc969ccbd5c9e85d2cd26d1.wasm", + "tx_init_validator.wasm": "tx_init_validator.9be88b92bc3bb1121f8dafa177ecef30f97e75b77def4c5ccb13a2a3752fb1b4.wasm", + "tx_reveal_pk.wasm": "tx_reveal_pk.5e0f6ba78f884c2834e925689d83eec821c331e3a81412f2cfabc8f7c5913e00.wasm", + "tx_transfer.wasm": "tx_transfer.950a79392ed084f8767476b019d3aab070afeaf007f4166e5f7072787e7a0e37.wasm", + "tx_unbond.wasm": "tx_unbond.8727707ce10a53a4adfcf1afc65043500690c0df0c6780845d9d4e35100e9220.wasm", + "tx_unjail_validator.wasm": "tx_unjail_validator.f2efaf3f329a4dca173822a776e9270158852565008e6baf289a9b1382851502.wasm", + "tx_update_account.wasm": "tx_update_account.30c63abbe4619cf842243953939a23d485a1406c0716279c6881ebff353ae981.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.351da757c771bbbfeb58dd60f2e957e05952722dd23d0722895c24cf9832912a.wasm", + "tx_withdraw.wasm": "tx_withdraw.038fa6845c7715f1b3338aa3ca2b5742486228c6423dd544c892bae457f3c425.wasm", + "vp_implicit.wasm": "vp_implicit.a3f75b7a0e5cde03d3ddfb6403c108c11f243195a5f4bb4f010ca58f34f2197d.wasm", + "vp_masp.wasm": "vp_masp.2e35ac5a656b816bc53a132a24e7f87de05b1df0c7a0d95584e1c670715e956b.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.e93ee5799d48fa15828891c74aafb1f66d1b275d27f23ebeb75707299c44fd9a.wasm", + "vp_user.wasm": "vp_user.e7e42f3c9d9d1572d22b6f6c66ddbaaa21cb3c47a41399a79d6997c1ec5ca981.wasm", + "vp_validator.wasm": "vp_validator.d8ded8bd3823b228c92e3f0d6d9b3c2ad1097f8254e8680914312347aa42e6a5.wasm" } \ No newline at end of file diff --git a/wasm/wasm_source/Cargo.toml b/wasm/wasm_source/Cargo.toml index 266392752a..45f6ce13f4 100644 --- a/wasm/wasm_source/Cargo.toml +++ b/wasm/wasm_source/Cargo.toml @@ -24,7 +24,7 @@ tx_reveal_pk = ["namada_tx_prelude"] tx_transfer = ["namada_tx_prelude"] tx_unbond = ["namada_tx_prelude"] tx_unjail_validator = ["namada_tx_prelude"] -tx_update_vp = ["namada_tx_prelude"] +tx_update_account = ["namada_tx_prelude"] tx_vote_proposal = ["namada_tx_prelude"] tx_withdraw = ["namada_tx_prelude"] vp_implicit = ["namada_vp_prelude", "once_cell"] diff --git a/wasm/wasm_source/Makefile b/wasm/wasm_source/Makefile index a2b5ab284d..a88065355f 100644 --- a/wasm/wasm_source/Makefile +++ b/wasm/wasm_source/Makefile @@ -16,13 +16,12 @@ wasms += tx_reveal_pk wasms += tx_transfer wasms += tx_unbond wasms += tx_unjail_validator -wasms += tx_update_vp +wasms += tx_update_account wasms += tx_vote_proposal wasms += tx_withdraw wasms += vp_implicit wasms += vp_masp wasms += vp_testnet_faucet -wasms += vp_token wasms += vp_user wasms += vp_validator diff --git a/wasm/wasm_source/src/lib.rs b/wasm/wasm_source/src/lib.rs index 3d5be30b56..ce5b1b1561 100644 --- a/wasm/wasm_source/src/lib.rs +++ b/wasm/wasm_source/src/lib.rs @@ -20,8 +20,8 @@ pub mod tx_transfer; pub mod tx_unbond; #[cfg(feature = "tx_unjail_validator")] pub mod tx_unjail_validator; -#[cfg(feature = "tx_update_vp")] -pub mod tx_update_vp; +#[cfg(feature = "tx_update_account")] +pub mod tx_update_account; #[cfg(feature = "tx_vote_proposal")] pub mod tx_vote_proposal; #[cfg(feature = "tx_withdraw")] @@ -33,8 +33,6 @@ pub mod vp_implicit; pub mod vp_masp; #[cfg(feature = "vp_testnet_faucet")] pub mod vp_testnet_faucet; -#[cfg(feature = "vp_token")] -pub mod vp_token; #[cfg(feature = "vp_user")] pub mod vp_user; #[cfg(feature = "vp_validator")] diff --git a/wasm/wasm_source/src/tx_bond.rs b/wasm/wasm_source/src/tx_bond.rs index df55270ceb..33d004591b 100644 --- a/wasm/wasm_source/src/tx_bond.rs +++ b/wasm/wasm_source/src/tx_bond.rs @@ -15,7 +15,7 @@ fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { #[cfg(test)] mod tests { - use std::collections::HashSet; + use std::collections::BTreeSet; use namada::ledger::pos::{GenesisValidator, PosParams, PosVP}; use namada::proof_of_stake::{ @@ -102,7 +102,7 @@ mod tests { // Ensure that the bond's source has enough tokens for the bond let target = bond.source.as_ref().unwrap_or(&bond.validator); let native_token = tx_env.wl_storage.storage.native_token.clone(); - tx_env.credit_tokens(target, &native_token, None, bond.amount); + tx_env.credit_tokens(target, &native_token, bond.amount); native_token }); @@ -132,7 +132,7 @@ mod tests { // Read some data before the tx is executed let mut epoched_total_stake_pre: Vec = Vec::new(); let mut epoched_validator_stake_pre: Vec = Vec::new(); - let mut epoched_validator_set_pre: Vec> = + let mut epoched_validator_set_pre: Vec> = Vec::new(); for epoch in 0..=pos_params.unbonding_len { @@ -163,7 +163,7 @@ mod tests { // Read the data after the tx is executed. let mut epoched_total_stake_post: Vec = Vec::new(); let mut epoched_validator_stake_post: Vec = Vec::new(); - let mut epoched_validator_set_post: Vec> = + let mut epoched_validator_set_post: Vec> = Vec::new(); println!("\nFILLING POST STATE\n"); diff --git a/wasm/wasm_source/src/tx_bridge_pool.rs b/wasm/wasm_source/src/tx_bridge_pool.rs index 001fdcffcf..bf73e83f7d 100644 --- a/wasm/wasm_source/src/tx_bridge_pool.rs +++ b/wasm/wasm_source/src/tx_bridge_pool.rs @@ -19,7 +19,6 @@ fn apply_tx(ctx: &mut Ctx, signed: Tx) -> TxResult { payer, &bridge_pool::BRIDGE_POOL_ADDRESS, &nam_addr, - None, amount.native_denominated(), &None, &None, @@ -39,7 +38,6 @@ fn apply_tx(ctx: &mut Ctx, signed: Tx) -> TxResult { sender, ð_bridge::ADDRESS, &nam_addr, - None, amount.native_denominated(), &None, &None, @@ -47,19 +45,13 @@ fn apply_tx(ctx: &mut Ctx, signed: Tx) -> TxResult { )?; } else { // Otherwise we escrow ERC20 tokens. - let sub_prefix = Some(wrapped_erc20s::sub_prefix(&asset)); - let amount = amount.denominated( - ð_bridge::ADDRESS, - sub_prefix.as_ref(), - ctx, - )?; + let token = wrapped_erc20s::token(&asset); token::transfer( ctx, sender, &bridge_pool::BRIDGE_POOL_ADDRESS, - ð_bridge::ADDRESS, - sub_prefix, - amount, + &token, + amount.native_denominated(), &None, &None, &None, diff --git a/wasm/wasm_source/src/tx_init_account.rs b/wasm/wasm_source/src/tx_init_account.rs index 2e85b70ae9..346afb2bec 100644 --- a/wasm/wasm_source/src/tx_init_account.rs +++ b/wasm/wasm_source/src/tx_init_account.rs @@ -7,7 +7,7 @@ use namada_tx_prelude::*; fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { let signed = tx_data; let data = signed.data().ok_or_err_msg("Missing data")?; - let tx_data = transaction::InitAccount::try_from_slice(&data[..]) + let tx_data = transaction::account::InitAccount::try_from_slice(&data[..]) .wrap_err("failed to decode InitAccount")?; debug_log!("apply_tx called to init a new established account"); @@ -18,8 +18,17 @@ fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { .ok_or_err_msg("vp code section must be tagged as extra")? .code .hash(); + let address = ctx.init_account(vp_code)?; - let pk_key = key::pk_key(&address); - ctx.write(&pk_key, &tx_data.public_key)?; + + match account::init_account(ctx, &address, tx_data) { + Ok(address) => { + debug_log!("Created account {}", address.encode(),) + } + Err(err) => { + debug_log!("Account creation failed with: {}", err); + panic!() + } + } Ok(()) } diff --git a/wasm/wasm_source/src/tx_init_validator.rs b/wasm/wasm_source/src/tx_init_validator.rs index 0cd37da111..eb80ec444e 100644 --- a/wasm/wasm_source/src/tx_init_validator.rs +++ b/wasm/wasm_source/src/tx_init_validator.rs @@ -1,7 +1,7 @@ //! A tx to initialize a new validator account with a given public keys and a //! validity predicates. -use namada_tx_prelude::transaction::InitValidator; +use namada_tx_prelude::transaction::pos::InitValidator; use namada_tx_prelude::*; #[transaction] diff --git a/wasm/wasm_source/src/tx_transfer.rs b/wasm/wasm_source/src/tx_transfer.rs index d2e3dc314f..33899640c4 100644 --- a/wasm/wasm_source/src/tx_transfer.rs +++ b/wasm/wasm_source/src/tx_transfer.rs @@ -15,7 +15,6 @@ fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { source, target, token, - sub_prefix, amount, key, shielded: shielded_hash, @@ -34,7 +33,6 @@ fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { &source, &target, &token, - sub_prefix, amount, &key, &shielded_hash, diff --git a/wasm/wasm_source/src/tx_unbond.rs b/wasm/wasm_source/src/tx_unbond.rs index 7fb1de8f4f..e453d48b14 100644 --- a/wasm/wasm_source/src/tx_unbond.rs +++ b/wasm/wasm_source/src/tx_unbond.rs @@ -15,7 +15,7 @@ fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { #[cfg(test)] mod tests { - use std::collections::HashSet; + use std::collections::BTreeSet; use namada::ledger::pos::{GenesisValidator, PosParams, PosVP}; use namada::proof_of_stake::types::WeightedValidator; @@ -109,12 +109,7 @@ mod tests { // bond first. // First, credit the bond's source with the initial stake, // before we initialize the bond below - tx_env.credit_tokens( - source, - &native_token, - None, - initial_stake, - ); + tx_env.credit_tokens(source, &native_token, initial_stake); } native_token }); @@ -164,7 +159,7 @@ mod tests { let mut epoched_total_stake_pre: Vec = Vec::new(); let mut epoched_validator_stake_pre: Vec = Vec::new(); let mut epoched_bonds_pre: Vec> = Vec::new(); - let mut epoched_validator_set_pre: Vec> = + let mut epoched_validator_set_pre: Vec> = Vec::new(); for epoch in 0..=pos_params.unbonding_len { diff --git a/wasm/wasm_source/src/tx_update_account.rs b/wasm/wasm_source/src/tx_update_account.rs new file mode 100644 index 0000000000..c2553759e5 --- /dev/null +++ b/wasm/wasm_source/src/tx_update_account.rs @@ -0,0 +1,45 @@ +//! A tx for updating an account's validity predicate. +//! This tx wraps the validity predicate inside `SignedTxData` as +//! its input as declared in `shared` crate. + +use namada_tx_prelude::key::pks_handle; +use namada_tx_prelude::*; + +#[transaction] +fn apply_tx(ctx: &mut Ctx, tx: Tx) -> TxResult { + let signed = tx; + let data = signed.data().ok_or_err_msg("Missing data")?; + let tx_data = + transaction::account::UpdateAccount::try_from_slice(&data[..]) + .wrap_err("failed to decode UpdateAccount")?; + + let owner = &tx_data.addr; + debug_log!("update VP for: {:#?}", tx_data.addr); + + if let Some(hash) = tx_data.vp_code_hash { + let vp_code_hash = signed + .get_section(&hash) + .ok_or_err_msg("vp code section not found")? + .extra_data_sec() + .ok_or_err_msg("vp code section must be tagged as extra")? + .code + .hash(); + + ctx.update_validity_predicate(owner, vp_code_hash)?; + } + + if let Some(threshold) = tx_data.threshold { + let threshold_key = key::threshold_key(owner); + ctx.write(&threshold_key, threshold)?; + } + + if !tx_data.public_keys.is_empty() { + storage_api::account::clear_public_keys(ctx, owner)?; + for (index, public_key) in tx_data.public_keys.iter().enumerate() { + let index = index as u8; + pks_handle(owner).insert(ctx, index, public_key.clone())?; + } + } + + Ok(()) +} diff --git a/wasm/wasm_source/src/tx_withdraw.rs b/wasm/wasm_source/src/tx_withdraw.rs index 85ab2b3af4..20b202bdb6 100644 --- a/wasm/wasm_source/src/tx_withdraw.rs +++ b/wasm/wasm_source/src/tx_withdraw.rs @@ -113,12 +113,7 @@ mod tests { // bond first. // First, credit the bond's source with the initial stake, // before we initialize the bond below - tx_env.credit_tokens( - source, - &native_token, - None, - initial_stake, - ); + tx_env.credit_tokens(source, &native_token, initial_stake); } native_token }); diff --git a/wasm/wasm_source/src/vp_implicit.rs b/wasm/wasm_source/src/vp_implicit.rs index 729ddb6fbe..3eaa7ad056 100644 --- a/wasm/wasm_source/src/vp_implicit.rs +++ b/wasm/wasm_source/src/vp_implicit.rs @@ -28,14 +28,10 @@ enum KeyType<'a> { impl<'a> From<&'a storage::Key> for KeyType<'a> { fn from(key: &'a storage::Key) -> KeyType<'a> { - if let Some(address) = key::is_pk_key(key) { + if let Some(address) = key::is_pks_key(key) { Self::Pk(address) } else if let Some([_, owner]) = token::is_any_token_balance_key(key) { Self::Token { owner } - } else if let Some((_, [_, owner])) = - token::is_any_multitoken_balance_key(key) - { - Self::Token { owner } } else if proof_of_stake::is_pos_key(key) { Self::PoS } else if gov_storage::is_vote_key(key) { @@ -66,18 +62,8 @@ fn validate_tx( verifiers ); - let valid_sig = Lazy::new(|| { - let pk = key::get(ctx, &addr); - match pk { - Ok(Some(pk)) => tx_data - .verify_signature( - &pk, - &[*tx_data.data_sechash(), *tx_data.code_sechash()], - ) - .is_ok(), - _ => false, - } - }); + let valid_sig = + Lazy::new(|| verify_signatures(ctx, &tx_data, &addr).is_ok()); if !is_valid_tx(ctx, &tx_data)? { return reject(); @@ -200,7 +186,7 @@ fn validate_tx( mod tests { // Use this as `#[test]` annotation to enable logging use namada::ledger::pos::{GenesisValidator, PosParams}; - use namada::proto::{Code, Data, Signature}; + use namada::proto::{Code, Data, MultiSignature}; use namada::types::dec::Dec; use namada::types::storage::Epoch; use namada::types::transaction::TxType; @@ -211,6 +197,7 @@ mod tests { use namada_tests::vp::vp_host_env::storage::Key; use namada_tests::vp::*; use namada_tx_prelude::{storage_api, StorageWrite, TxEnv}; + use namada_vp_prelude::account::AccountPublicKeysMap; use namada_vp_prelude::key::RefTo; use proptest::prelude::*; use storage::testing::arb_account_storage_key_no_vp; @@ -244,7 +231,8 @@ mod tests { let addr: Address = (&public_key).into(); // Initialize a tx environment - let tx_env = TestTxEnv::default(); + let mut tx_env = TestTxEnv::default(); + tx_env.init_parameters(None, None, None, None); // Initialize VP environment from a transaction vp_host_env::init_from_tx(addr.clone(), tx_env, |_address| { @@ -312,8 +300,12 @@ mod tests { // Initialize VP environment from a transaction vp_host_env::init_from_tx(addr.clone(), tx_env, |_address| { // Do the same as reveal_pk, but with the wrong key - let key = namada_tx_prelude::key::pk_key(&addr); - tx_host_env::ctx().write(&key, &mismatched_pk).unwrap(); + let _ = storage_api::account::set_public_key_at( + tx_host_env::ctx(), + &addr, + &mismatched_pk, + 0, + ); }); let vp_env = vp_host_env::take(); @@ -348,14 +340,13 @@ mod tests { storage_api::token::write_denom( &mut tx_env.wl_storage, &token, - None, token::NATIVE_MAX_DECIMAL_PLACES.into(), ) .unwrap(); // Credit the tokens to the source before running the transaction to be // able to transfer from it - tx_env.credit_tokens(&source, &token, None, amount); + tx_env.credit_tokens(&source, &token, amount); let amount = token::DenominatedAmount { amount, @@ -369,7 +360,6 @@ mod tests { &source, address, &token, - None, amount, &None, &None, @@ -420,6 +410,8 @@ mod tests { // Initialize a tx environment let mut tx_env = tx_host_env::take(); + tx_env.init_parameters(None, Some(vec![]), Some(vec![]), None); + let secret_key = key::testing::keypair_1(); let public_key = secret_key.ref_to(); let vp_owner: Address = (&public_key).into(); @@ -431,18 +423,19 @@ mod tests { // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&target, &token]); + tx_env.init_account_storage(&vp_owner, vec![public_key], 1); // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it - tx_env.credit_tokens(&vp_owner, &token, None, amount); + tx_env.credit_tokens(&vp_owner, &token, amount); // write the denomination of NAM into storage storage_api::token::write_denom( &mut tx_env.wl_storage, &token, - None, token::NATIVE_MAX_DECIMAL_PLACES.into(), ) .unwrap(); + // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |_address| { // Bond the tokens, then unbond some of them @@ -467,8 +460,8 @@ mod tests { ); } - /// Test that a PoS action that must be authorized is accepted with a valid - /// signature. + /// Test that a PoS action that must be authorized is accepted with a + /// valid signature. #[test] fn test_signed_pos_action_accepted() { // Init PoS genesis @@ -511,21 +504,19 @@ mod tests { // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&target, &token]); + tx_env.init_account_storage(&vp_owner, vec![public_key.clone()], 1); // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it - tx_env.credit_tokens(&vp_owner, &token, None, amount); + tx_env.credit_tokens(&vp_owner, &token, amount); // write the denomination of NAM into storage storage_api::token::write_denom( &mut tx_env.wl_storage, &token, - None, token::NATIVE_MAX_DECIMAL_PLACES.into(), ) .unwrap(); - tx_env.write_public_key(&vp_owner, &public_key); - // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |_address| { // Bond the tokens, then unbond some of them @@ -537,14 +528,18 @@ mod tests { .unwrap(); }); + let pks_map = AccountPublicKeysMap::from_iter(vec![public_key]); + let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); tx.set_data(Data::new(vec![])); tx.set_code(Code::new(vec![])); - tx.add_section(Section::Signature(Signature::new( + tx.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.data_sechash(), *tx.code_sechash()], - &secret_key, + &[secret_key], + &pks_map, ))); + let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); let keys_changed: BTreeSet = @@ -572,15 +567,15 @@ mod tests { // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner, &target, &token]); + tx_env.init_account_storage(&vp_owner, vec![public_key], 1); // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it - tx_env.credit_tokens(&vp_owner, &token, None, amount); + tx_env.credit_tokens(&vp_owner, &token, amount); // write the denomination of NAM into storage storage_api::token::write_denom( &mut tx_env.wl_storage, &token, - None, token::NATIVE_MAX_DECIMAL_PLACES.into(), ) .unwrap(); @@ -598,7 +593,6 @@ mod tests { address, &target, &token, - None, amount, &None, &None, @@ -633,23 +627,23 @@ mod tests { let token = address::nam(); let amount = token::Amount::from_uint(10_098_123, 0).unwrap(); + tx_env.init_parameters(None, None, None, None); + // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner, &target, &token]); + tx_env.init_account_storage(&vp_owner, vec![public_key.clone()], 1); // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it - tx_env.credit_tokens(&vp_owner, &token, None, amount); + tx_env.credit_tokens(&vp_owner, &token, amount); // write the denomination of NAM into storage storage_api::token::write_denom( &mut tx_env.wl_storage, &token, - None, token::NATIVE_MAX_DECIMAL_PLACES.into(), ) .unwrap(); - tx_env.write_public_key(&vp_owner, &public_key); - let amount = token::DenominatedAmount { amount, denom: token::NATIVE_MAX_DECIMAL_PLACES.into(), @@ -662,7 +656,6 @@ mod tests { address, &target, &token, - None, amount, &None, &None, @@ -671,20 +664,25 @@ mod tests { .unwrap(); }); + let pks_map = AccountPublicKeysMap::from_iter(vec![public_key]); + let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); tx.set_data(Data::new(vec![])); tx.set_code(Code::new(vec![])); - tx.add_section(Section::Signature(Signature::new( + tx.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.data_sechash(), *tx.code_sechash()], - &secret_key, + &[secret_key], + &pks_map, ))); + let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); vp_host_env::set(vp_env); + assert!( validate_tx(&CTX, signed_tx, vp_owner, keys_changed, verifiers) .unwrap() @@ -707,15 +705,15 @@ mod tests { // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner, &source, &target, &token]); + tx_env.init_account_storage(&vp_owner, vec![public_key], 1); // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it - tx_env.credit_tokens(&source, &token, None, amount); + tx_env.credit_tokens(&source, &token, amount); // write the denomination of NAM into storage storage_api::token::write_denom( &mut tx_env.wl_storage, &token, - None, token::NATIVE_MAX_DECIMAL_PLACES.into(), ) .unwrap(); @@ -734,7 +732,6 @@ mod tests { &source, &target, &token, - None, amount, &None, &None, @@ -773,8 +770,8 @@ mod tests { } proptest! { - /// Test that an unsigned tx that performs arbitrary storage writes or - /// deletes to the account is rejected. + /// Test that an unsigned tx that performs arbitrary storage writes + /// or deletes to the account is rejected. #[test] fn test_unsigned_arb_storage_write_rejected( (_sk, vp_owner, storage_key) in arb_account_storage_subspace_key(), @@ -808,17 +805,12 @@ mod tests { vp_host_env::set(vp_env); assert!(!validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers).unwrap()); } - } - proptest! { - /// Test that a signed tx that performs arbitrary storage writes or - /// deletes to the account is accepted. - #[test] - fn test_signed_arb_storage_write( - (secret_key, vp_owner, storage_key) in arb_account_storage_subspace_key(), - // Generate bytes to write. If `None`, delete from the key instead - storage_value in any::>>(), - ) { + fn test_signed_arb_storage_write( + (secret_key, vp_owner, storage_key) in arb_account_storage_subspace_key(), + // Generate bytes to write. If `None`, delete from the key instead + storage_value in any::>>(), + ) { // Initialize a tx environment let mut tx_env = TestTxEnv::default(); @@ -828,7 +820,7 @@ mod tests { tx_env.spawn_accounts(storage_key_addresses); let public_key = secret_key.ref_to(); - tx_env.write_public_key(&vp_owner, &public_key); + let _ = storage_api::account::set_public_key_at(tx_host_env::ctx(), &vp_owner, &public_key, 0); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |_address| { @@ -840,11 +832,17 @@ mod tests { } }); + let pks_map = AccountPublicKeysMap::from_iter(vec![public_key]); + let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); tx.set_data(Data::new(vec![])); tx.set_code(Code::new(vec![])); - tx.add_section(Section::Signature(Signature::new(vec![*tx.data_sechash(), *tx.code_sechash()], &secret_key))); + tx.add_section(Section::SectionSignature(MultiSignature::new( + vec![*tx.data_sechash(), *tx.code_sechash()], + &[secret_key], + &pks_map, + ))); let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); let keys_changed: BTreeSet = @@ -912,12 +910,12 @@ mod tests { None, Some(vec![vp_hash.to_string()]), Some(vec!["some_hash".to_string()]), + None, ); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); - - tx_env.write_public_key(&vp_owner, &public_key); + tx_env.init_account_storage(&vp_owner, vec![public_key.clone()], 1); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -927,13 +925,16 @@ mod tests { .unwrap(); }); + let pks_map = AccountPublicKeysMap::from_iter(vec![public_key]); + let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); tx.set_data(Data::new(vec![])); tx.set_code(Code::new(vec![])); - tx.add_section(Section::Signature(Signature::new( + tx.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.data_sechash(), *tx.code_sechash()], - &secret_key, + &[secret_key], + &pks_map, ))); let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); @@ -960,13 +961,16 @@ mod tests { // for the update tx_env.store_wasm_code(vp_code); - let empty_sha256 = sha256(&[]).to_string(); - tx_env.init_parameters(None, None, Some(vec![empty_sha256])); + tx_env.init_parameters( + None, + Some(vec![vp_hash.to_string()]), + None, + None, + ); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); - - tx_env.write_public_key(&vp_owner, &public_key); + tx_env.init_account_storage(&vp_owner, vec![public_key.clone()], 1); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -976,13 +980,16 @@ mod tests { .unwrap(); }); + let pks_map = AccountPublicKeysMap::from_iter(vec![public_key]); + let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); tx.set_code(Code::new(vec![])); tx.set_data(Data::new(vec![])); - tx.add_section(Section::Signature(Signature::new( + tx.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.data_sechash(), *tx.code_sechash()], - &secret_key, + &[secret_key], + &pks_map, ))); let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); diff --git a/wasm/wasm_source/src/vp_masp.rs b/wasm/wasm_source/src/vp_masp.rs index 39952339ea..1e43d93a25 100644 --- a/wasm/wasm_source/src/vp_masp.rs +++ b/wasm/wasm_source/src/vp_masp.rs @@ -13,11 +13,10 @@ use ripemd::{Digest, Ripemd160}; fn asset_type_from_epoched_address( epoch: Epoch, token: &Address, - sub_prefix: String, denom: token::MaspDenom, ) -> AssetType { // Timestamp the chosen token with the current epoch - let token_bytes = (token, sub_prefix, denom, epoch.0) + let token_bytes = (token, denom, epoch.0) .try_to_vec() .expect("token should serialize"); // Generate the unique asset identifier from the unique token address @@ -63,19 +62,10 @@ fn valid_transfer_amount( fn convert_amount( epoch: Epoch, token: &Address, - sub_prefix: &Option, val: token::Amount, denom: token::MaspDenom, ) -> (AssetType, Amount) { - let asset_type = asset_type_from_epoched_address( - epoch, - token, - sub_prefix - .as_ref() - .map(|k| k.to_string()) - .unwrap_or_default(), - denom, - ); + let asset_type = asset_type_from_epoched_address(epoch, token, denom); // Combine the value and unit into one amount let amount = Amount::from_nonnegative(asset_type, denom.denominate(&val)) .expect("invalid value or asset type for amount"); @@ -127,7 +117,6 @@ fn validate_tx( let (_transp_asset, transp_amt) = convert_amount( ctx.get_block_epoch().unwrap(), &transfer.token, - &transfer.sub_prefix, transfer.amount.into(), denom, ); @@ -191,11 +180,6 @@ fn validate_tx( asset_type_from_epoched_address( ctx.get_block_epoch().unwrap(), &transfer.token, - transfer - .sub_prefix - .as_ref() - .map(|k| k.to_string()) - .unwrap_or_default(), denom, ); @@ -215,7 +199,6 @@ fn validate_tx( let (_transp_asset, transp_amt) = convert_amount( ctx.get_block_epoch().unwrap(), &transfer.token, - &transfer.sub_prefix, transfer.amount.amount, denom, ); diff --git a/wasm/wasm_source/src/vp_testnet_faucet.rs b/wasm/wasm_source/src/vp_testnet_faucet.rs index 8e002663ee..8436bf9707 100644 --- a/wasm/wasm_source/src/vp_testnet_faucet.rs +++ b/wasm/wasm_source/src/vp_testnet_faucet.rs @@ -25,25 +25,15 @@ fn validate_tx( verifiers ); - let valid_sig = Lazy::new(|| { - let pk = key::get(ctx, &addr); - match pk { - Ok(Some(pk)) => tx_data - .verify_signature( - &pk, - &[*tx_data.data_sechash(), *tx_data.code_sechash()], - ) - .is_ok(), - _ => false, - } - }); + let valid_sig = + Lazy::new(|| verify_signatures(ctx, &tx_data, &addr).is_ok()); if !is_valid_tx(ctx, &tx_data)? { return reject(); } for key in keys_changed.iter() { - let is_valid = if let Some([_, owner]) = + let is_valid = if let Some([token, owner]) = token::is_any_token_balance_key(key) { if owner == &addr { @@ -51,7 +41,17 @@ fn validate_tx( let post: token::Amount = ctx.read_post(key)?.unwrap_or_default(); let change = post.change() - pre.change(); - + let maybe_denom = + storage_api::token::read_denom(&ctx.pre(), token)?; + if maybe_denom.is_none() { + debug_log!( + "A denomination for token address {} does not exist \ + in storage", + token, + ); + return reject(); + } + let denom = maybe_denom.unwrap(); if !change.non_negative() { // Allow to withdraw without a sig if there's a valid PoW if ctx.has_valid_pow() { @@ -60,7 +60,10 @@ fn validate_tx( &ctx.pre(), &addr, )?; - change >= -max_free_debit.change() + + token::Amount::from_uint(change.abs(), 0).unwrap() + <= token::Amount::from_uint(max_free_debit, denom) + .unwrap() } else { debug_log!("No PoW solution, a signature is required"); // Debit without a solution has to signed @@ -104,7 +107,7 @@ fn validate_tx( #[cfg(test)] mod tests { use address::testing::arb_non_internal_address; - use namada::proto::{Code, Data, Signature}; + use namada::proto::{Code, Data, MultiSignature, Signature}; use namada::types::transaction::TxType; use namada_test_utils::TestWasms; // Use this as `#[test]` annotation to enable logging @@ -113,6 +116,7 @@ mod tests { use namada_tests::vp::vp_host_env::storage::Key; use namada_tests::vp::*; use namada_tx_prelude::{StorageWrite, TxEnv}; + use namada_vp_prelude::account::AccountPublicKeysMap; use namada_vp_prelude::key::RefTo; use proptest::prelude::*; use storage::testing::arb_account_storage_key_no_vp; @@ -155,7 +159,7 @@ mod tests { // Credit the tokens to the source before running the transaction to be // able to transfer from it - tx_env.credit_tokens(&source, &token, None, amount); + tx_env.credit_tokens(&source, &token, amount); let amount = token::DenominatedAmount { amount, @@ -170,7 +174,6 @@ mod tests { &source, address, &token, - None, amount, &None, &None, @@ -246,8 +249,7 @@ mod tests { // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); - - tx_env.write_public_key(&vp_owner, public_key); + tx_env.init_account_storage(&vp_owner, vec![public_key.clone()], 1); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -257,13 +259,16 @@ mod tests { .unwrap(); }); + let pks_map = AccountPublicKeysMap::from_iter(vec![public_key.clone()]); + let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); tx.set_data(Data::new(vec![])); tx.set_code(Code::new(vec![])); - tx.add_section(Section::Signature(Signature::new( + tx.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.data_sechash(), *tx.code_sechash()], - &keypair, + &[keypair], + &pks_map, ))); let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); @@ -304,7 +309,7 @@ mod tests { let vp_owner = address::testing::established_address_1(); let difficulty = testnet_pow::Difficulty::try_new(0).unwrap(); let withdrawal_limit = token::Amount::from_uint(MAX_FREE_DEBIT as u64, 0).unwrap(); - testnet_pow::init_faucet_storage(&mut tx_env.wl_storage, &vp_owner, difficulty, withdrawal_limit).unwrap(); + testnet_pow::init_faucet_storage(&mut tx_env.wl_storage, &vp_owner, difficulty, withdrawal_limit.into()).unwrap(); let target = address::testing::established_address_2(); let token = address::nam(); @@ -315,7 +320,7 @@ mod tests { // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it - tx_env.credit_tokens(&vp_owner, &token, None, amount); + tx_env.credit_tokens(&vp_owner, &token, amount); tx_env.commit_genesis(); let amount = token::DenominatedAmount { amount, @@ -325,7 +330,7 @@ mod tests { // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Apply transfer in a transaction - tx_host_env::token::transfer(tx::ctx(), address, &target, &token, None, amount, &None, &None, &None).unwrap(); + tx_host_env::token::transfer(tx::ctx(), address, &target, &token, amount, &None, &None, &None).unwrap(); }); let vp_env = vp_host_env::take(); @@ -349,10 +354,11 @@ mod tests { let vp_owner = address::testing::established_address_1(); let difficulty = testnet_pow::Difficulty::try_new(0).unwrap(); let withdrawal_limit = token::Amount::from_uint(MAX_FREE_DEBIT as u64, 0).unwrap(); - testnet_pow::init_faucet_storage(&mut tx_env.wl_storage, &vp_owner, difficulty, withdrawal_limit).unwrap(); + testnet_pow::init_faucet_storage(&mut tx_env.wl_storage, &vp_owner, difficulty, withdrawal_limit.into()).unwrap(); let target = address::testing::established_address_2(); let target_key = key::testing::keypair_1(); + let _public_key = target_key.ref_to(); let token = address::nam(); let amount = token::Amount::from_uint(amount, 0).unwrap(); @@ -361,9 +367,9 @@ mod tests { // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it - tx_env.credit_tokens(&vp_owner, &token, None, amount); + tx_env.credit_tokens(&vp_owner, &token, amount); // write the denomination of NAM into storage - storage_api::token::write_denom(&mut tx_env.wl_storage, &token, None, token::NATIVE_MAX_DECIMAL_PLACES.into()).unwrap(); + storage_api::token::write_denom(&mut tx_env.wl_storage, &token, token::NATIVE_MAX_DECIMAL_PLACES.into()).unwrap(); tx_env.commit_genesis(); // Construct a PoW solution like a client would @@ -383,7 +389,7 @@ mod tests { let valid = solution.validate(tx::ctx(), address, target.clone()).unwrap(); assert!(valid); // Apply transfer in a transaction - tx_host_env::token::transfer(tx::ctx(), address, &target, &token, None, amount, &None, &None, &None).unwrap(); + tx_host_env::token::transfer(tx::ctx(), address, &target, &token, amount, &None, &None, &None).unwrap(); }); let mut vp_env = vp_host_env::take(); @@ -414,7 +420,7 @@ mod tests { // Init the VP let difficulty = testnet_pow::Difficulty::try_new(0).unwrap(); let withdrawal_limit = token::Amount::from_uint(MAX_FREE_DEBIT as u64, 0).unwrap(); - testnet_pow::init_faucet_storage(&mut tx_env.wl_storage, &vp_owner, difficulty, withdrawal_limit).unwrap(); + testnet_pow::init_faucet_storage(&mut tx_env.wl_storage, &vp_owner, difficulty, withdrawal_limit.into()).unwrap(); let keypair = key::testing::keypair_1(); let public_key = &keypair.ref_to(); @@ -424,7 +430,7 @@ mod tests { let storage_key_addresses = storage_key.find_addresses(); tx_env.spawn_accounts(storage_key_addresses); - tx_env.write_public_key(&vp_owner, public_key); + tx_env.init_account_storage(&vp_owner, vec![public_key.clone()], 1); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |_address| { @@ -436,11 +442,17 @@ mod tests { } }); + let pks_map = AccountPublicKeysMap::from_iter(vec![public_key.clone()]); + let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); tx.set_data(Data::new(vec![])); tx.set_code(Code::new(vec![])); - tx.add_section(Section::Signature(Signature::new(vec![*tx.data_sechash(), *tx.code_sechash()], &keypair))); + tx.add_section(Section::SectionSignature(MultiSignature::new( + vec![*tx.data_sechash(), *tx.code_sechash()], + &[keypair], + &pks_map, + ))); let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); let keys_changed: BTreeSet = diff --git a/wasm/wasm_source/src/vp_token.rs b/wasm/wasm_source/src/vp_token.rs deleted file mode 100644 index 29b639bd56..0000000000 --- a/wasm/wasm_source/src/vp_token.rs +++ /dev/null @@ -1,290 +0,0 @@ -//! A VP for a fungible token. Enforces that the total supply is unchanged in a -//! transaction that moves balance(s). - -use std::collections::BTreeSet; - -use namada_vp_prelude::address::{self, Address, InternalAddress}; -use namada_vp_prelude::storage::KeySeg; -use namada_vp_prelude::{storage, token, *}; - -#[validity_predicate] -fn validate_tx( - ctx: &Ctx, - tx_data: Tx, - addr: Address, - keys_changed: BTreeSet, - verifiers: BTreeSet
, -) -> VpResult { - debug_log!( - "validate_tx called with token addr: {}, key_changed: {:?}, \ - verifiers: {:?}", - addr, - keys_changed, - verifiers - ); - - if !is_valid_tx(ctx, &tx_data)? { - return reject(); - } - - for key in keys_changed.iter() { - if key.is_validity_predicate().is_some() { - let vp_hash: Vec = ctx.read_bytes_post(key)?.unwrap(); - if !is_vp_whitelisted(ctx, &vp_hash)? { - return reject(); - } - } - } - - token_checks(ctx, &addr, &keys_changed, &verifiers) -} - -/// A token validity predicate checks that the total supply is preserved. -/// This implies that: -/// -/// - The value associated with the `total_supply` storage key may not change. -/// - For any balance changes, the total of outputs must be equal to the total -/// of inputs. -fn token_checks( - ctx: &Ctx, - token: &Address, - keys_touched: &BTreeSet, - verifiers: &BTreeSet
, -) -> VpResult { - let mut change = token::Change::default(); - for key in keys_touched.iter() { - let owner: Option<&Address> = token::is_balance_key(token, key) - .or_else(|| { - token::is_multitoken_balance_key(token, key).map(|a| a.1) - }); - - match owner { - None => { - if token::is_total_supply_key(key, token) { - // check if total supply is changed, which it should never - // be from a tx - let total_pre: token::Amount = ctx.read_pre(key)?.unwrap(); - let total_post: token::Amount = - ctx.read_post(key)?.unwrap(); - if total_pre != total_post { - return reject(); - } - } else if key.segments.get(0) == Some(&token.to_db_key()) { - // Unknown changes to this address space are disallowed, but - // unknown changes anywhere else are permitted - return reject(); - } - } - Some(owner) => { - // accumulate the change - let pre: token::Change = match owner { - Address::Internal(InternalAddress::IbcMint) => { - token::Change::maximum() - } - Address::Internal(InternalAddress::IbcBurn) => { - token::Change::default() - } - _ => ctx - .read_pre::(key)? - .unwrap_or_default() - .change(), - }; - let post: token::Change = match owner { - Address::Internal(InternalAddress::IbcMint) => ctx - .read_temp::(key)? - .map(|x| x.change()) - .unwrap_or_else(token::Change::maximum), - Address::Internal(InternalAddress::IbcBurn) => ctx - .read_temp::(key)? - .unwrap_or_default() - .change(), - _ => ctx - .read_post::(key)? - .unwrap_or_default() - .change(), - }; - let this_change = post - pre; - change += this_change; - // make sure that the spender approved the transaction - if !(this_change.non_negative() - || verifiers.contains(owner) - || *owner == address::masp()) - { - return reject(); - } - } - } - } - Ok(change.is_zero()) -} - -#[cfg(test)] -mod tests { - // Use this as `#[test]` annotation to enable logging - use namada::core::ledger::storage_api::token; - use namada::proto::Data; - use namada::types::transaction::TxType; - use namada_tests::log::test; - use namada_tests::tx::{self, TestTxEnv}; - use namada_tests::vp::*; - use namada_vp_prelude::storage_api::StorageWrite; - - use super::*; - - #[test] - fn test_transfer_inputs_eq_outputs_is_accepted() { - // Initialize a tx environment - let mut tx_env = TestTxEnv::default(); - let token = address::nam(); - let src = address::testing::established_address_1(); - let dest = address::testing::established_address_2(); - let total_supply = - token::Amount::from_uint(10_098_123, 0).expect("Test failed"); - - // Spawn the accounts to be able to modify their storage - tx_env.spawn_accounts([&token, &src, &dest]); - token::credit_tokens( - &mut tx_env.wl_storage, - &token, - &src, - total_supply, - ) - .unwrap(); - // Commit the initial state - tx_env.commit_tx_and_block(); - - // Initialize VP environment from a transaction - vp_host_env::init_from_tx(token.clone(), tx_env, |_address| { - // Apply a transfer - - let amount = token::Amount::from_uint(100, 0).expect("Test failed"); - token::transfer(tx::ctx(), &token, &src, &dest, amount).unwrap(); - }); - - let vp_env = vp_host_env::take(); - let mut tx_data = Tx::new(TxType::Raw); - tx_data.set_data(Data::new(vec![])); - let keys_changed: BTreeSet = - vp_env.all_touched_storage_keys(); - let verifiers = vp_env.get_verifiers(); - vp_host_env::set(vp_env); - - assert!( - validate_tx(&CTX, tx_data, token, keys_changed, verifiers).unwrap(), - "A transfer where inputs == outputs should be accepted" - ); - } - - #[test] - fn test_transfer_inputs_neq_outputs_is_rejected() { - // Initialize a tx environment - let mut tx_env = TestTxEnv::default(); - let token = address::nam(); - let src = address::testing::established_address_1(); - let dest = address::testing::established_address_2(); - let total_supply = - token::Amount::from_uint(10_098_123, 0).expect("Test failed"); - - // Spawn the accounts to be able to modify their storage - tx_env.spawn_accounts([&token, &src, &dest]); - token::credit_tokens( - &mut tx_env.wl_storage, - &token, - &src, - total_supply, - ) - .unwrap(); - // Commit the initial state - tx_env.commit_tx_and_block(); - - // Initialize VP environment from a transaction - vp_host_env::init_from_tx(token.clone(), tx_env, |_address| { - // Apply a transfer - - let amount_in = - token::Amount::from_uint(100, 0).expect("Test failed"); - let amount_out = - token::Amount::from_uint(900, 0).expect("Test failed"); - - let src_key = token::balance_key(&token, &src); - let src_balance = - token::read_balance(tx::ctx(), &token, &src).unwrap(); - let new_src_balance = src_balance + amount_out; - let dest_key = token::balance_key(&token, &dest); - let dest_balance = - token::read_balance(tx::ctx(), &token, &dest).unwrap(); - let new_dest_balance = dest_balance + amount_in; - tx::ctx().write(&src_key, new_src_balance).unwrap(); - tx::ctx().write(&dest_key, new_dest_balance).unwrap(); - }); - - let vp_env = vp_host_env::take(); - let mut tx_data = Tx::new(TxType::Raw); - tx_data.set_data(Data::new(vec![])); - let keys_changed: BTreeSet = - vp_env.all_touched_storage_keys(); - let verifiers = vp_env.get_verifiers(); - vp_host_env::set(vp_env); - - assert!( - !validate_tx(&CTX, tx_data, token, keys_changed, verifiers) - .unwrap(), - "A transfer where inputs != outputs should be rejected" - ); - } - - #[test] - fn test_total_supply_change_is_rejected() { - // Initialize a tx environment - let mut tx_env = TestTxEnv::default(); - let token = address::nam(); - let owner = address::testing::established_address_1(); - let total_supply = - token::Amount::from_uint(10_098_123, 0).expect("Test failed"); - - // Spawn the accounts to be able to modify their storage - tx_env.spawn_accounts([&token, &owner]); - token::credit_tokens( - &mut tx_env.wl_storage, - &token, - &owner, - total_supply, - ) - .unwrap(); - // Commit the initial state - tx_env.commit_tx_and_block(); - - let total_supply_key = token::total_supply_key(&token); - - // Initialize VP environment from a transaction - vp_host_env::init_from_tx(token.clone(), tx_env, |_address| { - // Try to change total supply from a tx - - let current_supply = tx::ctx() - .read::(&total_supply_key) - .unwrap() - .unwrap_or_default(); - tx::ctx() - .write( - &total_supply_key, - current_supply - + token::Amount::from_uint(1, 0).expect("Test failed"), - ) - .unwrap(); - }); - - let vp_env = vp_host_env::take(); - let mut tx_data = Tx::new(TxType::Raw); - tx_data.set_data(Data::new(vec![])); - let keys_changed: BTreeSet = - vp_env.all_touched_storage_keys(); - let verifiers = vp_env.get_verifiers(); - vp_host_env::set(vp_env); - - assert!( - !validate_tx(&CTX, tx_data, token, keys_changed, verifiers) - .unwrap(), - "Change of a `total_supply` value should be rejected" - ); - } -} diff --git a/wasm/wasm_source/src/vp_user.rs b/wasm/wasm_source/src/vp_user.rs index b32d801ef2..895c845336 100644 --- a/wasm/wasm_source/src/vp_user.rs +++ b/wasm/wasm_source/src/vp_user.rs @@ -26,10 +26,6 @@ impl<'a> From<&'a storage::Key> for KeyType<'a> { fn from(key: &'a storage::Key) -> KeyType<'a> { if let Some([_, owner]) = token::is_any_token_balance_key(key) { Self::Token { owner } - } else if let Some((_, [_, owner])) = - token::is_any_multitoken_balance_key(key) - { - Self::Token { owner } } else if proof_of_stake::is_pos_key(key) { Self::PoS } else if gov_storage::is_vote_key(key) { @@ -64,18 +60,8 @@ fn validate_tx( verifiers ); - let valid_sig = Lazy::new(|| { - let pk = key::get(ctx, &addr); - match pk { - Ok(Some(pk)) => tx_data - .verify_signature( - &pk, - &[*tx_data.data_sechash(), *tx_data.code_sechash()], - ) - .is_ok(), - _ => false, - } - }); + let valid_sig = + Lazy::new(|| verify_signatures(ctx, &tx_data, &addr).is_ok()); if !is_valid_tx(ctx, &tx_data)? { return reject(); @@ -188,7 +174,7 @@ fn validate_tx( mod tests { use address::testing::arb_non_internal_address; use namada::ledger::pos::{GenesisValidator, PosParams}; - use namada::proto::{Code, Data, Signature}; + use namada::proto::{Code, Data, MultiSignature}; use namada::types::dec::Dec; use namada::types::storage::Epoch; use namada::types::transaction::TxType; @@ -200,6 +186,7 @@ mod tests { use namada_tests::vp::vp_host_env::storage::Key; use namada_tests::vp::*; use namada_tx_prelude::{StorageWrite, TxEnv}; + use namada_vp_prelude::account::AccountPublicKeysMap; use namada_vp_prelude::key::RefTo; use proptest::prelude::*; use storage::testing::arb_account_storage_key_no_vp; @@ -239,12 +226,11 @@ mod tests { // Credit the tokens to the source before running the transaction to be // able to transfer from it - tx_env.credit_tokens(&source, &token, None, amount); + tx_env.credit_tokens(&source, &token, amount); // write the denomination of NAM into storage storage_api::token::write_denom( &mut tx_env.wl_storage, &token, - None, token::NATIVE_MAX_DECIMAL_PLACES.into(), ) .unwrap(); @@ -261,7 +247,6 @@ mod tests { &source, address, &token, - None, amount, &None, &None, @@ -300,14 +285,13 @@ mod tests { storage_api::token::write_denom( &mut tx_env.wl_storage, &token, - None, token::NATIVE_MAX_DECIMAL_PLACES.into(), ) .unwrap(); // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it - tx_env.credit_tokens(&vp_owner, &token, None, amount); + tx_env.credit_tokens(&vp_owner, &token, amount); let amount = token::DenominatedAmount { amount, @@ -321,7 +305,6 @@ mod tests { address, &target, &token, - None, amount, &None, &None, @@ -358,21 +341,19 @@ mod tests { // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner, &target, &token]); + tx_env.init_account_storage(&vp_owner, vec![public_key.clone()], 1); // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it - tx_env.credit_tokens(&vp_owner, &token, None, amount); + tx_env.credit_tokens(&vp_owner, &token, amount); // write the denomination of NAM into storage storage_api::token::write_denom( &mut tx_env.wl_storage, &token, - None, token::NATIVE_MAX_DECIMAL_PLACES.into(), ) .unwrap(); - tx_env.write_public_key(&vp_owner, &public_key); - let amount = token::DenominatedAmount { amount, denom: token::NATIVE_MAX_DECIMAL_PLACES.into(), @@ -386,7 +367,6 @@ mod tests { address, &target, &token, - None, amount, &None, &None, @@ -395,13 +375,16 @@ mod tests { .unwrap(); }); + let pks_map = AccountPublicKeysMap::from_iter(vec![public_key]); + let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); tx.set_data(Data::new(vec![])); tx.set_code(Code::new(vec![])); - tx.add_section(Section::Signature(Signature::new( + tx.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.data_sechash(), *tx.code_sechash()], - &keypair, + &[keypair], + &pks_map, ))); let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); @@ -445,7 +428,7 @@ mod tests { let mut tx_env = tx_host_env::take(); let secret_key = key::testing::keypair_1(); - let _public_key = secret_key.ref_to(); + let public_key = secret_key.ref_to(); let vp_owner: Address = address::testing::established_address_2(); let target = address::testing::established_address_3(); let token = address::nam(); @@ -455,18 +438,18 @@ mod tests { // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&target, &token]); + tx_env.init_account_storage(&vp_owner, vec![public_key], 1); // write the denomination of NAM into storage storage_api::token::write_denom( &mut tx_env.wl_storage, &token, - None, token::NATIVE_MAX_DECIMAL_PLACES.into(), ) .unwrap(); // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it - tx_env.credit_tokens(&vp_owner, &token, None, amount); + tx_env.credit_tokens(&vp_owner, &token, amount); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |_address| { @@ -536,20 +519,19 @@ mod tests { // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&target, &token]); + tx_env.init_account_storage(&vp_owner, vec![public_key.clone()], 1); + // write the denomination of NAM into storage storage_api::token::write_denom( &mut tx_env.wl_storage, &token, - None, token::NATIVE_MAX_DECIMAL_PLACES.into(), ) .unwrap(); // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it - tx_env.credit_tokens(&vp_owner, &token, None, amount); - - tx_env.write_public_key(&vp_owner, &public_key); + tx_env.credit_tokens(&vp_owner, &token, amount); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |_address| { @@ -562,13 +544,16 @@ mod tests { .unwrap(); }); + let pks_map = AccountPublicKeysMap::from_iter(vec![public_key]); + let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); tx.set_data(Data::new(vec![])); tx.set_code(Code::new(vec![])); - tx.add_section(Section::Signature(Signature::new( + tx.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.data_sechash(), *tx.code_sechash()], - &secret_key, + &[secret_key], + &pks_map, ))); let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); @@ -599,7 +584,7 @@ mod tests { // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it - tx_env.credit_tokens(&source, &token, None, amount); + tx_env.credit_tokens(&source, &token, amount); let amount = token::DenominatedAmount { amount, @@ -615,7 +600,6 @@ mod tests { &source, &target, &token, - None, amount, &None, &None, @@ -710,8 +694,7 @@ mod tests { // their storage let storage_key_addresses = storage_key.find_addresses(); tx_env.spawn_accounts(storage_key_addresses); - - tx_env.write_public_key(&vp_owner, &public_key); + tx_env.init_account_storage(&vp_owner, vec![public_key.clone()], 1); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |_address| { @@ -723,11 +706,13 @@ mod tests { } }); + let pks_map = AccountPublicKeysMap::from_iter(vec![public_key]); + let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); tx.set_code(Code::new(vec![])); tx.set_data(Data::new(vec![])); - tx.add_section(Section::Signature(Signature::new(vec![*tx.data_sechash(), *tx.code_sechash()], &keypair))); + tx.add_section(Section::SectionSignature(MultiSignature::new(vec![*tx.data_sechash(), *tx.code_sechash()], &[keypair], &pks_map))); let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); let keys_changed: BTreeSet = @@ -782,7 +767,7 @@ mod tests { fn test_signed_vp_update_accepted() { // Initialize a tx environment let mut tx_env = TestTxEnv::default(); - tx_env.init_parameters(None, None, None); + tx_env.init_parameters(None, None, None, None); let vp_owner = address::testing::established_address_1(); let keypair = key::testing::keypair_1(); @@ -794,8 +779,7 @@ mod tests { // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); - - tx_env.write_public_key(&vp_owner, &public_key); + tx_env.init_account_storage(&vp_owner, vec![public_key.clone()], 1); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -805,13 +789,16 @@ mod tests { .unwrap(); }); + let pks_map = AccountPublicKeysMap::from_iter(vec![public_key]); + let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); tx.set_data(Data::new(vec![])); tx.set_code(Code::new(vec![])); - tx.add_section(Section::Signature(Signature::new( + tx.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.data_sechash(), *tx.code_sechash()], - &keypair, + &[keypair], + &pks_map, ))); let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); @@ -830,7 +817,12 @@ mod tests { fn test_signed_vp_update_not_whitelisted_rejected() { // Initialize a tx environment let mut tx_env = TestTxEnv::default(); - tx_env.init_parameters(None, Some(vec!["some_hash".to_string()]), None); + tx_env.init_parameters( + None, + Some(vec!["some_hash".to_string()]), + None, + None, + ); let vp_owner = address::testing::established_address_1(); let keypair = key::testing::keypair_1(); @@ -842,8 +834,7 @@ mod tests { // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); - - tx_env.write_public_key(&vp_owner, &public_key); + tx_env.init_account_storage(&vp_owner, vec![public_key.clone()], 1); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -853,13 +844,16 @@ mod tests { .unwrap(); }); + let pks_map = AccountPublicKeysMap::from_iter(vec![public_key]); + let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); tx.set_data(Data::new(vec![])); tx.set_code(Code::new(vec![])); - tx.add_section(Section::Signature(Signature::new( + tx.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.data_sechash(), *tx.code_sechash()], - &keypair, + &[keypair], + &pks_map, ))); let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); @@ -887,12 +881,16 @@ mod tests { // for the update tx_env.store_wasm_code(vp_code); - tx_env.init_parameters(None, Some(vec![vp_hash.to_string()]), None); + tx_env.init_parameters( + None, + Some(vec![vp_hash.to_string()]), + None, + None, + ); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); - - tx_env.write_public_key(&vp_owner, &public_key); + tx_env.init_account_storage(&vp_owner, vec![public_key.clone()], 1); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -902,13 +900,16 @@ mod tests { .unwrap(); }); + let pks_map = AccountPublicKeysMap::from_iter(vec![public_key]); + let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); tx.set_data(Data::new(vec![])); tx.set_code(Code::new(vec![])); - tx.add_section(Section::Signature(Signature::new( + tx.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.data_sechash(), *tx.code_sechash()], - &keypair, + &[keypair], + &pks_map, ))); let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); @@ -940,12 +941,12 @@ mod tests { None, Some(vec![vp_hash.to_string()]), Some(vec!["some_hash".to_string()]), + None, ); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); - - tx_env.write_public_key(&vp_owner, &public_key); + tx_env.init_account_storage(&vp_owner, vec![public_key.clone()], 1); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -955,13 +956,16 @@ mod tests { .unwrap(); }); + let pks_map = AccountPublicKeysMap::from_iter(vec![public_key]); + let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); tx.set_data(Data::new(vec![])); tx.set_code(Code::new(vec![])); - tx.add_section(Section::Signature(Signature::new( + tx.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.data_sechash(), *tx.code_sechash()], - &keypair, + &[keypair], + &pks_map, ))); let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); @@ -988,13 +992,17 @@ mod tests { // for the update tx_env.store_wasm_code(vp_code); - let empty_sha256 = sha256(&[]).to_string(); - tx_env.init_parameters(None, None, Some(vec![empty_sha256])); + // hardcoded hash of VP_ALWAYS_TRUE_WASM + tx_env.init_parameters( + None, + Some(vec![vp_hash.to_string()]), + None, + None, + ); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); - - tx_env.write_public_key(&vp_owner, &public_key); + tx_env.init_account_storage(&vp_owner, vec![public_key.clone()], 1); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -1004,13 +1012,16 @@ mod tests { .unwrap(); }); + let pks_map = AccountPublicKeysMap::from_iter(vec![public_key]); + let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); tx.set_code(Code::new(vec![])); tx.set_data(Data::new(vec![])); - tx.add_section(Section::Signature(Signature::new( + tx.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.data_sechash(), *tx.code_sechash()], - &keypair, + &[keypair], + &pks_map, ))); let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); diff --git a/wasm/wasm_source/src/vp_validator.rs b/wasm/wasm_source/src/vp_validator.rs index 9b10999d89..7d16d84380 100644 --- a/wasm/wasm_source/src/vp_validator.rs +++ b/wasm/wasm_source/src/vp_validator.rs @@ -28,10 +28,6 @@ impl<'a> From<&'a storage::Key> for KeyType<'a> { fn from(key: &'a storage::Key) -> KeyType<'a> { if let Some([_, owner]) = token::is_any_token_balance_key(key) { Self::Token { owner } - } else if let Some((_, [_, owner])) = - token::is_any_multitoken_balance_key(key) - { - Self::Token { owner } } else if proof_of_stake::is_pos_key(key) { Self::PoS } else if gov_storage::is_vote_key(key) { @@ -64,18 +60,8 @@ fn validate_tx( verifiers ); - let valid_sig = Lazy::new(|| { - let pk = key::get(ctx, &addr); - match pk { - Ok(Some(pk)) => tx_data - .verify_signature( - &pk, - &[*tx_data.data_sechash(), *tx_data.code_sechash()], - ) - .is_ok(), - _ => false, - } - }); + let valid_sig = + Lazy::new(|| verify_signatures(ctx, &tx_data, &addr).is_ok()); if !is_valid_tx(ctx, &tx_data)? { return reject(); @@ -195,7 +181,7 @@ fn validate_tx( mod tests { use address::testing::arb_non_internal_address; use namada::ledger::pos::{GenesisValidator, PosParams}; - use namada::proto::{Code, Data, Signature}; + use namada::proto::{Code, Data, MultiSignature}; use namada::types::dec::Dec; use namada::types::storage::Epoch; use namada::types::transaction::TxType; @@ -207,6 +193,7 @@ mod tests { use namada_tests::vp::vp_host_env::storage::Key; use namada_tests::vp::*; use namada_tx_prelude::{StorageWrite, TxEnv}; + use namada_vp_prelude::account::AccountPublicKeysMap; use namada_vp_prelude::key::RefTo; use proptest::prelude::*; use storage::testing::arb_account_storage_key_no_vp; @@ -246,12 +233,11 @@ mod tests { // Credit the tokens to the source before running the transaction to be // able to transfer from it - tx_env.credit_tokens(&source, &token, None, amount); + tx_env.credit_tokens(&source, &token, amount); // write the denomination of NAM into storage storage_api::token::write_denom( &mut tx_env.wl_storage, &token, - None, token::NATIVE_MAX_DECIMAL_PLACES.into(), ) .unwrap(); @@ -268,7 +254,6 @@ mod tests { &source, address, &token, - None, amount, &None, &None, @@ -306,7 +291,7 @@ mod tests { // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it - tx_env.credit_tokens(&vp_owner, &token, None, amount); + tx_env.credit_tokens(&vp_owner, &token, amount); let amount = token::DenominatedAmount { amount, denom: token::NATIVE_MAX_DECIMAL_PLACES.into(), @@ -315,7 +300,6 @@ mod tests { storage_api::token::write_denom( &mut tx_env.wl_storage, &token, - None, token::NATIVE_MAX_DECIMAL_PLACES.into(), ) .unwrap(); @@ -328,7 +312,6 @@ mod tests { address, &target, &token, - None, amount, &None, &None, @@ -365,20 +348,19 @@ mod tests { // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner, &target, &token]); + tx_env.init_account_storage(&vp_owner, vec![public_key.clone()], 1); // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it - tx_env.credit_tokens(&vp_owner, &token, None, amount); + tx_env.credit_tokens(&vp_owner, &token, amount); // write the denomination of NAM into storage storage_api::token::write_denom( &mut tx_env.wl_storage, &token, - None, token::NATIVE_MAX_DECIMAL_PLACES.into(), ) .unwrap(); - tx_env.write_public_key(&vp_owner, &public_key); let amount = token::DenominatedAmount { amount, denom: token::NATIVE_MAX_DECIMAL_PLACES.into(), @@ -392,7 +374,6 @@ mod tests { address, &target, &token, - None, amount, &None, &None, @@ -401,13 +382,16 @@ mod tests { .unwrap(); }); + let pks_map = AccountPublicKeysMap::from_iter(vec![public_key]); + let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); tx.set_data(Data::new(vec![])); tx.set_code(Code::new(vec![])); - tx.add_section(Section::Signature(Signature::new( + tx.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.data_sechash(), *tx.code_sechash()], - &keypair, + &[keypair], + &pks_map, ))); let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); @@ -451,7 +435,7 @@ mod tests { let mut tx_env = tx_host_env::take(); let secret_key = key::testing::keypair_1(); - let _public_key = secret_key.ref_to(); + let public_key = secret_key.ref_to(); let vp_owner: Address = address::testing::established_address_2(); let target = address::testing::established_address_3(); let token = address::nam(); @@ -461,15 +445,15 @@ mod tests { // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&target, &token]); + tx_env.init_account_storage(&vp_owner, vec![public_key], 1); // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it - tx_env.credit_tokens(&vp_owner, &token, None, amount); + tx_env.credit_tokens(&vp_owner, &token, amount); // write the denomination of NAM into storage storage_api::token::write_denom( &mut tx_env.wl_storage, &token, - None, token::NATIVE_MAX_DECIMAL_PLACES.into(), ) .unwrap(); @@ -547,22 +531,20 @@ mod tests { let unbond_amount = token::Amount::from_uint(3_098_123, 0).unwrap(); // Spawn the accounts to be able to modify their storage - tx_env.spawn_accounts([&target, &token]); + tx_env.spawn_accounts([&vp_owner, &target, &token]); + tx_env.init_account_storage(&vp_owner, vec![public_key.clone()], 1); // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it - tx_env.credit_tokens(&vp_owner, &token, None, amount); + tx_env.credit_tokens(&vp_owner, &token, amount); // write the denomination of NAM into storage storage_api::token::write_denom( &mut tx_env.wl_storage, &token, - None, token::NATIVE_MAX_DECIMAL_PLACES.into(), ) .unwrap(); - tx_env.write_public_key(&vp_owner, &public_key); - // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |_address| { // Bond the tokens, then unbond some of them @@ -580,13 +562,16 @@ mod tests { .unwrap(); }); + let pks_map = AccountPublicKeysMap::from_iter(vec![public_key]); + let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); tx.set_data(Data::new(vec![])); tx.set_code(Code::new(vec![])); - tx.add_section(Section::Signature(Signature::new( + tx.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.data_sechash(), *tx.code_sechash()], - &secret_key, + &[secret_key], + &pks_map, ))); let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); @@ -617,7 +602,7 @@ mod tests { // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it - tx_env.credit_tokens(&source, &token, None, amount); + tx_env.credit_tokens(&source, &token, amount); let amount = token::DenominatedAmount { amount, denom: token::NATIVE_MAX_DECIMAL_PLACES.into(), @@ -632,7 +617,6 @@ mod tests { &source, &target, &token, - None, amount, &None, &None, @@ -728,7 +712,7 @@ mod tests { let storage_key_addresses = storage_key.find_addresses(); tx_env.spawn_accounts(storage_key_addresses); - tx_env.write_public_key(&vp_owner, &public_key); + tx_env.init_account_storage(&vp_owner, vec![public_key.clone()], 1); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |_address| { @@ -740,11 +724,13 @@ mod tests { } }); + let pks_map = AccountPublicKeysMap::from_iter(vec![public_key]); + let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); tx.set_data(Data::new(vec![])); tx.set_code(Code::new(vec![])); - tx.add_section(Section::Signature(Signature::new(vec![*tx.data_sechash(), *tx.code_sechash()], &keypair))); + tx.add_section(Section::SectionSignature(MultiSignature::new(vec![*tx.data_sechash(), *tx.code_sechash()], &[keypair], &pks_map))); let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); let keys_changed: BTreeSet = @@ -798,7 +784,7 @@ mod tests { fn test_signed_vp_update_accepted() { // Initialize a tx environment let mut tx_env = TestTxEnv::default(); - tx_env.init_parameters(None, None, None); + tx_env.init_parameters(None, None, None, None); let vp_owner = address::testing::established_address_1(); let keypair = key::testing::keypair_1(); @@ -810,8 +796,7 @@ mod tests { // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); - - tx_env.write_public_key(&vp_owner, &public_key); + tx_env.init_account_storage(&vp_owner, vec![public_key.clone()], 1); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -821,13 +806,16 @@ mod tests { .unwrap(); }); + let pks_map = AccountPublicKeysMap::from_iter(vec![public_key]); + let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); tx.set_data(Data::new(vec![])); tx.set_code(Code::new(vec![])); - tx.add_section(Section::Signature(Signature::new( + tx.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.data_sechash(), *tx.code_sechash()], - &keypair, + &[keypair], + &pks_map, ))); let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); @@ -846,7 +834,12 @@ mod tests { fn test_signed_vp_update_not_whitelisted_rejected() { // Initialize a tx environment let mut tx_env = TestTxEnv::default(); - tx_env.init_parameters(None, Some(vec!["some_hash".to_string()]), None); + tx_env.init_parameters( + None, + Some(vec!["some_hash".to_string()]), + None, + None, + ); let vp_owner = address::testing::established_address_1(); let keypair = key::testing::keypair_1(); @@ -858,8 +851,7 @@ mod tests { // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); - - tx_env.write_public_key(&vp_owner, &public_key); + tx_env.init_account_storage(&vp_owner, vec![public_key.clone()], 1); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -869,13 +861,16 @@ mod tests { .unwrap(); }); + let pks_map = AccountPublicKeysMap::from_iter(vec![public_key]); + let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); tx.set_data(Data::new(vec![])); tx.set_code(Code::new(vec![])); - tx.add_section(Section::Signature(Signature::new( + tx.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.data_sechash(), *tx.code_sechash()], - &keypair, + &[keypair], + &pks_map, ))); let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); @@ -903,12 +898,16 @@ mod tests { // for the update tx_env.store_wasm_code(vp_code); - tx_env.init_parameters(None, Some(vec![vp_hash.to_string()]), None); + tx_env.init_parameters( + None, + Some(vec![vp_hash.to_string()]), + None, + None, + ); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); - - tx_env.write_public_key(&vp_owner, &public_key); + tx_env.init_account_storage(&vp_owner, vec![public_key.clone()], 1); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -918,13 +917,16 @@ mod tests { .unwrap(); }); + let pks_map = AccountPublicKeysMap::from_iter(vec![public_key]); + let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); tx.set_data(Data::new(vec![])); tx.set_code(Code::new(vec![])); - tx.add_section(Section::Signature(Signature::new( + tx.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.data_sechash(), *tx.code_sechash()], - &keypair, + &[keypair], + &pks_map, ))); let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); @@ -956,12 +958,12 @@ mod tests { None, Some(vec![vp_hash.to_string()]), Some(vec!["some_hash".to_string()]), + None, ); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); - - tx_env.write_public_key(&vp_owner, &public_key); + tx_env.init_account_storage(&vp_owner, vec![public_key.clone()], 1); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -971,13 +973,16 @@ mod tests { .unwrap(); }); + let pks_map = AccountPublicKeysMap::from_iter(vec![public_key]); + let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); tx.set_data(Data::new(vec![])); tx.set_code(Code::new(vec![])); - tx.add_section(Section::Signature(Signature::new( + tx.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.data_sechash(), *tx.code_sechash()], - &keypair, + &[keypair], + &pks_map, ))); let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); @@ -1004,13 +1009,17 @@ mod tests { // for the update tx_env.store_wasm_code(vp_code); - let empty_sha256 = sha256(&[]).to_string(); - tx_env.init_parameters(None, None, Some(vec![empty_sha256])); + // hardcoded hash of VP_ALWAYS_TRUE_WASM + tx_env.init_parameters( + None, + Some(vec![vp_hash.to_string()]), + None, + None, + ); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); - - tx_env.write_public_key(&vp_owner, &public_key); + tx_env.init_account_storage(&vp_owner, vec![public_key.clone()], 1); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -1020,13 +1029,16 @@ mod tests { .unwrap(); }); + let pks_map = AccountPublicKeysMap::from_iter(vec![public_key]); + let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); tx.set_code(Code::new(vec![])); tx.set_data(Data::new(vec![])); - tx.add_section(Section::Signature(Signature::new( + tx.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.data_sechash(), *tx.code_sechash()], - &keypair, + &[keypair], + &pks_map, ))); let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); diff --git a/wasm_for_tests/tx_memory_limit.wasm b/wasm_for_tests/tx_memory_limit.wasm index 2544e62548..67bd369c09 100755 Binary files a/wasm_for_tests/tx_memory_limit.wasm and b/wasm_for_tests/tx_memory_limit.wasm differ diff --git a/wasm_for_tests/tx_mint_tokens.wasm b/wasm_for_tests/tx_mint_tokens.wasm index f3932d367c..9b2329f215 100755 Binary files a/wasm_for_tests/tx_mint_tokens.wasm and b/wasm_for_tests/tx_mint_tokens.wasm differ diff --git a/wasm_for_tests/tx_no_op.wasm b/wasm_for_tests/tx_no_op.wasm index 283ed205f5..a471438144 100755 Binary files a/wasm_for_tests/tx_no_op.wasm and b/wasm_for_tests/tx_no_op.wasm differ diff --git a/wasm_for_tests/tx_proposal_code.wasm b/wasm_for_tests/tx_proposal_code.wasm index 7a285d2a1c..3f6190c752 100755 Binary files a/wasm_for_tests/tx_proposal_code.wasm and b/wasm_for_tests/tx_proposal_code.wasm differ diff --git a/wasm_for_tests/tx_read_storage_key.wasm b/wasm_for_tests/tx_read_storage_key.wasm index ad58475987..d34053bf29 100755 Binary files a/wasm_for_tests/tx_read_storage_key.wasm and b/wasm_for_tests/tx_read_storage_key.wasm differ diff --git a/wasm_for_tests/tx_write.wasm b/wasm_for_tests/tx_write.wasm index 90f2c3c8d5..632d99b37d 100755 Binary files a/wasm_for_tests/tx_write.wasm and b/wasm_for_tests/tx_write.wasm differ diff --git a/wasm_for_tests/tx_write_storage_key.wasm b/wasm_for_tests/tx_write_storage_key.wasm index 5d600d185f..a0fb758ae9 100755 Binary files a/wasm_for_tests/tx_write_storage_key.wasm and b/wasm_for_tests/tx_write_storage_key.wasm differ diff --git a/wasm_for_tests/vp_always_false.wasm b/wasm_for_tests/vp_always_false.wasm index 9f59d000a4..751f4a41d9 100755 Binary files a/wasm_for_tests/vp_always_false.wasm and b/wasm_for_tests/vp_always_false.wasm differ diff --git a/wasm_for_tests/vp_always_true.wasm b/wasm_for_tests/vp_always_true.wasm index 6ad5a02be7..656dca4d27 100755 Binary files a/wasm_for_tests/vp_always_true.wasm and b/wasm_for_tests/vp_always_true.wasm differ diff --git a/wasm_for_tests/vp_eval.wasm b/wasm_for_tests/vp_eval.wasm index 5c3e3ad86f..07669c104e 100755 Binary files a/wasm_for_tests/vp_eval.wasm and b/wasm_for_tests/vp_eval.wasm differ diff --git a/wasm_for_tests/vp_memory_limit.wasm b/wasm_for_tests/vp_memory_limit.wasm index 1e3e4edd24..13bafdbbc7 100755 Binary files a/wasm_for_tests/vp_memory_limit.wasm and b/wasm_for_tests/vp_memory_limit.wasm differ diff --git a/wasm_for_tests/vp_read_storage_key.wasm b/wasm_for_tests/vp_read_storage_key.wasm index a00d7f195c..866f9db307 100755 Binary files a/wasm_for_tests/vp_read_storage_key.wasm and b/wasm_for_tests/vp_read_storage_key.wasm differ diff --git a/wasm_for_tests/wasm_source/src/lib.rs b/wasm_for_tests/wasm_source/src/lib.rs index 3822a8a01f..bc99e36551 100644 --- a/wasm_for_tests/wasm_source/src/lib.rs +++ b/wasm_for_tests/wasm_source/src/lib.rs @@ -16,7 +16,8 @@ pub mod main { #[transaction] fn apply_tx(_ctx: &mut Ctx, tx_data: Tx) -> TxResult { - let len = usize::try_from_slice(&tx_data.data().as_ref().unwrap()[..]).unwrap(); + let len = usize::try_from_slice(&tx_data.data().as_ref().unwrap()[..]) + .unwrap(); log_string(format!("allocate len {}", len)); let bytes: Vec = vec![6_u8; len]; // use the variable to prevent it from compiler optimizing it away @@ -51,7 +52,9 @@ pub mod main { #[transaction] fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { // Allocates a memory of size given from the `tx_data (usize)` - let key = storage::Key::try_from_slice(&tx_data.data().as_ref().unwrap()[..]).unwrap(); + let key = + storage::Key::try_from_slice(&tx_data.data().as_ref().unwrap()[..]) + .unwrap(); log_string(format!("key {}", key)); let _result: Vec = ctx.read(&key)?.unwrap(); Ok(()) @@ -64,7 +67,7 @@ pub mod main { use borsh::BorshDeserialize; use namada_test_utils::tx_data::TxWriteData; use namada_tx_prelude::{ - log_string, transaction, Ctx, StorageRead, StorageWrite, TxResult, Tx, + log_string, transaction, Ctx, StorageRead, StorageWrite, Tx, TxResult, }; const TX_NAME: &str = "tx_write"; @@ -124,37 +127,6 @@ pub mod main { } } -/// A tx that attempts to mint tokens in the transfer's target without debiting -/// the tokens from the source. This tx is expected to be rejected by the -/// token's VP. -#[cfg(feature = "tx_mint_tokens")] -pub mod main { - use namada_tx_prelude::*; - - #[transaction] - fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { - let signed = tx_data; - let transfer = - token::Transfer::try_from_slice(&signed.data().unwrap()[..]).unwrap(); - log_string(format!("apply_tx called to mint tokens: {:#?}", transfer)); - let token::Transfer { - source: _, - target, - token, - sub_prefix: _, - amount, - key: _, - shielded: _, - } = transfer; - let target_key = token::balance_key(&token, &target); - let mut target_bal: token::Amount = - ctx.read(&target_key)?.unwrap_or_default(); - target_bal.receive(&amount.amount); - ctx.write(&target_key, target_bal)?; - Ok(()) - } -} - /// A VP that always returns `true`. #[cfg(feature = "vp_always_true")] pub mod main { @@ -204,8 +176,12 @@ pub mod main { _verifiers: BTreeSet
, ) -> VpResult { use validity_predicate::EvalVp; - let EvalVp { vp_code_hash, input }: EvalVp = - EvalVp::try_from_slice(&tx_data.data().as_ref().unwrap()[..]).unwrap(); + let EvalVp { + vp_code_hash, + input, + }: EvalVp = + EvalVp::try_from_slice(&tx_data.data().as_ref().unwrap()[..]) + .unwrap(); ctx.eval(vp_code_hash, input) } } @@ -224,7 +200,8 @@ pub mod main { _keys_changed: BTreeSet, _verifiers: BTreeSet
, ) -> VpResult { - let len = usize::try_from_slice(&tx_data.data().as_ref().unwrap()[..]).unwrap(); + let len = usize::try_from_slice(&tx_data.data().as_ref().unwrap()[..]) + .unwrap(); log_string(format!("allocate len {}", len)); let bytes: Vec = vec![6_u8; len]; // use the variable to prevent it from compiler optimizing it away @@ -248,7 +225,9 @@ pub mod main { _verifiers: BTreeSet
, ) -> VpResult { // Allocates a memory of size given from the `tx_data (usize)` - let key = storage::Key::try_from_slice(&tx_data.data().as_ref().unwrap()[..]).unwrap(); + let key = + storage::Key::try_from_slice(&tx_data.data().as_ref().unwrap()[..]) + .unwrap(); log_string(format!("key {}", key)); let _result: Vec = ctx.read_pre(&key)?.unwrap(); accept()