diff --git a/Cargo.lock b/Cargo.lock index ba4c885477ce95..708248f6d1c0da 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3324,18 +3324,26 @@ dependencies = [ "aptos-genesis", "aptos-infallible", "aptos-keygen", + "aptos-language-e2e-tests", + "aptos-move-debugger", "aptos-rest-client", "aptos-temppath", "aptos-types", + "aptos-vm", + "aptos-vm-logging", "bcs 0.1.4", "clap 4.4.14", "futures", "git2 0.16.1", "handlebars", "hex", + "move-binary-format", + "move-bytecode-verifier", "move-core-types", "move-model", + "move-vm-types", "once_cell", + "parking_lot 0.12.1", "reqwest", "serde", "serde_json", diff --git a/aptos-move/aptos-release-builder/Cargo.toml b/aptos-move/aptos-release-builder/Cargo.toml index a42cff88383d23..00def860dc3785 100644 --- a/aptos-move/aptos-release-builder/Cargo.toml +++ b/aptos-move/aptos-release-builder/Cargo.toml @@ -24,18 +24,26 @@ aptos-gas-schedule-updator = { workspace = true } aptos-genesis = { workspace = true } aptos-infallible = { workspace = true } aptos-keygen = { workspace = true } +aptos-language-e2e-tests = { workspace = true } +aptos-move-debugger = { workspace = true } aptos-rest-client = { workspace = true } aptos-temppath = { workspace = true } aptos-types = { workspace = true } +aptos-vm = { workspace = true } +aptos-vm-logging = { workspace = true } bcs = { workspace = true } clap = { workspace = true } futures = { workspace = true } git2 = { workspace = true } handlebars = { workspace = true } hex = { workspace = true } +move-binary-format = { workspace = true } +move-bytecode-verifier = { workspace = true } move-core-types = { workspace = true } move-model = { workspace = true } +move-vm-types = { workspace = true } once_cell = { workspace = true } +parking_lot = { workspace = true } reqwest = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } diff --git a/aptos-move/aptos-release-builder/data/release.yaml b/aptos-move/aptos-release-builder/data/release.yaml index 13afcdcbbbe778..ed24868b45ad62 100644 --- a/aptos-move/aptos-release-builder/data/release.yaml +++ b/aptos-move/aptos-release-builder/data/release.yaml @@ -8,12 +8,8 @@ proposals: description: "This includes changes in https://github.com/aptos-labs/aptos-core/commits/aptos-release-v1.13" execution_mode: MultiStep update_sequence: - - Framework: - bytecode_version: 6 - git_hash: ~ - Gas: new: current - FeatureFlag: enabled: - disallow_user_native - diff --git a/aptos-move/aptos-release-builder/src/lib.rs b/aptos-move/aptos-release-builder/src/lib.rs index b53dd7006f1063..2ac6a94f9003ec 100644 --- a/aptos-move/aptos-release-builder/src/lib.rs +++ b/aptos-move/aptos-release-builder/src/lib.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 pub mod components; +pub mod simulate; mod utils; pub mod validate; diff --git a/aptos-move/aptos-release-builder/src/main.rs b/aptos-move/aptos-release-builder/src/main.rs index 205e8d8224621f..3a5bf4fa885371 100644 --- a/aptos-move/aptos-release-builder/src/main.rs +++ b/aptos-move/aptos-release-builder/src/main.rs @@ -8,6 +8,7 @@ use aptos_gas_schedule::LATEST_GAS_FEATURE_VERSION; use aptos_release_builder::{ components::fetch_config, initialize_aptos_core_path, + simulate::simulate_multistep_proposal, validate::{DEFAULT_RESOLUTION_TIME, FAST_RESOLUTION_TIME}, }; use aptos_types::{ @@ -17,6 +18,7 @@ use aptos_types::{ }; use clap::{Parser, Subcommand}; use std::{path::PathBuf, str::FromStr}; +use url::Url; #[derive(Parser)] pub struct Argument { @@ -26,6 +28,43 @@ pub struct Argument { aptos_core_path: Option, } +// TODO(vgao1996): unify with `ReplayNetworkSelection` in the `aptos` crate. +#[derive(Clone, Debug)] +pub enum NetworkSelection { + Mainnet, + Testnet, + Devnet, + RestEndpoint(String), +} + +impl FromStr for NetworkSelection { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + Ok(match s { + "mainnet" => Self::Mainnet, + "testnet" => Self::Testnet, + "devnet" => Self::Devnet, + _ => Self::RestEndpoint(s.to_owned()), + }) + } +} + +impl NetworkSelection { + fn to_url(&self) -> anyhow::Result { + use NetworkSelection::*; + + let s = match &self { + Mainnet => "https://fullnode.mainnet.aptoslabs.com", + Testnet => "https://fullnode.testnet.aptoslabs.com", + Devnet => "https://fullnode.devnet.aptoslabs.com", + RestEndpoint(url) => url, + }; + + Ok(Url::parse(s)?) + } +} + #[derive(Subcommand, Debug)] pub enum Commands { /// Generate sets of governance proposals based on the release_config file passed in @@ -35,6 +74,20 @@ pub enum Commands { #[clap(short, long)] output_dir: PathBuf, }, + /// Simulate a multi-step proposal on the specified network, using its current states. + /// The simulation will execute the governance scripts, as if the proposal is already + /// approved. + SimulateMultiStepProposal { + /// Path to the directory storing the scripts (steps) belonging to the proposal. + #[clap(short, long)] + proposal_dir: PathBuf, + + /// The network to simulate on. + /// + /// Possible values: devnet, testnet, mainnet, + #[clap(long)] + network: NetworkSelection, + }, /// Generate sets of governance proposals with default release config. WriteDefault { #[clap(short, long)] @@ -134,6 +187,13 @@ async fn main() -> anyhow::Result<()> { .with_context(|| "Failed to generate release proposal scripts".to_string())?; Ok(()) }, + Commands::SimulateMultiStepProposal { + network, + proposal_dir, + } => { + simulate_multistep_proposal(network.to_url()?, &proposal_dir).await?; + Ok(()) + }, Commands::WriteDefault { output_path } => { aptos_release_builder::ReleaseConfig::default().save_config(output_path.as_path()) }, diff --git a/aptos-move/aptos-release-builder/src/simulate.rs b/aptos-move/aptos-release-builder/src/simulate.rs new file mode 100644 index 00000000000000..610b82c3972c18 --- /dev/null +++ b/aptos-move/aptos-release-builder/src/simulate.rs @@ -0,0 +1,601 @@ +// Copyright © Aptos Foundation +// SPDX-License-Identifier: Apache-2.0 + +//! This module implements the simulation of governance proposals. +//! Currently, it supports only multi-step proposals. +//! +//! It utilizes the remote debugger infrastructure to fetch real chain states +//! for local simulation, but adds another in-memory database to store the new side effects +//! generated by the governance scripts. +//! +//! Normally, governance scripts needs to be approved through on-chain governance +//! before they could be executed. This process involves setting up various states +//! (e.g., staking pool, delegated voter), which can be quite complex. +//! +//! This simulation bypasses these challenges by patching specific Move functions +//! with mock versions, most notably `fun resolve_multi_step_proposal`, thus allowing +//! the governance process to be skipped altogether. +//! +//! In other words, this simulation is intended for checking whether a governance +//! proposal will execute successfully, assuming it gets approved, not whether the +//! governance framework itself is working as intended. + +use crate::aptos_framework_path; +use anyhow::{anyhow, bail, Result}; +use aptos::{ + common::types::PromptOptions, governance::compile_in_temp_dir, move_tool::FrameworkPackageArgs, +}; +use aptos_gas_schedule::{AptosGasParameters, FromOnChainGasSchedule, ToOnChainGasSchedule}; +use aptos_language_e2e_tests::account::AccountData; +use aptos_move_debugger::aptos_debugger::AptosDebugger; +use aptos_rest_client::Client; +use aptos_types::{ + account_address::AccountAddress, + account_config::ChainIdResource, + on_chain_config::{Features, GasScheduleV2, OnChainConfig}, + state_store::{ + in_memory_state_view::InMemoryStateView, state_key::StateKey, + state_storage_usage::StateStorageUsage, state_value::StateValue, + Result as StateStoreResult, StateView, TStateView, + }, + transaction::{ExecutionStatus, Script, TransactionArgument, TransactionStatus}, + vm::configs::aptos_prod_deserializer_config, + write_set::{TransactionWrite, WriteSet}, +}; +use aptos_vm::{data_cache::AsMoveResolver, move_vm_ext::flush_warm_vm_cache, AptosVM}; +use aptos_vm_logging::log_schema::AdapterLogSchema; +use clap::Parser; +use move_binary_format::{ + access::ModuleAccess, + deserializer::DeserializerConfig, + file_format::{ + AddressIdentifierIndex, Bytecode, FunctionDefinition, FunctionHandle, FunctionHandleIndex, + IdentifierIndex, ModuleHandle, ModuleHandleIndex, Signature, SignatureIndex, + SignatureToken, Visibility, + }, + CompiledModule, +}; +use move_core_types::{ + identifier::{IdentStr, Identifier}, + language_storage::{ModuleId, StructTag}, + move_resource::MoveResource, +}; +use move_vm_types::resolver::ModuleResolver; +use once_cell::sync::Lazy; +use parking_lot::Mutex; +use std::{ + collections::HashMap, + io::Write, + path::{Path, PathBuf}, +}; +use url::Url; + +/*************************************************************************************************** + * Compiled Module Helpers + * + **************************************************************************************************/ +fn find_function_def_by_name<'a>( + m: &'a mut CompiledModule, + name: &IdentStr, +) -> Option<&'a mut FunctionDefinition> { + for (idx, func_def) in m.function_defs.iter().enumerate() { + let func_handle = m.function_handle_at(func_def.function); + let func_name = m.identifier_at(func_handle.name); + if name == func_name { + return Some(&mut m.function_defs[idx]); + } + } + None +} + +fn get_or_add(pool: &mut Vec, val: T) -> usize { + match pool.iter().position(|elem| elem == &val) { + Some(idx) => idx, + None => { + let idx = pool.len(); + pool.push(val); + idx + }, + } +} + +fn get_or_add_addr(m: &mut CompiledModule, addr: AccountAddress) -> AddressIdentifierIndex { + AddressIdentifierIndex::new(get_or_add(&mut m.address_identifiers, addr) as u16) +} + +fn get_or_add_ident(m: &mut CompiledModule, ident: Identifier) -> IdentifierIndex { + IdentifierIndex::new(get_or_add(&mut m.identifiers, ident) as u16) +} + +fn get_or_add_module_handle( + m: &mut CompiledModule, + addr: AccountAddress, + name: Identifier, +) -> ModuleHandleIndex { + let addr = get_or_add_addr(m, addr); + let name = get_or_add_ident(m, name); + let module_handle = ModuleHandle { + address: addr, + name, + }; + ModuleHandleIndex::new(get_or_add(&mut m.module_handles, module_handle) as u16) +} + +fn get_or_add_signature(m: &mut CompiledModule, sig: Vec) -> SignatureIndex { + SignatureIndex::new(get_or_add(&mut m.signatures, Signature(sig)) as u16) +} + +fn get_or_add_simple_function_handle( + m: &mut CompiledModule, + module_addr: AccountAddress, + module_name: Identifier, + func_name: Identifier, + params: Vec, + returns: Vec, +) -> FunctionHandleIndex { + let module = get_or_add_module_handle(m, module_addr, module_name); + let name = get_or_add_ident(m, func_name); + let parameters = get_or_add_signature(m, params); + let return_ = get_or_add_signature(m, returns); + let func_handle = FunctionHandle { + module, + name, + parameters, + return_, + type_parameters: vec![], + access_specifiers: None, + }; + FunctionHandleIndex::new(get_or_add(&mut m.function_handles, func_handle) as u16) +} +/*************************************************************************************************** + * Simulation State View + * + **************************************************************************************************/ +/// A state view specifically designed for managing the side effects generated by +/// the governance scripts. +/// +/// It comprises two components: +/// - A remote debugger state view to enable on-demand data fetching. +/// - A local state store to allow new changes to be stacked on top of the remote state. +struct SimulationStateView<'a, S> { + remote: &'a S, + states: Mutex>>, +} + +impl<'a, S> SimulationStateView<'a, S> +where + S: StateView, +{ + fn set_state_value(&self, state_key: StateKey, state_val: StateValue) { + self.states.lock().insert(state_key, Some(state_val)); + } + + #[allow(dead_code)] + fn remove_state_value(&mut self, state_key: &StateKey) { + self.states.lock().remove(state_key); + } + + fn apply_write_set(&self, write_set: WriteSet) { + let mut states = self.states.lock(); + + for (state_key, write_op) in write_set { + match write_op.as_state_value() { + None => { + states.remove(&state_key); + }, + Some(state_val) => { + states.insert(state_key, Some(state_val)); + }, + } + } + } + + #[allow(dead_code)] + fn read_resource(&self, addr: &AccountAddress) -> T { + let data_blob = self + .get_state_value_bytes( + &StateKey::resource_typed::(addr).expect("failed to create StateKey"), + ) + .expect("account must exist in data store") + .unwrap_or_else(|| panic!("Can't fetch {} resource for {}", T::STRUCT_NAME, addr)); + + bcs::from_bytes(&data_blob).expect("failed to deserialize resource") + } +} + +impl<'a, S> TStateView for SimulationStateView<'a, S> +where + S: StateView, +{ + type Key = StateKey; + + fn get_state_value(&self, state_key: &Self::Key) -> StateStoreResult> { + if let Some(res) = self.states.lock().get(state_key) { + return Ok(res.clone()); + } + self.remote.get_state_value(state_key) + } + + fn get_usage(&self) -> StateStoreResult { + panic!("not supported") + } + + fn as_in_memory_state_view(&self) -> InMemoryStateView { + panic!("not supported") + } +} + +/*************************************************************************************************** + * Patches + * + **************************************************************************************************/ +static MODULE_ID_CREATE_SIGNER: Lazy = Lazy::new(|| { + ModuleId::new( + AccountAddress::ONE, + Identifier::new("create_signer").unwrap(), + ) +}); + +static MODULE_ID_APTOS_GOVERNANCE: Lazy = Lazy::new(|| { + ModuleId::new( + AccountAddress::ONE, + Identifier::new("aptos_governance").unwrap(), + ) +}); + +static FUNC_NAME_CREATE_SIGNER: Lazy = + Lazy::new(|| Identifier::new("create_signer").unwrap()); + +static FUNC_NAME_RESOLVE_MULTI_STEP_PROPOSAL: Lazy = + Lazy::new(|| Identifier::new("resolve_multi_step_proposal").unwrap()); + +/// Helper to load a module from the state view, deserialize it, modify it with +/// the provided callback, reserialize it and finally write it back. +fn patch_module( + state_view: &SimulationStateView, + deserializer_config: &DeserializerConfig, + module_id: &ModuleId, + modify_module: F, +) -> Result<()> +where + F: FnOnce(&mut CompiledModule) -> Result<()>, +{ + let resolver = state_view.as_move_resolver(); + let blob = resolver + .get_module(module_id)? + .ok_or_else(|| anyhow!("module {} does not exist", module_id))?; + + let mut m = CompiledModule::deserialize_with_config(&blob, deserializer_config)?; + + modify_module(&mut m)?; + + // Sanity check to ensure the correctness of the check + move_bytecode_verifier::verify_module(&m).map_err(|err| { + anyhow!( + "patched module failed to verify -- check if the patch is correct: {}", + err + ) + })?; + + let mut blob = vec![]; + m.serialize(&mut blob)?; + + state_view.set_state_value( + StateKey::module_id(module_id), + StateValue::new_legacy(blob.into()), + ); + + Ok(()) +} + +/// Makes `aptos_framework::create_signer::create_signer` a public function so that +/// it can be used freely in the simulation. +fn patch_create_signer( + state_view: &SimulationStateView, + deserializer_config: &DeserializerConfig, +) -> Result<()> { + patch_module( + state_view, + deserializer_config, + &MODULE_ID_CREATE_SIGNER, + |m| { + let func_def = find_function_def_by_name(m, &FUNC_NAME_CREATE_SIGNER) + .ok_or_else(|| anyhow!("failed to locate `fun {}`", &*FUNC_NAME_CREATE_SIGNER))?; + func_def.visibility = Visibility::Public; + + Ok(()) + }, + ) +} + +const MAGIC_FAILED_NEXT_EXECUTION_HASH_CHECK: u64 = 0xDEADBEEF; + +/// Patches `aptos_framework::aptos_governance::resolve_multi_step_proposal` so that +/// it returns the requested signer directly, skipping the governance process altogether. +fn patch_resolve_multistep_proposal( + state_view: &SimulationStateView, + deserializer_config: &DeserializerConfig, + forbid_next_execution_hash: bool, +) -> Result<()> { + use Bytecode::*; + + patch_module( + state_view, + deserializer_config, + &MODULE_ID_APTOS_GOVERNANCE, + |m| { + let create_signer_handle_idx = get_or_add_simple_function_handle( + m, + MODULE_ID_CREATE_SIGNER.address, + MODULE_ID_CREATE_SIGNER.name.clone(), + FUNC_NAME_CREATE_SIGNER.clone(), + vec![SignatureToken::Address], + vec![SignatureToken::Signer], + ); + let sig_u8_idx = get_or_add_signature(m, vec![SignatureToken::U8]); + + let func_def = find_function_def_by_name(m, &FUNC_NAME_RESOLVE_MULTI_STEP_PROPOSAL) + .ok_or_else(|| { + anyhow!( + "failed to locate `fun {}`", + &*FUNC_NAME_RESOLVE_MULTI_STEP_PROPOSAL + ) + })?; + func_def.acquires_global_resources = vec![]; + let code = func_def.code.as_mut().ok_or_else(|| { + anyhow!( + "`fun {}` must have a Move-defined body", + &*FUNC_NAME_RESOLVE_MULTI_STEP_PROPOSAL + ) + })?; + + code.code.clear(); + if forbid_next_execution_hash { + // If it is needed to forbid a next execution hash, inject additional Move + // code at the beginning that aborts with a magic number if the vector + // representing the hash is not empty. + // + // if (!vector::is_empty(&next_execution_hash)) { + // abort MAGIC_FAILED_NEXT_EXECUTION_HASH_CHECK; + // } + // + // The magic number can later be checked in Rust to determine if such violation + // has happened. + code.code.extend([ + ImmBorrowLoc(2), + VecLen(sig_u8_idx), + LdU64(0), + Eq, + BrTrue(7), + LdU64(MAGIC_FAILED_NEXT_EXECUTION_HASH_CHECK), + Abort, + ]); + } + // Replace the original logic with `create_signer(signer_address)`, bypassing + // the governance process. + code.code + .extend([MoveLoc(1), Call(create_signer_handle_idx), Ret]); + + Ok(()) + }, + ) +} + +/// Fetches the gas schedule and patches the execution limits to use alternative (higher) values +/// for governance scripts. +/// +// In production we have a mechanism to do this automatically, but that depends on the +// scripts' hashes being added to the list of approved execution hashes, which gets skipped +// as we skip the governance process altogether. +// +// TODO: In the future, consider adding the execution hashes of the scripts to the +// approval list instead. +fn get_and_patch_gas_schedule( + state_view: &SimulationStateView, +) -> Result { + // Fetch and deserialize the gas schedule. + let gas_schedule = GasScheduleV2::fetch_config(&state_view) + .ok_or_else(|| anyhow!("failed to fetch gas schedule v2"))?; + let gas_feature_version = gas_schedule.feature_version; + let mut gas_params = AptosGasParameters::from_on_chain_gas_schedule( + &gas_schedule.into_btree_map(), + gas_feature_version, + ) + .map_err(|err| { + anyhow!( + "failed to construct gas params at gas version {}: {}", + gas_feature_version, + err + ) + })?; + + // Overwrite the normal execution limits and write the patched gas schedule back. + let txn_gas_params = &mut gas_params.vm.txn; + txn_gas_params.max_execution_gas = txn_gas_params.max_execution_gas_gov; + txn_gas_params.max_io_gas = txn_gas_params.max_io_gas_gov; + txn_gas_params.max_storage_fee = txn_gas_params.max_storage_fee_gov; + state_view.set_state_value( + StateKey::resource(&AccountAddress::ONE, &StructTag { + address: AccountAddress::ONE, + module: Identifier::new(GasScheduleV2::MODULE_IDENTIFIER).unwrap(), + name: Identifier::new(GasScheduleV2::TYPE_IDENTIFIER).unwrap(), + type_args: vec![], + })?, + StateValue::new_legacy( + bcs::to_bytes(&GasScheduleV2 { + feature_version: gas_feature_version, + entries: gas_params.to_on_chain_gas_schedule(gas_feature_version), + })? + .into(), + ), + ); + + Ok(gas_params) +} + +/*************************************************************************************************** + * Simulation Workflow + * + **************************************************************************************************/ +fn list_scripts(proposal_path: &Path) -> Result> { + let mut script_paths = std::fs::read_dir(proposal_path)? + .filter_map(|entry| entry.ok()) + .filter_map(|entry| { + let path = entry.path(); + if path.extension().map(|s| s == "move").unwrap_or(false) { + Some(path) + } else { + None + } + }) + .collect::>(); + + script_paths.sort(); + + Ok(script_paths) +} + +pub async fn simulate_multistep_proposal(remote_url: Url, proposal_path: &Path) -> Result<()> { + // List all governance scripts. + let scripts = list_scripts(proposal_path)?; + if scripts.is_empty() { + bail!("no scripts found") + } + + println!("Found {} scripts", scripts.len()); + for path in &scripts { + println!(" {}", path.display()); + } + + // Compile all scripts. + println!("Compiling scripts..."); + let mut compiled_scripts = vec![]; + for path in &scripts { + let framework_package_args = FrameworkPackageArgs::try_parse_from([ + "dummy_executable_name", + "--framework-local-dir", + &aptos_framework_path().to_string_lossy(), + "--skip-fetch-latest-git-deps", + ])?; + + let (blob, _hash) = compile_in_temp_dir( + "script", + path, + &framework_package_args, + PromptOptions::yes(), + None, + )?; + + compiled_scripts.push(blob); + } + + // Set up the simulation state view. + let client = Client::new(remote_url); + let debugger = AptosDebugger::rest_client(client.clone())?; + let state = client.get_ledger_information().await?.into_inner(); + + let state_view = SimulationStateView { + remote: &debugger.state_view_at_version(state.version), + states: Mutex::new(HashMap::new()), + }; + + // Create and fund a sender account that is used to send the governance scripts. + print!("Creating and funding sender account.. "); + std::io::stdout().flush()?; + let mut rng = aptos_keygen::KeyGen::from_seed([0; 32]); + let balance = 100 * 1_0000_0000; // 100 APT + let account = AccountData::new_from_seed(&mut rng, balance, 0); + state_view.apply_write_set(account.to_writeset()); + // TODO: should update coin info (total supply) + println!("done"); + + // Fetch the on-chain configs that are needed for the simulation. + let chain_id = ChainIdResource::fetch_config(&state_view) + .ok_or_else(|| anyhow!("failed to fetch chain id"))?; + + let gas_params = get_and_patch_gas_schedule(&state_view)?; + + // Execute the governance scripts in sorted order. + println!("Executing governance scripts..."); + + const DUMMY_PROPOSAL_ID: u64 = u64::MAX; + + for (script_idx, (script_path, script_blob)) in scripts.iter().zip(compiled_scripts).enumerate() + { + // Patch framework functions to skip the governance process. + // This is redone every time we execute a script because the previous script could have + // overwritten the framework. + let features = Features::fetch_config(&state_view) + .ok_or_else(|| anyhow!("failed to fetch feature flags"))?; + let deserializer_config = aptos_prod_deserializer_config(&features); + + patch_create_signer(&state_view, &deserializer_config)?; + // If the script is the last step of the proposal, it MUST NOT have a next execution hash. + // Set the boolean flag to true to use a modified patch to catch this. + let forbid_next_execution_hash = script_idx == scripts.len() - 1; + patch_resolve_multistep_proposal( + &state_view, + &deserializer_config, + forbid_next_execution_hash, + )?; + + let script_name = script_path.file_name().unwrap().to_string_lossy(); + println!(" {}", script_name); + + // Create a new VM to ensure the loader is clean. + // The warm vm cache also needs to be explicitly flushed as it cannot detect the + // patches we performed. + flush_warm_vm_cache(); + let vm = AptosVM::new(&state_view); + let log_context = AdapterLogSchema::new(state_view.id(), 0); + let resolver = state_view.as_move_resolver(); + let (_vm_status, vm_output) = vm.execute_user_transaction( + &resolver, + &account + .account() + .transaction() + .script(Script::new(script_blob, vec![], vec![ + TransactionArgument::U64(DUMMY_PROPOSAL_ID), // dummy proposal id, ignored by the patched function + ])) + .chain_id(chain_id.chain_id()) + .sequence_number(script_idx as u64) + .gas_unit_price(gas_params.vm.txn.min_price_per_gas_unit.into()) + .max_gas_amount(100000) + .ttl(u64::MAX) + .sign(), + &log_context, + ); + // TODO: ensure all scripts trigger reconfiguration. + + let txn_output = vm_output.try_materialize_into_transaction_output(&resolver)?; + let txn_status = txn_output.status(); + match txn_status { + TransactionStatus::Keep(ExecutionStatus::Success) => { + println!(" Success") + }, + TransactionStatus::Keep(ExecutionStatus::MoveAbort { code, .. }) + if *code == MAGIC_FAILED_NEXT_EXECUTION_HASH_CHECK => + { + bail!("the last script has a non-zero next execution hash") + }, + _ => { + println!( + "{}", + format!("{:#?}", txn_status) + .lines() + .map(|line| format!(" {}", line)) + .collect::>() + .join("\n") + ); + bail!("failed to execute governance script: {}", script_name) + }, + } + + let (write_set, _events) = txn_output.into(); + state_view.apply_write_set(write_set); + } + + println!("All scripts succeeded!"); + + Ok(()) +} diff --git a/aptos-move/aptos-vm/src/move_vm_ext/mod.rs b/aptos-move/aptos-vm/src/move_vm_ext/mod.rs index bcd7a1f381d1e0..7348a280dffdf2 100644 --- a/aptos-move/aptos-vm/src/move_vm_ext/mod.rs +++ b/aptos-move/aptos-vm/src/move_vm_ext/mod.rs @@ -21,6 +21,7 @@ use move_core_types::{ account_address::AccountAddress, language_storage::StructTag, vm_status::StatusCode, }; pub use session::session_id::SessionId; +pub use warm_vm_cache::flush_warm_vm_cache; pub(crate) fn resource_state_key( address: &AccountAddress, diff --git a/aptos-move/aptos-vm/src/move_vm_ext/warm_vm_cache.rs b/aptos-move/aptos-vm/src/move_vm_ext/warm_vm_cache.rs index d7f58bff2b5426..fa9d6ffd7e946d 100644 --- a/aptos-move/aptos-vm/src/move_vm_ext/warm_vm_cache.rs +++ b/aptos-move/aptos-vm/src/move_vm_ext/warm_vm_cache.rs @@ -30,6 +30,10 @@ static WARM_VM_CACHE: Lazy = Lazy::new(|| WarmVmCache { cache: RwLock::new(HashMap::new()), }); +pub fn flush_warm_vm_cache() { + WARM_VM_CACHE.cache.write().clear(); +} + impl WarmVmCache { pub(crate) fn get_warm_vm( native_builder: SafeNativeBuilder, diff --git a/crates/aptos/src/governance/mod.rs b/crates/aptos/src/governance/mod.rs index 883e06fa9f2c1f..092c88c6ae0e0e 100644 --- a/crates/aptos/src/governance/mod.rs +++ b/crates/aptos/src/governance/mod.rs @@ -769,7 +769,7 @@ impl std::fmt::Display for ProposalMetadata { } } -fn compile_in_temp_dir( +pub fn compile_in_temp_dir( script_name: &str, script_path: &Path, framework_package_args: &FrameworkPackageArgs,