diff --git a/aptos-move/move-examples/on_chain_dice/Move.toml b/aptos-move/move-examples/on_chain_dice/Move.toml new file mode 100644 index 0000000000000..32c79fb57df7f --- /dev/null +++ b/aptos-move/move-examples/on_chain_dice/Move.toml @@ -0,0 +1,9 @@ +[package] +name = "OnChainDice" +version = "0.0.0" + +[addresses] +module_owner = "_" + +[dependencies] +AptosFramework = { local = "../../framework/aptos-framework" } diff --git a/aptos-move/move-examples/on_chain_dice/sources/dice.move b/aptos-move/move-examples/on_chain_dice/sources/dice.move new file mode 100644 index 0000000000000..304b685284548 --- /dev/null +++ b/aptos-move/move-examples/on_chain_dice/sources/dice.move @@ -0,0 +1,21 @@ +module module_owner::dice { + use std::signer::address_of; + use std::vector; + use aptos_framework::randomness; + + struct DiceRollHistory has key { + rolls: vector, + } + + entry fun roll(account: signer) acquires DiceRollHistory { + let addr = address_of(&account); + let roll_history = if (exists(addr)) { + move_from(addr) + } else { + DiceRollHistory { rolls: vector[] } + }; + let new_roll = randomness::u64_range(0, 6); + vector::push_back(&mut roll_history.rolls, new_roll); + move_to(&account, roll_history); + } +} diff --git a/crates/aptos/src/test/mod.rs b/crates/aptos/src/test/mod.rs index b6353852db1f3..506b6beba555a 100644 --- a/crates/aptos/src/test/mod.rs +++ b/crates/aptos/src/test/mod.rs @@ -72,6 +72,7 @@ use std::{ time::Duration, }; use tempfile::TempDir; +use thiserror::__private::AsDisplay; use tokio::time::{sleep, Instant}; #[cfg(test)] @@ -470,6 +471,16 @@ impl CliTestFramework { .await } + pub fn add_file_in_package(&self, rel_path: &str, content: String) { + let source_path = self.move_dir().join(rel_path); + write_to_file( + source_path.as_path(), + &source_path.as_display().to_string(), + content.as_bytes(), + ) + .unwrap(); + } + pub async fn update_validator_network_addresses( &self, operator_index: usize, @@ -1026,7 +1037,7 @@ impl CliTestFramework { .await } - fn aptos_framework_dir() -> PathBuf { + pub fn aptos_framework_dir() -> PathBuf { PathBuf::from(env!("CARGO_MANIFEST_DIR")) .join("..") .join("..") diff --git a/testsuite/smoke-test/src/randomness/disable_feature_0.rs b/testsuite/smoke-test/src/randomness/disable_feature_0.rs new file mode 100644 index 0000000000000..949c20e654b6e --- /dev/null +++ b/testsuite/smoke-test/src/randomness/disable_feature_0.rs @@ -0,0 +1,101 @@ +// Copyright © Aptos Foundation + +use crate::{ + randomness::{decrypt_key_map, get_on_chain_resource, verify_dkg_transcript}, + smoke_test_environment::SwarmBuilder, +}; +use aptos_forge::{Node, Swarm, SwarmExt}; +use aptos_logger::{debug, info}; +use aptos_types::{ + dkg::DKGState, + on_chain_config::{FeatureFlag, Features}, + randomness::PerBlockRandomness, +}; +use std::{sync::Arc, time::Duration}; + +/// Disable on-chain randomness by only disabling feature `RECONFIGURE_WITH_DKG`. +#[tokio::test] +async fn disable_feature_0() { + let epoch_duration_secs = 20; + + let (mut swarm, mut cli, _faucet) = SwarmBuilder::new_local(4) + .with_num_fullnodes(1) + .with_aptos() + .with_init_genesis_config(Arc::new(move |conf| { + conf.epoch_duration_secs = epoch_duration_secs; + conf.allow_new_validators = true; + + // Ensure vtxn is enabled. + conf.consensus_config.enable_validator_txns(); + + // Ensure randomness flag is set. + let mut features = Features::default(); + features.enable(FeatureFlag::RECONFIGURE_WITH_DKG); + conf.initial_features_override = Some(features); + })) + .build_with_cli(0) + .await; + + let root_addr = swarm.chain_info().root_account().address(); + let root_idx = cli.add_account_with_address_to_cli(swarm.root_key(), root_addr); + + let decrypt_key_map = decrypt_key_map(&swarm); + + let client_endpoint = swarm.validators().nth(1).unwrap().rest_api_endpoint(); + let client = aptos_rest_client::Client::new(client_endpoint.clone()); + + swarm + .wait_for_all_nodes_to_catchup_to_epoch(3, Duration::from_secs(epoch_duration_secs * 2)) + .await + .expect("Waited too long for epoch 3."); + + info!("Now in epoch 3. Disabling feature RECONFIGURE_WITH_DKG."); + let disable_dkg_script = r#" +script { + use aptos_framework::aptos_governance; + fun main(core_resources: &signer) { + let framework_signer = aptos_governance::get_signer_testnet_only(core_resources, @0000000000000000000000000000000000000000000000000000000000000001); + let dkg_feature_id: u64 = std::features::get_reconfigure_with_dkg_feature(); + aptos_governance::toggle_features(&framework_signer, vector[], vector[dkg_feature_id]); + } +} +"#; + + let txn_summary = cli + .run_script(root_idx, disable_dkg_script) + .await + .expect("Txn execution error."); + debug!("disabling_dkg_summary={:?}", txn_summary); + + swarm + .wait_for_all_nodes_to_catchup_to_epoch(4, Duration::from_secs(epoch_duration_secs * 2)) + .await + .expect("Waited too long for epoch 4."); + + info!("Now in epoch 4. DKG transcript should still be available. Randomness seed should be unavailable."); + let dkg_session = get_on_chain_resource::(&client) + .await + .last_completed + .expect("dkg result for epoch 4 should be present"); + assert_eq!(4, dkg_session.target_epoch()); + assert!(verify_dkg_transcript(&dkg_session, &decrypt_key_map).is_ok()); + + let randomness_seed = get_on_chain_resource::(&client).await; + assert!(randomness_seed.seed.is_none()); + + swarm + .wait_for_all_nodes_to_catchup_to_epoch(5, Duration::from_secs(epoch_duration_secs * 2)) + .await + .expect("Waited too long for epoch 5."); + + info!("Now in epoch 5. DKG transcript should be unavailable. Randomness seed should be unavailable."); + let maybe_last_complete = get_on_chain_resource::(&client) + .await + .last_completed; + assert!( + maybe_last_complete.is_none() || maybe_last_complete.as_ref().unwrap().target_epoch() != 5 + ); + + let randomness_seed = get_on_chain_resource::(&client).await; + assert!(randomness_seed.seed.is_none()); +} diff --git a/testsuite/smoke-test/src/randomness/disable_feature_1.rs b/testsuite/smoke-test/src/randomness/disable_feature_1.rs new file mode 100644 index 0000000000000..07a281aff5071 --- /dev/null +++ b/testsuite/smoke-test/src/randomness/disable_feature_1.rs @@ -0,0 +1,111 @@ +// Copyright © Aptos Foundation + +use crate::{ + randomness::{decrypt_key_map, get_on_chain_resource, verify_dkg_transcript}, + smoke_test_environment::SwarmBuilder, + utils::get_current_consensus_config, +}; +use aptos_forge::{Node, Swarm, SwarmExt}; +use aptos_logger::{debug, info}; +use aptos_types::{ + dkg::DKGState, + on_chain_config::{FeatureFlag, Features}, + randomness::PerBlockRandomness, +}; +use std::{sync::Arc, time::Duration}; + +/// Disable on-chain randomness by only disabling validator transactions. +#[tokio::test] +async fn disable_feature_1() { + let epoch_duration_secs = 20; + + let (mut swarm, mut cli, _faucet) = SwarmBuilder::new_local(4) + .with_num_fullnodes(1) + .with_aptos() + .with_init_genesis_config(Arc::new(move |conf| { + conf.epoch_duration_secs = epoch_duration_secs; + conf.allow_new_validators = true; + + // Ensure vtxn is enabled. + conf.consensus_config.enable_validator_txns(); + + // Ensure randomness flag is set. + let mut features = Features::default(); + features.enable(FeatureFlag::RECONFIGURE_WITH_DKG); + conf.initial_features_override = Some(features); + })) + .build_with_cli(0) + .await; + + let root_addr = swarm.chain_info().root_account().address(); + let root_idx = cli.add_account_with_address_to_cli(swarm.root_key(), root_addr); + + let decrypt_key_map = decrypt_key_map(&swarm); + + let client_endpoint = swarm.validators().nth(1).unwrap().rest_api_endpoint(); + let client = aptos_rest_client::Client::new(client_endpoint.clone()); + + swarm + .wait_for_all_nodes_to_catchup_to_epoch(3, Duration::from_secs(epoch_duration_secs * 2)) + .await + .expect("Waited too long for epoch 3."); + + info!("Now in epoch 3. Disabling validator transactions."); + let mut config = get_current_consensus_config(&client).await; + assert!(config.is_vtxn_enabled()); + config.disable_validator_txns(); + let config_bytes = bcs::to_bytes(&config).unwrap(); + let disable_vtxn_script = format!( + r#" +script {{ + use aptos_framework::aptos_governance; + use aptos_framework::consensus_config; + fun main(core_resources: &signer) {{ + let framework_signer = aptos_governance::get_signer_testnet_only(core_resources, @0000000000000000000000000000000000000000000000000000000000000001); + let config_bytes = vector{:?}; + consensus_config::set_for_next_epoch(&framework_signer, config_bytes); + aptos_governance::reconfigure(&framework_signer); + }} +}} +"#, + config_bytes + ); + debug!("disable_vtxn_script={}", disable_vtxn_script); + let txn_summary = cli + .run_script(root_idx, disable_vtxn_script.as_str()) + .await + .expect("Txn execution error."); + debug!("disabling_vtxn_summary={:?}", txn_summary); + + swarm + .wait_for_all_nodes_to_catchup_to_epoch(4, Duration::from_secs(epoch_duration_secs * 2)) + .await + .expect("Waited too long for epoch 4."); + + info!("Now in epoch 4. DKG transcript should still be available. Randomness seed should be unavailable."); + let dkg_session = get_on_chain_resource::(&client) + .await + .last_completed + .expect("dkg result for epoch 4 should be present"); + assert_eq!(4, dkg_session.target_epoch()); + assert!(verify_dkg_transcript(&dkg_session, &decrypt_key_map).is_ok()); + + let randomness_seed = get_on_chain_resource::(&client).await; + assert!(randomness_seed.seed.is_none()); + + swarm + .wait_for_all_nodes_to_catchup_to_epoch(5, Duration::from_secs(epoch_duration_secs * 2)) + .await + .expect("Waited too long for epoch 5."); + + info!("Now in epoch 5. DKG transcript should be unavailable. Randomness seed should be unavailable."); + let maybe_last_complete = get_on_chain_resource::(&client) + .await + .last_completed; + assert!( + maybe_last_complete.is_none() || maybe_last_complete.as_ref().unwrap().target_epoch() != 5 + ); + + let randomness_seed = get_on_chain_resource::(&client).await; + assert!(randomness_seed.seed.is_none()); +} diff --git a/testsuite/smoke-test/src/randomness/dkg_with_validator_down.rs b/testsuite/smoke-test/src/randomness/dkg_with_validator_down.rs new file mode 100644 index 0000000000000..dd1e2b3212593 --- /dev/null +++ b/testsuite/smoke-test/src/randomness/dkg_with_validator_down.rs @@ -0,0 +1,59 @@ +// Copyright © Aptos Foundation + +use crate::{ + randomness::{decrypt_key_map, verify_dkg_transcript, wait_for_dkg_finish}, + smoke_test_environment::SwarmBuilder, +}; +use aptos_forge::NodeExt; +use aptos_types::on_chain_config::{FeatureFlag, Features}; +use std::sync::Arc; + +#[tokio::test] +async fn dkg_with_validator_down() { + let epoch_duration_secs = 10; + let estimated_dkg_latency_secs = 20; + let time_limit_secs = epoch_duration_secs + estimated_dkg_latency_secs; + + let mut swarm = SwarmBuilder::new_local(4) + .with_num_fullnodes(1) + .with_aptos() + .with_init_genesis_config(Arc::new(|conf| { + conf.epoch_duration_secs = 10; + + // Ensure vtxn is enabled. + conf.consensus_config.enable_validator_txns(); + + // Ensure randomness flag is set. + let mut features = Features::default(); + features.enable(FeatureFlag::RECONFIGURE_WITH_DKG); + conf.initial_features_override = Some(features); + })) + .build() + .await; + let decrypt_key_map = decrypt_key_map(&swarm); + + let client = swarm.validators().last().unwrap().rest_client(); + println!("Wait for an epoch start."); + let dkg_session_1 = wait_for_dkg_finish(&client, None, time_limit_secs).await; + + println!("Current epoch is {}.", dkg_session_1.target_epoch()); + + println!("Take one validator down."); + swarm.validators_mut().take(1).for_each(|v| { + v.stop(); + }); + + println!( + "Wait until we fully entered epoch {}.", + dkg_session_1.target_epoch() + 1 + ); + + let dkg_session_2 = wait_for_dkg_finish( + &client, + Some(dkg_session_1.target_epoch() + 1), + time_limit_secs, + ) + .await; + + assert!(verify_dkg_transcript(&dkg_session_2, &decrypt_key_map).is_ok()); +} diff --git a/testsuite/smoke-test/src/randomness/dkg_with_validator_join_leave.rs b/testsuite/smoke-test/src/randomness/dkg_with_validator_join_leave.rs new file mode 100644 index 0000000000000..3ba10aae34db3 --- /dev/null +++ b/testsuite/smoke-test/src/randomness/dkg_with_validator_join_leave.rs @@ -0,0 +1,144 @@ +// Copyright © Aptos Foundation + +use crate::{ + randomness::{decrypt_key_map, num_validators, verify_dkg_transcript, wait_for_dkg_finish}, + smoke_test_environment::SwarmBuilder, +}; +use aptos::test::CliTestFramework; +use aptos_forge::{Node, Swarm}; +use aptos_types::on_chain_config::{FeatureFlag, Features}; +use std::sync::Arc; + +#[tokio::test] +async fn dkg_with_validator_join_leave() { + let epoch_duration_secs = 40; + let estimated_dkg_latency_secs = 80; + let time_limit_secs = epoch_duration_secs + estimated_dkg_latency_secs; + + let mut swarm = SwarmBuilder::new_local(7) + .with_num_fullnodes(1) + .with_aptos() + .with_init_genesis_config(Arc::new(move |conf| { + conf.epoch_duration_secs = epoch_duration_secs; + conf.allow_new_validators = true; + + // Ensure vtxn is enabled. + conf.consensus_config.enable_validator_txns(); + + // Ensure randomness flag is set. + let mut features = Features::default(); + features.enable(FeatureFlag::RECONFIGURE_WITH_DKG); + conf.initial_features_override = Some(features); + })) + .build() + .await; + + let decrypt_key_map = decrypt_key_map(&swarm); + + println!("Wait for a moment when DKG is not running."); + let client_endpoint = swarm.validators().nth(1).unwrap().rest_api_endpoint(); + let client = aptos_rest_client::Client::new(client_endpoint.clone()); + let dkg_session_1 = wait_for_dkg_finish(&client, None, time_limit_secs).await; + println!( + "Current epoch is {}. Number of validators: {}.", + dkg_session_1.target_epoch(), + num_validators(&dkg_session_1) + ); + + println!( + "Wait until we fully entered epoch {}.", + dkg_session_1.target_epoch() + 1 + ); + let dkg_session_2 = wait_for_dkg_finish( + &client, + Some(dkg_session_1.target_epoch() + 1), + time_limit_secs, + ) + .await; + + println!( + "Current epoch is {}. Number of validators: {}.", + dkg_session_2.target_epoch(), + num_validators(&dkg_session_2) + ); + + println!("Letting one of the validators leave."); + let (victim_validator_sk, victim_validator_addr) = { + let victim_validator = swarm.validators().next().unwrap(); + let sk = victim_validator + .account_private_key() + .clone() + .unwrap() + .private_key(); + let addr = victim_validator.peer_id(); + (sk, addr) + }; + + println!("Give the victim some money so it can first send transactions."); + let mut public_info = swarm.chain_info().into_aptos_public_info(); + public_info + .mint(victim_validator_addr, 100000000000000) + .await + .unwrap(); + + println!("Send the txn to request leave."); + let faucet_endpoint: reqwest::Url = "http://localhost:8081".parse().unwrap(); + let mut cli = CliTestFramework::new( + client_endpoint, + faucet_endpoint, + /*num_cli_accounts=*/ 0, + ) + .await; + let idx = cli.add_account_to_cli(victim_validator_sk); + let txn_result = cli.leave_validator_set(idx, None).await.unwrap(); + println!("Txn result: {:?}", txn_result); + + println!( + "Wait until we fully entered epoch {}.", + dkg_session_2.target_epoch() + 1 + ); + let dkg_session_3 = wait_for_dkg_finish( + &client, + Some(dkg_session_2.target_epoch() + 1), + time_limit_secs, + ) + .await; + + println!( + "Current epoch is {}. Number of validators: {}.", + dkg_session_3.target_epoch(), + num_validators(&dkg_session_3) + ); + + assert!(verify_dkg_transcript(&dkg_session_3, &decrypt_key_map).is_ok()); + assert_eq!( + num_validators(&dkg_session_3), + num_validators(&dkg_session_2) - 1 + ); + + println!("Now re-join."); + let txn_result = cli.join_validator_set(idx, None).await; + println!("Txn result: {:?}", txn_result); + println!( + "Wait until we fully entered epoch {}.", + dkg_session_3.target_epoch() + 1 + ); + let dkg_session_4 = wait_for_dkg_finish( + &client, + Some(dkg_session_3.target_epoch() + 1), + time_limit_secs, + ) + .await; + + println!( + "Current epoch is {}. Number of validators: {}.", + dkg_session_4.target_epoch(), + num_validators(&dkg_session_4) + ); + + assert!(verify_dkg_transcript(&dkg_session_4, &decrypt_key_map).is_ok()); + assert_eq!( + num_validators(&dkg_session_4), + num_validators(&dkg_session_3) + 1 + ); +} diff --git a/testsuite/smoke-test/src/randomness/e2e_basic_consumption.rs b/testsuite/smoke-test/src/randomness/e2e_basic_consumption.rs new file mode 100644 index 0000000000000..f11d02e255eb6 --- /dev/null +++ b/testsuite/smoke-test/src/randomness/e2e_basic_consumption.rs @@ -0,0 +1,107 @@ +// Copyright © Aptos Foundation + +use crate::smoke_test_environment::SwarmBuilder; +use aptos::{move_tool::MemberId, test::CliTestFramework}; +use aptos_forge::{NodeExt, Swarm, SwarmExt}; +use aptos_logger::info; +use aptos_types::on_chain_config::{FeatureFlag, Features}; +use serde::{Deserialize, Serialize}; +use std::{collections::BTreeMap, str::FromStr, sync::Arc, time::Duration}; + +/// Publish the `on-chain-dice` example module, +/// run its function that consume on-chain randomness, and +/// print out the random results. +#[tokio::test] +async fn e2e_basic_consumption() { + let epoch_duration_secs = 20; + + let (mut swarm, mut cli, _faucet) = SwarmBuilder::new_local(4) + .with_num_fullnodes(1) + .with_aptos() + .with_init_genesis_config(Arc::new(move |conf| { + conf.epoch_duration_secs = epoch_duration_secs; + + // Ensure vtxn is enabled. + conf.consensus_config.enable_validator_txns(); + + // Ensure randomness flag is set. + let mut features = Features::default(); + features.enable(FeatureFlag::RECONFIGURE_WITH_DKG); + conf.initial_features_override = Some(features); + })) + .build_with_cli(0) + .await; + + let rest_client = swarm.validators().next().unwrap().rest_client(); + + info!("Wait for epoch 2. Epoch 1 does not have randomness."); + swarm + .wait_for_all_nodes_to_catchup_to_epoch(2, Duration::from_secs(epoch_duration_secs * 2)) + .await + .expect("Epoch 2 taking too long to arrive!"); + + let root_address = swarm.chain_info().root_account().address(); + info!("Root account: {}", root_address); + let _root_idx = cli.add_account_with_address_to_cli(swarm.root_key(), root_address); + + info!("Publishing OnChainDice module."); + publish_on_chain_dice_module(&mut cli, 0).await; + + info!("Rolling the dice."); + let account = cli.account_id(0).to_hex_literal(); + let roll_func_id = MemberId::from_str(&format!("{}::dice::roll", account)).unwrap(); + for _ in 0..10 { + let txn_summary = cli + .run_function(0, None, roll_func_id.clone(), vec![], vec![]) + .await + .unwrap(); + info!("Roll txn summary: {:?}", txn_summary); + } + + info!("Collecting roll history."); + let dice_roll_history = rest_client + .get_account_resource_bcs::( + root_address, + format!("{}::dice::DiceRollHistory", account).as_str(), + ) + .await + .unwrap() + .into_inner(); + + info!("Roll history: {:?}", dice_roll_history.rolls); +} + +#[derive(Deserialize, Serialize)] +struct DiceRollHistory { + rolls: Vec, +} + +async fn publish_on_chain_dice_module(cli: &mut CliTestFramework, publisher_account_idx: usize) { + cli.init_move_dir(); + let mut package_addresses = BTreeMap::new(); + package_addresses.insert("module_owner", "_"); + + cli.init_package( + "OnChainDice".to_string(), + package_addresses, + Some(CliTestFramework::aptos_framework_dir()), + ) + .await + .unwrap(); + + let content = + include_str!("../../../../aptos-move/move-examples/on_chain_dice/sources/dice.move") + .to_string(); + cli.add_file_in_package("sources/dice.move", content); + + cli.wait_for_account(publisher_account_idx).await.unwrap(); + + info!("Move package dir: {}", cli.move_dir().display()); + + let mut named_addresses = BTreeMap::new(); + let account_str = cli.account_id(publisher_account_idx).to_string(); + named_addresses.insert("module_owner", account_str.as_str()); + cli.publish_package(0, None, named_addresses, None) + .await + .unwrap(); +} diff --git a/testsuite/smoke-test/src/randomness/enable_feature_0.rs b/testsuite/smoke-test/src/randomness/enable_feature_0.rs new file mode 100644 index 0000000000000..54659dedf62ff --- /dev/null +++ b/testsuite/smoke-test/src/randomness/enable_feature_0.rs @@ -0,0 +1,132 @@ +// Copyright © Aptos Foundation + +use crate::{ + randomness::{decrypt_key_map, get_on_chain_resource, verify_dkg_transcript}, + smoke_test_environment::SwarmBuilder, + utils::get_current_consensus_config, +}; +use aptos_forge::{Node, Swarm, SwarmExt}; +use aptos_logger::{debug, info}; +use aptos_types::{ + dkg::DKGState, + on_chain_config::{FeatureFlag, Features}, +}; +use std::{sync::Arc, time::Duration}; + +/// Enable on-chain randomness in the following steps. +/// - Enable feature `RECONFIGURE_WITH_DKG` in epoch `e`. +/// - Enable validator transactions in consensus config in epoch `e + 1`. +#[tokio::test] +async fn enable_feature_0() { + let epoch_duration_secs = 20; + let estimated_dkg_latency_secs = 40; + + let (mut swarm, mut cli, _faucet) = SwarmBuilder::new_local(4) + .with_num_fullnodes(1) + .with_aptos() + .with_init_genesis_config(Arc::new(move |conf| { + conf.epoch_duration_secs = epoch_duration_secs; + conf.allow_new_validators = true; + + // start with vtxn disabled. + conf.consensus_config.disable_validator_txns(); + + // start with dkg disabled. + let mut features = Features::default(); + features.disable(FeatureFlag::RECONFIGURE_WITH_DKG); + conf.initial_features_override = Some(features); + })) + .build_with_cli(0) + .await; + + let root_addr = swarm.chain_info().root_account().address(); + let root_idx = cli.add_account_with_address_to_cli(swarm.root_key(), root_addr); + + let decrypt_key_map = decrypt_key_map(&swarm); + + let client_endpoint = swarm.validators().nth(1).unwrap().rest_api_endpoint(); + let client = aptos_rest_client::Client::new(client_endpoint.clone()); + + swarm + .wait_for_all_nodes_to_catchup_to_epoch(3, Duration::from_secs(epoch_duration_secs * 2)) + .await + .expect("Waited too long for epoch 3."); + + info!("Now in epoch 3. Enabling feature RECONFIGURE_WITH_DKG."); + let enable_dkg_script = r#" +script { + use aptos_framework::aptos_governance; + fun main(core_resources: &signer) { + let framework_signer = aptos_governance::get_signer_testnet_only(core_resources, @0000000000000000000000000000000000000000000000000000000000000001); + let dkg_feature_id: u64 = std::features::get_reconfigure_with_dkg_feature(); + aptos_governance::toggle_features(&framework_signer, vector[dkg_feature_id], vector[]); + } +} +"#; + + let txn_summary = cli + .run_script(root_idx, enable_dkg_script) + .await + .expect("Txn execution error."); + debug!("enabling_dkg_summary={:?}", txn_summary); + + swarm + .wait_for_all_nodes_to_catchup_to_epoch(4, Duration::from_secs(epoch_duration_secs * 2)) + .await + .expect("Waited too long for epoch 4."); + + info!("Now in epoch 4. Enabling validator transactions."); + let mut config = get_current_consensus_config(&client).await; + config.enable_validator_txns(); + let config_bytes = bcs::to_bytes(&config).unwrap(); + let enable_vtxn_script = format!( + r#" +script {{ + use aptos_framework::aptos_governance; + use aptos_framework::consensus_config; + fun main(core_resources: &signer) {{ + let framework_signer = aptos_governance::get_signer_testnet_only(core_resources, @0000000000000000000000000000000000000000000000000000000000000001); + let config_bytes = vector{:?}; + consensus_config::set_for_next_epoch(&framework_signer, config_bytes); + aptos_governance::reconfigure(&framework_signer); + }} +}} +"#, + config_bytes + ); + debug!("enable_vtxn_script={}", enable_vtxn_script); + let txn_summary = cli + .run_script(root_idx, enable_vtxn_script.as_str()) + .await + .expect("Txn execution error."); + debug!("enabling_vtxn_summary={:?}", txn_summary); + + swarm + .wait_for_all_nodes_to_catchup_to_epoch(5, Duration::from_secs(epoch_duration_secs * 2)) + .await + .expect("Waited too long for epoch 5."); + + info!("Now in epoch 5. Both DKG and vtxn are enabled. There should be no randomness since DKG did not happen at the end of last epoch."); + let maybe_last_complete = get_on_chain_resource::(&client) + .await + .last_completed; + assert!( + maybe_last_complete.is_none() || maybe_last_complete.as_ref().unwrap().target_epoch() != 5 + ); + + info!("Waiting for epoch 6."); + swarm + .wait_for_all_nodes_to_catchup_to_epoch( + 6, + Duration::from_secs(epoch_duration_secs + estimated_dkg_latency_secs), + ) + .await + .expect("Waited too long for epoch 6."); + + let dkg_session = get_on_chain_resource::(&client) + .await + .last_completed + .expect("dkg result for epoch 6 should be present"); + assert_eq!(6, dkg_session.target_epoch()); + assert!(verify_dkg_transcript(&dkg_session, &decrypt_key_map).is_ok()); +} diff --git a/testsuite/smoke-test/src/randomness/enable_feature_1.rs b/testsuite/smoke-test/src/randomness/enable_feature_1.rs new file mode 100644 index 0000000000000..e4b124dae90f1 --- /dev/null +++ b/testsuite/smoke-test/src/randomness/enable_feature_1.rs @@ -0,0 +1,133 @@ +// Copyright © Aptos Foundation + +use crate::{ + randomness::{decrypt_key_map, get_on_chain_resource, verify_dkg_transcript}, + smoke_test_environment::SwarmBuilder, + utils::get_current_consensus_config, +}; +use aptos_forge::{Node, Swarm, SwarmExt}; +use aptos_logger::{debug, info}; +use aptos_types::{ + dkg::DKGState, + on_chain_config::{FeatureFlag, Features}, +}; +use std::{sync::Arc, time::Duration}; + +/// Enable on-chain randomness in the following steps. +/// - Enable validator transactions in consensus config in epoch `e`. +/// - Enable feature `RECONFIGURE_WITH_DKG` in epoch `e + 1`. +#[tokio::test] +async fn enable_feature_1() { + let epoch_duration_secs = 20; + let estimated_dkg_latency_secs = 40; + + let (mut swarm, mut cli, _faucet) = SwarmBuilder::new_local(4) + .with_num_fullnodes(1) + .with_aptos() + .with_init_genesis_config(Arc::new(move |conf| { + conf.epoch_duration_secs = epoch_duration_secs; + conf.allow_new_validators = true; + + // start with vtxn disabled. + conf.consensus_config.disable_validator_txns(); + + // start with dkg disabled. + let mut features = Features::default(); + features.disable(FeatureFlag::RECONFIGURE_WITH_DKG); + conf.initial_features_override = Some(features); + })) + .build_with_cli(0) + .await; + + let root_addr = swarm.chain_info().root_account().address(); + let root_idx = cli.add_account_with_address_to_cli(swarm.root_key(), root_addr); + + let decrypt_key_map = decrypt_key_map(&swarm); + + let client_endpoint = swarm.validators().nth(1).unwrap().rest_api_endpoint(); + let client = aptos_rest_client::Client::new(client_endpoint.clone()); + + swarm + .wait_for_all_nodes_to_catchup_to_epoch(3, Duration::from_secs(epoch_duration_secs * 2)) + .await + .expect("Waited too long for epoch 3."); + + info!("Now in epoch 3. Enabling validator transactions."); + let mut config = get_current_consensus_config(&client).await; + config.enable_validator_txns(); + let config_bytes = bcs::to_bytes(&config).unwrap(); + let enable_vtxn_script = format!( + r#" +script {{ + use aptos_framework::aptos_governance; + use aptos_framework::consensus_config; + fun main(core_resources: &signer) {{ + let framework_signer = aptos_governance::get_signer_testnet_only(core_resources, @0000000000000000000000000000000000000000000000000000000000000001); + let config_bytes = vector{:?}; + consensus_config::set_for_next_epoch(&framework_signer, config_bytes); + aptos_governance::reconfigure(&framework_signer); + }} +}} +"#, + config_bytes + ); + + debug!("enable_vtxn_script={}", enable_vtxn_script); + let txn_summary = cli + .run_script(root_idx, enable_vtxn_script.as_str()) + .await + .expect("Txn execution error."); + debug!("enabling_vtxn_summary={:?}", txn_summary); + + swarm + .wait_for_all_nodes_to_catchup_to_epoch(4, Duration::from_secs(epoch_duration_secs * 2)) + .await + .expect("Waited too long for epoch 4."); + + info!("Now in epoch 4. Enabling feature RECONFIGURE_WITH_DKG."); + let enable_dkg_script = r#" +script { + use aptos_framework::aptos_governance; + fun main(core_resources: &signer) { + let framework_signer = aptos_governance::get_signer_testnet_only(core_resources, @0000000000000000000000000000000000000000000000000000000000000001); + let dkg_feature_id: u64 = std::features::get_reconfigure_with_dkg_feature(); + aptos_governance::toggle_features(&framework_signer, vector[dkg_feature_id], vector[]); + } +} +"#; + + let txn_summary = cli + .run_script(root_idx, enable_dkg_script) + .await + .expect("Txn execution error."); + debug!("enabling_dkg_summary={:?}", txn_summary); + + swarm + .wait_for_all_nodes_to_catchup_to_epoch(5, Duration::from_secs(epoch_duration_secs * 2)) + .await + .expect("Waited too long for epoch 5."); + + info!("Now in epoch 5. Both DKG and vtxn are enabled. There should be no randomness since DKG did not happen at the end of last epoch."); + let maybe_last_complete = get_on_chain_resource::(&client) + .await + .last_completed; + assert!( + maybe_last_complete.is_none() || maybe_last_complete.as_ref().unwrap().target_epoch() != 5 + ); + + info!("Waiting for epoch 6."); + swarm + .wait_for_all_nodes_to_catchup_to_epoch( + 6, + Duration::from_secs(epoch_duration_secs + estimated_dkg_latency_secs), + ) + .await + .expect("Waited too long for epoch 6."); + + let dkg_session = get_on_chain_resource::(&client) + .await + .last_completed + .expect("dkg result for epoch 6 should be present"); + assert_eq!(6, dkg_session.target_epoch()); + assert!(verify_dkg_transcript(&dkg_session, &decrypt_key_map).is_ok()); +} diff --git a/testsuite/smoke-test/src/randomness/enable_feature_2.rs b/testsuite/smoke-test/src/randomness/enable_feature_2.rs new file mode 100644 index 0000000000000..a291258d2ad7c --- /dev/null +++ b/testsuite/smoke-test/src/randomness/enable_feature_2.rs @@ -0,0 +1,111 @@ +// Copyright © Aptos Foundation + +use crate::{ + randomness::{decrypt_key_map, get_on_chain_resource, verify_dkg_transcript}, + smoke_test_environment::SwarmBuilder, + utils::get_current_consensus_config, +}; +use aptos_forge::{Node, Swarm, SwarmExt}; +use aptos_logger::{debug, info}; +use aptos_types::{ + dkg::DKGState, + on_chain_config::{FeatureFlag, Features}, +}; +use std::{sync::Arc, time::Duration}; + +/// Enable on-chain randomness by enabling validator transactions and feature `RECONFIGURE_WITH_DKG` simultaneously. +#[tokio::test] +async fn enable_feature_2() { + let epoch_duration_secs = 20; + let estimated_dkg_latency_secs = 40; + + let (mut swarm, mut cli, _faucet) = SwarmBuilder::new_local(4) + .with_num_fullnodes(1) + .with_aptos() + .with_init_genesis_config(Arc::new(move |conf| { + conf.epoch_duration_secs = epoch_duration_secs; + conf.allow_new_validators = true; + + // start with vtxn disabled. + conf.consensus_config.disable_validator_txns(); + + // start with dkg disabled. + let mut features = Features::default(); + features.disable(FeatureFlag::RECONFIGURE_WITH_DKG); + conf.initial_features_override = Some(features); + })) + .build_with_cli(0) + .await; + + let root_addr = swarm.chain_info().root_account().address(); + let root_idx = cli.add_account_with_address_to_cli(swarm.root_key(), root_addr); + + let decrypt_key_map = decrypt_key_map(&swarm); + + let client_endpoint = swarm.validators().nth(1).unwrap().rest_api_endpoint(); + let client = aptos_rest_client::Client::new(client_endpoint.clone()); + + swarm + .wait_for_all_nodes_to_catchup_to_epoch(3, Duration::from_secs(epoch_duration_secs * 2)) + .await + .expect("Waited too long for epoch 3."); + + info!("Now in epoch 3. Enabling features."); + let mut config = get_current_consensus_config(&client).await; + config.enable_validator_txns(); + let config_bytes = bcs::to_bytes(&config).unwrap(); + let script = format!( + r#" +script {{ + use aptos_framework::aptos_governance; + use aptos_framework::consensus_config; + use std::features; + fun main(core_resources: &signer) {{ + let framework_signer = aptos_governance::get_signer_testnet_only(core_resources, @0000000000000000000000000000000000000000000000000000000000000001); + let config_bytes = vector{:?}; + consensus_config::set_for_next_epoch(&framework_signer, config_bytes); + let dkg_feature_id: u64 = features::get_reconfigure_with_dkg_feature(); + features::change_feature_flags_for_next_epoch(&framework_signer, vector[dkg_feature_id], vector[]); + aptos_governance::reconfigure(&framework_signer); + }} +}} +"#, + config_bytes + ); + + debug!("script={}", script); + let txn_summary = cli + .run_script(root_idx, script.as_str()) + .await + .expect("Txn execution error."); + debug!("txn_summary={:?}", txn_summary); + + swarm + .wait_for_all_nodes_to_catchup_to_epoch(4, Duration::from_secs(epoch_duration_secs * 2)) + .await + .expect("Waited too long for epoch 4."); + + info!("Now in epoch 4. Both DKG and vtxn are enabled. There should be no randomness since DKG did not happen at the end of last epoch."); + let maybe_last_complete = get_on_chain_resource::(&client) + .await + .last_completed; + assert!( + maybe_last_complete.is_none() || maybe_last_complete.as_ref().unwrap().target_epoch() != 4 + ); + + info!("Waiting for epoch 5."); + swarm + .wait_for_all_nodes_to_catchup_to_epoch( + 5, + Duration::from_secs(epoch_duration_secs + estimated_dkg_latency_secs), + ) + .await + .expect("Waited too long for epoch 5."); + + let dkg_session = get_on_chain_resource::(&client) + .await + .last_completed + .expect("dkg result for epoch 6 should be present"); + assert_eq!(5, dkg_session.target_epoch()); + assert!(verify_dkg_transcript(&dkg_session, &decrypt_key_map).is_ok()); +} diff --git a/testsuite/smoke-test/src/randomness/mod.rs b/testsuite/smoke-test/src/randomness/mod.rs index 12c40f7ed21b9..b68da06af1c35 100644 --- a/testsuite/smoke-test/src/randomness/mod.rs +++ b/testsuite/smoke-test/src/randomness/mod.rs @@ -18,7 +18,16 @@ use rand::{prelude::StdRng, SeedableRng}; use std::{collections::HashMap, time::Duration}; use tokio::time::Instant; +mod disable_feature_0; +mod disable_feature_1; +mod dkg_with_validator_down; +mod dkg_with_validator_join_leave; +mod e2e_basic_consumption; mod e2e_correctness; +mod enable_feature_0; +mod enable_feature_1; +mod enable_feature_2; +mod validator_restart_during_dkg; #[allow(dead_code)] async fn get_current_version(rest_client: &Client) -> u64 { diff --git a/testsuite/smoke-test/src/randomness/validator_restart_during_dkg.rs b/testsuite/smoke-test/src/randomness/validator_restart_during_dkg.rs new file mode 100644 index 0000000000000..4c1fbd748a9eb --- /dev/null +++ b/testsuite/smoke-test/src/randomness/validator_restart_during_dkg.rs @@ -0,0 +1,109 @@ +// Copyright © Aptos Foundation + +use crate::{ + randomness::{ + decrypt_key_map, get_on_chain_resource, verify_dkg_transcript, wait_for_dkg_finish, + }, + smoke_test_environment::SwarmBuilder, +}; +use aptos_forge::{NodeExt, SwarmExt}; +use aptos_logger::{debug, info}; +use aptos_rest_client::Client; +use aptos_types::{ + dkg::DKGState, + on_chain_config::{FeatureFlag, Features}, +}; +use futures::future::join_all; +use std::{sync::Arc, time::Duration}; + +#[tokio::test] +async fn validator_restart_during_dkg() { + let epoch_duration_secs = 30; + let estimated_dkg_latency_secs = 30; + let time_limit_secs = epoch_duration_secs + estimated_dkg_latency_secs; + let num_validators = 4; + let num_validators_to_restart = 3; + let mut swarm = SwarmBuilder::new_local(num_validators) + .with_num_fullnodes(1) + .with_aptos() + .with_init_config(Arc::new(|_, conf, _| { + conf.api.failpoints_enabled = true; + })) + .with_init_genesis_config(Arc::new(|conf| { + conf.epoch_duration_secs = 30; + + // Ensure vtxn is enabled. + conf.consensus_config.enable_validator_txns(); + + // Ensure randomness flag is set. + let mut features = Features::default(); + features.enable(FeatureFlag::RECONFIGURE_WITH_DKG); + conf.initial_features_override = Some(features); + })) + .build() + .await; + + swarm + .wait_for_all_nodes_to_catchup_to_epoch(2, Duration::from_secs(epoch_duration_secs * 10)) + .await + .unwrap(); + + let decrypt_key_map = decrypt_key_map(&swarm); + + info!("Wait for an epoch start."); + let validator_clients: Vec = + swarm.validators().map(|node| node.rest_client()).collect(); + let dkg_session_1 = wait_for_dkg_finish(&validator_clients[3], None, time_limit_secs).await; + + info!( + "Current epoch is {}.", + dkg_session_1.metadata.dealer_epoch + 1 + ); + + info!("Inject fault to all validators so they get stuck upon the first DKG message received."); + let tasks = validator_clients + .iter() + .take(num_validators_to_restart) + .map(|client| { + client.set_failpoint( + "dkg::process_dkg_start_event".to_string(), + "panic".to_string(), + ) + }) + .collect::>(); + let aptos_results = join_all(tasks).await; + debug!("aptos_results={:?}", aptos_results); + + info!("Restart nodes after they panic."); + for (node_idx, node) in swarm + .validators_mut() + .enumerate() + .take(num_validators_to_restart) + { + while node.health_check().await.is_ok() { + tokio::time::sleep(Duration::from_secs(1)).await; + } + info!("node {} panicked", node_idx); + node.restart().await.unwrap(); + info!("node {} restarted", node_idx); + } + + info!( + "DKG should be able to continue. Wait until we fully entered epoch {}.", + dkg_session_1.target_epoch() + 1 + ); + + swarm + .wait_for_all_nodes_to_catchup_to_epoch( + dkg_session_1.target_epoch() + 1, + Duration::from_secs(time_limit_secs), + ) + .await + .unwrap(); + let dkg_session_2 = get_on_chain_resource::(&validator_clients[3]) + .await + .last_completed + .clone() + .unwrap(); + assert!(verify_dkg_transcript(&dkg_session_2, &decrypt_key_map).is_ok()); +}