From b27da8c0834ccca034c3343b44a83bd75f02304e Mon Sep 17 00:00:00 2001 From: Victor Gao Date: Tue, 21 May 2024 19:28:09 +0000 Subject: [PATCH] [gas] generate delta when upgrading the gas schedule --- Cargo.lock | 3 + api/src/context.rs | 4 +- aptos-move/aptos-release-builder/Cargo.toml | 2 + .../aptos-release-builder/data/release.yaml | 3 +- .../src/components/gas.rs | 150 ++++++--- .../src/components/mod.rs | 298 ++++++++---------- aptos-move/aptos-release-builder/src/main.rs | 30 +- .../aptos-release-builder/src/validate.rs | 8 +- aptos-move/aptos-vm/src/gas.rs | 4 +- aptos-move/e2e-move-tests/Cargo.toml | 1 + aptos-move/e2e-move-tests/src/harness.rs | 2 +- aptos-move/e2e-move-tests/src/tests/gas.rs | 40 +++ .../aptos-framework/doc/gas_schedule.md | 88 +++++- .../sources/configs/gas_schedule.move | 32 ++ .../sources/configs/gas_schedule.spec.move | 17 + .../framework/move-stdlib/doc/features.md | 11 + .../sources/configs/features.spec.move | 4 + testsuite/smoke-test/src/rosetta.rs | 2 +- testsuite/smoke-test/src/upgrade.rs | 10 +- types/src/on_chain_config/gas_schedule.rs | 45 ++- types/src/on_chain_config/mod.rs | 2 +- 21 files changed, 540 insertions(+), 216 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 158cdbc30fc8b..44d7536b9fe37 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3286,9 +3286,11 @@ dependencies = [ "move-core-types", "move-model", "once_cell", + "reqwest", "serde", "serde_json", "serde_yaml 0.8.26", + "sha3 0.9.1", "strum 0.24.1", "strum_macros 0.24.3", "tokio", @@ -7194,6 +7196,7 @@ dependencies = [ "rand 0.7.3", "rstest", "serde", + "sha3 0.9.1", "test-case", ] diff --git a/api/src/context.rs b/api/src/context.rs index be5bb56b0acaf..d8f17ab06d887 100644 --- a/api/src/context.rs +++ b/api/src/context.rs @@ -1180,14 +1180,14 @@ impl Context { let gas_schedule_params = match GasScheduleV2::fetch_config(&state_view).and_then(|gas_schedule| { let feature_version = gas_schedule.feature_version; - let gas_schedule = gas_schedule.to_btree_map(); + let gas_schedule = gas_schedule.into_btree_map(); AptosGasParameters::from_on_chain_gas_schedule(&gas_schedule, feature_version) .ok() }) { Some(gas_schedule) => Ok(gas_schedule), None => GasSchedule::fetch_config(&state_view) .and_then(|gas_schedule| { - let gas_schedule = gas_schedule.to_btree_map(); + let gas_schedule = gas_schedule.into_btree_map(); AptosGasParameters::from_on_chain_gas_schedule(&gas_schedule, 0).ok() }) .ok_or_else(|| { diff --git a/aptos-move/aptos-release-builder/Cargo.toml b/aptos-move/aptos-release-builder/Cargo.toml index 41d33cd42e1cb..a42cff88383d2 100644 --- a/aptos-move/aptos-release-builder/Cargo.toml +++ b/aptos-move/aptos-release-builder/Cargo.toml @@ -36,9 +36,11 @@ hex = { workspace = true } move-core-types = { workspace = true } move-model = { workspace = true } once_cell = { workspace = true } +reqwest = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } serde_yaml = { workspace = true } +sha3 = { workspace = true } strum = { workspace = true } strum_macros = { workspace = true } tokio = { workspace = true } diff --git a/aptos-move/aptos-release-builder/data/release.yaml b/aptos-move/aptos-release-builder/data/release.yaml index fd324546126e1..fa0ceb70ce5ac 100644 --- a/aptos-move/aptos-release-builder/data/release.yaml +++ b/aptos-move/aptos-release-builder/data/release.yaml @@ -11,4 +11,5 @@ proposals: - Framework: bytecode_version: 6 git_hash: ~ - - DefaultGas + - Gas: + new: current diff --git a/aptos-move/aptos-release-builder/src/components/gas.rs b/aptos-move/aptos-release-builder/src/components/gas.rs index 92e490609b6e0..7e247e8a402a1 100644 --- a/aptos-move/aptos-release-builder/src/components/gas.rs +++ b/aptos-move/aptos-release-builder/src/components/gas.rs @@ -3,35 +3,65 @@ use crate::{components::get_signer_arg, utils::*}; use anyhow::Result; -use aptos_types::on_chain_config::GasScheduleV2; +use aptos_types::on_chain_config::{DiffItem, GasScheduleV2}; use move_model::{code_writer::CodeWriter, emit, emitln, model::Loc}; +use sha3::{Digest, Sha3_512}; -pub fn generate_gas_upgrade_proposal( - post_randomness_framework: bool, - gas_schedule: &GasScheduleV2, - is_testnet: bool, - next_execution_hash: Vec, -) -> Result> { - let signer_arg = get_signer_arg(is_testnet, &next_execution_hash); - let mut result = vec![]; - - let writer = CodeWriter::new(Loc::default()); +fn emit_gas_schedule_diff( + writer: &CodeWriter, + old_gas_schedule: &GasScheduleV2, + new_gas_schedule: &GasScheduleV2, +) -> Result<()> { + emitln!(writer, "// Changes"); + if old_gas_schedule.feature_version != new_gas_schedule.feature_version { + emitln!( + writer, + "// Feature version: {} -> {}", + old_gas_schedule.feature_version, + new_gas_schedule.feature_version + ); + } + let changes = GasScheduleV2::diff(old_gas_schedule, new_gas_schedule); + if !changes.is_empty() { + let max_len = changes + .iter() + .fold(0, |acc, (name, _)| usize::max(acc, name.len())); - emitln!( - writer, - "// source commit hash: {}\n", - aptos_build_info::get_git_hash() - ); + emitln!(writer, "// Parameters"); + for (param_name, delta) in &changes { + let name_with_spaces = + format!("{}{}", param_name, " ".repeat(max_len - param_name.len())); + match delta { + DiffItem::Add { new_val } => { + emitln!(writer, "// + {} : {}", name_with_spaces, new_val); + }, + DiffItem::Delete { old_val } => { + emitln!(writer, "// - {} : {}", name_with_spaces, old_val); + }, + DiffItem::Modify { old_val, new_val } => { + emitln!( + writer, + "// {} : {} -> {}", + name_with_spaces, + old_val, + new_val + ); + }, + } + } + } - emitln!(writer, "// Gas schedule upgrade proposal\n"); + Ok(()) +} +fn emit_full_gas_schedule(writer: &CodeWriter, gas_schedule: &GasScheduleV2) -> Result<()> { + emitln!(writer, "// Full gas schedule"); emitln!( writer, - "// Feature version: {}", + "// Feature version: {}", gas_schedule.feature_version ); - emitln!(writer, "//"); - emitln!(writer, "// Entries:"); + emitln!(writer, "// Parameters:"); let max_len = gas_schedule .entries .iter() @@ -42,35 +72,77 @@ pub fn generate_gas_upgrade_proposal( } emitln!(writer); + Ok(()) +} + +pub fn generate_gas_upgrade_proposal( + old_gas_schedule: Option<&GasScheduleV2>, + new_gas_schedule: &GasScheduleV2, + is_testnet: bool, + next_execution_hash: Vec, +) -> Result> { + let signer_arg = get_signer_arg(is_testnet, &next_execution_hash); + let mut result = vec![]; + + let writer = CodeWriter::new(Loc::default()); + + emitln!( + writer, + "// Source commit hash: {}", + aptos_build_info::get_git_hash() + ); + emitln!(writer); + + emitln!(writer, "// Gas schedule upgrade proposal"); + + let old_hash = match old_gas_schedule { + Some(old_gas_schedule) => { + let old_bytes = bcs::to_bytes(old_gas_schedule)?; + let old_hash = hex::encode(Sha3_512::digest(old_bytes.as_slice())); + emitln!(writer, "//"); + emitln!(writer, "// Old Gas Schedule Hash (Sha3-512): {}", old_hash); + + emit_gas_schedule_diff(&writer, old_gas_schedule, new_gas_schedule)?; + + Some(old_hash) + }, + None => None, + }; + emitln!(writer, "//"); + emit_full_gas_schedule(&writer, new_gas_schedule)?; + let proposal = generate_governance_proposal( &writer, is_testnet, next_execution_hash.clone(), &["aptos_framework::gas_schedule"], |writer| { - let gas_schedule_blob = bcs::to_bytes(gas_schedule).unwrap(); + let gas_schedule_blob = bcs::to_bytes(new_gas_schedule).unwrap(); assert!(gas_schedule_blob.len() < 65536); + emit!(writer, "let gas_schedule_blob: vector = "); generate_blob_as_hex_string(writer, &gas_schedule_blob); - emitln!(writer, ";\n"); - if !post_randomness_framework { - emitln!( - writer, - "gas_schedule::set_gas_schedule({}, gas_schedule_blob)", - signer_arg - ); - } else { - // The else statement has & before the framework_signer. - // The testnet single-step generation had something like let framework_signer = &core_signer; - // so that their framework_signer is of type &signer, but for mainnet single-step and multi-step, - // the framework_signer is of type signer. - emitln!( - writer, - "gas_schedule::set_for_next_epoch({}, gas_schedule_blob);", - signer_arg - ); - emitln!(writer, "aptos_governance::reconfigure({});", signer_arg); + emitln!(writer, ";"); + emitln!(writer); + + match old_hash { + Some(old_hash) => { + emitln!( + writer, + "gas_schedule::set_for_next_epoch_check_hash({}, x\"{}\", gas_schedule_blob);", + signer_arg, + old_hash, + ); + }, + None => { + emitln!( + writer, + "gas_schedule::set_for_next_epoch({}, gas_schedule_blob);", + signer_arg + ); + }, } + emitln!(writer, "aptos_governance::reconfigure({});", signer_arg); }, ); diff --git a/aptos-move/aptos-release-builder/src/components/mod.rs b/aptos-move/aptos-release-builder/src/components/mod.rs index de332baaf8713..f5169c3b89e36 100644 --- a/aptos-move/aptos-release-builder/src/components/mod.rs +++ b/aptos-move/aptos-release-builder/src/components/mod.rs @@ -26,10 +26,10 @@ use aptos_types::{ use futures::executor::block_on; use handlebars::Handlebars; use once_cell::sync::Lazy; -use serde::{Deserialize, Serialize}; +use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer}; use std::{ collections::HashMap, - fs::File, + fs::{self, File}, io::{Read, Write}, path::{Path, PathBuf}, thread::sleep, @@ -73,10 +73,7 @@ impl Proposal { features_diff.squash(feature_flags.clone()) }, ReleaseEntry::Framework(_) - | ReleaseEntry::CustomGas(_) - | ReleaseEntry::DefaultGas - | ReleaseEntry::DefaultGasWithOverride(_) - | ReleaseEntry::DefaultGasWithOverrideOld(_) + | ReleaseEntry::Gas { .. } | ReleaseEntry::Version(_) | ReleaseEntry::Consensus(_) | ReleaseEntry::Execution(_) @@ -116,10 +113,11 @@ pub enum ExecutionMode { RootSigner, } -#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)] -pub struct GasOverrideConfig { - feature_version: Option, - overrides: Option>, +#[derive(Debug, Clone, Eq, PartialEq)] +pub enum GasScheduleLocator { + LocalFile(String), + RemoteFile(Url), + Current, } #[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)] @@ -131,11 +129,10 @@ pub struct GasOverride { #[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)] pub enum ReleaseEntry { Framework(FrameworkReleaseConfig), - CustomGas(GasScheduleV2), - DefaultGas, - DefaultGasWithOverride(GasOverrideConfig), - /// Only used before randomness framework upgrade. - DefaultGasWithOverrideOld(GasOverrideConfig), + Gas { + old: Option, + new: GasScheduleLocator, + }, Version(AptosVersion), FeatureFlag(Features), Consensus(OnChainConsensusConfig), @@ -147,8 +144,76 @@ pub enum ReleaseEntry { Randomness(ReleaseFriendlyRandomnessConfig), } +impl Serialize for GasScheduleLocator { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match self { + GasScheduleLocator::LocalFile(path) => serializer.serialize_str(path), + GasScheduleLocator::RemoteFile(url) => serializer.serialize_str(url.as_str()), + GasScheduleLocator::Current => serializer.serialize_str("current"), + } + } +} + +impl<'de> Deserialize<'de> for GasScheduleLocator { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct GasScheduleLocatorVisitor; + + impl<'de> Visitor<'de> for GasScheduleLocatorVisitor { + type Value = GasScheduleLocator; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str( + "a valid gas schedule locator (path to local file, url to remote file or `current`)", + ) + } + + fn visit_str(self, value: &str) -> Result + where + E: serde::de::Error, + { + if value == "current" { + Ok(GasScheduleLocator::Current) + } else if let Ok(url) = Url::parse(value) { + Ok(GasScheduleLocator::RemoteFile(url)) + } else { + Ok(GasScheduleLocator::LocalFile(value.to_string())) + } + } + } + + deserializer.deserialize_str(GasScheduleLocatorVisitor) + } +} + +impl GasScheduleLocator { + async fn fetch_gas_schedule(&self) -> Result { + println!("{:?}", self); + match self { + GasScheduleLocator::LocalFile(path) => { + let file_contents = fs::read_to_string(path)?; + let gas_schedule: GasScheduleV2 = serde_json::from_str(&file_contents)?; + Ok(gas_schedule) + }, + GasScheduleLocator::RemoteFile(url) => { + let response = reqwest::get(url.as_str()).await?; + let gas_schedule: GasScheduleV2 = response.json().await?; + Ok(gas_schedule) + }, + GasScheduleLocator::Current => Ok(aptos_gas_schedule_updator::current_gas_schedule( + LATEST_GAS_FEATURE_VERSION, + )), + } + } +} + impl ReleaseEntry { - pub fn generate_release_script( + pub async fn generate_release_script( &self, client: Option<&Client>, result: &mut Vec<(String, String)>, @@ -173,77 +238,39 @@ impl ReleaseEntry { .unwrap(), ); }, - ReleaseEntry::CustomGas(gas_schedule) => { - if !fetch_and_equals::(client, gas_schedule)? { - result.append(&mut gas::generate_gas_upgrade_proposal( - true, - gas_schedule, - is_testnet, - if is_multi_step { - get_execution_hash(result) - } else { - "".to_owned().into_bytes() - }, - )?); - } - }, - ReleaseEntry::DefaultGas => { - let gas_schedule = - aptos_gas_schedule_updator::current_gas_schedule(LATEST_GAS_FEATURE_VERSION); - if !fetch_and_equals::(client, &gas_schedule)? { - result.append(&mut gas::generate_gas_upgrade_proposal( - true, - &gas_schedule, - is_testnet, - if is_multi_step { - get_execution_hash(result) - } else { - "".to_owned().into_bytes() - }, - )?); - } - }, - ReleaseEntry::DefaultGasWithOverride(GasOverrideConfig { - feature_version, - overrides, - }) => { - let feature_version = feature_version.unwrap_or(LATEST_GAS_FEATURE_VERSION); - let gas_schedule = gas_override_default( - feature_version, - overrides - .as_ref() - .map(|overrides| overrides.as_slice()) - .unwrap_or(&[]), - )?; - if !fetch_and_equals::(client, &gas_schedule)? { - result.append(&mut gas::generate_gas_upgrade_proposal( - true, - &gas_schedule, - is_testnet, - if is_multi_step { - get_execution_hash(result) - } else { - "".to_owned().into_bytes() - }, - )?); - } - }, - ReleaseEntry::DefaultGasWithOverrideOld(GasOverrideConfig { - feature_version, - overrides, - }) => { - let feature_version = feature_version.unwrap_or(LATEST_GAS_FEATURE_VERSION); - let gas_schedule = gas_override_default( - feature_version, - overrides - .as_ref() - .map(|overrides| overrides.as_slice()) - .unwrap_or(&[]), - )?; - if !fetch_and_equals::(client, &gas_schedule)? { + ReleaseEntry::Gas { old, new } => { + let new_gas_schedule = new + .fetch_gas_schedule() + .await + .map_err(|err| anyhow!("Failed to fetch new gas schedule: {}", err))?; + let old_gas_schedule = match old { + Some(old) => Some( + old.fetch_gas_schedule() + .await + .map_err(|err| anyhow!("Failed to fetch old gas schedule: {}", err))?, + ), + None => { + match client { + Some(client) => Some(fetch_config::(client)?), + None => { + println!("!!! WARNING !!!"); + println!("Generating gas schedule upgrade without a base for comparison."); + println!("It is strongly recommended you specify an old gas schedule or a remote end point where it can be fetched."); + println!("!!! WARNING !!!"); + None + }, + } + }, + }; + + if old_gas_schedule + .as_ref() + .map(|old| old != &new_gas_schedule) + .unwrap_or(true) + { result.append(&mut gas::generate_gas_upgrade_proposal( - false, - &gas_schedule, + old_gas_schedule.as_ref(), + &new_gas_schedule, is_testnet, if is_multi_step { get_execution_hash(result) @@ -404,45 +431,15 @@ impl ReleaseEntry { Ok(()) } - pub fn validate_upgrade(&self, client: &Client) -> Result<()> { + pub async fn validate_upgrade(&self, client: &Client) -> Result<()> { let client_opt = Some(client); match self { ReleaseEntry::Framework(_) => (), ReleaseEntry::RawScript(_) => (), - ReleaseEntry::CustomGas(gas_schedule) => { - if !wait_until_equals(client_opt, gas_schedule, *MAX_ASYNC_RECONFIG_TIME) { - bail!("Gas schedule config mismatch: Expected {:?}", gas_schedule); - } - }, - ReleaseEntry::DefaultGas => { - if !wait_until_equals( - client_opt, - &aptos_gas_schedule_updator::current_gas_schedule(LATEST_GAS_FEATURE_VERSION), - *MAX_ASYNC_RECONFIG_TIME, - ) { - bail!("Gas schedule config mismatch: Expected Default"); - } - }, - ReleaseEntry::DefaultGasWithOverrideOld(config) - | ReleaseEntry::DefaultGasWithOverride(config) => { - let GasOverrideConfig { - overrides, - feature_version, - } = config; - - let feature_version = feature_version.unwrap_or(LATEST_GAS_FEATURE_VERSION); - - if !wait_until_equals( - client_opt, - &gas_override_default( - feature_version, - overrides - .as_ref() - .map(|overrides| overrides.as_slice()) - .unwrap_or(&[]), - )?, - Duration::from_secs(60), - ) { + ReleaseEntry::Gas { old: _old, new } => { + let new_gas_schedule = new.fetch_gas_schedule().await?; + + if !wait_until_equals(client_opt, &new_gas_schedule, Duration::from_secs(60)) { bail!("Gas schedule config mismatch: Expected Default"); } }, @@ -512,30 +509,6 @@ impl ReleaseEntry { } } -fn gas_override_default( - feature_version: u64, - gas_overrides: &[GasOverride], -) -> Result { - let mut gas_schedule = aptos_gas_schedule_updator::current_gas_schedule(feature_version); - for gas_override in gas_overrides { - let mut found = false; - for (name, value) in &mut gas_schedule.entries { - if name == &gas_override.name { - *value = gas_override.value; - found = true; - break; - } - } - if !found { - bail!( - "Gas override config mismatch: Expected {:?} to be in the gas schedule", - gas_override.name - ); - } - } - Ok(gas_schedule) -} - // Compare the current on chain config with the value recorded on chain. Return false if there's a difference. fn fetch_and_equals( client: Option<&Client>, @@ -587,7 +560,7 @@ pub fn fetch_config(client: &Client) -> Result { } impl ReleaseConfig { - pub fn generate_release_proposal_scripts(&self, base_path: &Path) -> Result<()> { + pub async fn generate_release_proposal_scripts(&self, base_path: &Path) -> Result<()> { let client = self .remote_endpoint .as_ref() @@ -640,20 +613,24 @@ impl ReleaseConfig { let mut result: Vec<(String, String)> = vec![]; if let ExecutionMode::MultiStep = &proposal.execution_mode { for entry in proposal.update_sequence.iter().rev() { - entry.generate_release_script( - client.as_ref(), - &mut result, - proposal.execution_mode, - )?; + entry + .generate_release_script( + client.as_ref(), + &mut result, + proposal.execution_mode, + ) + .await?; } result.reverse(); } else { for entry in proposal.update_sequence.iter() { - entry.generate_release_script( - client.as_ref(), - &mut result, - proposal.execution_mode, - )?; + entry + .generate_release_script( + client.as_ref(), + &mut result, + proposal.execution_mode, + ) + .await?; } } @@ -720,10 +697,10 @@ impl ReleaseConfig { } // Fetch all configs from a remote rest endpoint and assert all the configs are the same as the ones specified locally. - pub fn validate_upgrade(&self, endpoint: &Url, proposal: &Proposal) -> Result<()> { + pub async fn validate_upgrade(&self, endpoint: &Url, proposal: &Proposal) -> Result<()> { let client = Client::new(endpoint.clone()); for entry in proposal.consolidated_side_effects() { - entry.validate_upgrade(&client)?; + entry.validate_upgrade(&client).await?; } Ok(()) } @@ -748,7 +725,10 @@ impl Default for ReleaseConfig { execution_mode: ExecutionMode::MultiStep, metadata: ProposalMetadata::default(), name: "gas".to_string(), - update_sequence: vec![ReleaseEntry::DefaultGas], + update_sequence: vec![ReleaseEntry::Gas { + old: None, + new: GasScheduleLocator::Current, + }], }, Proposal { execution_mode: ExecutionMode::MultiStep, diff --git a/aptos-move/aptos-release-builder/src/main.rs b/aptos-move/aptos-release-builder/src/main.rs index ec1fb08c643c9..205e8d8224621 100644 --- a/aptos-move/aptos-release-builder/src/main.rs +++ b/aptos-move/aptos-release-builder/src/main.rs @@ -4,6 +4,7 @@ use anyhow::Context; use aptos_crypto::{ed25519::Ed25519PrivateKey, ValidCryptoMaterialStringExt}; use aptos_framework::natives::code::PackageRegistry; +use aptos_gas_schedule::LATEST_GAS_FEATURE_VERSION; use aptos_release_builder::{ components::fetch_config, initialize_aptos_core_path, @@ -15,7 +16,7 @@ use aptos_types::{ jwks::{ObservedJWKs, SupportedOIDCProviders}, }; use clap::{Parser, Subcommand}; -use std::path::PathBuf; +use std::{path::PathBuf, str::FromStr}; #[derive(Parser)] pub struct Argument { @@ -57,6 +58,16 @@ pub enum Commands { #[clap(long)] mint_to_validator: bool, }, + /// Generate a gas schedule using the current values and store it to a file. + GenerateGasSchedule { + /// The version of the gas schedule to generate. + #[clap(short, long)] + version: Option, + + /// Path of the output file. + #[clap(short, long)] + output_path: Option, + }, /// Print out current values of on chain configs. PrintConfigs { /// Url endpoint for the desired network. e.g: https://fullnode.mainnet.aptoslabs.com/v1. @@ -119,6 +130,7 @@ async fn main() -> anyhow::Result<()> { aptos_release_builder::ReleaseConfig::load_config(release_config.as_path()) .with_context(|| "Failed to load release config".to_string())? .generate_release_proposal_scripts(output_dir.as_path()) + .await .with_context(|| "Failed to generate release proposal scripts".to_string())?; Ok(()) }, @@ -201,6 +213,22 @@ async fn main() -> anyhow::Result<()> { .await?; Ok(()) }, + Commands::GenerateGasSchedule { + version, + output_path, + } => { + let version = version.unwrap_or(LATEST_GAS_FEATURE_VERSION); + let output_path = + output_path.unwrap_or_else(|| PathBuf::from_str("gas_schedule.json").unwrap()); + + let gas_schedule = aptos_gas_schedule_updator::current_gas_schedule(version); + let json = serde_json::to_string_pretty(&gas_schedule)?; + + std::fs::write(&output_path, json)?; + println!("Gas scheduled saved to {}.", output_path.display()); + + Ok(()) + }, Commands::PrintConfigs { endpoint, print_gas_schedule, diff --git a/aptos-move/aptos-release-builder/src/validate.rs b/aptos-move/aptos-release-builder/src/validate.rs index 96aea9f9e9ddc..b994ef499bbc9 100644 --- a/aptos-move/aptos-release-builder/src/validate.rs +++ b/aptos-move/aptos-release-builder/src/validate.rs @@ -399,7 +399,9 @@ async fn execute_release( } else { scripts_path.path() }; - release_config.generate_release_proposal_scripts(proposal_folder)?; + release_config + .generate_release_proposal_scripts(proposal_folder) + .await?; network_config.increase_lockup().await?; @@ -466,7 +468,9 @@ async fn execute_release( }, }; if validate_release { - release_config.validate_upgrade(&network_config.endpoint, proposal)?; + release_config + .validate_upgrade(&network_config.endpoint, proposal) + .await?; } } Ok(()) diff --git a/aptos-move/aptos-vm/src/gas.rs b/aptos-move/aptos-vm/src/gas.rs index b3ccc739539a9..619ba71f3d68e 100644 --- a/aptos-move/aptos-vm/src/gas.rs +++ b/aptos-move/aptos-vm/src/gas.rs @@ -31,7 +31,7 @@ pub fn get_gas_config_from_storage( match GasScheduleV2::fetch_config(config_storage) { Some(gas_schedule) => { let feature_version = gas_schedule.feature_version; - let map = gas_schedule.to_btree_map(); + let map = gas_schedule.into_btree_map(); ( AptosGasParameters::from_on_chain_gas_schedule(&map, feature_version), feature_version, @@ -39,7 +39,7 @@ pub fn get_gas_config_from_storage( }, None => match GasSchedule::fetch_config(config_storage) { Some(gas_schedule) => { - let map = gas_schedule.to_btree_map(); + let map = gas_schedule.into_btree_map(); (AptosGasParameters::from_on_chain_gas_schedule(&map, 0), 0) }, None => (Err("Neither gas schedule v2 nor v1 exists.".to_string()), 0), diff --git a/aptos-move/e2e-move-tests/Cargo.toml b/aptos-move/e2e-move-tests/Cargo.toml index b795c55795071..6158f0f52a9f2 100644 --- a/aptos-move/e2e-move-tests/Cargo.toml +++ b/aptos-move/e2e-move-tests/Cargo.toml @@ -39,6 +39,7 @@ proptest = { workspace = true } rand = { workspace = true } rstest = { workspace = true } serde = { workspace = true } +sha3 = { workspace = true } test-case = { workspace = true } [dev-dependencies] diff --git a/aptos-move/e2e-move-tests/src/harness.rs b/aptos-move/e2e-move-tests/src/harness.rs index 8cfd9f4ad5fb3..84d693e95c7ff 100644 --- a/aptos-move/e2e-move-tests/src/harness.rs +++ b/aptos-move/e2e-move-tests/src/harness.rs @@ -908,7 +908,7 @@ impl MoveHarness { let gas_schedule: GasScheduleV2 = self.get_gas_schedule(); let feature_version = gas_schedule.feature_version; let params = AptosGasParameters::from_on_chain_gas_schedule( - &gas_schedule.to_btree_map(), + &gas_schedule.into_btree_map(), feature_version, ) .unwrap(); diff --git a/aptos-move/e2e-move-tests/src/tests/gas.rs b/aptos-move/e2e-move-tests/src/tests/gas.rs index 3b542a2614c97..da5fc2a5941ff 100644 --- a/aptos-move/e2e-move-tests/src/tests/gas.rs +++ b/aptos-move/e2e-move-tests/src/tests/gas.rs @@ -16,12 +16,52 @@ use aptos_crypto::{bls12381, PrivateKey, Uniform}; use aptos_gas_profiling::TransactionGasLog; use aptos_types::{ account_address::{default_stake_pool_address, AccountAddress}, + account_config::CORE_CODE_ADDRESS, transaction::{EntryFunction, TransactionPayload}, }; use aptos_vm::AptosVM; use move_core_types::{identifier::Identifier, language_storage::ModuleId}; +use sha3::{Digest, Sha3_512}; use std::path::Path; +#[test] +fn test_modify_gas_schedule_check_hash() { + let mut harness = MoveHarness::new(); + + let mut gas_schedule = harness.get_gas_schedule(); + let old_hash = Sha3_512::digest(&bcs::to_bytes(&gas_schedule).unwrap()).to_vec(); + + const MAGIC: u64 = 42424242; + + let (_, val) = gas_schedule + .entries + .iter_mut() + .find(|(name, _)| name == "instr.nop") + .unwrap(); + assert_ne!(*val, MAGIC); + *val = MAGIC; + + harness.executor.exec( + "gas_schedule", + "set_for_next_epoch_check_hash", + vec![], + vec![ + bcs::to_bytes(&CORE_CODE_ADDRESS).unwrap(), + bcs::to_bytes(&old_hash).unwrap(), + bcs::to_bytes(&bcs::to_bytes(&gas_schedule).unwrap()).unwrap(), + ], + ); + + harness + .executor + .exec("reconfiguration_with_dkg", "finish", vec![], vec![ + bcs::to_bytes(&CORE_CODE_ADDRESS).unwrap(), + ]); + + let (_, gas_params) = harness.get_gas_params(); + assert_eq!(gas_params.vm.instr.nop, MAGIC.into()); +} + fn save_profiling_results(name: &str, log: &TransactionGasLog) { let path = Path::new("gas-profiling").join(name); log.generate_html_report(path, format!("Gas Report - {}", name)) diff --git a/aptos-move/framework/aptos-framework/doc/gas_schedule.md b/aptos-move/framework/aptos-framework/doc/gas_schedule.md index 102058589b5eb..ea4a92886698f 100644 --- a/aptos-move/framework/aptos-framework/doc/gas_schedule.md +++ b/aptos-move/framework/aptos-framework/doc/gas_schedule.md @@ -14,6 +14,7 @@ it costs to execute Move on the network. - [Function `initialize`](#0x1_gas_schedule_initialize) - [Function `set_gas_schedule`](#0x1_gas_schedule_set_gas_schedule) - [Function `set_for_next_epoch`](#0x1_gas_schedule_set_for_next_epoch) +- [Function `set_for_next_epoch_check_hash`](#0x1_gas_schedule_set_for_next_epoch_check_hash) - [Function `on_new_epoch`](#0x1_gas_schedule_on_new_epoch) - [Function `set_storage_gas_config`](#0x1_gas_schedule_set_storage_gas_config) - [Function `set_storage_gas_config_for_next_epoch`](#0x1_gas_schedule_set_storage_gas_config_for_next_epoch) @@ -23,12 +24,15 @@ it costs to execute Move on the network. - [Function `initialize`](#@Specification_1_initialize) - [Function `set_gas_schedule`](#@Specification_1_set_gas_schedule) - [Function `set_for_next_epoch`](#@Specification_1_set_for_next_epoch) + - [Function `set_for_next_epoch_check_hash`](#@Specification_1_set_for_next_epoch_check_hash) - [Function `on_new_epoch`](#@Specification_1_on_new_epoch) - [Function `set_storage_gas_config`](#@Specification_1_set_storage_gas_config) - [Function `set_storage_gas_config_for_next_epoch`](#@Specification_1_set_storage_gas_config_for_next_epoch) -
use 0x1::chain_status;
+
use 0x1::aptos_hash;
+use 0x1::bcs;
+use 0x1::chain_status;
 use 0x1::config_buffer;
 use 0x1::error;
 use 0x1::reconfiguration;
@@ -158,6 +162,15 @@ The provided gas schedule bytes are empty or invalid
 
 
 
+
+
+
+
+
const EINVALID_GAS_SCHEDULE_HASH: u64 = 3;
+
+ + + ## Function `initialize` @@ -279,6 +292,55 @@ aptos_framework::aptos_governance::reconfigure(&framework_signer); + + + + +## Function `set_for_next_epoch_check_hash` + +Set the gas schedule for the next epoch, typically called by on-chain governance. +Abort if the version of the given schedule is lower than the current version. +Require a hash of the old gas schedule to be provided and will abort if the hashes mismatch. + + +
public fun set_for_next_epoch_check_hash(aptos_framework: &signer, old_gas_schedule_hash: vector<u8>, new_gas_schedule_blob: vector<u8>)
+
+ + + +
+Implementation + + +
public fun set_for_next_epoch_check_hash(
+    aptos_framework: &signer,
+    old_gas_schedule_hash: vector<u8>,
+    new_gas_schedule_blob: vector<u8>
+) acquires GasScheduleV2 {
+    system_addresses::assert_aptos_framework(aptos_framework);
+    assert!(!vector::is_empty(&new_gas_schedule_blob), error::invalid_argument(EINVALID_GAS_SCHEDULE));
+
+    let new_gas_schedule: GasScheduleV2 = from_bytes(new_gas_schedule_blob);
+    if (exists<GasScheduleV2>(@aptos_framework)) {
+        let cur_gas_schedule = borrow_global<GasScheduleV2>(@aptos_framework);
+        assert!(
+            new_gas_schedule.feature_version >= cur_gas_schedule.feature_version,
+            error::invalid_argument(EINVALID_GAS_FEATURE_VERSION)
+        );
+        let cur_gas_schedule_bytes = bcs::to_bytes(cur_gas_schedule);
+        let cur_gas_schedule_hash = aptos_hash::sha3_512(cur_gas_schedule_bytes);
+        assert!(
+            cur_gas_schedule_hash == old_gas_schedule_hash,
+            error::invalid_argument(EINVALID_GAS_SCHEDULE_HASH)
+        );
+    };
+
+    config_buffer::upsert(new_gas_schedule);
+}
+
+ + +
@@ -505,6 +567,30 @@ Only used in reconfigurations to apply the pending + +### Function `set_for_next_epoch_check_hash` + + +
public fun set_for_next_epoch_check_hash(aptos_framework: &signer, old_gas_schedule_hash: vector<u8>, new_gas_schedule_blob: vector<u8>)
+
+ + + + +
include system_addresses::AbortsIfNotAptosFramework{ account: aptos_framework };
+include config_buffer::SetForNextEpochAbortsIf {
+    account: aptos_framework,
+    config: new_gas_schedule_blob
+};
+let new_gas_schedule = util::spec_from_bytes<GasScheduleV2>(new_gas_schedule_blob);
+let cur_gas_schedule = global<GasScheduleV2>(@aptos_framework);
+aborts_if exists<GasScheduleV2>(@aptos_framework) && new_gas_schedule.feature_version < cur_gas_schedule.feature_version;
+aborts_if exists<GasScheduleV2>(@aptos_framework) && (!features::spec_sha_512_and_ripemd_160_enabled() || aptos_hash::spec_sha3_512_internal(bcs::serialize(cur_gas_schedule)) != old_gas_schedule_hash);
+
+ + + ### Function `on_new_epoch` diff --git a/aptos-move/framework/aptos-framework/sources/configs/gas_schedule.move b/aptos-move/framework/aptos-framework/sources/configs/gas_schedule.move index 87d6cf4c72adc..9156c1ae2574e 100644 --- a/aptos-move/framework/aptos-framework/sources/configs/gas_schedule.move +++ b/aptos-move/framework/aptos-framework/sources/configs/gas_schedule.move @@ -1,9 +1,11 @@ /// This module defines structs and methods to initialize the gas schedule, which dictates how much /// it costs to execute Move on the network. module aptos_framework::gas_schedule { + use std::bcs; use std::error; use std::string::String; use std::vector; + use aptos_std::aptos_hash; use aptos_framework::chain_status; use aptos_framework::config_buffer; @@ -21,6 +23,7 @@ module aptos_framework::gas_schedule { /// The provided gas schedule bytes are empty or invalid const EINVALID_GAS_SCHEDULE: u64 = 1; const EINVALID_GAS_FEATURE_VERSION: u64 = 2; + const EINVALID_GAS_SCHEDULE_HASH: u64 = 3; struct GasEntry has store, copy, drop { key: String, @@ -99,6 +102,35 @@ module aptos_framework::gas_schedule { config_buffer::upsert(new_gas_schedule); } + /// Set the gas schedule for the next epoch, typically called by on-chain governance. + /// Abort if the version of the given schedule is lower than the current version. + /// Require a hash of the old gas schedule to be provided and will abort if the hashes mismatch. + public fun set_for_next_epoch_check_hash( + aptos_framework: &signer, + old_gas_schedule_hash: vector, + new_gas_schedule_blob: vector + ) acquires GasScheduleV2 { + system_addresses::assert_aptos_framework(aptos_framework); + assert!(!vector::is_empty(&new_gas_schedule_blob), error::invalid_argument(EINVALID_GAS_SCHEDULE)); + + let new_gas_schedule: GasScheduleV2 = from_bytes(new_gas_schedule_blob); + if (exists(@aptos_framework)) { + let cur_gas_schedule = borrow_global(@aptos_framework); + assert!( + new_gas_schedule.feature_version >= cur_gas_schedule.feature_version, + error::invalid_argument(EINVALID_GAS_FEATURE_VERSION) + ); + let cur_gas_schedule_bytes = bcs::to_bytes(cur_gas_schedule); + let cur_gas_schedule_hash = aptos_hash::sha3_512(cur_gas_schedule_bytes); + assert!( + cur_gas_schedule_hash == old_gas_schedule_hash, + error::invalid_argument(EINVALID_GAS_SCHEDULE_HASH) + ); + }; + + config_buffer::upsert(new_gas_schedule); + } + /// Only used in reconfigurations to apply the pending `GasScheduleV2`, if there is any. public(friend) fun on_new_epoch(framework: &signer) acquires GasScheduleV2 { system_addresses::assert_aptos_framework(framework); diff --git a/aptos-move/framework/aptos-framework/sources/configs/gas_schedule.spec.move b/aptos-move/framework/aptos-framework/sources/configs/gas_schedule.spec.move index 1d59784d22504..7ce238f7b9959 100644 --- a/aptos-move/framework/aptos-framework/sources/configs/gas_schedule.spec.move +++ b/aptos-move/framework/aptos-framework/sources/configs/gas_schedule.spec.move @@ -107,6 +107,23 @@ spec aptos_framework::gas_schedule { aborts_if exists(@aptos_framework) && new_gas_schedule.feature_version < cur_gas_schedule.feature_version; } + spec set_for_next_epoch_check_hash(aptos_framework: &signer, old_gas_schedule_hash: vector, new_gas_schedule_blob: vector) { + use aptos_std::aptos_hash; + use std::bcs; + use std::features; + use aptos_framework::util; + + include system_addresses::AbortsIfNotAptosFramework{ account: aptos_framework }; + include config_buffer::SetForNextEpochAbortsIf { + account: aptos_framework, + config: new_gas_schedule_blob + }; + let new_gas_schedule = util::spec_from_bytes(new_gas_schedule_blob); + let cur_gas_schedule = global(@aptos_framework); + aborts_if exists(@aptos_framework) && new_gas_schedule.feature_version < cur_gas_schedule.feature_version; + aborts_if exists(@aptos_framework) && (!features::spec_sha_512_and_ripemd_160_enabled() || aptos_hash::spec_sha3_512_internal(bcs::serialize(cur_gas_schedule)) != old_gas_schedule_hash); + } + spec on_new_epoch(framework: &signer) { requires @aptos_framework == std::signer::address_of(framework); include config_buffer::OnNewEpochRequirement; diff --git a/aptos-move/framework/move-stdlib/doc/features.md b/aptos-move/framework/move-stdlib/doc/features.md index 628a01672cb55..4b1cd8181e625 100644 --- a/aptos-move/framework/move-stdlib/doc/features.md +++ b/aptos-move/framework/move-stdlib/doc/features.md @@ -3600,6 +3600,17 @@ Helper to check whether a feature flag is enabled. + + + + +
fun spec_sha_512_and_ripemd_160_enabled(): bool {
+   spec_is_enabled(SHA_512_AND_RIPEMD_160_NATIVES)
+}
+
+ + + ### Function `is_enabled` diff --git a/aptos-move/framework/move-stdlib/sources/configs/features.spec.move b/aptos-move/framework/move-stdlib/sources/configs/features.spec.move index 8779c94835b22..20d8890694551 100644 --- a/aptos-move/framework/move-stdlib/sources/configs/features.spec.move +++ b/aptos-move/framework/move-stdlib/sources/configs/features.spec.move @@ -103,4 +103,8 @@ spec std::features { ensures exists(@std) ==> features_std == features_pending; aborts_if false; } + + spec fun spec_sha_512_and_ripemd_160_enabled(): bool { + spec_is_enabled(SHA_512_AND_RIPEMD_160_NATIVES) + } } diff --git a/testsuite/smoke-test/src/rosetta.rs b/testsuite/smoke-test/src/rosetta.rs index f14cc2ffc0e54..2c3ae86fd7403 100644 --- a/testsuite/smoke-test/src/rosetta.rs +++ b/testsuite/smoke-test/src/rosetta.rs @@ -728,7 +728,7 @@ async fn test_block() { .into_inner(); let feature_version = gas_schedule.feature_version; let gas_params = AptosGasParameters::from_on_chain_gas_schedule( - &gas_schedule.to_btree_map(), + &gas_schedule.into_btree_map(), feature_version, ) .unwrap(); diff --git a/testsuite/smoke-test/src/upgrade.rs b/testsuite/smoke-test/src/upgrade.rs index 364f0c12d90f8..e782bf3758c89 100644 --- a/testsuite/smoke-test/src/upgrade.rs +++ b/testsuite/smoke-test/src/upgrade.rs @@ -14,7 +14,7 @@ use aptos_release_builder::{ feature_flags::{FeatureFlag, Features}, framework::FrameworkReleaseConfig, gas::generate_gas_upgrade_proposal, - ExecutionMode, Proposal, ProposalMetadata, + ExecutionMode, GasScheduleLocator, Proposal, ProposalMetadata, }, ReleaseEntry, }; @@ -59,7 +59,7 @@ async fn test_upgrade_flow() { }; let (_, update_gas_script) = - generate_gas_upgrade_proposal(true, &gas_schedule, true, "".to_owned().into_bytes()) + generate_gas_upgrade_proposal(None, &gas_schedule, true, "".to_owned().into_bytes()) .unwrap() .pop() .unwrap(); @@ -121,7 +121,10 @@ async fn test_upgrade_flow() { execution_mode: ExecutionMode::RootSigner, name: "gas".to_string(), metadata: ProposalMetadata::default(), - update_sequence: vec![ReleaseEntry::DefaultGas], + update_sequence: vec![ReleaseEntry::Gas { + old: None, + new: GasScheduleLocator::Current, + }], }, Proposal { execution_mode: ExecutionMode::RootSigner, @@ -143,6 +146,7 @@ async fn test_upgrade_flow() { config .generate_release_proposal_scripts(upgrade_scripts_folder.path()) + .await .unwrap(); let mut scripts = walkdir::WalkDir::new(upgrade_scripts_folder.path()) .sort_by_file_name() diff --git a/types/src/on_chain_config/gas_schedule.rs b/types/src/on_chain_config/gas_schedule.rs index d9817e4b84479..8f2811f4c51e8 100644 --- a/types/src/on_chain_config/gas_schedule.rs +++ b/types/src/on_chain_config/gas_schedule.rs @@ -3,7 +3,7 @@ use crate::on_chain_config::OnChainConfig; use serde::{Deserialize, Serialize}; -use std::collections::BTreeMap; +use std::collections::{btree_map, BTreeMap}; #[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)] pub struct GasSchedule { @@ -16,6 +16,13 @@ pub struct GasScheduleV2 { pub entries: Vec<(String, u64)>, } +#[derive(Debug)] +pub enum DiffItem { + Add { new_val: T }, + Delete { old_val: T }, + Modify { old_val: T, new_val: T }, +} + #[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)] pub struct StorageGasSchedule { pub per_item_read: u64, @@ -40,17 +47,49 @@ impl StorageGasSchedule { } impl GasSchedule { - pub fn to_btree_map(self) -> BTreeMap { + pub fn into_btree_map(self) -> BTreeMap { // TODO: what if the gas schedule contains duplicated entries? self.entries.into_iter().collect() } } impl GasScheduleV2 { - pub fn to_btree_map(self) -> BTreeMap { + pub fn into_btree_map(self) -> BTreeMap { // TODO: what if the gas schedule contains duplicated entries? self.entries.into_iter().collect() } + + pub fn to_btree_map_borrowed(&self) -> BTreeMap<&str, u64> { + self.entries.iter().map(|(k, v)| (k.as_str(), *v)).collect() + } + + pub fn diff<'a>(old: &'a Self, new: &'a Self) -> BTreeMap<&'a str, DiffItem> { + let mut old = old.to_btree_map_borrowed(); + let new = new.to_btree_map_borrowed(); + + let mut diff = BTreeMap::new(); + for (param_name, new_val) in new { + match old.entry(param_name) { + btree_map::Entry::Occupied(entry) => { + let (param_name, old_val) = entry.remove_entry(); + + if old_val != new_val { + diff.insert(param_name, DiffItem::Modify { old_val, new_val }); + } + }, + btree_map::Entry::Vacant(entry) => { + let param_name = entry.into_key(); + diff.insert(param_name, DiffItem::Add { new_val }); + }, + } + } + diff.extend( + old.into_iter() + .map(|(param_name, old_val)| (param_name, DiffItem::Delete { old_val })), + ); + + diff + } } impl OnChainConfig for GasSchedule { diff --git a/types/src/on_chain_config/mod.rs b/types/src/on_chain_config/mod.rs index 1284385d58f88..32692e9aec19a 100644 --- a/types/src/on_chain_config/mod.rs +++ b/types/src/on_chain_config/mod.rs @@ -52,7 +52,7 @@ pub use self::{ BlockGasLimitType, ExecutionConfigV1, ExecutionConfigV2, ExecutionConfigV4, OnChainExecutionConfig, TransactionDeduperType, TransactionShufflerType, }, - gas_schedule::{GasSchedule, GasScheduleV2, StorageGasSchedule}, + gas_schedule::{DiffItem, GasSchedule, GasScheduleV2, StorageGasSchedule}, jwk_consensus_config::{ ConfigV1 as JWKConsensusConfigV1, OIDCProvider, OnChainJWKConsensusConfig, },