diff --git a/.changelog/unreleased/improvements/2933-migrated-e2e-to-int.md b/.changelog/unreleased/improvements/2933-migrated-e2e-to-int.md new file mode 100644 index 0000000000..af13718a45 --- /dev/null +++ b/.changelog/unreleased/improvements/2933-migrated-e2e-to-int.md @@ -0,0 +1,3 @@ + This PR moves many e2e tests over to integration test. In the future, it may be possible to move more + tests over. Moving some of these tests over revealed issues and these have also been resolved, + including \#2927. ([\#2933](https://github.com/anoma/namada/pull/2933)) \ No newline at end of file diff --git a/.github/workflows/scripts/e2e.json b/.github/workflows/scripts/e2e.json index 56bca7fc25..b27559b4ef 100644 --- a/.github/workflows/scripts/e2e.json +++ b/.github/workflows/scripts/e2e.json @@ -7,18 +7,11 @@ "e2e::ibc_tests::ibc_rate_limit": 500, "e2e::eth_bridge_tests::test_add_to_bridge_pool": 10, "e2e::ledger_tests::double_signing_gets_slashed": 12, - "e2e::ledger_tests::invalid_transactions": 13, "e2e::ledger_tests::ledger_many_txs_in_a_block": 55, - "e2e::ledger_tests::ledger_txs_and_queries": 30, - "e2e::ledger_tests::masp_txs_and_queries": 82, "e2e::ledger_tests::pos_bonds": 77, - "e2e::ledger_tests::implicit_account_reveal_pk": 30, "e2e::ledger_tests::pos_init_validator": 40, "e2e::ledger_tests::rollback": 21, - "e2e::ledger_tests::pgf_governance_proposal": 320, - "e2e::ledger_tests::proposal_submission": 200, "e2e::ledger_tests::proposal_change_shielded_reward": 200, - "e2e::pgf_steward_change_commissions": 30, "e2e::ledger_tests::run_ledger": 5, "e2e::ledger_tests::run_ledger_load_state_and_reset": 23, "e2e::ledger_tests::test_namada_shuts_down_if_tendermint_dies": 2, @@ -27,10 +20,7 @@ "e2e::ledger_tests::test_epoch_sleep": 12, "e2e::ledger_tests::wrapper_disposable_signer": 28, "e2e::ledger_tests::deactivate_and_reactivate_validator": 67, - "e2e::ledger_tests::change_validator_metadata": 31, - "e2e::ledger_tests::pos_rewards": 44, "e2e::ledger_tests::test_invalid_validator_txs": 73, - "e2e::ledger_tests::test_bond_queries": 95, "e2e::ledger_tests::suspend_ledger": 30, "e2e::ledger_tests::stop_ledger_at_height": 18, "e2e::ledger_tests::change_consensus_key": 91, diff --git a/Cargo.lock b/Cargo.lock index 0e6810a11d..c8852a4035 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4935,6 +4935,7 @@ name = "namada_tests" version = "0.32.1" dependencies = [ "assert_cmd", + "assert_matches", "async-trait", "borsh 1.2.1", "borsh-ext", diff --git a/crates/apps/src/lib/client/tx.rs b/crates/apps/src/lib/client/tx.rs index 669b7c1df2..2290900c74 100644 --- a/crates/apps/src/lib/client/tx.rs +++ b/crates/apps/src/lib/client/tx.rs @@ -211,7 +211,8 @@ pub async fn submit_reveal_aux( if tx::is_reveal_pk_needed(context.client(), address, args.force) .await? { - println!( + display_line!( + context.io(), "Submitting a tx to reveal the public key for address \ {address}..." ); diff --git a/crates/apps/src/lib/node/ledger/mod.rs b/crates/apps/src/lib/node/ledger/mod.rs index af2402b27f..605e2eae6a 100644 --- a/crates/apps/src/lib/node/ledger/mod.rs +++ b/crates/apps/src/lib/node/ledger/mod.rs @@ -14,7 +14,7 @@ use std::thread; use byte_unit::Byte; use data_encoding::HEXUPPER; use futures::future::TryFutureExt; -use namada::core::storage::{BlockHeight, Key}; +use namada::core::storage::BlockHeight; use namada::core::time::DateTimeUtc; use namada::eth_bridge::ethers::providers::{Http, Provider}; use namada::state::DB; @@ -208,7 +208,7 @@ pub fn dump_db( #[cfg(feature = "migrations")] pub fn query_db( config: config::Ledger, - key: &Key, + key: &namada::core::storage::Key, type_hash: &[u8; 32], cf: &DbColFam, ) { diff --git a/crates/apps/src/lib/node/ledger/shell/mod.rs b/crates/apps/src/lib/node/ledger/shell/mod.rs index d575243cf6..7c880e7693 100644 --- a/crates/apps/src/lib/node/ledger/shell/mod.rs +++ b/crates/apps/src/lib/node/ledger/shell/mod.rs @@ -546,7 +546,7 @@ where /// Load the Merkle root hash and the height of the last committed block, if /// any. This is returned when ABCI sends an `info` request. - pub fn last_state(&mut self) -> response::Info { + pub fn last_state(&self) -> response::Info { if ledger::migrating_state().is_some() { // When migrating state, return a height of 0, such // that CometBFT calls InitChain and subsequently diff --git a/crates/apps/src/lib/node/ledger/shell/testing/node.rs b/crates/apps/src/lib/node/ledger/shell/testing/node.rs index b3b21dff4f..9e3e984f46 100644 --- a/crates/apps/src/lib/node/ledger/shell/testing/node.rs +++ b/crates/apps/src/lib/node/ledger/shell/testing/node.rs @@ -1,3 +1,4 @@ +use std::fmt::{Debug, Formatter}; use std::future::poll_fn; use std::mem::ManuallyDrop; use std::path::PathBuf; @@ -9,6 +10,7 @@ use color_eyre::eyre::{Report, Result}; use data_encoding::HEXUPPER; use itertools::Either; use lazy_static::lazy_static; +use namada::address::Address; use namada::control_flow::time::Duration; use namada::core::collections::HashMap; use namada::core::ethereum_events::EthereumEvent; @@ -29,7 +31,9 @@ use namada::proof_of_stake::storage::{ validator_consensus_key_handle, }; use namada::proof_of_stake::types::WeightedValidator; -use namada::state::{LastBlock, Sha256Hasher, EPOCH_SWITCH_BLOCKS_DELAY}; +use namada::state::{ + LastBlock, Sha256Hasher, StorageRead, EPOCH_SWITCH_BLOCKS_DELAY, +}; use namada::tendermint::abci::response::Info; use namada::tendermint::abci::types::VoteInfo; use namada_sdk::queries::Client; @@ -233,7 +237,7 @@ pub fn mock_services(cfg: MockServicesCfg) -> MockServicesPackage { } /// Status of tx -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum NodeResults { /// Success Ok, @@ -253,6 +257,14 @@ pub struct MockNode { pub auto_drive_services: bool, } +impl Debug for MockNode { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("MockNode") + .field("shell", &self.shell) + .finish() + } +} + impl Drop for MockNode { fn drop(&mut self) { unsafe { @@ -356,6 +368,11 @@ impl MockNode { .0 } + pub fn native_token(&self) -> Address { + let locked = self.shell.lock().unwrap(); + locked.state.get_native_token().unwrap() + } + /// Get the address of the block proposer and the votes for the block fn prepare_request(&self) -> (Vec, Vec) { let (val1, ck) = { @@ -638,6 +655,14 @@ impl MockNode { .all(|r| *r == NodeResults::Ok) } + /// Return a tx result if the tx failed in mempool + pub fn is_broadcast_err(&self) -> Option { + self.results.lock().unwrap().iter().find_map(|r| match r { + NodeResults::Ok | NodeResults::Failed(_) => None, + NodeResults::Rejected(tx_result) => Some(tx_result.clone()), + }) + } + pub fn clear_results(&self) { self.results.lock().unwrap().clear(); } @@ -761,13 +786,15 @@ impl<'a> Client for &'a MockNode { }; let tx_bytes: Vec = tx.into(); self.submit_txs(vec![tx_bytes]); - if !self.success() { - // TODO: submit_txs should return the correct error code + message - resp.code = 1337.into(); - return Ok(resp); - } else { - self.clear_results(); + + // If the error happened during broadcasting, attach its result to + // response + if let Some(TxResult { code, info }) = self.is_broadcast_err() { + resp.code = code.into(); + resp.log = info; } + + self.clear_results(); Ok(resp) } diff --git a/crates/apps/src/lib/node/ledger/shell/testing/utils.rs b/crates/apps/src/lib/node/ledger/shell/testing/utils.rs index c742559b84..52485b9d24 100644 --- a/crates/apps/src/lib/node/ledger/shell/testing/utils.rs +++ b/crates/apps/src/lib/node/ledger/shell/testing/utils.rs @@ -1,3 +1,4 @@ +use std::fmt::Display; use std::ops::{Deref, DerefMut}; use std::path::{Path, PathBuf}; use std::pin::Pin; @@ -172,15 +173,29 @@ impl CapturedOutput { CapturedOutput::of(func) } - /// Check if the captured output contains the regex. - pub fn matches(&self, needle: regex::Regex) -> bool { - needle.captures(&self.output).is_some() + /// Return the first capture of the regex from the output. + pub fn matches(&self, needle: &str) -> Option<&str> { + let needle = regex::Regex::new(needle).unwrap(); + needle.find(&self.output).map(|x| x.as_str()) } /// Check if the captured output contains the string. pub fn contains(&self, needle: &str) -> bool { + self.matches(needle).is_some() + } +} + +impl CapturedOutput> { + pub fn err_contains(&self, needle: &str) -> bool { + if self.result.is_ok() { + return false; + } + let err_str = match self.result.as_ref() { + Ok(_) => unreachable!(), + Err(e) => e.to_string(), + }; let needle = regex::Regex::new(needle).unwrap(); - self.matches(needle) + needle.find(&err_str).is_some() } } diff --git a/crates/apps/src/lib/node/ledger/shims/abcipp_shim_types.rs b/crates/apps/src/lib/node/ledger/shims/abcipp_shim_types.rs index 2038beaf19..8f99c7c5bf 100644 --- a/crates/apps/src/lib/node/ledger/shims/abcipp_shim_types.rs +++ b/crates/apps/src/lib/node/ledger/shims/abcipp_shim_types.rs @@ -218,7 +218,7 @@ pub mod shim { #[derive(Debug, Default)] pub struct VerifyHeader; - #[derive(Debug, Default, Clone, PartialEq, Eq)] + #[derive(Debug, Default, Clone, PartialEq, Eq, Hash)] pub struct TxResult { pub code: u32, pub info: String, diff --git a/crates/core/src/address.rs b/crates/core/src/address.rs index c537da96a5..9efaea3f7d 100644 --- a/crates/core/src/address.rs +++ b/crates/core/src/address.rs @@ -59,6 +59,8 @@ pub const POS_SLASH_POOL: Address = Address::Internal(InternalAddress::PosSlashPool); /// Internal Governance address pub const GOV: Address = Address::Internal(InternalAddress::Governance); +/// Internal Public Goods funding address +pub const PGF: Address = Address::Internal(InternalAddress::Pgf); /// Internal MASP address pub const MASP: Address = Address::Internal(InternalAddress::Masp); /// Internal Multitoken address diff --git a/crates/proof_of_stake/src/rewards.rs b/crates/proof_of_stake/src/rewards.rs index 481df04d9d..64ab7de345 100644 --- a/crates/proof_of_stake/src/rewards.rs +++ b/crates/proof_of_stake/src/rewards.rs @@ -502,7 +502,7 @@ where credit_tokens( storage, staking_token, - &address::GOV, + &address::PGF, reward_tokens_remaining, )?; } diff --git a/crates/proof_of_stake/src/tests/test_pos.rs b/crates/proof_of_stake/src/tests/test_pos.rs index c1d1be9ad8..3444faafe6 100644 --- a/crates/proof_of_stake/src/tests/test_pos.rs +++ b/crates/proof_of_stake/src/tests/test_pos.rs @@ -1398,8 +1398,8 @@ fn test_update_rewards_products_aux(validators: Vec) { // Read some data before applying rewards let pos_balance_pre = read_balance(&s, &staking_token, &address::POS).unwrap(); - let gov_balance_pre = - read_balance(&s, &staking_token, &address::GOV).unwrap(); + let pgf_balance_pre = + read_balance(&s, &staking_token, &address::PGF).unwrap(); let num_consensus_validators = consensus_set.len() as u64; let accum_val = Dec::one() / num_consensus_validators; @@ -1437,17 +1437,17 @@ fn test_update_rewards_products_aux(validators: Vec) { let pos_balance_post = read_balance(&s, &staking_token, &address::POS).unwrap(); - let gov_balance_post = - read_balance(&s, &staking_token, &address::GOV).unwrap(); + let pgf_balance_post = + read_balance(&s, &staking_token, &address::PGF).unwrap(); assert_eq!( - pos_balance_pre + gov_balance_pre + inflation, - pos_balance_post + gov_balance_post, - "Expected inflation to be minted to PoS and left-over amount to Gov" + pos_balance_pre + pgf_balance_pre + inflation, + pos_balance_post + pgf_balance_post, + "Expected inflation to be minted to PoS and left-over amount to PGF" ); let pos_credit = pos_balance_post - pos_balance_pre; - let gov_credit = gov_balance_post - gov_balance_pre; + let gov_credit = pgf_balance_post - pgf_balance_pre; assert!( pos_credit > gov_credit, "PoS must receive more tokens than Gov, but got {} in PoS and {} in \ diff --git a/crates/tests/Cargo.toml b/crates/tests/Cargo.toml index d2716be4b6..a729be296b 100644 --- a/crates/tests/Cargo.toml +++ b/crates/tests/Cargo.toml @@ -64,6 +64,7 @@ tracing.workspace = true namada_apps = {path = "../apps", features = ["testing"]} namada_vm_env = {path = "../vm_env"} assert_cmd.workspace = true +assert_matches.workspace = true borsh.workspace = true borsh-ext.workspace = true color-eyre.workspace = true @@ -79,6 +80,7 @@ pretty_assertions.workspace = true proptest.workspace = true proptest-state-machine.workspace = true rand.workspace = true +test-log.workspace = true toml.workspace = true # This is used to enable logging from tests diff --git a/crates/tests/src/e2e/ibc_tests.rs b/crates/tests/src/e2e/ibc_tests.rs index 50bb9e0c62..d63fe59f65 100644 --- a/crates/tests/src/e2e/ibc_tests.rs +++ b/crates/tests/src/e2e/ibc_tests.rs @@ -1865,8 +1865,13 @@ fn propose_funding( let rpc_a = get_actor_rpc(test_a, Who::Validator(0)); let epoch = get_epoch(test_a, &rpc_a)?; let start_epoch = (epoch.0 + 3) / 3 * 3; - let proposal_json_path = - prepare_proposal_data(test_a, 0, albert, pgf_funding, start_epoch); + let proposal_json_path = prepare_proposal_data( + test_a.test_dir.path(), + 0, + albert, + pgf_funding, + start_epoch, + ); let submit_proposal_args = vec![ "init-proposal", diff --git a/crates/tests/src/e2e/ledger_tests.rs b/crates/tests/src/e2e/ledger_tests.rs index d280831d67..4e03777d6c 100644 --- a/crates/tests/src/e2e/ledger_tests.rs +++ b/crates/tests/src/e2e/ledger_tests.rs @@ -17,36 +17,27 @@ use std::str::FromStr; use std::sync::Arc; use std::time::{Duration, Instant}; -use borsh_ext::BorshSerializeExt; use color_eyre::eyre::Result; use color_eyre::owo_colors::OwoColorize; -use data_encoding::HEXLOWER; use namada::core::address::Address; use namada::core::storage::Epoch; -use namada::governance::cli::onchain::{PgfFunding, StewardsUpdate}; -use namada::governance::storage::proposal::{PGFInternalTarget, PGFTarget}; use namada::token; use namada_apps::cli::context::ENV_VAR_CHAIN_ID; use namada_apps::config::ethereum_bridge; use namada_apps::config::utils::convert_tm_addr_to_socket_addr; use namada_apps::facade::tendermint_config::net::Address as TendermintAddress; use namada_core::chain::ChainId; -use namada_core::collections::HashMap; use namada_core::token::NATIVE_MAX_DECIMAL_PLACES; -use namada_sdk::governance::pgf::cli::steward::Commission; use namada_sdk::masp::fs::FsShieldedUtils; use namada_test_utils::TestWasms; -use namada_tx_prelude::dec::Dec; -use namada_vp_prelude::BTreeSet; use serde::Serialize; use serde_json::json; use setup::constants::*; use setup::Test; use super::helpers::{ - epochs_per_year_from_min_duration, get_established_addr_from_pregenesis, - get_height, get_pregenesis_wallet, wait_for_block_height, - wait_for_wasm_pre_compile, + epochs_per_year_from_min_duration, get_height, get_pregenesis_wallet, + wait_for_block_height, wait_for_wasm_pre_compile, }; use super::setup::{set_ethereum_bridge_mode, working_dir, NamadaCmd}; use crate::e2e::helpers::{ @@ -59,7 +50,7 @@ use crate::e2e::setup::{ }; use crate::strings::{ LEDGER_SHUTDOWN, LEDGER_STARTED, NON_VALIDATOR_NODE, TX_APPLIED_SUCCESS, - TX_FAILED, TX_REJECTED, VALIDATOR_NODE, + TX_REJECTED, VALIDATOR_NODE, }; use crate::{run, run_as}; @@ -483,280 +474,6 @@ fn stop_ledger_at_height() -> Result<()> { Ok(()) } -/// In this test we: -/// 1. Run the ledger node -/// 2. Submit a token transfer tx -/// 3. Submit a transaction to update an account's validity predicate -/// 4. Submit a custom tx -/// 5. Submit a tx to initialize a new account -/// 6. Submit a tx to withdraw from faucet account (requires PoW challenge -/// solution) -/// 7. Query token balance -/// 8. Query the raw bytes of a storage key -#[test] -fn ledger_txs_and_queries() -> Result<()> { - let test = setup::single_node_net()?; - set_ethereum_bridge_mode( - &test, - &test.net.chain_id, - Who::Validator(0), - ethereum_bridge::ledger::Mode::Off, - None, - ); - - // 1. Run the ledger node - let _bg_ledger = - start_namada_ledger_node_wait_wasm(&test, Some(0), Some(40))? - .background(); - - // for a custom tx - let transfer = token::Transfer { - source: find_address(&test, BERTHA).unwrap(), - target: find_address(&test, ALBERT).unwrap(), - token: find_address(&test, NAM).unwrap(), - amount: token::DenominatedAmount::new( - token::Amount::native_whole(10), - token::NATIVE_MAX_DECIMAL_PLACES.into(), - ), - key: None, - shielded: None, - } - .serialize_to_vec(); - let tx_data_path = test.test_dir.path().join("tx.data"); - std::fs::write(&tx_data_path, transfer).unwrap(); - let tx_data_path = tx_data_path.to_string_lossy(); - - let validator_one_rpc = get_actor_rpc(&test, Who::Validator(0)); - - let multisig_account = - format!("{},{},{}", BERTHA_KEY, ALBERT_KEY, CHRISTEL_KEY); - - let txs_args = vec![ - // 2. Submit a token transfer tx (from an established account) - vec![ - "transfer", - "--source", - BERTHA, - "--target", - ALBERT, - "--token", - NAM, - "--amount", - "10.1", - "--signing-keys", - BERTHA_KEY, - "--node", - &validator_one_rpc, - ], - // Submit a token transfer tx (from an ed25519 implicit account) - vec![ - "transfer", - "--source", - DAEWON, - "--target", - ALBERT, - "--token", - NAM, - "--amount", - "10.1", - "--signing-keys", - DAEWON, - "--node", - &validator_one_rpc, - ], - // Submit a token transfer tx (from a secp256k1 implicit account) - vec![ - "transfer", - "--source", - ESTER, - "--target", - ALBERT, - "--token", - NAM, - "--amount", - "10.1", - "--node", - &validator_one_rpc, - ], - // 3. Submit a transaction to update an account's validity - // predicate - vec![ - "update-account", - "--address", - BERTHA, - "--code-path", - VP_USER_WASM, - "--signing-keys", - BERTHA_KEY, - "--node", - &validator_one_rpc, - ], - // 4. Submit a custom tx - vec![ - "tx", - "--code-path", - TX_TRANSFER_WASM, - "--data-path", - &tx_data_path, - "--owner", - BERTHA, - "--signing-keys", - BERTHA_KEY, - "--node", - &validator_one_rpc, - ], - // 5. Submit a tx to initialize a new account - vec![ - "init-account", - "--public-keys", - // Value obtained from `namada::core::key::ed25519::tests::gen_keypair` - "tpknam1qpqfzxu3gt05jx2mvg82f4anf90psqerkwqhjey4zlqv0qfgwuvkzt5jhkp", - "--threshold", - "1", - "--code-path", - VP_USER_WASM, - "--alias", - "Test-Account", - "--signing-keys", - BERTHA_KEY, - "--node", - &validator_one_rpc, - ], - // 5. Submit a tx to initialize a new multisig account - vec![ - "init-account", - "--public-keys", - &multisig_account, - "--threshold", - "2", - "--code-path", - VP_USER_WASM, - "--alias", - "Test-Account-2", - "--signing-keys", - BERTHA_KEY, - "--node", - &validator_one_rpc, - ], - ]; - - for tx_args in &txs_args { - for &dry_run in &[true, false] { - let tx_args = if dry_run && tx_args[0] == "tx" { - continue; - } else if dry_run { - [tx_args.clone(), vec!["--dry-run"]].concat() - } else { - tx_args.clone() - }; - let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string(TX_APPLIED_SUCCESS)?; - client.assert_success(); - } - } - - let query_args_and_expected_response = vec![ - // 7. Query token balance - ( - vec![ - "balance", - "--owner", - BERTHA, - "--token", - NAM, - "--node", - &validator_one_rpc, - ], - // expect a decimal - vec![r"nam: \d+(\.\d+)?"], - // check also as validator node - true, - ), - // Unspecified token expect all tokens from wallet derived from genesis - ( - vec!["balance", "--owner", ALBERT, "--node", &validator_one_rpc], - // expect all genesis tokens, sorted by alias - vec![ - r"apfel: \d+(\.\d+)?", - r"btc: \d+(\.\d+)?", - r"dot: \d+(\.\d+)?", - r"eth: \d+(\.\d+)?", - r"kartoffel: \d+(\.\d+)?", - r"schnitzel: \d+(\.\d+)?", - ], - // check also as validator node - true, - ), - ( - vec![ - "query-account", - "--owner", - "Test-Account-2", - "--node", - &validator_one_rpc, - ], - vec!["Threshold: 2"], - // check also as validator node - false, - ), - ]; - for (query_args, expected, check_as_validator) in - &query_args_and_expected_response - { - // Run as a non-validator - let mut client = run!(test, Bin::Client, query_args, Some(40))?; - for pattern in expected { - client.exp_regex(pattern)?; - } - client.assert_success(); - - if !check_as_validator { - continue; - } - - // Run as a validator - let mut client = run_as!( - test, - Who::Validator(0), - Bin::Client, - query_args, - Some(40) - )?; - for pattern in expected { - client.exp_regex(pattern)?; - } - client.assert_success(); - } - let christel = find_address(&test, CHRISTEL)?; - // as setup in `genesis/e2e-tests-single-node.toml` - let christel_balance = token::Amount::native_whole(2000000); - let nam = find_address(&test, NAM)?; - let storage_key = - token::storage_key::balance_key(&nam, &christel).to_string(); - let query_args_and_expected_response = vec![ - // 8. Query storage key and get hex-encoded raw bytes - ( - vec![ - "query-bytes", - "--storage-key", - &storage_key, - "--node", - &validator_one_rpc, - ], - // expect hex encoded of borsh encoded bytes - HEXLOWER.encode(&christel_balance.serialize_to_vec()), - ), - ]; - for (query_args, expected) in &query_args_and_expected_response { - let mut client = run!(test, Bin::Client, query_args, Some(40))?; - client.exp_string(expected)?; - - client.assert_success(); - } - - Ok(()) -} - /// Test the optional disposable keypair for wrapper signing /// /// 1. Test that a tx requesting a disposable signer with a correct unshielding @@ -918,107 +635,6 @@ fn wrapper_disposable_signer() -> Result<()> { Ok(()) } -/// In this test we: -/// 1. Run the ledger node -/// 2. Submit an invalid transaction (disallowed by state machine) -/// 3. Shut down the ledger -/// 4. Restart the ledger -/// 5. Submit and invalid transactions (malformed) -#[test] -fn invalid_transactions() -> Result<()> { - let test = setup::single_node_net()?; - - set_ethereum_bridge_mode( - &test, - &test.net.chain_id, - Who::Validator(0), - ethereum_bridge::ledger::Mode::Off, - None, - ); - - // 1. Run the ledger node - let bg_ledger = - start_namada_ledger_node_wait_wasm(&test, Some(0), Some(40))? - .background(); - - // 2. Submit a an invalid transaction (trying to transfer tokens should fail - // in the user's VP due to the wrong signer) - let validator_one_rpc = get_actor_rpc(&test, Who::Validator(0)); - - let tx_args = vec![ - "transfer", - "--source", - BERTHA, - "--target", - ALBERT, - "--token", - NAM, - "--amount", - "1", - "--signing-keys", - ALBERT_KEY, - "--node", - &validator_one_rpc, - "--force", - ]; - - let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string(TX_REJECTED)?; - - client.assert_success(); - let mut ledger = bg_ledger.foreground(); - ledger.exp_string("rejected inner txs: 1")?; - - // Wait to commit a block - ledger.exp_regex(r"Committed block hash.*, height: [0-9]+")?; - - // 3. Shut it down - ledger.interrupt()?; - // Wait for the node to stop running to finish writing the state and tx - // queue - ledger.exp_string(LEDGER_SHUTDOWN)?; - ledger.exp_eof()?; - drop(ledger); - - // 4. Restart the ledger - let mut ledger = start_namada_ledger_node(&test, Some(0), Some(40))?; - - // There should be previous state now - ledger.exp_string("Last state root hash:")?; - // Wait for a block by which time the RPC should be ready - 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-keys", - &daewon_lower, - "--target", - ALBERT, - "--token", - BERTHA, - "--amount", - "1000000.1", - // Force to ignore client check that fails on the balance check of the - // source address - "--force", - "--node", - &validator_one_rpc, - ]; - - let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string(TX_FAILED)?; - client.assert_success(); - Ok(()) -} - /// PoS bonding, unbonding and withdrawal tests. In this test we: /// /// 1. Run the ledger node with shorter epochs for faster progression @@ -1276,480 +892,152 @@ fn pos_bonds() -> Result<()> { Ok(()) } -/// Test for claiming PoS inflationary rewards +/// PoS validator creation test. In this test we: /// -/// 1. Run the ledger node -/// 2. Wait some epochs while inflationary rewards accumulate in the PoS system -/// 3. Submit a claim-rewards tx -/// 4. Query the validator's balance before and after the claim tx to ensure -/// that reward tokens were actually transferred +/// 1. Run the ledger node with shorter epochs for faster progression +/// 2. Initialize a new validator account +/// 3. Submit a delegation to the new validator +/// 4. Transfer some NAM to the new validator +/// 5. Submit a self-bond for the new validator +/// 6. Wait for the pipeline epoch +/// 7. Check the new validator's bonded stake #[test] -fn pos_rewards() -> Result<()> { +fn pos_init_validator() -> Result<()> { + let pipeline_len = 1; + let validator_stake = token::Amount::native_whole(100000_u64); let test = setup::network( - |mut genesis, base_dir| { - genesis.parameters.parameters.max_expected_time_per_block = 4; + |mut genesis, base_dir: &_| { + genesis.parameters.parameters.min_num_of_blocks = 4; genesis.parameters.parameters.epochs_per_year = 31_536_000; genesis.parameters.parameters.max_expected_time_per_block = 1; - genesis.parameters.pos_params.pipeline_len = 2; - genesis.parameters.pos_params.unbonding_len = 4; - setup::set_validators(1, genesis, base_dir, default_port_offset) + genesis.parameters.pos_params.pipeline_len = pipeline_len; + genesis.parameters.pos_params.unbonding_len = 2; + let genesis = setup::set_validators( + 1, + genesis, + base_dir, + default_port_offset, + ); + println!("{:?}", genesis.transactions.bond); + let stake = genesis + .transactions + .bond + .as_ref() + .unwrap() + .iter() + .map(|bond| { + bond.data + .amount + .increase_precision(NATIVE_MAX_DECIMAL_PLACES.into()) + .unwrap() + .amount() + }) + .sum::(); + assert_eq!( + stake, validator_stake, + "Assuming this stake, we give the same amount to the new \ + validator to have half of voting power", + ); + genesis }, None, )?; - for i in 0..1 { - set_ethereum_bridge_mode( - &test, - &test.net.chain_id, - Who::Validator(i), - ethereum_bridge::ledger::Mode::Off, - None, - ); - } + // 1. Run a validator and non-validator ledger node + let mut validator_0 = + start_namada_ledger_node_wait_wasm(&test, Some(0), Some(60))?; + let mut non_validator = + start_namada_ledger_node_wait_wasm(&test, None, Some(60))?; - // 1. Run 3 genesis validator ledger nodes - let _bg_validator_0 = - start_namada_ledger_node_wait_wasm(&test, Some(0), Some(40))? - .background(); + // Wait for a first block + validator_0.exp_string("Committed block hash")?; + let _bg_validator_0 = validator_0.background(); + non_validator.exp_string("Committed block hash")?; + let bg_non_validator = non_validator.background(); - let validator_0_rpc = get_actor_rpc(&test, Who::Validator(0)); + let non_validator_rpc = get_actor_rpc(&test, Who::NonValidator); - // Query the current rewards for the validator self-bond + // 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 tx_args = vec![ - "rewards", - "--validator", - "validator-0", + "init-validator", + "--alias", + new_validator, + "--account-keys", + "bertha-key", + "--commission-rate", + "0.05", + "--max-commission-rate-change", + "0.01", + "--email", + "null@null.net", + "--signing-keys", + "bertha-key", "--node", - &validator_0_rpc, - ]; - let mut client = - run_as!(test, Who::Validator(0), Bin::Client, tx_args, Some(40))?; - let (_, res) = client - .exp_regex(r"Current rewards available for claim: [0-9\.]+ NAM") - .unwrap(); - let words = res.split(' ').collect::>(); - let res = words[words.len() - 2]; - let mut last_amount = token::Amount::from_str( - res.split(' ').last().unwrap(), - NATIVE_MAX_DECIMAL_PLACES, - ) - .unwrap(); - client.assert_success(); - - // Wait some epochs - let mut last_epoch = get_epoch(&test, &validator_0_rpc)?; - let wait_epoch = last_epoch + 4_u64; - - #[allow(clippy::disallowed_methods)] - let start = Instant::now(); - let loop_timeout = Duration::new(40, 0); - loop { - if { - #[allow(clippy::disallowed_methods)] - Instant::now() - } - .duration_since(start) - > loop_timeout - { - panic!("Timed out waiting for epoch: {}", wait_epoch); - } - - let epoch = epoch_sleep(&test, &validator_0_rpc, 40)?; - if dbg!(epoch) >= wait_epoch { - break; - } - - // Query the current rewards for the validator self-bond and see that it - // grows - let tx_args = vec![ - "rewards", - "--validator", - "validator-0", - "--node", - &validator_0_rpc, - ]; - let mut client = - run_as!(test, Who::Validator(0), Bin::Client, tx_args, Some(40))?; - let (_, res) = client - .exp_regex(r"Current rewards available for claim: [0-9\.]+ NAM") - .unwrap(); - let words = res.split(' ').collect::>(); - let res = words[words.len() - 2]; - let amount = token::Amount::from_str( - res.split(' ').last().unwrap(), - NATIVE_MAX_DECIMAL_PLACES, - ) - .unwrap(); - client.assert_success(); - - if epoch > last_epoch { - assert!(amount > last_amount); - } else { - assert_eq!(amount, last_amount); - } - - last_amount = amount; - last_epoch = epoch; - } - - // Query the balance of the validator account - let query_balance_args = vec![ - "balance", - "--owner", - "validator-0", - "--token", - NAM, - "--node", - &validator_0_rpc, - ]; - let mut client = run!(test, Bin::Client, query_balance_args, Some(40))?; - let (_, res) = client.exp_regex(r"nam: [0-9\.]+").unwrap(); - let amount_pre = token::Amount::from_str( - res.split(' ').last().unwrap(), - NATIVE_MAX_DECIMAL_PLACES, - ) - .unwrap(); - client.assert_success(); - - // Claim rewards - let tx_args = vec![ - "claim-rewards", - "--validator", - "validator-0", - "--signing-keys", - "validator-0-balance-key", - "--node", - &validator_0_rpc, - ]; - let mut client = - run_as!(test, Who::Validator(0), Bin::Client, tx_args, Some(40))?; - client.exp_string(TX_APPLIED_SUCCESS)?; - client.assert_success(); - - // Query the validator balance again and check that the balance has grown - // after claiming - let query_balance_args = vec![ - "balance", - "--owner", - "validator-0", - "--token", - NAM, - "--node", - &validator_0_rpc, - ]; - let mut client = run!(test, Bin::Client, query_balance_args, Some(40))?; - let (_, res) = client.exp_regex(r"nam: [0-9\.]+").unwrap(); - let amount_post = token::Amount::from_str( - res.split(' ').last().unwrap(), - NATIVE_MAX_DECIMAL_PLACES, - ) - .unwrap(); - client.assert_success(); - - assert!(amount_post > amount_pre); - - Ok(()) -} - -/// Test for PoS bonds and unbonds queries. -/// -/// 1. Run the ledger node -/// 2. Submit a delegation to the genesis validator -/// 3. Wait for epoch 4 -/// 4. Submit another delegation to the genesis validator -/// 5. Submit an unbond of the delegation -/// 6. Wait for epoch 7 -/// 7. Check the output of the bonds query -#[test] -fn test_bond_queries() -> Result<()> { - let pipeline_len = 2; - let unbonding_len = 4; - let test = setup::network( - |mut genesis, base_dir: &_| { - genesis.parameters.parameters.min_num_of_blocks = 2; - genesis.parameters.parameters.max_expected_time_per_block = 1; - genesis.parameters.parameters.epochs_per_year = 31_536_000; - genesis.parameters.pos_params.pipeline_len = pipeline_len; - genesis.parameters.pos_params.unbonding_len = unbonding_len; - setup::set_validators(1, genesis, base_dir, default_port_offset) - }, - None, - )?; - - // 1. Run the ledger node - let _bg_ledger = - start_namada_ledger_node_wait_wasm(&test, Some(0), Some(40))? - .background(); - - let validator_one_rpc = get_actor_rpc(&test, Who::Validator(0)); - let validator_alias = "validator-0"; - - // 2. Submit a delegation to the genesis validator - let tx_args = vec![ - "bond", - "--validator", - validator_alias, - "--amount", - "100", - "--ledger-address", - &validator_one_rpc, + &non_validator_rpc, + "--unsafe-dont-encrypt", ]; - let mut client = - run_as!(test, Who::Validator(0), Bin::Client, tx_args, Some(40))?; + let mut client = run!(test, Bin::Client, tx_args, Some(40))?; client.exp_string(TX_APPLIED_SUCCESS)?; client.assert_success(); - // 3. Submit a delegation to the genesis validator + // 3. Submit a delegation to the new validator First, transfer some tokens + // to the validator's key for fees: let tx_args = vec![ - "bond", - "--validator", - "validator-0", + "transfer", "--source", BERTHA, + "--target", + new_validator, + "--token", + NAM, "--amount", - "200", + "10000.5", "--signing-keys", BERTHA_KEY, - "--ledger-address", - &validator_one_rpc, + "--node", + &non_validator_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; client.exp_string(TX_APPLIED_SUCCESS)?; client.assert_success(); - - // 3. Wait for epoch 4 - #[allow(clippy::disallowed_methods)] - let start = Instant::now(); - let loop_timeout = Duration::new(20, 0); - loop { - if { - #[allow(clippy::disallowed_methods)] - Instant::now() - } - .duration_since(start) - > loop_timeout - { - panic!("Timed out waiting for epoch: {}", 1); - } - let epoch = epoch_sleep(&test, &validator_one_rpc, 40)?; - if epoch >= Epoch(4) { - break; - } - } - - // 4. Submit another delegation to the genesis validator + // Then self-bond the tokens: + let delegation = 5_u64; + let delegation_str = &delegation.to_string(); let tx_args = vec![ "bond", "--validator", - validator_alias, + new_validator, "--source", BERTHA, "--amount", - "300", + delegation_str, "--signing-keys", BERTHA_KEY, - "--ledger-address", - &validator_one_rpc, + "--node", + &non_validator_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; client.exp_string(TX_APPLIED_SUCCESS)?; client.assert_success(); - // 5. Submit an unbond of the delegation + // 4. Transfer some NAM to the new validator + let validator_stake_str = &validator_stake.to_string_native(); let tx_args = vec![ - "unbond", - "--validator", - validator_alias, + "transfer", "--source", BERTHA, + "--target", + new_validator, + "--token", + NAM, "--amount", - "412", + validator_stake_str, "--signing-keys", BERTHA_KEY, - "--ledger-address", - &validator_one_rpc, - ]; - let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string(TX_APPLIED_SUCCESS)?; - let (_, res) = client - .exp_regex(r"withdrawable starting from epoch [0-9]+") - .unwrap(); - let withdraw_epoch = - Epoch::from_str(res.split(' ').last().unwrap()).unwrap(); - client.assert_success(); - - // 6. Wait for withdraw_epoch - loop { - let epoch = epoch_sleep(&test, &validator_one_rpc, 120)?; - // NOTE: test passes from epoch ~13 onwards - if epoch >= withdraw_epoch { - break; - } - } - - // 7. Check the output of the bonds query - let tx_args = vec!["bonds", "--ledger-address", &validator_one_rpc]; - let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string( - "All bonds total active: 100188.000000\r -All bonds total: 100188.000000\r -All bonds total slashed: 0.000000\r -All unbonds total active: 412.000000\r -All unbonds total: 412.000000\r -All unbonds total withdrawable: 412.000000\r -All unbonds total slashed: 0.000000\r", - )?; - client.assert_success(); - - Ok(()) -} - -/// PoS validator creation test. In this test we: -/// -/// 1. Run the ledger node with shorter epochs for faster progression -/// 2. Initialize a new validator account -/// 3. Submit a delegation to the new validator -/// 4. Transfer some NAM to the new validator -/// 5. Submit a self-bond for the new validator -/// 6. Wait for the pipeline epoch -/// 7. Check the new validator's bonded stake -#[test] -fn pos_init_validator() -> Result<()> { - let pipeline_len = 1; - let validator_stake = token::Amount::native_whole(100000_u64); - let test = setup::network( - |mut genesis, base_dir: &_| { - genesis.parameters.parameters.min_num_of_blocks = 4; - genesis.parameters.parameters.epochs_per_year = 31_536_000; - genesis.parameters.parameters.max_expected_time_per_block = 1; - genesis.parameters.pos_params.pipeline_len = pipeline_len; - genesis.parameters.pos_params.unbonding_len = 2; - let genesis = setup::set_validators( - 1, - genesis, - base_dir, - default_port_offset, - ); - println!("{:?}", genesis.transactions.bond); - let stake = genesis - .transactions - .bond - .as_ref() - .unwrap() - .iter() - .map(|bond| { - bond.data - .amount - .increase_precision(NATIVE_MAX_DECIMAL_PLACES.into()) - .unwrap() - .amount() - }) - .sum::(); - assert_eq!( - stake, validator_stake, - "Assuming this stake, we give the same amount to the new \ - validator to have half of voting power", - ); - genesis - }, - None, - )?; - - // 1. Run a validator and non-validator ledger node - let mut validator_0 = - start_namada_ledger_node_wait_wasm(&test, Some(0), Some(60))?; - let mut non_validator = - start_namada_ledger_node_wait_wasm(&test, None, Some(60))?; - - // Wait for a first block - validator_0.exp_string("Committed block hash")?; - let _bg_validator_0 = validator_0.background(); - non_validator.exp_string("Committed block hash")?; - let bg_non_validator = non_validator.background(); - - let non_validator_rpc = get_actor_rpc(&test, Who::NonValidator); - - // 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 tx_args = vec![ - "init-validator", - "--alias", - new_validator, - "--account-keys", - "bertha-key", - "--commission-rate", - "0.05", - "--max-commission-rate-change", - "0.01", - "--email", - "null@null.net", - "--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(TX_APPLIED_SUCCESS)?; - client.assert_success(); - - // 3. Submit a delegation to the new validator First, transfer some tokens - // to the validator's key for fees: - let tx_args = vec![ - "transfer", - "--source", - BERTHA, - "--target", - new_validator, - "--token", - NAM, - "--amount", - "10000.5", - "--signing-keys", - BERTHA_KEY, - "--node", - &non_validator_rpc, - ]; - let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string(TX_APPLIED_SUCCESS)?; - client.assert_success(); - // Then self-bond the tokens: - let delegation = 5_u64; - let delegation_str = &delegation.to_string(); - let tx_args = vec![ - "bond", - "--validator", - new_validator, - "--source", - BERTHA, - "--amount", - delegation_str, - "--signing-keys", - BERTHA_KEY, - "--node", - &non_validator_rpc, - ]; - let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string(TX_APPLIED_SUCCESS)?; - client.assert_success(); - - // 4. Transfer some NAM to the new validator - let validator_stake_str = &validator_stake.to_string_native(); - let tx_args = vec![ - "transfer", - "--source", - BERTHA, - "--target", - new_validator, - "--token", - NAM, - "--amount", - validator_stake_str, - "--signing-keys", - BERTHA_KEY, - "--node", - &non_validator_rpc, + "--node", + &non_validator_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; client.exp_string(TX_APPLIED_SUCCESS)?; @@ -1765,850 +1053,90 @@ fn pos_init_validator() -> Result<()> { "--node", &non_validator_rpc, ]; - let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string(TX_APPLIED_SUCCESS)?; - client.assert_success(); - - // Stop the non-validator node and run it as the new validator - let mut non_validator = bg_non_validator.foreground(); - 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( - Bin::Node, - ["ledger"], - Some(60), - &test.working_dir, - validator_1_base_dir, - loc, - )?; - - validator_1.exp_string(LEDGER_STARTED)?; - validator_1.exp_string(VALIDATOR_NODE)?; - validator_1.exp_string("Committed block hash")?; - let _bg_validator_1 = validator_1.background(); - - // 6. Wait for the pipeline epoch when the validator's bonded stake should - // be non-zero - let epoch = get_epoch(&test, &non_validator_rpc)?; - let earliest_update_epoch = epoch + pipeline_len; - println!( - "Current epoch: {}, earliest epoch with updated bonded stake: {}", - epoch, earliest_update_epoch - ); - #[allow(clippy::disallowed_methods)] - let start = Instant::now(); - let loop_timeout = Duration::new(20, 0); - loop { - if { - #[allow(clippy::disallowed_methods)] - Instant::now() - } - .duration_since(start) - > loop_timeout - { - panic!("Timed out waiting for epoch: {}", earliest_update_epoch); - } - let epoch = epoch_sleep(&test, &non_validator_rpc, 40)?; - if epoch >= earliest_update_epoch { - break; - } - } - - // 7. Check the new validator's bonded stake - let bonded_stake = - find_bonded_stake(&test, new_validator, &non_validator_rpc)?; - assert_eq!( - bonded_stake, - token::Amount::native_whole(delegation) + validator_stake - ); - - Ok(()) -} - -/// Test that multiple txs submitted in the same block all get the tx result. -/// -/// In this test we: -/// 1. Run the ledger node with 10s consensus timeout -/// 2. Spawn threads each submitting token transfer tx -#[test] -fn ledger_many_txs_in_a_block() -> Result<()> { - let test = Arc::new(setup::network( - |genesis, base_dir: &_| { - setup::set_validators(1, genesis, base_dir, |_| 0) - }, - // Set 10s consensus timeout to have more time to submit txs - Some("10s"), - )?); - - set_ethereum_bridge_mode( - &test, - &test.net.chain_id, - Who::Validator(0), - ethereum_bridge::ledger::Mode::Off, - None, - ); - - // 1. Run the ledger node - let bg_ledger = - start_namada_ledger_node_wait_wasm(&test, Some(0), Some(40))? - .background(); - - let validator_one_rpc = Arc::new(get_actor_rpc(&test, Who::Validator(0))); - - // A token transfer tx args - let tx_args = Arc::new(vec![ - "transfer", - "--source", - BERTHA, - "--target", - ALBERT, - "--token", - NAM, - "--amount", - "1.01", - "--signing-keys", - BERTHA_KEY, - "--node", - ]); - - // 2. Spawn threads each submitting token transfer tx - // We collect to run the threads in parallel. - #[allow(clippy::needless_collect)] - let tasks: Vec> = (0..4) - .map(|_| { - let test = Arc::clone(&test); - let validator_one_rpc = Arc::clone(&validator_one_rpc); - let tx_args = Arc::clone(&tx_args); - std::thread::spawn(move || { - let mut args = (*tx_args).clone(); - args.push(&*validator_one_rpc); - let mut client = run!(*test, Bin::Client, args, Some(80))?; - client.exp_string(TX_APPLIED_SUCCESS)?; - client.assert_success(); - let res: Result<()> = Ok(()); - res - }) - }) - .collect(); - for task in tasks.into_iter() { - task.join().unwrap()?; - } - // Wait to commit a block - let mut ledger = bg_ledger.foreground(); - ledger.exp_regex(r"Committed block hash.*, height: [0-9]+")?; - - Ok(()) -} - -// In this test we: -// 1. Run the ledger node -// 2. Submit a valid proposal -// 3. Query the proposal -// 4. Query token balance (submitted funds) -// 5. Query governance address balance -// 6. Submit an invalid proposal -// 7. Check invalid proposal was not accepted -// 8. Query token balance (funds shall not be submitted) -// 9. Send a yay vote from a validator -// 10. Send a yay vote from a normal user -// 11. Query the proposal and check the result -// 12. Wait proposal grace and check proposal author funds -// 13. Check governance address funds are 0 -// 14. Query the new parameters -// 15. Try to initialize a new account which should fail -// 16. Submit a tx that triggers an already existing account which should -// succeed -#[test] -fn proposal_submission() -> Result<()> { - let test = setup::network( - |mut genesis, base_dir: &_| { - genesis.parameters.gov_params.max_proposal_code_size = 600000; - genesis.parameters.parameters.max_expected_time_per_block = 1; - setup::set_validators(1, genesis, base_dir, |_| 0u16) - }, - None, - )?; - set_ethereum_bridge_mode( - &test, - &test.net.chain_id, - Who::Validator(0), - ethereum_bridge::ledger::Mode::Off, - None, - ); - - let namadac_help = vec!["--help"]; - - let mut client = run!(test, Bin::Client, namadac_help, Some(40))?; - client.exp_string("Namada client command line interface.")?; - client.assert_success(); - - // 1. Run the ledger node - let bg_ledger = - start_namada_ledger_node_wait_wasm(&test, Some(0), Some(40))? - .background(); - - let validator_0_rpc = get_actor_rpc(&test, Who::Validator(0)); - - // 1.1 Delegate some token - let tx_args = vec![ - "bond", - "--validator", - "validator-0", - "--source", - BERTHA, - "--amount", - "900", - "--node", - &validator_0_rpc, - ]; - let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string(TX_APPLIED_SUCCESS)?; - client.assert_success(); - - // 2. Submit valid proposal - let albert = find_address(&test, ALBERT)?; - let valid_proposal_json_path = prepare_proposal_data( - &test, - 0, - albert, - TestWasms::TxProposalCode.read_bytes(), - 12, - ); - let validator_one_rpc = get_actor_rpc(&test, Who::Validator(0)); - - let submit_proposal_args = vec![ - "init-proposal", - "--data-path", - valid_proposal_json_path.to_str().unwrap(), - "--gas-limit", - "2000000", - "--node", - &validator_one_rpc, - ]; - let mut client = run!(test, Bin::Client, submit_proposal_args, Some(40))?; - client.exp_string(TX_APPLIED_SUCCESS)?; - client.assert_success(); - - // Wait for the proposal to be committed - let mut ledger = bg_ledger.foreground(); - ledger.exp_string("Committed block hash")?; - let _bg_ledger = ledger.background(); - - // 3. Query the proposal - let proposal_query_args = vec![ - "query-proposal", - "--proposal-id", - "0", - "--node", - &validator_one_rpc, - ]; - - let mut client = run!(test, Bin::Client, proposal_query_args, Some(40))?; - client.exp_string("Proposal Id: 0")?; - client.assert_success(); - - // 4. Query token balance proposal author (submitted funds) - let query_balance_args = vec![ - "balance", - "--owner", - ALBERT, - "--token", - NAM, - "--node", - &validator_one_rpc, - ]; - - let mut client = run!(test, Bin::Client, query_balance_args, Some(40))?; - client.exp_string("nam: 1999500")?; - client.assert_success(); - - // 5. Query token balance governance - let query_balance_args = vec![ - "balance", - "--owner", - GOVERNANCE_ADDRESS, - "--token", - NAM, - "--node", - &validator_one_rpc, - ]; - - let mut client = run!(test, Bin::Client, query_balance_args, Some(40))?; - client.exp_string("nam: 500")?; - client.assert_success(); - - // 6. Submit an invalid proposal - // proposal is invalid due to voting_end_epoch - voting_start_epoch < 3 - let albert = find_address(&test, ALBERT)?; - let invalid_proposal_json = prepare_proposal_data( - &test, - 1, - albert, - TestWasms::TxProposalCode.read_bytes(), - 1, - ); - - let submit_proposal_args = vec![ - "init-proposal", - "--data-path", - invalid_proposal_json.to_str().unwrap(), - "--node", - &validator_one_rpc, - ]; - let mut client = run!(test, Bin::Client, submit_proposal_args, Some(40))?; - client.exp_regex( - "Proposal data are invalid: Invalid proposal start epoch: 1 must be \ - greater than current epoch .* and a multiple of 3", - )?; - client.assert_failure(); - - // 7. Check invalid proposal was not submitted - let proposal_query_args = vec![ - "query-proposal", - "--proposal-id", - "1", - "--node", - &validator_one_rpc, - ]; - - let mut client = run!(test, Bin::Client, proposal_query_args, Some(40))?; - client.exp_string("No proposal found with id: 1")?; - client.assert_success(); - - // 8. Query token balance (funds shall not be submitted) - let query_balance_args = vec![ - "balance", - "--owner", - ALBERT, - "--token", - NAM, - "--node", - &validator_one_rpc, - ]; - - let mut client = run!(test, Bin::Client, query_balance_args, Some(40))?; - client.exp_string("nam: 1999500")?; - client.assert_success(); - - // 9. Send a yay vote from a validator - let mut epoch = get_epoch(&test, &validator_one_rpc).unwrap(); - while epoch.0 <= 13 { - sleep(10); - epoch = get_epoch(&test, &validator_one_rpc).unwrap(); - } - - let submit_proposal_vote = vec![ - "vote-proposal", - "--proposal-id", - "0", - "--vote", - "yay", - "--address", - "validator-0", - "--node", - &validator_one_rpc, - ]; - - let mut client = run_as!( - test, - Who::Validator(0), - Bin::Client, - submit_proposal_vote, - Some(15) - )?; - client.exp_string(TX_APPLIED_SUCCESS)?; - client.assert_success(); - - let submit_proposal_vote_delagator = vec![ - "vote-proposal", - "--proposal-id", - "0", - "--vote", - "nay", - "--address", - BERTHA, - "--node", - &validator_one_rpc, - ]; - - let mut client = - run!(test, Bin::Client, submit_proposal_vote_delagator, Some(40))?; - client.exp_string(TX_APPLIED_SUCCESS)?; - client.assert_success(); - - // 10. Send a yay vote from a non-validator/non-delegator user - let submit_proposal_vote = vec![ - "vote-proposal", - "--proposal-id", - "0", - "--vote", - "yay", - "--address", - ALBERT, - "--node", - &validator_one_rpc, - ]; - - // this is valid because the client filter ALBERT delegation and there are - // none - let mut client = run!(test, Bin::Client, submit_proposal_vote, Some(15))?; - client.exp_string("Voter address must have delegations")?; - client.assert_failure(); - - // 11. Query the proposal and check the result - let mut epoch = get_epoch(&test, &validator_one_rpc).unwrap(); - while epoch.0 <= 25 { - sleep(10); - epoch = get_epoch(&test, &validator_one_rpc).unwrap(); - } - - let query_proposal = vec![ - "query-proposal-result", - "--proposal-id", - "0", - "--node", - &validator_one_rpc, - ]; - - let mut client = run!(test, Bin::Client, query_proposal, Some(15))?; - client.exp_string("Proposal Id: 0")?; - client.exp_string( - "passed with 100000.000000 yay votes, 900.000000 nay votes and \ - 0.000000 abstain votes, total voting power: 100900.000000, threshold \ - (fraction) of total voting power needed to tally: 67266.666667 \ - (0.666666666669)", - )?; - client.assert_success(); - - // 12. Wait proposal grace and check proposal author funds - let mut epoch = get_epoch(&test, &validator_one_rpc).unwrap(); - while epoch.0 < 31 { - sleep(10); - epoch = get_epoch(&test, &validator_one_rpc).unwrap(); - } - - let query_balance_args = vec![ - "balance", - "--owner", - ALBERT, - "--token", - NAM, - "--node", - &validator_one_rpc, - ]; - - let mut client = run!(test, Bin::Client, query_balance_args, Some(30))?; - client.exp_string("nam: 200000")?; - client.assert_success(); - - // 13. Check if governance funds are 0 - let query_balance_args = vec![ - "balance", - "--owner", - GOVERNANCE_ADDRESS, - "--token", - NAM, - "--node", - &validator_one_rpc, - ]; - - let mut client = run!(test, Bin::Client, query_balance_args, Some(30))?; - client.exp_string("nam: 0")?; - client.assert_success(); - - // 14. Query parameters - let query_protocol_parameters = - vec!["query-protocol-parameters", "--node", &validator_one_rpc]; - - let mut client = - run!(test, Bin::Client, query_protocol_parameters, Some(30))?; - client.exp_regex(".*Min. proposal grace epochs: 9.*")?; - client.assert_success(); - - // 15. Try to initialize a new account with the no more allowlisted vp - let init_account = vec![ - "init-account", - "--public-keys", - // Value obtained from `namada::core::key::ed25519::tests::gen_keypair` - "tpknam1qpqfzxu3gt05jx2mvg82f4anf90psqerkwqhjey4zlqv0qfgwuvkzt5jhkp", - "--threshold", - "1", - "--code-path", - VP_USER_WASM, - "--alias", - "Test-Account", - "--signing-keys", - BERTHA_KEY, - "--node", - &validator_one_rpc, - ]; - let mut client = run!(test, Bin::Client, init_account, Some(30))?; - client.exp_regex("VP code is not allowed in allowlist parameter")?; - client.assert_success(); - - // 16. Submit a tx touching a previous account with the no more allowlisted - // vp and verify that the transaction succeeds, i.e. the non allowlisted - // vp can still run - let transfer = [ - "transfer", - "--source", - BERTHA, - "--target", - ALBERT, - "--token", - NAM, - "--amount", - "10", - "--signing-keys", - BERTHA_KEY, - "--node", - &validator_one_rpc, - ]; - let mut client = run!(test, Bin::Client, transfer, Some(40))?; - client.exp_string(TX_APPLIED_SUCCESS)?; - client.assert_success(); - - Ok(()) -} - -/// Test submission and vote of a PGF proposal -/// -/// 1 - Submit two proposals -/// 2 - Check balance -/// 3 - Vote for the accepted proposals -/// 4 - Check one proposal passed and the other one didn't -/// 5 - Check funds -#[test] -fn pgf_governance_proposal() -> Result<()> { - let test = setup::network( - |mut genesis, base_dir: &_| { - genesis.parameters.parameters.epochs_per_year = - epochs_per_year_from_min_duration(1); - genesis.parameters.parameters.max_proposal_bytes = - Default::default(); - genesis.parameters.parameters.min_num_of_blocks = 4; - genesis.parameters.parameters.max_expected_time_per_block = 1; - setup::set_validators(1, genesis, base_dir, |_| 0) - }, - None, - )?; - - set_ethereum_bridge_mode( - &test, - &test.net.chain_id, - Who::Validator(0), - ethereum_bridge::ledger::Mode::Off, - None, - ); - - let namadac_help = vec!["--help"]; - - let mut client = run!(test, Bin::Client, namadac_help, Some(40))?; - client.exp_string("Namada client command line interface.")?; - client.assert_success(); - - // Run the ledger node - let _bg_ledger = - start_namada_ledger_node_wait_wasm(&test, Some(0), Some(40))? - .background(); - - let validator_one_rpc = get_actor_rpc(&test, Who::Validator(0)); - - // Delegate some token - let tx_args = vec![ - "bond", - "--validator", - "validator-0", - "--source", - BERTHA, - "--amount", - "900", - "--ledger-address", - &validator_one_rpc, - ]; - let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string(TX_APPLIED_SUCCESS)?; - client.assert_success(); - - // 1 - Submit proposal - let albert = find_address(&test, ALBERT)?; - let pgf_stewards = StewardsUpdate { - add: Some(albert.clone()), - remove: vec![], - }; - - let valid_proposal_json_path = - prepare_proposal_data(&test, 0, albert, pgf_stewards, 12); - let validator_one_rpc = get_actor_rpc(&test, Who::Validator(0)); - - let submit_proposal_args = vec![ - "init-proposal", - "--pgf-stewards", - "--data-path", - valid_proposal_json_path.to_str().unwrap(), - "--ledger-address", - &validator_one_rpc, - ]; - let mut client = run!(test, Bin::Client, submit_proposal_args, Some(40))?; - client.exp_string(TX_APPLIED_SUCCESS)?; - client.assert_success(); - - // 2 - Query the proposal - let proposal_query_args = vec![ - "query-proposal", - "--proposal-id", - "0", - "--ledger-address", - &validator_one_rpc, - ]; - - client = run!(test, Bin::Client, proposal_query_args, Some(40))?; - client.exp_string("Proposal Id: 0")?; - client.assert_success(); - - // Query token balance proposal author (submitted funds) - let query_balance_args = vec![ - "balance", - "--owner", - ALBERT, - "--token", - NAM, - "--ledger-address", - &validator_one_rpc, - ]; - - client = run!(test, Bin::Client, query_balance_args, Some(40))?; - client.exp_string("nam: 1999500")?; - client.assert_success(); - - // Query token balance governance - let query_balance_args = vec![ - "balance", - "--owner", - GOVERNANCE_ADDRESS, - "--token", - NAM, - "--ledger-address", - &validator_one_rpc, - ]; - - client = run!(test, Bin::Client, query_balance_args, Some(40))?; - client.exp_string("nam: 500")?; - client.assert_success(); - - // 3 - Send a yay vote from a validator - let mut epoch = get_epoch(&test, &validator_one_rpc).unwrap(); - while epoch.0 <= 13 { - sleep(1); - epoch = get_epoch(&test, &validator_one_rpc).unwrap(); - } - - let albert_address = find_address(&test, ALBERT)?; - let submit_proposal_vote = vec![ - "vote-proposal", - "--proposal-id", - "0", - "--vote", - "yay", - "--address", - "validator-0", - "--ledger-address", - &validator_one_rpc, - ]; - - client = run_as!( - test, - Who::Validator(0), - Bin::Client, - submit_proposal_vote, - Some(15) - )?; - client.exp_string(TX_APPLIED_SUCCESS)?; - client.assert_success(); - - // Send different yay vote from delegator to check majority on 1/3 - let submit_proposal_vote_delagator = vec![ - "vote-proposal", - "--proposal-id", - "0", - "--vote", - "yay", - "--address", - BERTHA, - "--ledger-address", - &validator_one_rpc, - ]; - - let mut client = - run!(test, Bin::Client, submit_proposal_vote_delagator, Some(40))?; - client.exp_string(TX_APPLIED_SUCCESS)?; - client.assert_success(); - - // 4 - Query the proposal and check the result is the one voted by the - // validator (majority) - epoch = get_epoch(&test, &validator_one_rpc).unwrap(); - while epoch.0 <= 25 { - sleep(1); - epoch = get_epoch(&test, &validator_one_rpc).unwrap(); - } - - let query_proposal = vec![ - "query-proposal-result", - "--proposal-id", - "0", - "--ledger-address", - &validator_one_rpc, - ]; - - client = run!(test, Bin::Client, query_proposal, Some(15))?; - client.exp_string("passed")?; - client.assert_success(); - - // 12. Wait proposals grace and check proposal author funds - while epoch.0 < 31 { - sleep(2); - epoch = get_epoch(&test, &validator_one_rpc).unwrap(); - } - - let query_balance_args = vec![ - "balance", - "--owner", - ALBERT, - "--token", - NAM, - "--ledger-address", - &validator_one_rpc, - ]; - - client = run!(test, Bin::Client, query_balance_args, Some(30))?; - client.exp_string("nam: 2000000.")?; - client.assert_success(); - - // Check if governance funds are 0 - let query_balance_args = vec![ - "balance", - "--owner", - GOVERNANCE_ADDRESS, - "--token", - NAM, - "--ledger-address", - &validator_one_rpc, - ]; - - client = run!(test, Bin::Client, query_balance_args, Some(30))?; - client.exp_string("nam: 0")?; - client.assert_success(); - - // 14. Query pgf stewards - let query_pgf = vec!["query-pgf", "--node", &validator_one_rpc]; - - let mut client = run!(test, Bin::Client, query_pgf, Some(30))?; - client.exp_string("Pgf stewards:")?; - client.exp_string(&format!("- {}", albert_address))?; - client.exp_string("Reward distribution:")?; - client.exp_string(&format!("- 1 to {}", albert_address))?; - client.exp_string("Pgf fundings: no fundings are currently set.")?; - client.assert_success(); - - // 15 - Submit proposal funding - let albert = find_address(&test, ALBERT)?; - let bertha = find_address(&test, BERTHA)?; - let christel = find_address(&test, CHRISTEL)?; - - let pgf_funding = PgfFunding { - continuous: vec![PGFTarget::Internal(PGFInternalTarget { - amount: token::Amount::from_u64(10), - target: bertha.clone(), - })], - retro: vec![PGFTarget::Internal(PGFInternalTarget { - amount: token::Amount::from_u64(5), - target: christel, - })], - }; - - let valid_proposal_json_path = - prepare_proposal_data(&test, 1, albert, pgf_funding, 36); - let validator_one_rpc = get_actor_rpc(&test, Who::Validator(0)); - - let submit_proposal_args = vec![ - "init-proposal", - "--pgf-funding", - "--data-path", - valid_proposal_json_path.to_str().unwrap(), - "--ledger-address", - &validator_one_rpc, - ]; - let mut client = run!(test, Bin::Client, submit_proposal_args, Some(40))?; + let mut client = run!(test, Bin::Client, tx_args, Some(40))?; client.exp_string(TX_APPLIED_SUCCESS)?; client.assert_success(); - // 2 - Query the funding proposal - let proposal_query_args = vec![ - "query-proposal", - "--proposal-id", - "1", - "--ledger-address", - &validator_one_rpc, - ]; + // Stop the non-validator node and run it as the new validator + let mut non_validator = bg_non_validator.foreground(); + non_validator.interrupt()?; + non_validator.exp_eof()?; - client = run!(test, Bin::Client, proposal_query_args, Some(40))?; - client.exp_string("Proposal Id: 1")?; - client.assert_success(); + // it takes a bit before the node is shutdown. We dont want flasky test. + if is_debug_mode() { + sleep(10); + } else { + sleep(5); + } - // 13. Wait proposals grace and check proposal author funds - while epoch.0 < 55 { - sleep(2); - epoch = get_epoch(&test, &validator_one_rpc).unwrap(); + 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( + Bin::Node, + ["ledger"], + Some(60), + &test.working_dir, + validator_1_base_dir, + loc, + )?; + + validator_1.exp_string(LEDGER_STARTED)?; + validator_1.exp_string(VALIDATOR_NODE)?; + validator_1.exp_string("Committed block hash")?; + let _bg_validator_1 = validator_1.background(); + + // 6. Wait for the pipeline epoch when the validator's bonded stake should + // be non-zero + let epoch = get_epoch(&test, &non_validator_rpc)?; + let earliest_update_epoch = epoch + pipeline_len; + println!( + "Current epoch: {}, earliest epoch with updated bonded stake: {}", + epoch, earliest_update_epoch + ); + #[allow(clippy::disallowed_methods)] + let start = Instant::now(); + let loop_timeout = Duration::new(20, 0); + loop { + if { + #[allow(clippy::disallowed_methods)] + Instant::now() + } + .duration_since(start) + > loop_timeout + { + panic!("Timed out waiting for epoch: {}", earliest_update_epoch); + } + let epoch = epoch_sleep(&test, &non_validator_rpc, 40)?; + if epoch >= earliest_update_epoch { + break; + } } - // 14. Query pgf fundings - let query_pgf = vec!["query-pgf", "--node", &validator_one_rpc]; - let mut client = run!(test, Bin::Client, query_pgf, Some(30))?; - client.exp_string("Pgf fundings")?; - client.exp_string(&format!( - "{} for {}", - bertha, - token::Amount::from_u64(10).to_string_native() - ))?; - client.assert_success(); + // 7. Check the new validator's bonded stake + let bonded_stake = + find_bonded_stake(&test, new_validator, &non_validator_rpc)?; + assert_eq!( + bonded_stake, + token::Amount::native_whole(delegation) + validator_stake + ); Ok(()) } -/// Test if a steward can correctly change his distribution reward +/// Test that multiple txs submitted in the same block all get the tx result. +/// +/// In this test we: +/// 1. Run the ledger node with 10s consensus timeout +/// 2. Spawn threads each submitting token transfer tx #[test] -fn pgf_steward_change_commissions() -> Result<()> { - let test = setup::network( - |mut genesis, base_dir: &_| { - genesis.parameters.parameters.epochs_per_year = - epochs_per_year_from_min_duration(1); - genesis.parameters.parameters.max_proposal_bytes = - Default::default(); - genesis.parameters.parameters.min_num_of_blocks = 4; - genesis.parameters.parameters.max_expected_time_per_block = 1; - genesis.parameters.pgf_params.stewards_inflation_rate = - Dec::from_str("0.1").unwrap(); - genesis.parameters.pgf_params.stewards = - BTreeSet::from_iter([get_established_addr_from_pregenesis( - "albert-key", - base_dir, - &genesis, - ) - .unwrap()]); +fn ledger_many_txs_in_a_block() -> Result<()> { + let test = Arc::new(setup::network( + |genesis, base_dir: &_| { setup::set_validators(1, genesis, base_dir, |_| 0) }, - None, - )?; + // Set 10s consensus timeout to have more time to submit txs + Some("10s"), + )?); set_ethereum_bridge_mode( &test, @@ -2618,71 +1146,54 @@ fn pgf_steward_change_commissions() -> Result<()> { None, ); - let namadac_help = vec!["--help"]; - - let mut client = run!(test, Bin::Client, namadac_help, Some(40))?; - client.exp_string("Namada client command line interface.")?; - client.assert_success(); - - // Run the ledger node - let _bg_ledger = + // 1. Run the ledger node + let bg_ledger = start_namada_ledger_node_wait_wasm(&test, Some(0), Some(40))? .background(); - let validator_one_rpc = get_actor_rpc(&test, Who::Validator(0)); - - let albert = find_address(&test, ALBERT)?; - let bertha = find_address(&test, BERTHA)?; - let christel = find_address(&test, CHRISTEL)?; - - // Query pgf stewards - let query_pgf = vec!["query-pgf", "--node", &validator_one_rpc]; - - let mut client = run!(test, Bin::Client, query_pgf, Some(30))?; - client.exp_string("Pgf stewards:")?; - client.exp_string(&format!("- {}", albert))?; - client.exp_string("Reward distribution:")?; - client.exp_string(&format!("- 1 to {}", albert))?; - client.exp_string("Pgf fundings: no fundings are currently set.")?; - client.assert_success(); - - let commission = Commission { - reward_distribution: HashMap::from_iter([ - (albert.clone(), Dec::from_str("0.25").unwrap()), - (bertha.clone(), Dec::from_str("0.70").unwrap()), - (christel.clone(), Dec::from_str("0.05").unwrap()), - ]), - }; - - let commission_path = - prepare_steward_commission_update_data(&test, commission); + let validator_one_rpc = Arc::new(get_actor_rpc(&test, Who::Validator(0))); - // Update steward commissions - let tx_args = vec![ - "update-steward-rewards", - "--steward", + // A token transfer tx args + let tx_args = Arc::new(vec![ + "transfer", + "--source", + BERTHA, + "--target", ALBERT, - "--data-path", - commission_path.to_str().unwrap(), + "--token", + NAM, + "--amount", + "1.01", + "--signing-keys", + BERTHA_KEY, "--node", - &validator_one_rpc, - ]; - let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string(TX_APPLIED_SUCCESS)?; - client.assert_success(); + ]); - // 14. Query pgf stewards - let query_pgf = vec!["query-pgf", "--node", &validator_one_rpc]; - - let mut client = run!(test, Bin::Client, query_pgf, Some(30))?; - client.exp_string("Pgf stewards:")?; - client.exp_string(&format!("- {}", albert))?; - client.exp_string("Reward distribution:")?; - client.exp_string(&format!("- 0.25 to {}", albert))?; - client.exp_string(&format!("- 0.7 to {}", bertha))?; - client.exp_string(&format!("- 0.05 to {}", christel))?; - client.exp_string("Pgf fundings: no fundings are currently set.")?; - client.assert_success(); + // 2. Spawn threads each submitting token transfer tx + // We collect to run the threads in parallel. + #[allow(clippy::needless_collect)] + let tasks: Vec> = (0..4) + .map(|_| { + let test = Arc::clone(&test); + let validator_one_rpc = Arc::clone(&validator_one_rpc); + let tx_args = Arc::clone(&tx_args); + std::thread::spawn(move || { + let mut args = (*tx_args).clone(); + args.push(&*validator_one_rpc); + let mut client = run!(*test, Bin::Client, args, Some(80))?; + client.exp_string(TX_APPLIED_SUCCESS)?; + client.assert_success(); + let res: Result<()> = Ok(()); + res + }) + }) + .collect(); + for task in tasks.into_iter() { + task.join().unwrap()?; + } + // Wait to commit a block + let mut ledger = bg_ledger.foreground(); + ledger.exp_regex(r"Committed block hash.*, height: [0-9]+")?; Ok(()) } @@ -3011,150 +1522,6 @@ fn double_signing_gets_slashed() -> Result<()> { Ok(()) } -/// In this test we: -/// 1. Run the ledger node -/// 2. For some transactions that need signature authorization: 2a. Generate a -/// new key for an implicit account. 2b. Send some funds to the implicit -/// account. 2c. Submit the tx with the implicit account as the source, that -/// requires that the account has revealed its PK. This should be done by the -/// client automatically. 2d. Submit same tx again, this time the client -/// shouldn't reveal again. -#[test] -fn implicit_account_reveal_pk() -> Result<()> { - let test = setup::single_node_net()?; - - // 1. Run the ledger node - let _bg_ledger = - start_namada_ledger_node_wait_wasm(&test, Some(0), Some(40))? - .background(); - - // 2. Some transactions that need signature authorization: - let validator_0_rpc = get_actor_rpc(&test, Who::Validator(0)); - let txs_args: Vec Vec>> = vec![ - // A token transfer tx - Box::new(|source| { - [ - "transfer", - "--source", - source, - "--target", - ALBERT, - "--token", - NAM, - "--amount", - "10.1", - "--signing-keys", - source, - "--node", - &validator_0_rpc, - ] - .into_iter() - .map(|x| x.to_owned()) - .collect() - }), - // A bond - Box::new(|source| { - vec![ - "bond", - "--validator", - "validator-0", - "--source", - source, - "--amount", - "10.1", - "--signing-keys", - source, - "--node", - &validator_0_rpc, - ] - .into_iter() - .map(|x| x.to_owned()) - .collect() - }), - // Submit proposal - Box::new(|source| { - // Gen data for proposal tx - let author = find_address(&test, source).unwrap(); - let valid_proposal_json_path = prepare_proposal_data( - &test, - 0, - author, - TestWasms::TxProposalCode.read_bytes(), - 12, - ); - vec![ - "init-proposal", - "--data-path", - valid_proposal_json_path.to_str().unwrap(), - "--signing-keys", - source, - "--gas-limit", - "2000000", - "--node", - &validator_0_rpc, - ] - .into_iter() - .map(|x| x.to_owned()) - .collect() - }), - ]; - - for (ix, tx_args) in txs_args.into_iter().enumerate() { - let key_alias = format!("key-{ix}"); - - // 2a. Generate a new key for an implicit account. - let mut cmd = run!( - test, - Bin::Wallet, - &[ - "gen", - "--alias", - &key_alias, - "--unsafe-dont-encrypt", - "--raw" - ], - Some(20), - )?; - cmd.assert_success(); - - // Apply the key_alias once the key is generated to obtain tx args - let tx_args = tx_args(&key_alias); - - // 2b. Send some funds to the implicit account. - let credit_args = [ - "transfer", - "--source", - BERTHA, - "--target", - &key_alias, - "--token", - NAM, - "--amount", - "1000", - "--signing-keys", - BERTHA_KEY, - "--node", - &validator_0_rpc, - ]; - let mut client = run!(test, Bin::Client, credit_args, Some(40))?; - client.assert_success(); - - // 2c. Submit the tx with the implicit account as the source. - let expected_reveal = "Submitting a tx to reveal the public key"; - let mut client = run!(test, Bin::Client, &tx_args, Some(40))?; - client.exp_string(expected_reveal)?; - client.assert_success(); - - // 2d. Submit same tx again, this time the client shouldn't reveal - // again. - let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - let unread = client.exp_eof()?; - assert!(!unread.contains(expected_reveal)) - } - - Ok(()) -} - #[test] fn test_epoch_sleep() -> Result<()> { // Use slightly longer epochs to give us time to sleep @@ -3199,7 +1566,7 @@ fn test_epoch_sleep() -> Result<()> { /// Prepare proposal data in the test's temp dir from the given source address. /// This can be submitted with "init-proposal" command. pub fn prepare_proposal_data( - test: &setup::Test, + test_dir: impl AsRef, id: u64, source: Address, data: impl serde::Serialize, @@ -3228,23 +1595,11 @@ pub fn prepare_proposal_data( }); let valid_proposal_json_path = - test.test_dir.path().join("valid_proposal.json"); + test_dir.as_ref().join("valid_proposal.json"); write_json_file(valid_proposal_json_path.as_path(), valid_proposal_json); valid_proposal_json_path } -/// Prepare steward commission reward in the test temp directory. -/// This can be submitted with "update-steward-commission" command. -pub fn prepare_steward_commission_update_data( - test: &setup::Test, - data: impl serde::Serialize, -) -> PathBuf { - let valid_commission_json_path = - test.test_dir.path().join("commission.json"); - write_json_file(valid_commission_json_path.as_path(), &data); - valid_commission_json_path -} - #[test] fn deactivate_and_reactivate_validator() -> Result<()> { let pipeline_len = 2; @@ -3419,117 +1774,6 @@ fn deactivate_and_reactivate_validator() -> Result<()> { Ok(()) } -/// Change validator metadata -#[test] -fn change_validator_metadata() -> Result<()> { - let test = setup::single_node_net()?; - - set_ethereum_bridge_mode( - &test, - &test.net.chain_id, - Who::Validator(0), - ethereum_bridge::ledger::Mode::Off, - None, - ); - - // 1. Run the ledger node - let _bg_ledger = - start_namada_ledger_node_wait_wasm(&test, Some(0), Some(40))? - .background(); - - let validator_0_rpc = get_actor_rpc(&test, Who::Validator(0)); - - // 2. Query the validator metadata loaded from genesis - let metadata_query_args = vec![ - "validator-metadata", - "--validator", - "validator-0", - "--node", - &validator_0_rpc, - ]; - let mut client = - run!(test, Bin::Client, metadata_query_args.clone(), Some(40))?; - client.exp_string("Email:")?; - client.exp_string("No description")?; - client.exp_string("No website")?; - client.exp_string("No discord handle")?; - client.exp_string("commission rate:")?; - client.exp_string("max change per epoch:")?; - client.assert_success(); - - // 3. Add some metadata to the validator - let metadata_change_args = vec![ - "change-metadata", - "--validator", - "validator-0", - "--email", - "theokayestvalidator@namada.net", - "--description", - "We are just an okay validator node trying to get by", - "--website", - "theokayestvalidator.com", - "--node", - &validator_0_rpc, - ]; - let mut client = run_as!( - test, - Who::Validator(0), - Bin::Client, - metadata_change_args, - Some(40) - )?; - client.exp_string(TX_APPLIED_SUCCESS)?; - client.assert_success(); - - // 4. Query the metadata after the change - let mut client = - run!(test, Bin::Client, metadata_query_args.clone(), Some(40))?; - client.exp_string("Email: theokayestvalidator@namada.net")?; - client.exp_string( - "Description: We are just an okay validator node trying to get by", - )?; - client.exp_string("Website: theokayestvalidator.com")?; - client.exp_string("No discord handle")?; - client.exp_string("commission rate:")?; - client.exp_string("max change per epoch:")?; - client.assert_success(); - - // 5. Remove the validator website - let metadata_change_args = vec![ - "change-metadata", - "--validator", - "validator-0", - "--website", - "", - "--node", - &validator_0_rpc, - ]; - let mut client = run_as!( - test, - Who::Validator(0), - Bin::Client, - metadata_change_args, - Some(40) - )?; - client.exp_string(TX_APPLIED_SUCCESS)?; - client.assert_success(); - - // 6. Query the metadata to see that the validator website is removed - let mut client = - run!(test, Bin::Client, metadata_query_args.clone(), Some(40))?; - client.exp_string("Email: theokayestvalidator@namada.net")?; - client.exp_string( - "Description: We are just an okay validator node trying to get by", - )?; - client.exp_string("No website")?; - client.exp_string("No discord handle")?; - client.exp_string("commission rate:")?; - client.exp_string("max change per epoch:")?; - client.assert_success(); - - Ok(()) -} - #[test] fn test_invalid_validator_txs() -> Result<()> { let pipeline_len = 2; @@ -3870,7 +2114,7 @@ fn proposal_change_shielded_reward() -> Result<()> { // 2. Submit valid proposal let albert = find_address(&test, ALBERT)?; let valid_proposal_json_path = prepare_proposal_data( - &test, + test.test_dir.path(), 0, albert, TestWasms::TxProposalMaspRewards.read_bytes(), diff --git a/crates/tests/src/e2e/setup.rs b/crates/tests/src/e2e/setup.rs index 21a4cd6b90..267ea15d1d 100644 --- a/crates/tests/src/e2e/setup.rs +++ b/crates/tests/src/e2e/setup.rs @@ -869,6 +869,7 @@ impl NamadaCmd { } /// Assert that the process exited with failure + #[allow(dead_code)] pub fn assert_failure(&mut self) { // Make sure that there is no unread output first let _ = self.exp_eof().unwrap(); @@ -1230,7 +1231,7 @@ where #[allow(dead_code)] pub mod constants { // Paths to the WASMs used for tests - pub use namada_sdk::tx::{TX_IBC_WASM, TX_TRANSFER_WASM, VP_USER_WASM}; + pub use namada_sdk::tx::TX_IBC_WASM; // User addresses aliases pub const ALBERT: &str = "Albert"; diff --git a/crates/tests/src/integration.rs b/crates/tests/src/integration.rs index 1a7c84dbfb..00423e8dc0 100644 --- a/crates/tests/src/integration.rs +++ b/crates/tests/src/integration.rs @@ -1,2 +1,4 @@ +mod helpers; +mod ledger_tests; mod masp; mod setup; diff --git a/crates/tests/src/integration/helpers.rs b/crates/tests/src/integration/helpers.rs new file mode 100644 index 0000000000..b8268858e7 --- /dev/null +++ b/crates/tests/src/integration/helpers.rs @@ -0,0 +1,68 @@ +use std::path::PathBuf; +use std::str::FromStr; + +use eyre::eyre; +use namada_apps::node::ledger::shell::testing::client::run; +use namada_apps::node::ledger::shell::testing::node::MockNode; +use namada_apps::node::ledger::shell::testing::utils::{Bin, CapturedOutput}; +use namada_core::address::Address; + +/// Query the wallet to get an address from a given alias. +pub fn find_address( + node: &MockNode, + alias: impl AsRef, +) -> eyre::Result
{ + let captured = CapturedOutput::of(|| { + run( + node, + Bin::Wallet, + vec!["find", "--addr", "--alias", alias.as_ref()], + ) + }); + assert!(captured.result.is_ok()); + assert!(captured.contains("Found transparent address:")); + let matched = captured.matches("\".*\": .*").unwrap(); + let address_str = strip_trailing_newline(matched) + .trim() + .rsplit_once(' ') + .unwrap() + .1; + let address = Address::from_str(address_str).map_err(|e| { + eyre!(format!( + "Address: {} parsed from {}, Error: {}", + address_str, matched, e, + )) + })?; + println!("Found {}", address); + Ok(address) +} + +fn strip_trailing_newline(input: &str) -> &str { + input + .strip_suffix("\r\n") + .or_else(|| input.strip_suffix('\n')) + .unwrap_or(input) +} + +pub fn prepare_steward_commission_update_data( + test_dir: &std::path::Path, + data: impl serde::Serialize, +) -> PathBuf { + let valid_commission_json_path = test_dir.join("commission.json"); + write_json_file(valid_commission_json_path.as_path(), &data); + valid_commission_json_path +} + +fn write_json_file(proposal_path: &std::path::Path, proposal_content: T) +where + T: serde::Serialize, +{ + let intent_writer = std::fs::OpenOptions::new() + .create(true) + .write(true) + .truncate(true) + .open(proposal_path) + .unwrap(); + + serde_json::to_writer(intent_writer, &proposal_content).unwrap(); +} diff --git a/crates/tests/src/integration/ledger_tests.rs b/crates/tests/src/integration/ledger_tests.rs new file mode 100644 index 0000000000..a7b0b8ca26 --- /dev/null +++ b/crates/tests/src/integration/ledger_tests.rs @@ -0,0 +1,1513 @@ +use std::collections::BTreeSet; +use std::str::FromStr; + +use assert_matches::assert_matches; +use borsh_ext::BorshSerializeExt; +use color_eyre::eyre::Result; +use data_encoding::HEXLOWER; +use namada::core::collections::HashMap; +use namada::token; +use namada_apps::node::ledger::shell::testing::client::run; +use namada_apps::node::ledger::shell::testing::utils::{Bin, CapturedOutput}; +use namada_apps::wallet::defaults; +use namada_core::dec::Dec; +use namada_core::storage::Epoch; +use namada_core::token::NATIVE_MAX_DECIMAL_PLACES; +use namada_sdk::tx::{TX_TRANSFER_WASM, VP_USER_WASM}; +use namada_test_utils::TestWasms; +use test_log::test; + +use crate::e2e::ledger_tests::prepare_proposal_data; +use crate::e2e::setup::constants::{ + ALBERT, ALBERT_KEY, BERTHA, BERTHA_KEY, CHRISTEL, CHRISTEL_KEY, DAEWON, + ESTER, GOVERNANCE_ADDRESS, NAM, +}; +use crate::integration::helpers::{ + find_address, prepare_steward_commission_update_data, +}; +use crate::integration::setup; +use crate::strings::{TX_APPLIED_SUCCESS, TX_FAILED, TX_REJECTED}; +use crate::tx::tx_host_env::gov_storage::proposal::{ + PGFInternalTarget, PGFTarget, +}; +use crate::tx::tx_host_env::governance::cli::onchain::{ + PgfFunding, StewardsUpdate, +}; +use crate::tx::tx_host_env::governance::pgf::cli::steward::Commission; + +/// In this test we: +/// 1. Run the ledger node +/// 2. Submit a token transfer tx +/// 3. Submit a transaction to update an account's validity predicate +/// 4. Submit a custom tx +/// 5. Submit a tx to initialize a new account +/// 6. Submit a tx to withdraw from faucet account (requires PoW challenge +/// solution) +/// 7. Query token balance +/// 8. Query the raw bytes of a storage key +#[test] +fn ledger_txs_and_queries() -> Result<()> { + // This address doesn't matter for tests. But an argument is required. + let validator_one_rpc = "http://127.0.0.1:26567"; + + let (node, _services) = setup::setup()?; + let transfer = token::Transfer { + source: defaults::bertha_address(), + target: defaults::albert_address(), + token: node.native_token(), + amount: token::DenominatedAmount::new( + token::Amount::native_whole(10), + token::NATIVE_MAX_DECIMAL_PLACES.into(), + ), + key: None, + shielded: None, + } + .serialize_to_vec(); + let tx_data_path = node.test_dir.path().join("tx.data"); + std::fs::write(&tx_data_path, transfer).unwrap(); + let tx_data_path = tx_data_path.to_string_lossy(); + + let multisig_account = + format!("{},{},{}", BERTHA_KEY, ALBERT_KEY, CHRISTEL_KEY); + + let txs_args = vec![ + // 2. Submit a token transfer tx (from an established account) + vec![ + "transfer", + "--source", + BERTHA, + "--target", + ALBERT, + "--token", + NAM, + "--amount", + "10.1", + "--signing-keys", + BERTHA_KEY, + "--node", + &validator_one_rpc, + ], + // Submit a token transfer tx (from an ed25519 implicit account) + vec![ + "transfer", + "--source", + DAEWON, + "--target", + ALBERT, + "--token", + NAM, + "--amount", + "10.1", + "--signing-keys", + DAEWON, + "--node", + &validator_one_rpc, + ], + // Submit a token transfer tx (from a secp256k1 implicit account) + vec![ + "transfer", + "--source", + ESTER, + "--target", + ALBERT, + "--token", + NAM, + "--amount", + "10.1", + "--node", + &validator_one_rpc, + ], + // 3. Submit a transaction to update an account's validity + // predicate + vec![ + "update-account", + "--address", + BERTHA, + "--code-path", + VP_USER_WASM, + "--signing-keys", + BERTHA_KEY, + "--node", + &validator_one_rpc, + ], + // 4. Submit a custom tx + vec![ + "tx", + "--code-path", + TX_TRANSFER_WASM, + "--data-path", + &tx_data_path, + "--owner", + BERTHA, + "--signing-keys", + BERTHA_KEY, + "--node", + &validator_one_rpc, + ], + // 5. Submit a tx to initialize a new account + vec![ + "init-account", + "--public-keys", + // Value obtained from `namada::core::key::ed25519::tests::gen_keypair` + "tpknam1qpqfzxu3gt05jx2mvg82f4anf90psqerkwqhjey4zlqv0qfgwuvkzt5jhkp", + "--threshold", + "1", + "--code-path", + VP_USER_WASM, + "--alias", + "Test-Account", + "--signing-keys", + BERTHA_KEY, + "--node", + &validator_one_rpc, + ], + // 5. Submit a tx to initialize a new multisig account + vec![ + "init-account", + "--public-keys", + &multisig_account, + "--threshold", + "2", + "--code-path", + VP_USER_WASM, + "--alias", + "Test-Account-2", + "--signing-keys", + BERTHA_KEY, + "--node", + &validator_one_rpc, + ], + ]; + + for tx_args in &txs_args { + for &dry_run in &[true, false] { + let tx_args = if dry_run && tx_args[0] == "tx" { + continue; + } else if dry_run { + [tx_args.clone(), vec!["--dry-run"]].concat() + } else { + tx_args.clone() + }; + let captured = + CapturedOutput::of(|| run(&node, Bin::Client, tx_args)); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains(TX_APPLIED_SUCCESS)); + } + } + + let query_args_and_expected_response = vec![ + // 7. Query token balance + ( + vec![ + "balance", + "--owner", + BERTHA, + "--token", + NAM, + "--node", + &validator_one_rpc, + ], + // expect a decimal + vec![r"nam: \d+(\.\d+)?"], + ), + // Unspecified token expect all tokens from wallet derived from genesis + ( + vec!["balance", "--owner", ALBERT, "--node", &validator_one_rpc], + // expect all genesis tokens, sorted by alias + vec![ + r"apfel: \d+(\.\d+)?", + r"btc: \d+(\.\d+)?", + r"dot: \d+(\.\d+)?", + r"eth: \d+(\.\d+)?", + r"kartoffel: \d+(\.\d+)?", + r"schnitzel: \d+(\.\d+)?", + ], + ), + ( + vec![ + "query-account", + "--owner", + "Test-Account-2", + "--node", + &validator_one_rpc, + ], + vec!["Threshold: 2"], + ), + ]; + + for (query_args, expected) in query_args_and_expected_response { + // Run as a non-validator + let captured = + CapturedOutput::of(|| run(&node, Bin::Client, query_args)); + assert_matches!(captured.result, Ok(_)); + for pattern in expected { + assert!(captured.contains(pattern)); + } + } + + let christel = defaults::christel_address(); + let nam = node.native_token(); + // as setup in `genesis/e2e-tests-single-node.toml` + let christel_balance = token::Amount::native_whole(2000000); + let storage_key = + token::storage_key::balance_key(&nam, &christel).to_string(); + let query_args_and_expected_response = vec![ + // 8. Query storage key and get hex-encoded raw bytes + ( + vec![ + "query-bytes", + "--storage-key", + &storage_key, + "--node", + &validator_one_rpc, + ], + // expect hex encoded of borsh encoded bytes + HEXLOWER.encode(&christel_balance.serialize_to_vec()), + ), + ]; + for (query_args, expected) in query_args_and_expected_response { + let captured = + CapturedOutput::of(|| run(&node, Bin::Client, query_args)); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains(&expected)); + } + + Ok(()) +} + +/// In this test we: +/// 1. Run the ledger node +/// 2. Submit an invalid transaction (disallowed by state machine) +/// 3. Check that the state was changed +/// 5. Submit and invalid transactions (malformed) +#[test] +fn invalid_transactions() -> Result<()> { + // This address doesn't matter for tests. But an argument is required. + let validator_one_rpc = "http://127.0.0.1:26567"; + + let (node, _services) = setup::setup()?; + + // 2. Submit an invalid transaction (trying to transfer tokens should fail + // in the user's VP due to the wrong signer) + let tx_args = vec![ + "transfer", + "--source", + BERTHA, + "--target", + ALBERT, + "--token", + NAM, + "--amount", + "1", + "--signing-keys", + ALBERT_KEY, + "--node", + &validator_one_rpc, + "--force", + ]; + + let captured = CapturedOutput::of(|| run(&node, Bin::Client, tx_args)); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains(TX_REJECTED)); + + node.finalize_and_commit(); + // There should be state now + { + let locked = node.shell.lock().unwrap(); + assert_ne!(locked.last_state().last_block_app_hash, Default::default()); + } + + let daewon_lower = DAEWON.to_lowercase(); + let tx_args = vec![ + "transfer", + "--source", + DAEWON, + "--signing-keys", + &daewon_lower, + "--target", + ALBERT, + "--token", + BERTHA, + "--amount", + "1000000.1", + // Force to ignore client check that fails on the balance check of the + // source address + "--force", + "--node", + &validator_one_rpc, + ]; + let captured = CapturedOutput::of(|| run(&node, Bin::Client, tx_args)); + assert!(captured.contains(TX_FAILED)); + + Ok(()) +} + +/// Test for claiming PoS inflationary rewards +/// +/// 1. Run the ledger node +/// 2. Wait some epochs while inflationary rewards accumulate in the PoS system +/// 3. Submit a claim-rewards tx +/// 4. Query the validator's balance before and after the claim tx to ensure +/// that reward tokens were actually transferred +#[test] +fn pos_rewards() -> Result<()> { + // This address doesn't matter for tests. But an argument is required. + let validator_one_rpc = "http://127.0.0.1:26567"; + + let (mut node, _services) = setup::setup()?; + // Query the current rewards for the validator self-bond + let tx_args = vec![ + "rewards", + "--validator", + "validator-0", + "--node", + &validator_one_rpc, + ]; + let captured = CapturedOutput::of(|| run(&node, Bin::Client, tx_args)); + assert_matches!(captured.result, Ok(_)); + let res = captured + .matches(r"Current rewards available for claim: [0-9\.]+ NAM") + .expect("Test failed"); + + let words = res.split(' ').collect::>(); + let res = words[words.len() - 2]; + let mut last_amount = token::Amount::from_str( + res.split(' ').last().unwrap(), + NATIVE_MAX_DECIMAL_PLACES, + ) + .unwrap(); + + for _ in 0..4 { + node.next_epoch(); + // Query the current rewards for the validator self-bond and see that it + // grows + let tx_args = vec![ + "rewards", + "--validator", + "validator-0", + "--node", + &validator_one_rpc, + ]; + let captured = CapturedOutput::of(|| run(&node, Bin::Client, tx_args)); + assert_matches!(captured.result, Ok(_)); + let res = captured + .matches(r"Current rewards available for claim: [0-9\.]+ NAM") + .expect("Test failed"); + + let words = res.split(' ').collect::>(); + let res = words[words.len() - 2]; + let amount = token::Amount::from_str( + res.split(' ').last().unwrap(), + NATIVE_MAX_DECIMAL_PLACES, + ) + .unwrap(); + + assert!(amount > last_amount); + last_amount = amount; + } + + // Query the balance of the validator account + let query_balance_args = vec![ + "balance", + "--owner", + "validator-0", + "--token", + NAM, + "--node", + &validator_one_rpc, + ]; + let captured = + CapturedOutput::of(|| run(&node, Bin::Client, query_balance_args)); + assert_matches!(captured.result, Ok(_)); + let res = captured.matches(r"nam: [0-9\.]+").expect("Test failed"); + let amount_pre = token::Amount::from_str( + res.split(' ').last().unwrap(), + NATIVE_MAX_DECIMAL_PLACES, + ) + .unwrap(); + + // Claim rewards + let tx_args = vec![ + "claim-rewards", + "--validator", + "validator-0", + "--signing-keys", + "validator-0-account-key", + "--node", + &validator_one_rpc, + ]; + let captured = CapturedOutput::of(|| run(&node, Bin::Client, tx_args)); + println!("{:?}", captured.result); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains(TX_APPLIED_SUCCESS)); + + // Query the validator balance again and check that the balance has grown + // after claiming + let query_balance_args = vec![ + "balance", + "--owner", + "validator-0", + "--token", + NAM, + "--node", + &validator_one_rpc, + ]; + let captured = + CapturedOutput::of(|| run(&node, Bin::Client, query_balance_args)); + assert_matches!(captured.result, Ok(_)); + let res = captured.matches(r"nam: [0-9\.]+").expect("Test failed"); + let amount_post = token::Amount::from_str( + res.split(' ').last().unwrap(), + NATIVE_MAX_DECIMAL_PLACES, + ) + .unwrap(); + assert!(amount_post > amount_pre); + + Ok(()) +} + +/// Test for PoS bonds and unbonds queries. +/// +/// 1. Run the ledger node +/// 2. Submit a delegation to the genesis validator +/// 3. Wait for epoch 4 +/// 4. Submit another delegation to the genesis validator +/// 5. Submit an unbond of the delegation +/// 6. Wait for epoch 7 +/// 7. Check the output of the bonds query +#[test] +fn test_bond_queries() -> Result<()> { + // This address doesn't matter for tests. But an argument is required. + let validator_one_rpc = "http://127.0.0.1:26567"; + // 1. start the ledger node + let (mut node, _services) = setup::setup()?; + + let validator_alias = "validator-0"; + // 2. Submit a delegation to the genesis validator + let tx_args = vec![ + "bond", + "--validator", + validator_alias, + "--amount", + "100", + "--ledger-address", + &validator_one_rpc, + ]; + let captured = CapturedOutput::of(|| run(&node, Bin::Client, tx_args)); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains(TX_APPLIED_SUCCESS)); + + // 3. Submit a delegation to the genesis validator + let tx_args = vec![ + "bond", + "--validator", + "validator-0", + "--source", + BERTHA, + "--amount", + "200", + "--signing-keys", + BERTHA_KEY, + "--ledger-address", + &validator_one_rpc, + ]; + + let captured = CapturedOutput::of(|| run(&node, Bin::Client, tx_args)); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains(TX_APPLIED_SUCCESS)); + + // 3. Wait for epoch 4 + for _ in 0..4 { + node.next_epoch(); + } + + // 4. Submit another delegation to the genesis validator + let tx_args = vec![ + "bond", + "--validator", + validator_alias, + "--source", + BERTHA, + "--amount", + "300", + "--signing-keys", + BERTHA_KEY, + "--ledger-address", + &validator_one_rpc, + ]; + let captured = CapturedOutput::of(|| run(&node, Bin::Client, tx_args)); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains(TX_APPLIED_SUCCESS)); + + // 5. Submit an unbond of the delegation + let tx_args = vec![ + "unbond", + "--validator", + validator_alias, + "--source", + BERTHA, + "--amount", + "412", + "--signing-keys", + BERTHA_KEY, + "--ledger-address", + &validator_one_rpc, + ]; + + let captured = CapturedOutput::of(|| run(&node, Bin::Client, tx_args)); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains(TX_APPLIED_SUCCESS)); + + let res = captured + .matches(r"withdrawable starting from epoch [0-9]+") + .expect("Test failed"); + let withdraw_epoch = + Epoch::from_str(res.split(' ').last().unwrap()).unwrap(); + + // 6. Wait for withdraw_epoch + loop { + if node.current_epoch() >= withdraw_epoch { + break; + } else { + node.next_epoch(); + } + } + + // 7. Check the output of the bonds query + let tx_args = vec!["bonds", "--ledger-address", &validator_one_rpc]; + let captured = CapturedOutput::of(|| run(&node, Bin::Client, tx_args)); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains( + "All bonds total active: 120188.000000 +All bonds total: 120188.000000 +All bonds total slashed: 0.000000 +All unbonds total active: 412.000000 +All unbonds total: 412.000000 +All unbonds total withdrawable: 412.000000 +All unbonds total slashed: 0.000000", + )); + + Ok(()) +} + +/// In this test we: +/// 1. Run the ledger node +/// 2. Submit a valid proposal +/// 3. Query the proposal +/// 4. Query token balance (submitted funds) +/// 5. Query governance address balance +/// 6. Submit an invalid proposal +/// 7. Check invalid proposal was not accepted +/// 8. Query token balance (funds shall not be submitted) +/// 9. Send a yay vote from a validator +/// 10. Send a yay vote from a normal user +/// 11. Query the proposal and check the result +/// 12. Wait proposal grace and check proposal author funds +/// 13. Check governance address funds are 0 +/// 14. Query the new parameters +/// 15. Try to initialize a new account which should fail +/// 16. Submit a tx that triggers an already existing account which should +/// succeed +#[test] +fn proposal_submission() -> Result<()> { + // This address doesn't matter for tests. But an argument is required. + let validator_one_rpc = "http://127.0.0.1:26567"; + // 1. start the ledger node + let (mut node, _services) = setup::setup()?; + + // 1.1 Delegate some token + let tx_args = vec![ + "bond", + "--validator", + "validator-0", + "--source", + BERTHA, + "--amount", + "900", + "--node", + &validator_one_rpc, + ]; + let captured = CapturedOutput::of(|| run(&node, Bin::Client, tx_args)); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains(TX_APPLIED_SUCCESS)); + + // 2. Submit valid proposal + let albert = defaults::albert_address(); + let valid_proposal_json_path = prepare_proposal_data( + node.test_dir.path(), + 0, + albert.clone(), + TestWasms::TxProposalCode.read_bytes(), + 12, + ); + + let submit_proposal_args = vec![ + "init-proposal", + "--data-path", + valid_proposal_json_path.to_str().unwrap(), + "--gas-limit", + "2000000", + "--node", + &validator_one_rpc, + ]; + let captured = + CapturedOutput::of(|| run(&node, Bin::Client, submit_proposal_args)); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains(TX_APPLIED_SUCCESS)); + + // 3. Query the proposal + let proposal_query_args = vec![ + "query-proposal", + "--proposal-id", + "0", + "--node", + &validator_one_rpc, + ]; + + let captured = + CapturedOutput::of(|| run(&node, Bin::Client, proposal_query_args)); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains("Proposal Id: 0")); + + // 4. Query token balance proposal author (submitted funds) + let query_balance_args = vec![ + "balance", + "--owner", + ALBERT, + "--token", + NAM, + "--node", + &validator_one_rpc, + ]; + let captured = + CapturedOutput::of(|| run(&node, Bin::Client, query_balance_args)); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains("nam: 1979500")); + + // 5. Query token balance governance + let query_balance_args = vec![ + "balance", + "--owner", + GOVERNANCE_ADDRESS, + "--token", + NAM, + "--node", + &validator_one_rpc, + ]; + let captured = + CapturedOutput::of(|| run(&node, Bin::Client, query_balance_args)); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains("nam: 500")); + + // 6. Submit an invalid proposal + // proposal is invalid due to voting_end_epoch - voting_start_epoch < 3 + let invalid_proposal_json = prepare_proposal_data( + node.test_dir.path(), + 1, + albert, + TestWasms::TxProposalCode.read_bytes(), + 1, + ); + + let submit_proposal_args = vec![ + "init-proposal", + "--data-path", + invalid_proposal_json.to_str().unwrap(), + "--node", + &validator_one_rpc, + ]; + + let captured = + CapturedOutput::of(|| run(&node, Bin::Client, submit_proposal_args)); + assert!(captured.result.is_err()); + println!("{:?}", captured.result); + assert!(captured.err_contains( + "Proposal data are invalid: Invalid proposal start epoch: 1 must be \ + greater than current epoch .* and a multiple of 3" + )); + + // 7. Check invalid proposal was not submitted + let proposal_query_args = vec![ + "query-proposal", + "--proposal-id", + "1", + "--node", + &validator_one_rpc, + ]; + let captured = + CapturedOutput::of(|| run(&node, Bin::Client, proposal_query_args)); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains("No proposal found with id: 1")); + + // 8. Query token balance (funds shall not be submitted) + let query_balance_args = vec![ + "balance", + "--owner", + ALBERT, + "--token", + NAM, + "--node", + &validator_one_rpc, + ]; + let captured = + CapturedOutput::of(|| run(&node, Bin::Client, query_balance_args)); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains("nam: 1979500")); + + // 9. Send a yay vote from a validator + while node.current_epoch().0 <= 13 { + node.next_epoch(); + } + + let submit_proposal_vote = vec![ + "vote-proposal", + "--proposal-id", + "0", + "--vote", + "yay", + "--address", + "validator-0", + "--node", + &validator_one_rpc, + ]; + + let captured = + CapturedOutput::of(|| run(&node, Bin::Client, submit_proposal_vote)); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains(TX_APPLIED_SUCCESS)); + + let submit_proposal_vote_delegator = vec![ + "vote-proposal", + "--proposal-id", + "0", + "--vote", + "nay", + "--address", + BERTHA, + "--node", + &validator_one_rpc, + ]; + + let captured = CapturedOutput::of(|| { + run(&node, Bin::Client, submit_proposal_vote_delegator) + }); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains(TX_APPLIED_SUCCESS)); + + // 10. Send a yay vote from a non-validator/non-delegator user + let submit_proposal_vote = vec![ + "vote-proposal", + "--proposal-id", + "0", + "--vote", + "yay", + "--address", + CHRISTEL, + "--node", + &validator_one_rpc, + ]; + + // this is valid because the client filter CHRISTEL delegation and there are + // none + let captured = + CapturedOutput::of(|| run(&node, Bin::Client, submit_proposal_vote)); + assert!(captured.result.is_err()); + assert!(captured.err_contains("Voter address must have delegations")); + + // 11. Query the proposal and check the result + while node.current_epoch().0 <= 25 { + node.next_epoch(); + } + + let query_proposal = vec![ + "query-proposal-result", + "--proposal-id", + "0", + "--node", + &validator_one_rpc, + ]; + let captured = + CapturedOutput::of(|| run(&node, Bin::Client, query_proposal)); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains("Proposal Id: 0")); + let expected = regex::escape( + "passed with 120000.000000 yay votes, 900.000000 nay votes and \ + 0.000000 abstain votes, total voting power: 120900.000000, threshold \ + (fraction) of total voting power needed to tally: 80600.000000 \ + (0.666666666666)", + ); + assert!(captured.contains(&expected)); + + // 12. Wait proposal grace and check proposal author funds + while node.current_epoch().0 < 31 { + node.next_epoch(); + } + + let query_balance_args = vec![ + "balance", + "--owner", + ALBERT, + "--token", + NAM, + "--node", + &validator_one_rpc, + ]; + let captured = + CapturedOutput::of(|| run(&node, Bin::Client, query_balance_args)); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains("nam: 1980000")); + + // 13. Check if governance funds are 0 + let query_balance_args = vec![ + "balance", + "--owner", + GOVERNANCE_ADDRESS, + "--token", + NAM, + "--node", + &validator_one_rpc, + ]; + let captured = + CapturedOutput::of(|| run(&node, Bin::Client, query_balance_args)); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains("nam: 0")); + + // 14. Query parameters + let query_protocol_parameters = + vec!["query-protocol-parameters", "--node", &validator_one_rpc]; + let captured = CapturedOutput::of(|| { + run(&node, Bin::Client, query_protocol_parameters) + }); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains(".*Min. proposal grace epochs: 9.*")); + + // 15. Try to initialize a new account with the no more allowlisted vp + let init_account = vec![ + "init-account", + "--public-keys", + // Value obtained from `namada::core::key::ed25519::tests::gen_keypair` + "tpknam1qpqfzxu3gt05jx2mvg82f4anf90psqerkwqhjey4zlqv0qfgwuvkzt5jhkp", + "--threshold", + "1", + "--code-path", + VP_USER_WASM, + "--alias", + "Test-Account", + "--signing-keys", + BERTHA_KEY, + "--node", + &validator_one_rpc, + ]; + let captured = CapturedOutput::of(|| run(&node, Bin::Client, init_account)); + assert_matches!(captured.result, Ok(_)); + assert!( + captured.contains(".*VP code is not allowed in allowlist parameter.*") + ); + + // 16. Submit a tx touching a previous account with the no more allowlisted + // vp and verify that the transaction succeeds, i.e. the non allowlisted + // vp can still run + let transfer = vec![ + "transfer", + "--source", + BERTHA, + "--target", + ALBERT, + "--token", + NAM, + "--amount", + "10", + "--signing-keys", + BERTHA_KEY, + "--node", + &validator_one_rpc, + ]; + let captured = CapturedOutput::of(|| run(&node, Bin::Client, transfer)); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains(TX_APPLIED_SUCCESS)); + + Ok(()) +} + +/// Test submission and vote of a PGF proposal +/// +/// 1. Submit proposal +/// 2. Query the proposal +/// 3. Vote for the accepted proposals and query balances +/// 4. Query the proposal and check the result is the one voted by the validator +/// (majority) +/// 5. Wait proposals grace and check proposal author funds +/// 6. Check if governance funds are 0 +/// 7. Query pgf stewards +/// 8. Submit proposal funding +/// 9. Query the funding proposal +/// 10. Wait proposals grace and check proposal author funds +/// 11. Query pgf fundings +#[test] +fn pgf_governance_proposal() -> Result<()> { + // This address doesn't matter for tests. But an argument is required. + let validator_one_rpc = "http://127.0.0.1:26567"; + // 1. start the ledger node + let (mut node, _services) = setup::setup()?; + + let tx_args = vec![ + "bond", + "--validator", + "validator-0", + "--source", + BERTHA, + "--amount", + "900", + "--ledger-address", + &validator_one_rpc, + ]; + let captured = CapturedOutput::of(|| run(&node, Bin::Client, tx_args)); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains(TX_APPLIED_SUCCESS)); + + // 1. Submit proposal + let albert = defaults::albert_address(); + let pgf_stewards = StewardsUpdate { + add: Some(albert.clone()), + remove: vec![], + }; + + let valid_proposal_json_path = prepare_proposal_data( + node.test_dir.path(), + 0, + albert, + pgf_stewards, + 12, + ); + let submit_proposal_args = vec![ + "init-proposal", + "--pgf-stewards", + "--data-path", + valid_proposal_json_path.to_str().unwrap(), + "--ledger-address", + &validator_one_rpc, + ]; + let captured = + CapturedOutput::of(|| run(&node, Bin::Client, submit_proposal_args)); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains(TX_APPLIED_SUCCESS)); + + // 2. Query the proposal + let proposal_query_args = vec![ + "query-proposal", + "--proposal-id", + "0", + "--ledger-address", + &validator_one_rpc, + ]; + let captured = + CapturedOutput::of(|| run(&node, Bin::Client, proposal_query_args)); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains("Proposal Id: 0")); + + // Query token balance proposal author (submitted funds) + let query_balance_args = vec![ + "balance", + "--owner", + ALBERT, + "--token", + NAM, + "--ledger-address", + &validator_one_rpc, + ]; + let captured = + CapturedOutput::of(|| run(&node, Bin::Client, query_balance_args)); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains("nam: 1979500")); + + // Query token balance governance + let query_balance_args = vec![ + "balance", + "--owner", + GOVERNANCE_ADDRESS, + "--token", + NAM, + "--ledger-address", + &validator_one_rpc, + ]; + let captured = + CapturedOutput::of(|| run(&node, Bin::Client, query_balance_args)); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains("nam: 500")); + + // 3. Send a yay vote from a validator + while node.current_epoch().0 <= 13 { + node.next_epoch(); + } + + let submit_proposal_vote = vec![ + "vote-proposal", + "--proposal-id", + "0", + "--vote", + "yay", + "--address", + "validator-0", + "--ledger-address", + &validator_one_rpc, + ]; + let captured = + CapturedOutput::of(|| run(&node, Bin::Client, submit_proposal_vote)); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains(TX_APPLIED_SUCCESS)); + + // Send different yay vote from delegator to check majority on 1/3 + let submit_proposal_vote_delegator = vec![ + "vote-proposal", + "--proposal-id", + "0", + "--vote", + "yay", + "--address", + BERTHA, + "--ledger-address", + &validator_one_rpc, + ]; + let captured = CapturedOutput::of(|| { + run(&node, Bin::Client, submit_proposal_vote_delegator) + }); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains(TX_APPLIED_SUCCESS)); + + // 4. Query the proposal and check the result is the one voted by the + // validator (majority) + while node.current_epoch().0 <= 25 { + node.next_epoch(); + } + + let query_proposal = vec![ + "query-proposal-result", + "--proposal-id", + "0", + "--ledger-address", + &validator_one_rpc, + ]; + let captured = + CapturedOutput::of(|| run(&node, Bin::Client, query_proposal)); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains("passed")); + + // 5. Wait proposals grace and check proposal author funds + while node.current_epoch().0 < 31 { + node.next_epoch(); + } + let query_balance_args = vec![ + "balance", + "--owner", + ALBERT, + "--token", + NAM, + "--ledger-address", + &validator_one_rpc, + ]; + let captured = + CapturedOutput::of(|| run(&node, Bin::Client, query_balance_args)); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains("nam: 1980000")); + + // 6. Check if governance funds are 0 + let query_balance_args = vec![ + "balance", + "--owner", + GOVERNANCE_ADDRESS, + "--token", + NAM, + "--ledger-address", + &validator_one_rpc, + ]; + let captured = + CapturedOutput::of(|| run(&node, Bin::Client, query_balance_args)); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains("nam: 0")); + + // 7. Query pgf stewards + let query_pgf = vec!["query-pgf", "--node", &validator_one_rpc]; + let captured = CapturedOutput::of(|| run(&node, Bin::Client, query_pgf)); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains("Pgf stewards:")); + assert!(captured.contains(&format!("- {}", defaults::albert_address()))); + assert!(captured.contains("Reward distribution:")); + assert!( + captured.contains(&format!("- 1 to {}", defaults::albert_address())) + ); + assert!(captured.contains("Pgf fundings: no fundings are currently set.")); + + // 8. Submit proposal funding + let albert = defaults::albert_address(); + let bertha = defaults::bertha_address(); + let christel = defaults::christel_address(); + + let pgf_funding = PgfFunding { + continuous: vec![PGFTarget::Internal(PGFInternalTarget { + amount: token::Amount::from_u64(10), + target: bertha.clone(), + })], + retro: vec![PGFTarget::Internal(PGFInternalTarget { + amount: token::Amount::from_u64(5), + target: christel, + })], + }; + let valid_proposal_json_path = + prepare_proposal_data(node.test_dir.path(), 1, albert, pgf_funding, 36); + + let submit_proposal_args = vec![ + "init-proposal", + "--pgf-funding", + "--data-path", + valid_proposal_json_path.to_str().unwrap(), + "--ledger-address", + &validator_one_rpc, + ]; + let captured = + CapturedOutput::of(|| run(&node, Bin::Client, submit_proposal_args)); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains(TX_APPLIED_SUCCESS)); + + // 9. Query the funding proposal + let proposal_query_args = vec![ + "query-proposal", + "--proposal-id", + "1", + "--ledger-address", + &validator_one_rpc, + ]; + let captured = + CapturedOutput::of(|| run(&node, Bin::Client, proposal_query_args)); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains("Proposal Id: 1")); + + // 10. Wait proposals grace and check proposal author funds + while node.current_epoch().0 < 55 { + node.next_epoch(); + } + + // 11. Query pgf fundings + let query_pgf = vec!["query-pgf", "--node", &validator_one_rpc]; + let captured = CapturedOutput::of(|| run(&node, Bin::Client, query_pgf)); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains("Pgf fundings")); + assert!(captured.contains(&format!( + "{} for {}", + bertha, + token::Amount::from_u64(10).to_string_native() + ))); + + Ok(()) +} + +/// Test if a steward can correctly change his distribution reward +#[test] +fn pgf_steward_change_commission() -> Result<()> { + // This address doesn't matter for tests. But an argument is required. + let validator_one_rpc = "http://127.0.0.1:26567"; + // 1. start the ledger node + let (node, _services) = setup::initialize_genesis(|mut genesis| { + genesis.parameters.pgf_params.stewards_inflation_rate = + Dec::from_str("0.1").unwrap(); + genesis.parameters.pgf_params.stewards = + BTreeSet::from_iter([defaults::albert_address()]); + genesis + })?; + + // Query pgf stewards + let query_pgf = vec!["query-pgf", "--node", &validator_one_rpc]; + let captured = CapturedOutput::of(|| run(&node, Bin::Client, query_pgf)); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains("Pgf stewards:")); + assert!(captured.contains(&format!("- {}", defaults::albert_address()))); + assert!(captured.contains("Reward distribution:")); + assert!( + captured.contains(&format!("- 1 to {}", defaults::albert_address())) + ); + assert!(captured.contains("Pgf fundings: no fundings are currently set.")); + + let commission = Commission { + reward_distribution: HashMap::from_iter([ + (defaults::albert_address(), Dec::from_str("0.25").unwrap()), + (defaults::bertha_address(), Dec::from_str("0.70").unwrap()), + (defaults::christel_address(), Dec::from_str("0.05").unwrap()), + ]), + }; + let commission_path = prepare_steward_commission_update_data( + node.test_dir.path(), + commission, + ); + // Update steward commissions + let tx_args = vec![ + "update-steward-rewards", + "--steward", + ALBERT, + "--data-path", + commission_path.to_str().unwrap(), + "--node", + &validator_one_rpc, + ]; + let captured = CapturedOutput::of(|| run(&node, Bin::Client, tx_args)); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains(TX_APPLIED_SUCCESS)); + + // 14. Query pgf stewards + let query_pgf = vec!["query-pgf", "--node", &validator_one_rpc]; + let captured = CapturedOutput::of(|| run(&node, Bin::Client, query_pgf)); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains("Pgf stewards:")); + assert!(captured.contains(&format!("- {}", defaults::albert_address()))); + assert!(captured.contains("Reward distribution:")); + assert!( + captured.contains(&format!("- 0.25 to {}", defaults::albert_address())) + ); + assert!( + captured.contains(&format!("- 0.7 to {}", defaults::bertha_address())) + ); + assert!( + captured + .contains(&format!("- 0.05 to {}", defaults::christel_address())) + ); + assert!(captured.contains("Pgf fundings: no fundings are currently set.")); + + Ok(()) +} + +/// In this test we: +/// 1. Run the ledger node +/// 2. For some transactions that need signature authorization: 2a. Generate a +/// new key for an implicit account. 2b. Send some funds to the implicit +/// account. 2c. Submit the tx with the implicit account as the source, that +/// requires that the account has revealed its PK. This should be done by the +/// client automatically. 2d. Submit same tx again, this time the client +/// shouldn't reveal again. +#[test] +fn implicit_account_reveal_pk() -> Result<()> { + // This address doesn't matter for tests. But an argument is required. + let validator_one_rpc = "http://127.0.0.1:26567"; + // 1. start the ledger node + let (node, _services) = setup::setup()?; + // 2. Some transactions that need signature authorization: + #[allow(clippy::type_complexity)] + let txs_args: Vec Vec>> = vec![ + // Submit proposal + Box::new(|source| { + // Gen data for proposal tx + let author = find_address(&node, source).unwrap(); + let valid_proposal_json_path = prepare_proposal_data( + node.test_dir.path(), + 0, + author, + TestWasms::TxProposalCode.read_bytes(), + 12, + ); + vec![ + "init-proposal", + "--data-path", + valid_proposal_json_path.to_str().unwrap(), + "--signing-keys", + source, + "--gas-limit", + "2000000", + "--node", + &validator_one_rpc, + ] + .into_iter() + .map(|x| x.to_owned()) + .collect() + }), + // A token transfer tx + Box::new(|source| { + [ + "transfer", + "--source", + source, + "--target", + ALBERT, + "--token", + NAM, + "--amount", + "10.1", + "--signing-keys", + source, + "--node", + validator_one_rpc, + ] + .into_iter() + .map(|x| x.to_owned()) + .collect() + }), + // A bond + Box::new(|source| { + vec![ + "bond", + "--validator", + "validator-0", + "--source", + source, + "--amount", + "10.1", + "--signing-keys", + source, + "--node", + &validator_one_rpc, + ] + .into_iter() + .map(|x| x.to_owned()) + .collect() + }), + ]; + + for (ix, tx_args) in txs_args.into_iter().enumerate() { + let key_alias = format!("key-{ix}"); + // 2a. Generate a new key for an implicit account. + run( + &node, + Bin::Wallet, + vec![ + "gen", + "--alias", + &key_alias, + "--unsafe-dont-encrypt", + "--raw", + ], + )?; + // Apply the key_alias once the key is generated to obtain tx args + let tx_args = tx_args(&key_alias); + // 2b. Send some funds to the implicit account. + let credit_args = vec![ + "transfer", + "--source", + BERTHA, + "--target", + &key_alias, + "--token", + NAM, + "--amount", + "2000", + "--signing-keys", + BERTHA_KEY, + "--node", + &validator_one_rpc, + ]; + run(&node, Bin::Client, credit_args)?; + node.assert_success(); + + // 2c. Submit the tx with the implicit account as the source. + let captured = CapturedOutput::of(|| { + run( + &node, + Bin::Client, + tx_args.iter().map(|arg| arg.as_ref()).collect(), + ) + }); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains("Submitting a tx to reveal the public key")); + + // 2d. Submit same tx again, this time the client shouldn't reveal + // again. + let captured = CapturedOutput::of(|| { + run( + &node, + Bin::Client, + tx_args.iter().map(|arg| arg.as_ref()).collect(), + ) + }); + assert!(!captured.contains("Submitting a tx to reveal the public key")); + node.assert_success(); + } + + Ok(()) +} + +/// Change validator metadata +#[test] +fn change_validator_metadata() -> Result<()> { + // This address doesn't matter for tests. But an argument is required. + let validator_one_rpc = "http://127.0.0.1:26567"; + // 1. start the ledger node + let (node, _services) = setup::setup()?; + + // 2. Query the validator metadata loaded from genesis + let metadata_query_args = vec![ + "validator-metadata", + "--validator", + "validator-0", + "--node", + &validator_one_rpc, + ]; + let captured = CapturedOutput::of(|| { + run(&node, Bin::Client, metadata_query_args.clone()) + }); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains("Email:")); + assert!(captured.contains("No description")); + assert!(captured.contains("No website")); + assert!(captured.contains("No discord handle")); + assert!(captured.contains("commission rate:")); + assert!(captured.contains("max change per epoch:")); + + // 3. Add some metadata to the validator + let metadata_change_args = vec![ + "change-metadata", + "--validator", + "validator-0", + "--email", + "theokayestvalidator@namada.net", + "--description", + "We are just an okay validator node trying to get by", + "--website", + "theokayestvalidator.com", + "--node", + &validator_one_rpc, + ]; + + let captured = + CapturedOutput::of(|| run(&node, Bin::Client, metadata_change_args)); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains(TX_APPLIED_SUCCESS)); + + // 4. Query the metadata after the change + let captured = CapturedOutput::of(|| { + run(&node, Bin::Client, metadata_query_args.clone()) + }); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains("Email: theokayestvalidator@namada.net")); + assert!(captured.contains( + "Description: We are just an okay validator node trying to get by" + )); + assert!(captured.contains("Website: theokayestvalidator.com")); + assert!(captured.contains("No discord handle")); + assert!(captured.contains("commission rate:")); + assert!(captured.contains("max change per epoch:")); + + // 5. Remove the validator website + let metadata_change_args = vec![ + "change-metadata", + "--validator", + "validator-0", + "--website", + "", + "--node", + &validator_one_rpc, + ]; + let captured = + CapturedOutput::of(|| run(&node, Bin::Client, metadata_change_args)); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains(TX_APPLIED_SUCCESS)); + + // 6. Query the metadata to see that the validator website is removed + let captured = + CapturedOutput::of(|| run(&node, Bin::Client, metadata_query_args)); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains("Email: theokayestvalidator@namada.net")); + assert!(captured.contains( + "Description: We are just an okay validator node trying to get by" + )); + assert!(captured.contains("No website")); + assert!(captured.contains("No discord handle")); + assert!(captured.contains("commission rate:")); + assert!(captured.contains("max change per epoch:")); + + Ok(()) +} diff --git a/crates/tests/src/integration/masp.rs b/crates/tests/src/integration/masp.rs index 3e0bc5dd64..ba05f5af36 100644 --- a/crates/tests/src/integration/masp.rs +++ b/crates/tests/src/integration/masp.rs @@ -368,7 +368,7 @@ fn masp_incentives() -> Result<()> { ) }); assert!(captured.result.is_ok()); - assert!(captured.contains("nam: 0.359578")); + assert!(captured.contains("nam: 0.362747")); // Assert NAM balance at MASP pool is an accumulation of // rewards from both the shielded BTC and shielded ETH @@ -388,7 +388,7 @@ fn masp_incentives() -> Result<()> { ) }); assert!(captured.result.is_ok()); - assert!(captured.contains("nam: 0.671183")); + assert!(captured.contains("nam: 0.674354")); // Wait till epoch boundary node.next_epoch(); @@ -468,7 +468,7 @@ fn masp_incentives() -> Result<()> { ) }); assert!(captured.result.is_ok()); - assert!(captured.contains("nam: 0.719514")); + assert!(captured.contains("nam: 0.725855")); node.next_epoch(); // sync the shielded context @@ -497,7 +497,7 @@ fn masp_incentives() -> Result<()> { ) }); assert!(captured.result.is_ok()); - assert!(captured.contains("nam: 1.58943")); + assert!(captured.contains("nam: 1.595775")); // Wait till epoch boundary node.next_epoch(); @@ -588,7 +588,7 @@ fn masp_incentives() -> Result<()> { ) }); assert!(captured.result.is_ok()); - assert!(captured.contains("nam: 1.83743")); + assert!(captured.contains("nam: 1.843775")); // Wait till epoch boundary node.next_epoch(); @@ -640,7 +640,7 @@ fn masp_incentives() -> Result<()> { ) }); assert!(captured.result.is_ok()); - assert!(captured.contains("nam: 0.719514")); + assert!(captured.contains("nam: 0.725855")); // Assert NAM balance at MASP pool is // the accumulation of rewards from the shielded assets (BTC and ETH) @@ -660,7 +660,7 @@ fn masp_incentives() -> Result<()> { ) }); assert!(captured.result.is_ok()); - assert!(captured.contains("nam: 1.83743")); + assert!(captured.contains("nam: 1.843775")); // Wait till epoch boundary to prevent conversion expiry during transaction // construction @@ -685,7 +685,7 @@ fn masp_incentives() -> Result<()> { "--token", NAM, "--amount", - "0.719514", + "0.725855", "--signing-keys", BERTHA_KEY, "--node", @@ -795,7 +795,7 @@ fn masp_incentives() -> Result<()> { ) }); assert!(captured.result.is_ok()); - assert!(captured.contains("nam: 0.004005")); + assert!(captured.contains("nam: 0.004009")); Ok(()) } diff --git a/crates/tests/src/integration/setup.rs b/crates/tests/src/integration/setup.rs index f7604ad507..798df70665 100644 --- a/crates/tests/src/integration/setup.rs +++ b/crates/tests/src/integration/setup.rs @@ -33,11 +33,15 @@ const ENV_VAR_KEEP_TEMP: &str = "NAMADA_INT_KEEP_TEMP"; /// Setup a network with a single genesis validator node. pub fn setup() -> Result<(MockNode, MockServicesController)> { - initialize_genesis() + initialize_genesis(|genesis| genesis) } /// Setup folders with genesis, configs, wasm, etc. -pub fn initialize_genesis() -> Result<(MockNode, MockServicesController)> { +pub fn initialize_genesis( + mut update_genesis: impl FnMut( + templates::All, + ) -> templates::All, +) -> Result<(MockNode, MockServicesController)> { let working_dir = std::fs::canonicalize("../..").unwrap(); let keep_temp = match std::env::var(ENV_VAR_KEEP_TEMP) { Ok(val) => val.to_ascii_lowercase() != "false", @@ -57,6 +61,7 @@ pub fn initialize_genesis() -> Result<(MockNode, MockServicesController)> { locked_amount_target: 1_000_000u64, }); } + let templates = update_genesis(templates); let genesis_path = test_dir.path().join("int-test-genesis-src"); std::fs::create_dir(&genesis_path) .expect("Could not create test chain directory."); diff --git a/crates/tx/src/data/mod.rs b/crates/tx/src/data/mod.rs index 836d69244b..9c5dfa8ead 100644 --- a/crates/tx/src/data/mod.rs +++ b/crates/tx/src/data/mod.rs @@ -49,6 +49,7 @@ use crate::data::protocol::ProtocolTx; ToPrimitive, PartialEq, Eq, + Hash, Serialize, Deserialize, )] diff --git a/genesis/localnet/balances.toml b/genesis/localnet/balances.toml index a14c274456..1a41f87dad 100644 --- a/genesis/localnet/balances.toml +++ b/genesis/localnet/balances.toml @@ -31,6 +31,8 @@ tpknam1qypvqpzu74nafjahlwyq272dj76qq9rz30dulyc94883tmj893mquqs74gxv4 = "1000000" # validator-0 tnam1q9vhfdur7gadtwx4r223agpal0fvlqhywylf2mzx = "200000" tpknam1qpzrttnzfyt6xfu2vy092eruasll3z52rjfexwapdw0rdww5uktlk3j73dw = "200000" +# validator-0-account-key +tpknam1qpg2tsrplvhu3fd7z7tq5ztc2ne3s7e2ahjl2a2cddufrzdyr752g666ytj = "1000000" [token.BTC] # albert @@ -60,6 +62,7 @@ tpknam1qypvqpzu74nafjahlwyq272dj76qq9rz30dulyc94883tmj893mquqs74gxv4 = "1000000" # validator-0 tnam1q9vhfdur7gadtwx4r223agpal0fvlqhywylf2mzx = "1000000" + [token.DOT] # albert tnam1qxfj3sf6a0meahdu9t6znp05g8zx4dkjtgyn9gfu = "1000000"