diff --git a/Cargo.lock b/Cargo.lock index f3980fdd530c4..442c13b498fd0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3495,6 +3495,7 @@ dependencies = [ "aptos-build-info", "aptos-crypto", "aptos-framework", + "aptos-gas-profiling", "aptos-gas-schedule", "aptos-gas-schedule-updator", "aptos-genesis", diff --git a/aptos-move/aptos-release-builder/Cargo.toml b/aptos-move/aptos-release-builder/Cargo.toml index 42f02244848ab..604335b629dfd 100644 --- a/aptos-move/aptos-release-builder/Cargo.toml +++ b/aptos-move/aptos-release-builder/Cargo.toml @@ -18,6 +18,7 @@ aptos = { workspace = true, features = [ "no-upload-proposal" ] } aptos-build-info = { workspace = true } aptos-crypto = { workspace = true } aptos-framework = { workspace = true } +aptos-gas-profiling = { workspace = true } aptos-gas-schedule = { workspace = true } aptos-gas-schedule-updator = { workspace = true } aptos-genesis = { workspace = true } diff --git a/aptos-move/aptos-release-builder/src/main.rs b/aptos-move/aptos-release-builder/src/main.rs index 8352800e81cd7..57cde7cee78bb 100644 --- a/aptos-move/aptos-release-builder/src/main.rs +++ b/aptos-move/aptos-release-builder/src/main.rs @@ -1,7 +1,7 @@ // Copyright © Aptos Foundation // SPDX-License-Identifier: Apache-2.0 -use anyhow::Context; +use anyhow::{bail, Context}; use aptos_crypto::{ed25519::Ed25519PrivateKey, ValidCryptoMaterialStringExt}; use aptos_framework::natives::code::PackageRegistry; use aptos_gas_schedule::LATEST_GAS_FEATURE_VERSION; @@ -69,13 +69,22 @@ impl NetworkSelection { pub enum Commands { /// Generate sets of governance proposals based on the release_config file passed in GenerateProposals { + /// Path to the release config. #[clap(short, long)] release_config: PathBuf, + + /// Output directory to store the generated artifacts. #[clap(short, long)] output_dir: PathBuf, + /// If set, simulate the governance proposals after generation. #[clap(long)] simulate: Option, + + /// Set this flag to enable the gas profiler. + /// Can only be used in combination with `--simulate`. + #[clap(long)] + profile_gas: Option, }, /// 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 @@ -91,6 +100,10 @@ pub enum Commands { /// Possible values: devnet, testnet, mainnet, #[clap(long)] network: NetworkSelection, + + /// Set this flag to enable the gas profiler + #[clap(long, default_value_t = false)] + profile_gas: bool, }, /// Generate sets of governance proposals with default release config. WriteDefault { @@ -184,6 +197,7 @@ async fn main() -> anyhow::Result<()> { release_config, output_dir, simulate, + profile_gas, } => { aptos_release_builder::ReleaseConfig::load_config(release_config.as_path()) .with_context(|| "Failed to load release config".to_string())? @@ -191,15 +205,28 @@ async fn main() -> anyhow::Result<()> { .await .with_context(|| "Failed to generate release proposal scripts".to_string())?; - if let Some(network) = simulate { - let remote_endpoint = network.to_url()?; - simulate_all_proposals(remote_endpoint, output_dir.as_path()).await?; + match simulate { + Some(network) => { + let profile_gas = profile_gas.unwrap_or(false); + let remote_endpoint = network.to_url()?; + simulate_all_proposals(remote_endpoint, output_dir.as_path(), profile_gas) + .await?; + }, + None => { + if profile_gas.is_some() { + bail!("--profile-gas can only be set in combination with --simulate") + } + }, } Ok(()) }, - Commands::Simulate { network, path } => { - simulate_all_proposals(network.to_url()?, &path).await?; + Commands::Simulate { + network, + path, + profile_gas, + } => { + simulate_all_proposals(network.to_url()?, &path, profile_gas).await?; Ok(()) }, Commands::WriteDefault { output_path } => { diff --git a/aptos-move/aptos-release-builder/src/simulate.rs b/aptos-move/aptos-release-builder/src/simulate.rs index f37c8edf49c0d..dcfdbb7d6d2fc 100644 --- a/aptos-move/aptos-release-builder/src/simulate.rs +++ b/aptos-move/aptos-release-builder/src/simulate.rs @@ -26,6 +26,7 @@ use aptos::{ common::types::PromptOptions, governance::compile_in_temp_dir, move_tool::FrameworkPackageArgs, }; use aptos_crypto::HashValue; +use aptos_gas_profiling::GasProfiler; use aptos_gas_schedule::{AptosGasParameters, FromOnChainGasSchedule}; use aptos_language_e2e_tests::account::AccountData; use aptos_move_debugger::aptos_debugger::AptosDebugger; @@ -510,6 +511,7 @@ pub async fn simulate_multistep_proposal( remote_url: Url, proposal_dir: &Path, proposal_scripts: &[PathBuf], + profile_gas: bool, ) -> Result<()> { println!("Simulating proposal at {}", proposal_dir.display()); @@ -626,28 +628,51 @@ pub async fn simulate_multistep_proposal( let resolver = state_view.as_move_resolver(); let code_storage = state_view.as_aptos_code_storage(env); - let (_vm_status, vm_output) = vm.execute_user_transaction( - &resolver, - &code_storage, - &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, - ); + let txn = 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(); + + let vm_output = if !profile_gas { + let (_vm_status, vm_output) = + vm.execute_user_transaction(&resolver, &code_storage, &txn, &log_context); + vm_output + } else { + let (_vm_status, vm_output, gas_profiler) = vm + .execute_user_transaction_with_modified_gas_meter( + &resolver, + &code_storage, + &txn, + &log_context, + GasProfiler::new_script, + )?; + + let gas_log = gas_profiler.finish(); + let report_path = proposal_dir + .join("gas-profiling") + .join(script_path.file_stem().unwrap()); + gas_log.generate_html_report(&report_path, format!("Gas Report - {}", script_name))?; + + println!(" Gas report saved to {}", report_path.display()); + + vm_output + }; // TODO: ensure all scripts trigger reconfiguration. let txn_output = vm_output .try_materialize_into_transaction_output(&resolver) .context("failed to materialize transaction output")?; + + println!(" Gas used: {}", txn_output.gas_used()); + let txn_status = txn_output.status(); match txn_status { TransactionStatus::Keep(ExecutionStatus::Success) => { @@ -710,7 +735,11 @@ pub fn collect_proposals(root_dir: &Path) -> Result)> Ok(result) } -pub async fn simulate_all_proposals(remote_url: Url, output_dir: &Path) -> Result<()> { +pub async fn simulate_all_proposals( + remote_url: Url, + output_dir: &Path, + profile_gas: bool, +) -> Result<()> { let proposals = collect_proposals(output_dir).context("failed to collect proposals for simulation")?; @@ -735,11 +764,14 @@ pub async fn simulate_all_proposals(remote_url: Url, output_dir: &Path) -> Resul } for (proposal_dir, proposal_scripts) in &proposals { - simulate_multistep_proposal(remote_url.clone(), proposal_dir, proposal_scripts) - .await - .with_context(|| { - format!("failed to simulate proposal at {}", proposal_dir.display()) - })?; + simulate_multistep_proposal( + remote_url.clone(), + proposal_dir, + proposal_scripts, + profile_gas, + ) + .await + .with_context(|| format!("failed to simulate proposal at {}", proposal_dir.display()))?; } println!("All proposals succeeded!");