From 8f7a1b08601ba9e93cb89808d325a9e3edcd79ae Mon Sep 17 00:00:00 2001 From: "Raymond E. Pasco" Date: Tue, 27 Jun 2023 14:19:55 -0400 Subject: [PATCH 1/6] apps: add namadac epoch-sleep Add the epoch-sleep helper, which queries the current epoch and then sleeps until the epoch is greater (polling once a second). This can replace the multiple namadac invocations used in e2e tests. --- apps/src/bin/namada-client/cli.rs | 11 ++++++++ apps/src/lib/cli.rs | 28 +++++++++++++++++++++ apps/src/lib/client/rpc.rs | 15 +++++++++++ tests/src/e2e/ledger_tests.rs | 42 +++++++++++++++++++++++++++++++ 4 files changed, 96 insertions(+) diff --git a/apps/src/bin/namada-client/cli.rs b/apps/src/bin/namada-client/cli.rs index 609588045d..11e35fa789 100644 --- a/apps/src/bin/namada-client/cli.rs +++ b/apps/src/bin/namada-client/cli.rs @@ -433,6 +433,17 @@ pub async fn main() -> Result<()> { let args = args.to_sdk(&mut ctx); rpc::query_protocol_parameters(&client, args).await; } + Sub::EpochSleep(EpochSleep(mut args)) => { + let client = HttpClient::new(utils::take_config_address( + &mut args.ledger_address, + )) + .unwrap(); + wait_until_node_is_synched(&client).await; + let client = + HttpClient::new(args.ledger_address.clone()).unwrap(); + let args = args.to_sdk(&mut ctx); + rpc::epoch_sleep(&client, args).await; + } } } cli::NamadaClient::WithoutContext(cmd, global_args) => match cmd { diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 06d6f3f406..b585ac4fa0 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -238,6 +238,9 @@ pub mod cmds { .subcommand(QueryProposal::def().display_order(4)) .subcommand(QueryProposalResult::def().display_order(4)) .subcommand(QueryProtocolParameters::def().display_order(4)) + // wait until next epoch (can't be in utils, needs the ledger + // address) + .subcommand(EpochSleep::def().display_order(4)) // Utils .subcommand(Utils::def().display_order(5)) } @@ -284,6 +287,7 @@ pub mod cmds { Self::parse_with_ctx(matches, QueryProtocolParameters); let add_to_eth_bridge_pool = Self::parse_with_ctx(matches, AddToEthBridgePool); + let epoch_sleep = Self::parse_with_ctx(matches, EpochSleep); let utils = SubCmd::parse(matches).map(Self::WithoutContext); tx_custom .or(tx_transfer) @@ -314,6 +318,7 @@ pub mod cmds { .or(query_proposal) .or(query_proposal_result) .or(query_protocol_parameters) + .or(epoch_sleep) .or(utils) } } @@ -381,6 +386,7 @@ pub mod cmds { QueryProposal(QueryProposal), QueryProposalResult(QueryProposalResult), QueryProtocolParameters(QueryProtocolParameters), + EpochSleep(EpochSleep), } #[allow(clippy::large_enum_variant)] @@ -1678,6 +1684,28 @@ pub mod cmds { } } + #[derive(Clone, Debug)] + pub struct EpochSleep(pub args::Query); + + impl SubCmd for EpochSleep { + const CMD: &'static str = "epoch-sleep"; + + fn parse(matches: &ArgMatches) -> Option { + matches + .subcommand_matches(Self::CMD) + .map(|matches| Self(args::Query::parse(matches))) + } + + fn def() -> App { + App::new(Self::CMD) + .about( + "Query for the current epoch, then sleep until the next \ + epoch.", + ) + .add_args::>() + } + } + #[derive(Clone, Debug)] pub enum Utils { JoinNetwork(JoinNetwork), diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 44955967d0..2b0b71650e 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -2030,6 +2030,21 @@ pub async fn query_result( } } +pub async fn epoch_sleep( + client: &C, + _args: args::Query, +) { + let start_epoch = query_and_print_epoch(client).await; + loop { + tokio::time::sleep(core::time::Duration::from_secs(1)).await; + let current_epoch = query_epoch(client).await; + if current_epoch > start_epoch { + println!("Reached epoch {}", current_epoch); + break; + } + } +} + pub async fn get_proposal_votes( client: &C, epoch: Epoch, diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index ca7bd974c7..cfddcb2442 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -4663,6 +4663,48 @@ fn implicit_account_reveal_pk() -> Result<()> { Ok(()) } +#[test] +fn test_epoch_sleep() -> Result<()> { + // Use slightly longer epochs to give us time to sleep + let test = setup::network( + |genesis| { + let parameters = ParametersConfig { + epochs_per_year: epochs_per_year_from_min_duration(30), + min_num_of_blocks: 1, + ..genesis.parameters + }; + GenesisConfig { + parameters, + ..genesis + } + }, + None, + )?; + + // 1. Run the ledger node + let mut ledger = + run_as!(test, Who::Validator(0), Bin::Node, &["ledger"], Some(40))?; + wait_for_wasm_pre_compile(&mut ledger)?; + + let _bg_ledger = ledger.background(); + + let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); + + // 2. Query the current epoch + let start_epoch = get_epoch(&test, &validator_one_rpc).unwrap(); + + // 3. Use epoch-sleep to sleep for an epoch + let args = ["epoch-sleep", "--node", &validator_one_rpc]; + let mut client = run!(test, Bin::Client, &args, None)?; + client.assert_success(); + + // 4. Confirm the current epoch is larger + let current_epoch = get_epoch(&test, &validator_one_rpc).unwrap(); + assert!(current_epoch > start_epoch); + + Ok(()) +} + /// Prepare proposal data in the test's temp dir from the given source address. /// This can be submitted with "init-proposal" command. fn prepare_proposal_data( From ab083952c9a252b0f4f2c1312973798518b1bcdb Mon Sep 17 00:00:00 2001 From: "Raymond E. Pasco" Date: Tue, 27 Jun 2023 16:20:08 -0400 Subject: [PATCH 2/6] tests/e2e: change epoch_sleep to use namadac epoch-sleep Instead of manually querying, we now epoch-sleep until the next epoch, limiting the number of clients invoked to 1. Also update the epoch sleep e2e test to check the return value (parsed from the output of epoch-sleep). --- tests/src/e2e/helpers.rs | 37 ++++++++++++++++++++++------------- tests/src/e2e/ledger_tests.rs | 5 +++++ 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/tests/src/e2e/helpers.rs b/tests/src/e2e/helpers.rs index 7c79fdfdb6..ac76e3f1a9 100644 --- a/tests/src/e2e/helpers.rs +++ b/tests/src/e2e/helpers.rs @@ -424,20 +424,29 @@ pub fn epoch_sleep( ledger_address: &str, timeout_secs: u64, ) -> Result { - let old_epoch = get_epoch(test, ledger_address)?; - let start = Instant::now(); - let loop_timeout = Duration::new(timeout_secs, 0); - loop { - if Instant::now().duration_since(start) > loop_timeout { - panic!("Timed out waiting for the next epoch"); - } - let epoch = get_epoch(test, ledger_address)?; - if epoch > old_epoch { - break Ok(epoch); - } else { - sleep(10); - } - } + let mut find = run!( + test, + Bin::Client, + &["epoch-sleep", "--node", ledger_address], + Some(timeout_secs) + )?; + parse_reached_epoch(&mut find) +} + +pub fn parse_reached_epoch(find: &mut NamadaCmd) -> Result { + let (unread, matched) = find.exp_regex("Reached epoch .*")?; + let epoch_str = strip_trailing_newline(&matched) + .trim() + .rsplit_once(' ') + .unwrap() + .1; + let epoch = u64::from_str(epoch_str).map_err(|e| { + eyre!(format!( + "Epoch: {} parsed from {}, Error: {}\n\nOutput: {}", + epoch_str, matched, e, unread + )) + })?; + Ok(Epoch(epoch)) } /// Wait for txs and VPs WASM compilations to finish. This is useful to avoid a diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index cfddcb2442..d24aa9e2a5 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -41,6 +41,7 @@ use super::helpers::{ use super::setup::{get_all_wasms_hashes, set_ethereum_bridge_mode}; use crate::e2e::helpers::{ epoch_sleep, find_address, find_bonded_stake, get_actor_rpc, get_epoch, + parse_reached_epoch, }; use crate::e2e::setup::{self, default_port_offset, sleep, Bin, Who}; use crate::{run, run_as}; @@ -4696,11 +4697,15 @@ fn test_epoch_sleep() -> Result<()> { // 3. Use epoch-sleep to sleep for an epoch let args = ["epoch-sleep", "--node", &validator_one_rpc]; let mut client = run!(test, Bin::Client, &args, None)?; + let reached_epoch = parse_reached_epoch(&mut client)?; client.assert_success(); // 4. Confirm the current epoch is larger + // possibly badly, we assume we get here within 30 seconds of the last step + // should be fine haha (future debuggers: sorry) let current_epoch = get_epoch(&test, &validator_one_rpc).unwrap(); assert!(current_epoch > start_epoch); + assert_eq!(current_epoch, reached_epoch); Ok(()) } From 4de68f7660bf5d938cf027b2a18cab54252ff158 Mon Sep 17 00:00:00 2001 From: "Raymond E. Pasco" Date: Tue, 27 Jun 2023 16:21:51 -0400 Subject: [PATCH 3/6] tests/e2e: replace PoS test epoch waits with epoch-sleeps PoS hammered the node as fast as possible with epoch queries in its tests to wait for a specified epoch. Instead, epoch-sleep repeatedly until the target epoch has been reached. This could be improved further by adding an --until parameter to epoch-sleep. --- tests/src/e2e/ledger_tests.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index d24aa9e2a5..034a9503f1 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -2151,7 +2151,7 @@ fn pos_bonds() -> Result<()> { delegation_withdrawable_epoch ); } - let epoch = get_epoch(&test, &validator_one_rpc)?; + let epoch = epoch_sleep(&test, &validator_one_rpc, 40)?; if epoch >= delegation_withdrawable_epoch { break; } @@ -2358,7 +2358,7 @@ fn pos_rewards() -> Result<()> { if Instant::now().duration_since(start) > loop_timeout { panic!("Timed out waiting for epoch: {}", wait_epoch); } - let epoch = get_epoch(&test, &validator_zero_rpc)?; + let epoch = epoch_sleep(&test, &validator_zero_rpc, 40)?; if dbg!(epoch) >= wait_epoch { break; } @@ -2441,7 +2441,7 @@ fn test_bond_queries() -> Result<()> { if Instant::now().duration_since(start) > loop_timeout { panic!("Timed out waiting for epoch: {}", 1); } - let epoch = get_epoch(&test, &validator_one_rpc)?; + let epoch = epoch_sleep(&test, &validator_one_rpc, 40)?; if epoch >= Epoch(4) { break; } @@ -2500,7 +2500,7 @@ fn test_bond_queries() -> Result<()> { // 6. Wait for withdraw_epoch loop { - let epoch = epoch_sleep(&test, &validator_one_rpc, 120)?; + let epoch = epoch_sleep(&test, &validator_one_rpc, 40)?; if epoch >= withdraw_epoch { break; } @@ -2740,7 +2740,7 @@ fn pos_init_validator() -> Result<()> { if Instant::now().duration_since(start) > loop_timeout { panic!("Timed out waiting for epoch: {}", earliest_update_epoch); } - let epoch = get_epoch(&test, &non_validator_rpc)?; + let epoch = epoch_sleep(&test, &non_validator_rpc, 40)?; if epoch >= earliest_update_epoch { break; } From 1f27630081756954e2939eee65cdbd68ab10cb36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 11 Jul 2023 11:26:04 +0100 Subject: [PATCH 4/6] apps: move epoch-sleep client cmd under utils --- apps/src/bin/namada-client/cli.rs | 23 ++++++++++++----------- apps/src/lib/cli.rs | 10 ++++------ tests/src/e2e/helpers.rs | 2 +- tests/src/e2e/ledger_tests.rs | 2 +- 4 files changed, 18 insertions(+), 19 deletions(-) diff --git a/apps/src/bin/namada-client/cli.rs b/apps/src/bin/namada-client/cli.rs index 11e35fa789..50e307d5c6 100644 --- a/apps/src/bin/namada-client/cli.rs +++ b/apps/src/bin/namada-client/cli.rs @@ -433,17 +433,6 @@ pub async fn main() -> Result<()> { let args = args.to_sdk(&mut ctx); rpc::query_protocol_parameters(&client, args).await; } - Sub::EpochSleep(EpochSleep(mut args)) => { - let client = HttpClient::new(utils::take_config_address( - &mut args.ledger_address, - )) - .unwrap(); - wait_until_node_is_synched(&client).await; - let client = - HttpClient::new(args.ledger_address.clone()).unwrap(); - let args = args.to_sdk(&mut ctx); - rpc::epoch_sleep(&client, args).await; - } } } cli::NamadaClient::WithoutContext(cmd, global_args) => match cmd { @@ -466,6 +455,18 @@ pub async fn main() -> Result<()> { Utils::DefaultBaseDir(DefaultBaseDir(args)) => { utils::default_base_dir(global_args, args) } + Utils::EpochSleep(EpochSleep(mut args)) => { + let mut ctx = cli::Context::new(global_args)?; + let ledger_address = args.ledger_address.clone(); + let client = HttpClient::new(utils::take_config_address( + &mut args.ledger_address, + )) + .unwrap(); + wait_until_node_is_synched(&client).await; + let client = HttpClient::new(ledger_address).unwrap(); + let args = args.to_sdk(&mut ctx); + rpc::epoch_sleep(&client, args).await; + } }, } Ok(()) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index b585ac4fa0..40f459d10a 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -238,9 +238,6 @@ pub mod cmds { .subcommand(QueryProposal::def().display_order(4)) .subcommand(QueryProposalResult::def().display_order(4)) .subcommand(QueryProtocolParameters::def().display_order(4)) - // wait until next epoch (can't be in utils, needs the ledger - // address) - .subcommand(EpochSleep::def().display_order(4)) // Utils .subcommand(Utils::def().display_order(5)) } @@ -287,7 +284,6 @@ pub mod cmds { Self::parse_with_ctx(matches, QueryProtocolParameters); let add_to_eth_bridge_pool = Self::parse_with_ctx(matches, AddToEthBridgePool); - let epoch_sleep = Self::parse_with_ctx(matches, EpochSleep); let utils = SubCmd::parse(matches).map(Self::WithoutContext); tx_custom .or(tx_transfer) @@ -318,7 +314,6 @@ pub mod cmds { .or(query_proposal) .or(query_proposal_result) .or(query_protocol_parameters) - .or(epoch_sleep) .or(utils) } } @@ -386,7 +381,6 @@ pub mod cmds { QueryProposal(QueryProposal), QueryProposalResult(QueryProposalResult), QueryProtocolParameters(QueryProtocolParameters), - EpochSleep(EpochSleep), } #[allow(clippy::large_enum_variant)] @@ -1714,6 +1708,7 @@ pub mod cmds { InitGenesisValidator(InitGenesisValidator), PkToTmAddress(PkToTmAddress), DefaultBaseDir(DefaultBaseDir), + EpochSleep(EpochSleep), } impl SubCmd for Utils { @@ -1732,12 +1727,14 @@ pub mod cmds { SubCmd::parse(matches).map(Self::PkToTmAddress); let default_base_dir = SubCmd::parse(matches).map(Self::DefaultBaseDir); + let epoch_sleep = SubCmd::parse(matches).map(Self::EpochSleep); join_network .or(fetch_wasms) .or(init_network) .or(init_genesis) .or(pk_to_tm_address) .or(default_base_dir) + .or(epoch_sleep) }) } @@ -1750,6 +1747,7 @@ pub mod cmds { .subcommand(InitGenesisValidator::def()) .subcommand(PkToTmAddress::def()) .subcommand(DefaultBaseDir::def()) + .subcommand(EpochSleep::def()) .subcommand_required(true) .arg_required_else_help(true) } diff --git a/tests/src/e2e/helpers.rs b/tests/src/e2e/helpers.rs index ac76e3f1a9..b4235c7347 100644 --- a/tests/src/e2e/helpers.rs +++ b/tests/src/e2e/helpers.rs @@ -427,7 +427,7 @@ pub fn epoch_sleep( let mut find = run!( test, Bin::Client, - &["epoch-sleep", "--node", ledger_address], + &["utils", "epoch-sleep", "--node", ledger_address], Some(timeout_secs) )?; parse_reached_epoch(&mut find) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 034a9503f1..f21e7bc053 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -4695,7 +4695,7 @@ fn test_epoch_sleep() -> Result<()> { let start_epoch = get_epoch(&test, &validator_one_rpc).unwrap(); // 3. Use epoch-sleep to sleep for an epoch - let args = ["epoch-sleep", "--node", &validator_one_rpc]; + let args = ["utils", "epoch-sleep", "--node", &validator_one_rpc]; let mut client = run!(test, Bin::Client, &args, None)?; let reached_epoch = parse_reached_epoch(&mut client)?; client.assert_success(); From 28a636227b25f8ceacdfbe15ff6239ffde91ce56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 11 Jul 2023 11:29:41 +0100 Subject: [PATCH 5/6] changelog: add #1621 --- .changelog/unreleased/improvements/1621-utils-next-epoch.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/improvements/1621-utils-next-epoch.md diff --git a/.changelog/unreleased/improvements/1621-utils-next-epoch.md b/.changelog/unreleased/improvements/1621-utils-next-epoch.md new file mode 100644 index 0000000000..5f663a8e00 --- /dev/null +++ b/.changelog/unreleased/improvements/1621-utils-next-epoch.md @@ -0,0 +1,2 @@ +- Added a command to wait for the next epoch: `client utils epoch-sleep`. + ([\#1621](https://github.com/anoma/namada/pull/1621)) \ No newline at end of file From 23de497339ded227ad04af27dcd31a2bcbb54590 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 11 Jul 2023 12:36:55 +0100 Subject: [PATCH 6/6] ci/test/e2e: add new test --- .github/workflows/scripts/e2e.json | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/scripts/e2e.json b/.github/workflows/scripts/e2e.json index 74b08e0fb8..bf91fc1588 100644 --- a/.github/workflows/scripts/e2e.json +++ b/.github/workflows/scripts/e2e.json @@ -20,6 +20,7 @@ "e2e::ledger_tests::test_namada_shuts_down_if_tendermint_dies": 2, "e2e::ledger_tests::test_genesis_validators": 14, "e2e::ledger_tests::test_node_connectivity_and_consensus": 28, + "e2e::ledger_tests::test_epoch_sleep": 12, "e2e::wallet_tests::wallet_address_cmds": 1, "e2e::wallet_tests::wallet_encrypted_key_cmds": 1, "e2e::wallet_tests::wallet_encrypted_key_cmds_env_var": 1,