Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions aptos-move/aptos-gas-meter/src/meter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,15 @@ where
fn use_heap_memory_in_native_context(&mut self, _amount: u64) -> PartialVMResult<()> {
Ok(())
}

fn charge_io_gas_for_native_write(&mut self, num_bytes: NumBytes) -> PartialVMResult<()> {
// A single new state slot whose key and value together occupy `num_bytes` bytes. This
// mirrors the per-write I/O in `io_gas_per_write` for gas feature version >= 12 (the only
// versions where deferred module publishing can be enabled).
let cost = STORAGE_IO_PER_STATE_SLOT_WRITE * NumArgs::new(1)
+ STORAGE_IO_PER_STATE_BYTE_WRITE * num_bytes;
self.algebra.charge_io(cost)
}
}

impl<A> GasMeter for StandardGasMeter<A>
Expand Down
6 changes: 6 additions & 0 deletions aptos-move/aptos-gas-profiling/src/profiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,12 @@ where
res
}

fn charge_io_gas_for_native_write(&mut self, num_bytes: NumBytes) -> PartialVMResult<()> {
let (_cost, res) =
self.delegate_charge(|base| base.charge_io_gas_for_native_write(num_bytes));
res
}

fn charge_native_execution(&mut self, amount: InternalGas) -> PartialVMResult<()> {
// Delegate first so we can record the actual amount charged -- this matters
// when the base meter only partially charges (e.g. hitting OUT_OF_GAS).
Expand Down
2 changes: 2 additions & 0 deletions aptos-move/aptos-memory-usage-tracker/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,8 @@ where

delegate_mut! {
fn charge_native_execution(&mut self, amount: InternalGas) -> PartialVMResult<()>;

fn charge_io_gas_for_native_write(&mut self, num_bytes: NumBytes) -> PartialVMResult<()>;
}

#[inline]
Expand Down
13 changes: 12 additions & 1 deletion aptos-move/aptos-native-interface/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ use aptos_gas_schedule::{
use aptos_types::on_chain_config::{Features, TimedFeatureFlag, TimedFeatures};
use move_binary_format::errors::{Location, PartialVMResult, VMResult};
use move_core_types::{
gas_algebra::InternalGas, identifier::Identifier, language_storage::ModuleId,
gas_algebra::{InternalGas, NumBytes},
identifier::Identifier,
language_storage::ModuleId,
};
use move_vm_runtime::{
native_extensions::NativeContextExtensions,
Expand Down Expand Up @@ -117,6 +119,15 @@ impl<'b, 'c> SafeNativeContext<'_, 'b, 'c, '_> {
.map_err(LimitExceededError::from_err)
}

/// Charges I/O gas for a future write of a state slot whose key and value together occupy
/// `num_bytes` bytes. Used by `code::verify_package` to pre-charge the module-write I/O that is
/// materialized at the block epilogue.
pub fn charge_io_gas_for_write(&mut self, num_bytes: NumBytes) -> SafeNativeResult<()> {
self.gas_meter()
.charge_io_gas_for_native_write(num_bytes)
.map_err(LimitExceededError::from_err)
}

/// Evaluates the given gas expression within the current context immediately.
///
/// This can be useful if you have branch conditions depending on gas parameters.
Expand Down
201 changes: 102 additions & 99 deletions aptos-move/aptos-vm/src/aptos_vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,15 @@ use crate::{
},
view_with_change_set::ExecutorViewWithChangeSet,
},
write_op_converter::WriteOpConverter,
AptosMoveResolver, AsExecutorView, AsResourceGroupView, SessionExt, SessionId,
UserTransactionContext,
},
sharded_block_executor::{executor_client::ExecutorClient, ShardedBlockExecutor},
system_module_names::*,
transaction_metadata::TransactionMetadata,
transaction_validation,
verifier::{
event_validation, native_validation, resource_groups, transaction_arg_validation,
view_function,
},
verifier::{transaction_arg_validation, view_function},
VMBlockExecutor, VMValidator,
};
use aptos_block_executor::{
Expand Down Expand Up @@ -85,7 +83,6 @@ use aptos_types::{
},
vm::module_metadata::{
get_compilation_metadata, get_metadata, get_randomness_annotation_for_entry_function,
verify_module_metadata_for_module_publishing,
},
vm_status::{AbortLocation, StatusCode, VMStatus},
write_set::WriteOp,
Expand All @@ -112,6 +109,7 @@ use aptos_vm_types::{
},
storage::{change_set_configs::ChangeSetConfigs, StorageGasParameters},
};
use bytes::Bytes;
use claims::assert_err;
use fail::fail_point;
use move_binary_format::{
Expand Down Expand Up @@ -984,7 +982,7 @@ impl AptosVM {
// Check that unstable bytecode cannot be executed on mainnet and verify events.
let script = func.owner_as_script()?;
self.reject_unstable_bytecode_for_script(script)?;
event_validation::verify_no_event_emission_in_compiled_script(script)?;
aptos_framework_natives::publish_verification::event_validation::verify_no_event_emission_in_compiled_script(script)?;

// Record the script's declared module dependencies as reads. These are a function of
// the script bytecode, so recording them here keeps the read set independent of the
Expand Down Expand Up @@ -1808,77 +1806,19 @@ impl AptosVM {
traversal_context: &mut TraversalContext,
gas_meter: &mut impl GasMeter,
modules: &[CompiledModule],
mut expected_modules: BTreeSet<String>,
expected_modules: BTreeSet<String>,
allowed_deps: Option<BTreeMap<AccountAddress, BTreeSet<String>>>,
) -> VMResult<()> {
self.reject_unstable_bytecode(modules)?;
native_validation::validate_module_natives(modules)?;

for m in modules {
if !expected_modules.remove(m.self_id().name().as_str()) {
return Err(Self::metadata_validation_error(&format!(
"unregistered module: '{}'",
m.self_id().name()
)));
}
if let Some(allowed) = &allowed_deps {
for dep in m.immediate_dependencies() {
if !allowed
.get(dep.address())
.map(|modules| {
modules.contains("") || modules.contains(dep.name().as_str())
})
.unwrap_or(false)
{
return Err(Self::metadata_validation_error(&format!(
"unregistered dependency: '{}'",
dep
)));
}
}
}
verify_module_metadata_for_module_publishing(m, self.features())
.map_err(|err| Self::metadata_validation_error(&err.to_string()))?;
}

resource_groups::validate_resource_groups(
aptos_framework_natives::publish_verification::publish::validate_publish_request(
self.features(),
self.chain_id().is_mainnet(),
module_storage,
traversal_context,
gas_meter,
modules,
)?;
event_validation::validate_module_events(
self.features(),
module_storage,
traversal_context,
modules,
)?;

if !expected_modules.is_empty() {
return Err(Self::metadata_validation_error(
"not all registered modules published",
));
}
Ok(())
}

/// Check whether the bytecode can be published to mainnet based on the unstable tag in the metadata
fn reject_unstable_bytecode(&self, modules: &[CompiledModule]) -> VMResult<()> {
if self.chain_id().is_mainnet() {
for module in modules {
if let Some(metadata) = get_compilation_metadata(module) {
if metadata.unstable {
return Err(PartialVMError::new(StatusCode::UNSTABLE_BYTECODE_REJECTED)
.with_message(
"code marked unstable is not published on mainnet".to_string(),
)
.finish(Location::Undefined));
}
}
}
}
Ok(())
expected_modules,
allowed_deps,
)
}

/// Check whether the script can be run on mainnet based on the unstable tag in the metadata
Expand All @@ -1895,12 +1835,6 @@ impl AptosVM {
Ok(())
}

fn metadata_validation_error(msg: &str) -> VMError {
PartialVMError::new(StatusCode::CONSTRAINT_NOT_SATISFIED)
.with_message(format!("metadata and code bundle mismatch: {}", msg))
.finish(Location::Undefined)
}

fn validate_signed_transaction(
&self,
session: &mut SessionExt<impl AptosMoveResolver>,
Expand Down Expand Up @@ -2764,6 +2698,38 @@ impl AptosVM {
Ok((VMStatus::Executed, output))
}

/// Materializes the packages returned by `block_epilogue` (each a vector of module bytecode
/// blobs) into a module write set. The bytecode was already verified by `code::verify_package`
/// when it was queued, so this only deserializes to recover the module ids and builds the
/// write ops (deciding creation vs. modification from the current on-chain state).
fn build_epilogue_module_write_set(
&self,
resolver: &impl AptosMoveResolver,
module_storage: &impl AptosModuleStorage,
packages: Vec<Vec<Vec<u8>>>,
) -> Result<ModuleWriteSet, VMStatus> {
let mut bundle = vec![];
for package in packages {
for blob in package {
let module =
CompiledModule::deserialize_with_config(&blob, self.deserializer_config())
.map_err(|_| {
PartialVMError::new(StatusCode::CODE_DESERIALIZATION_ERROR)
.finish(Location::Undefined)
.into_vm_status()
})?;
bundle.push((module.self_id(), Bytes::from(blob)));
}
}

let woc =
WriteOpConverter::new(resolver, self.features().is_storage_slot_metadata_enabled());
let write_ops = woc
.convert_modules_into_write_ops(module_storage, bundle.into_iter())
.map_err(|e| e.finish(Location::Undefined).into_vm_status())?;
Ok(ModuleWriteSet::new(write_ops))
}

fn process_block_epilogue(
&self,
resolver: &impl AptosMoveResolver,
Expand Down Expand Up @@ -2810,30 +2776,63 @@ impl AptosVM {
let traversal_storage = TraversalStorage::new();
let mut traversal_context = TraversalContext::new(&traversal_storage);

let output = match session
.execute_function_bypass_visibility(
&BLOCK_MODULE,
BLOCK_EPILOGUE,
vec![],
serialize_values(&args),
&mut gas_meter,
&mut traversal_context,
module_storage,
)
.map(|_return_vals| ())
.or_else(|e| expect_only_successful_execution(e, BLOCK_EPILOGUE.as_str(), log_context))
{
Ok(_) => get_system_transaction_output(
session,
module_storage,
&self.storage_gas_params(log_context)?.change_set_configs,
)?,
let change_set_configs = &self.storage_gas_params(log_context)?.change_set_configs;
let output = match session.execute_function_bypass_visibility(
&BLOCK_MODULE,
BLOCK_EPILOGUE,
vec![],
serialize_values(&args),
&mut gas_meter,
&mut traversal_context,
module_storage,
) {
Ok(return_vals) => {
// When deferred module publishing is enabled, `block_epilogue` returns the packages
// queued during the block. Materialize them into module writes on the epilogue
// output. When disabled, the returned vector is empty and is ignored.
let module_write_set = if self.features().is_deferred_module_publishing_enabled() {
let bytes = return_vals
.return_values
.into_iter()
.next()
.map(|(bytes, _layout)| bytes)
.ok_or_else(|| {
VMStatus::error(
StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR,
Some("block_epilogue must return the queued packages".to_string()),
)
})?;
let packages: Vec<Vec<Vec<u8>>> = bcs::from_bytes(&bytes).map_err(|_| {
VMStatus::error(
StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR,
Some("failed to deserialize queued packages".to_string()),
)
})?;
self.build_epilogue_module_write_set(resolver, module_storage, packages)?
} else {
ModuleWriteSet::empty()
};
let change_set = session.finish(change_set_configs, module_storage)?;
VMOutput::new(
change_set,
module_write_set,
FeeStatement::zero(),
TransactionStatus::Keep(ExecutionStatus::Success),
)
},
Err(e) => {
error!(
"Unexpected error from BlockEpilogue txn: {e:?}, fallback to return success."
);
let status = TransactionStatus::Keep(ExecutionStatus::Success);
VMOutput::empty_with_status(status)
match expect_only_successful_execution(e, BLOCK_EPILOGUE.as_str(), log_context) {
Ok(_) => {
get_system_transaction_output(session, module_storage, change_set_configs)?
},
Err(e) => {
error!(
"Unexpected error from BlockEpilogue txn: {e:?}, fallback to return success."
);
let status = TransactionStatus::Keep(ExecutionStatus::Success);
VMOutput::empty_with_status(status)
},
}
},
};

Expand Down Expand Up @@ -3407,6 +3406,10 @@ impl VMBlockExecutor for AptosVMBlockExecutor {
}
}

fn invalidate_published_modules(&self, module_ids: Vec<ModuleId>) {
self.module_cache_manager.invalidate(module_ids);
}

fn execute_block(
&self,
txn_provider: &DefaultTxnProvider<SignatureVerifiedTransaction, AuxiliaryInfo>,
Expand Down
7 changes: 7 additions & 0 deletions aptos-move/aptos-vm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ use aptos_types::{
},
vm_status::VMStatus,
};
use move_core_types::language_storage::ModuleId;
use move_vm_runtime::ModuleStorage;
use std::{marker::Sync, sync::Arc};
pub use verifier::view_function::determine_is_view;
Expand Down Expand Up @@ -175,6 +176,12 @@ pub trait VMBlockExecutor: Send + Sync {
transaction_slice_metadata: TransactionSliceMetadata,
) -> Result<BlockOutput<SignatureVerifiedTransaction, TransactionOutput>, VMStatus>;

/// Invalidates the given modules in the executor's cross-block module cache. Called at commit
/// time for a block that published modules via the deferred publishing flow, so the next block
/// re-reads the new code from storage. Default is a no-op for executors without a persistent
/// module cache.
fn invalidate_published_modules(&self, _module_ids: Vec<ModuleId>) {}

/// Executes a block of transactions and returns output for each one of them, without applying
/// any block limit.
fn execute_block_no_limit(
Expand Down
3 changes: 0 additions & 3 deletions aptos-move/aptos-vm/src/verifier/mod.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
// Copyright (c) Aptos Foundation
// Licensed pursuant to the Innovation-Enabling Source Code License, available at https://github.com/aptos-labs/aptos-core/blob/main/LICENSE
pub(crate) mod event_validation;
pub(crate) mod module_init;
pub(crate) mod native_validation;
pub(crate) mod resource_groups;
pub mod transaction_arg_validation;
pub(crate) mod view_function;
Loading
Loading