diff --git a/Cargo.lock b/Cargo.lock index c5cfc9f139a4e..3300f69884a5d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4737,9 +4737,11 @@ dependencies = [ "aptos-vm-environment", "aptos-vm-genesis", "aptos-vm-logging", - "aptos-vm-types", "fail", + "move-binary-format", "move-core-types", + "move-vm-runtime", + "move-vm-types", "rand 0.7.3", ] diff --git a/aptos-move/aptos-vm/src/aptos_vm.rs b/aptos-move/aptos-vm/src/aptos_vm.rs index db50ca10e1fca..6a9eee602f73e 100644 --- a/aptos-move/aptos-vm/src/aptos_vm.rs +++ b/aptos-move/aptos-vm/src/aptos_vm.rs @@ -119,7 +119,7 @@ use move_vm_metrics::{Timer, VM_TIMER}; use move_vm_runtime::{ logging::expect_no_verification_errors, module_traversal::{TraversalContext, TraversalStorage}, - RuntimeEnvironment, WithRuntimeEnvironment, + ModuleStorage, RuntimeEnvironment, WithRuntimeEnvironment, }; use move_vm_types::gas::{GasMeter, UnmeteredGasMeter}; use num_cpus; @@ -1824,7 +1824,7 @@ impl AptosVM { &self, session: &mut SessionExt, resolver: &impl AptosMoveResolver, - module_storage: &impl AptosModuleStorage, + module_storage: &impl ModuleStorage, transaction: &SignedTransaction, transaction_data: &TransactionMetadata, log_context: &AdapterLogSchema, @@ -2553,7 +2553,7 @@ impl AptosVM { &self, session: &mut SessionExt, resolver: &impl AptosMoveResolver, - module_storage: &impl AptosModuleStorage, + module_storage: &impl ModuleStorage, payload: &TransactionPayload, txn_data: &TransactionMetadata, log_context: &AdapterLogSchema, @@ -2899,7 +2899,7 @@ impl VMValidator for AptosVM { &self, transaction: SignedTransaction, state_view: &impl StateView, - module_storage: &impl AptosCodeStorage, + module_storage: &impl ModuleStorage, ) -> VMValidatorResult { let _timer = TXN_VALIDATION_SECONDS.start_timer(); let log_context = AdapterLogSchema::new(state_view.id(), 0); @@ -3057,7 +3057,7 @@ pub(crate) fn is_account_init_for_sponsored_transaction( txn_data: &TransactionMetadata, features: &Features, resolver: &impl AptosMoveResolver, - module_storage: &impl AptosModuleStorage, + module_storage: &impl ModuleStorage, ) -> VMResult { if features.is_enabled(FeatureFlag::SPONSORED_AUTOMATIC_ACCOUNT_V1_CREATION) && txn_data.fee_payer.is_some() @@ -3084,7 +3084,7 @@ pub(crate) fn is_account_init_for_sponsored_transaction( pub(crate) fn fetch_module_metadata_for_struct_tag( struct_tag: &StructTag, resolver: &impl AptosMoveResolver, - module_storage: &impl AptosModuleStorage, + module_storage: &impl ModuleStorage, ) -> VMResult> { if module_storage.is_enabled() { module_storage.fetch_existing_module_metadata(&struct_tag.address, &struct_tag.module) diff --git a/aptos-move/aptos-vm/src/gas.rs b/aptos-move/aptos-vm/src/gas.rs index e1e12e4ddfb50..26d9543e1e846 100644 --- a/aptos-move/aptos-vm/src/gas.rs +++ b/aptos-move/aptos-vm/src/gas.rs @@ -12,11 +12,9 @@ use aptos_logger::{enabled, Level}; use aptos_memory_usage_tracker::MemoryTrackedGasMeter; use aptos_types::on_chain_config::Features; use aptos_vm_logging::{log_schema::AdapterLogSchema, speculative_log, speculative_warn}; -use aptos_vm_types::{ - module_and_script_storage::module_storage::AptosModuleStorage, - storage::{space_pricing::DiskSpacePricing, StorageGasParameters}, -}; +use aptos_vm_types::storage::{space_pricing::DiskSpacePricing, StorageGasParameters}; use move_core_types::vm_status::{StatusCode, VMStatus}; +use move_vm_runtime::ModuleStorage; /// This is used until gas version 18, which introduces a configurable entry for this. const MAXIMUM_APPROVED_TRANSACTION_SIZE_LEGACY: u64 = 1024 * 1024; @@ -47,7 +45,7 @@ pub(crate) fn check_gas( gas_params: &AptosGasParameters, gas_feature_version: u64, resolver: &impl AptosMoveResolver, - module_storage: &impl AptosModuleStorage, + module_storage: &impl ModuleStorage, txn_metadata: &TransactionMetadata, features: &Features, is_approved_gov_script: bool, diff --git a/aptos-move/aptos-vm/src/keyless_validation.rs b/aptos-move/aptos-vm/src/keyless_validation.rs index 338ac4386115c..06e7376f7f156 100644 --- a/aptos-move/aptos-vm/src/keyless_validation.rs +++ b/aptos-move/aptos-vm/src/keyless_validation.rs @@ -15,7 +15,6 @@ use aptos_types::{ transaction::authenticator::{EphemeralPublicKey, EphemeralSignature}, vm_status::{StatusCode, VMStatus}, }; -use aptos_vm_types::module_and_script_storage::module_storage::AptosModuleStorage; use ark_bn254::Bn254; use ark_groth16::PreparedVerifyingKey; use move_binary_format::errors::Location; @@ -23,6 +22,7 @@ use move_core_types::{ account_address::AccountAddress, language_storage::CORE_CODE_ADDRESS, move_resource::MoveStructType, }; +use move_vm_runtime::ModuleStorage; use serde::Deserialize; macro_rules! value_deserialization_error { @@ -36,7 +36,7 @@ macro_rules! value_deserialization_error { fn get_resource_on_chain Deserialize<'a>>( resolver: &impl AptosMoveResolver, - module_storage: &impl AptosModuleStorage, + module_storage: &impl ModuleStorage, ) -> anyhow::Result { get_resource_on_chain_at_addr(&CORE_CODE_ADDRESS, resolver, module_storage) } @@ -44,7 +44,7 @@ fn get_resource_on_chain Deserialize<'a>>( fn get_resource_on_chain_at_addr Deserialize<'a>>( addr: &AccountAddress, resolver: &impl AptosMoveResolver, - module_storage: &impl AptosModuleStorage, + module_storage: &impl ModuleStorage, ) -> anyhow::Result { let metadata = fetch_module_metadata_for_struct_tag(&T::struct_tag(), resolver, module_storage) .map_err(|e| e.into_vm_status())?; @@ -87,21 +87,21 @@ fn get_jwks_onchain(resolver: &impl AptosMoveResolver) -> anyhow::Result anyhow::Result { get_resource_on_chain_at_addr::(jwk_addr, resolver, module_storage) } pub(crate) fn get_groth16_vk_onchain( resolver: &impl AptosMoveResolver, - module_storage: &impl AptosModuleStorage, + module_storage: &impl ModuleStorage, ) -> anyhow::Result { get_resource_on_chain::(resolver, module_storage) } fn get_configs_onchain( resolver: &impl AptosMoveResolver, - module_storage: &impl AptosModuleStorage, + module_storage: &impl ModuleStorage, ) -> anyhow::Result { get_resource_on_chain::(resolver, module_storage) } @@ -160,7 +160,7 @@ pub(crate) fn validate_authenticators( authenticators: &Vec<(AnyKeylessPublicKey, KeylessSignature)>, features: &Features, resolver: &impl AptosMoveResolver, - module_storage: &impl AptosModuleStorage, + module_storage: &impl ModuleStorage, ) -> Result<(), VMStatus> { let mut with_zk = false; for (pk, sig) in authenticators { diff --git a/aptos-move/aptos-vm/src/lib.rs b/aptos-move/aptos-vm/src/lib.rs index 8aa474f762699..9d66988b08f47 100644 --- a/aptos-move/aptos-vm/src/lib.rs +++ b/aptos-move/aptos-vm/src/lib.rs @@ -139,7 +139,7 @@ use aptos_types::{ }, vm_status::VMStatus, }; -use aptos_vm_types::module_and_script_storage::code_storage::AptosCodeStorage; +use move_vm_runtime::ModuleStorage; use std::{marker::Sync, sync::Arc}; pub use verifier::view_function::determine_is_view; @@ -150,7 +150,7 @@ pub trait VMValidator { &self, transaction: SignedTransaction, state_view: &impl StateView, - module_storage: &impl AptosCodeStorage, + module_storage: &impl ModuleStorage, ) -> VMValidatorResult; } diff --git a/aptos-move/aptos-vm/src/transaction_validation.rs b/aptos-move/aptos-vm/src/transaction_validation.rs index 38f255524917f..b9c680f54240c 100644 --- a/aptos-move/aptos-vm/src/transaction_validation.rs +++ b/aptos-move/aptos-vm/src/transaction_validation.rs @@ -17,7 +17,6 @@ use aptos_types::{ on_chain_config::Features, transaction::Multisig, }; use aptos_vm_logging::log_schema::AdapterLogSchema; -use aptos_vm_types::module_and_script_storage::module_storage::AptosModuleStorage; use fail::fail_point; use move_binary_format::errors::VMResult; use move_core_types::{ @@ -28,7 +27,9 @@ use move_core_types::{ value::{serialize_values, MoveValue}, vm_status::{AbortLocation, StatusCode, VMStatus}, }; -use move_vm_runtime::{logging::expect_no_verification_errors, module_traversal::TraversalContext}; +use move_vm_runtime::{ + logging::expect_no_verification_errors, module_traversal::TraversalContext, ModuleStorage, +}; use move_vm_types::gas::UnmeteredGasMeter; use once_cell::sync::Lazy; @@ -85,7 +86,7 @@ impl TransactionValidation { pub(crate) fn run_script_prologue( session: &mut SessionExt, - module_storage: &impl AptosModuleStorage, + module_storage: &impl ModuleStorage, txn_data: &TransactionMetadata, features: &Features, log_context: &AdapterLogSchema, @@ -232,7 +233,7 @@ pub(crate) fn run_script_prologue( /// match that hash. pub(crate) fn run_multisig_prologue( session: &mut SessionExt, - module_storage: &impl AptosModuleStorage, + module_storage: &impl ModuleStorage, txn_data: &TransactionMetadata, payload: &Multisig, features: &Features, @@ -272,7 +273,7 @@ pub(crate) fn run_multisig_prologue( fn run_epilogue( session: &mut SessionExt, - module_storage: &impl AptosModuleStorage, + module_storage: &impl ModuleStorage, gas_remaining: Gas, fee_statement: FeeStatement, txn_data: &TransactionMetadata, @@ -377,7 +378,7 @@ fn run_epilogue( fn emit_fee_statement( session: &mut SessionExt, - module_storage: &impl AptosModuleStorage, + module_storage: &impl ModuleStorage, fee_statement: FeeStatement, traversal_context: &mut TraversalContext, ) -> VMResult<()> { @@ -398,7 +399,7 @@ fn emit_fee_statement( /// in the `ACCOUNT_MODULE` on chain. pub(crate) fn run_success_epilogue( session: &mut SessionExt, - module_storage: &impl AptosModuleStorage, + module_storage: &impl ModuleStorage, gas_remaining: Gas, fee_statement: FeeStatement, features: &Features, @@ -431,7 +432,7 @@ pub(crate) fn run_success_epilogue( /// stored in the `ACCOUNT_MODULE` on chain. pub(crate) fn run_failure_epilogue( session: &mut SessionExt, - module_storage: &impl AptosModuleStorage, + module_storage: &impl ModuleStorage, gas_remaining: Gas, fee_statement: FeeStatement, features: &Features, diff --git a/aptos-move/block-executor/src/code_cache.rs b/aptos-move/block-executor/src/code_cache.rs index 14e5977d314d4..4b12acfa8e635 100644 --- a/aptos-move/block-executor/src/code_cache.rs +++ b/aptos-move/block-executor/src/code_cache.rs @@ -75,7 +75,7 @@ impl<'a, T: Transaction, S: TStateView> ModuleCache for LatestView deserialized_code: Self::Deserialized, extension: Arc, version: Self::Version, - ) -> VMResult<()> { + ) -> VMResult>> { self.as_module_cache().insert_deserialized_module( key, deserialized_code, diff --git a/third_party/move/move-vm/types/src/code/cache/module_cache.rs b/third_party/move/move-vm/types/src/code/cache/module_cache.rs index 7697c20e3a8c6..b36693fbde415 100644 --- a/third_party/move/move-vm/types/src/code/cache/module_cache.rs +++ b/third_party/move/move-vm/types/src/code/cache/module_cache.rs @@ -92,13 +92,14 @@ pub trait ModuleCache { /// 1. returns an error if the version of existing entry is higher, /// 2. does not perform the insertion if the version is the same, /// 3. inserts the new code if the new version is higher. + /// Returns the newly inserted (or existing) module at the specified key. fn insert_deserialized_module( &self, key: Self::Key, deserialized_code: Self::Deserialized, extension: Arc, version: Self::Version, - ) -> VMResult<()>; + ) -> VMResult>>; /// Stores verified code at specified version to the module cache if there was no entry /// associated with this key before. If module cache already contains an entry, then: @@ -231,6 +232,12 @@ where .into_iter() .map(|(k, m)| (k, m.into_module_code())) } + + /// Returns the version of the module stored in cache. Used for tests only. + #[cfg(any(test, feature = "testing"))] + pub fn get_module_version(&self, key: &K) -> Option { + self.module_cache.borrow().get(key).map(|m| m.version()) + } } impl ModuleCache for UnsyncModuleCache @@ -251,23 +258,29 @@ where deserialized_code: Self::Deserialized, extension: Arc, version: Self::Version, - ) -> VMResult<()> { + ) -> VMResult>> { use hashbrown::hash_map::Entry::*; match self.module_cache.borrow_mut().entry(key) { Occupied(mut entry) => match version.cmp(&entry.get().version()) { Ordering::Less => Err(version_too_small_error!()), - Ordering::Equal => Ok(()), + Ordering::Equal => Ok(entry.get().module_code().clone()), Ordering::Greater => { - let module = ModuleCode::from_deserialized(deserialized_code, extension); - entry.insert(VersionedModuleCode::new(module, version)); - Ok(()) + let versioned_module = VersionedModuleCode::new( + ModuleCode::from_deserialized(deserialized_code, extension), + version, + ); + let module = versioned_module.module_code().clone(); + entry.insert(versioned_module); + Ok(module) }, }, Vacant(entry) => { let module = ModuleCode::from_deserialized(deserialized_code, extension); - entry.insert(VersionedModuleCode::new(module, version)); - Ok(()) + Ok(entry + .insert(VersionedModuleCode::new(module, version)) + .module_code() + .clone()) }, } } @@ -399,23 +412,29 @@ where deserialized_code: Self::Deserialized, extension: Arc, version: Self::Version, - ) -> VMResult<()> { + ) -> VMResult>> { use dashmap::mapref::entry::Entry::*; match self.module_cache.entry(key) { Occupied(mut entry) => match version.cmp(&entry.get().version()) { Ordering::Less => Err(version_too_small_error!()), - Ordering::Equal => Ok(()), + Ordering::Equal => Ok(entry.get().module_code().clone()), Ordering::Greater => { - let module = ModuleCode::from_deserialized(deserialized_code, extension); - entry.insert(CachePadded::new(VersionedModuleCode::new(module, version))); - Ok(()) + let versioned_module = VersionedModuleCode::new( + ModuleCode::from_deserialized(deserialized_code, extension), + version, + ); + let module = versioned_module.module_code().clone(); + entry.insert(CachePadded::new(versioned_module)); + Ok(module) }, }, Vacant(entry) => { let module = ModuleCode::from_deserialized(deserialized_code, extension); - entry.insert(CachePadded::new(VersionedModuleCode::new(module, version))); - Ok(()) + Ok(entry + .insert(CachePadded::new(VersionedModuleCode::new(module, version))) + .module_code() + .clone()) }, } } diff --git a/third_party/move/move-vm/types/src/code/errors.rs b/third_party/move/move-vm/types/src/code/errors.rs index 6cd0ae508b4e9..54bfd5a8e5fa7 100644 --- a/third_party/move/move-vm/types/src/code/errors.rs +++ b/third_party/move/move-vm/types/src/code/errors.rs @@ -14,7 +14,7 @@ macro_rules! panic_error { #[macro_export] macro_rules! module_storage_error { - ($addr:ident, $name:ident, $err:ident) => { + ($addr:expr, $name:expr, $err:ident) => { move_binary_format::errors::PartialVMError::new( move_core_types::vm_status::StatusCode::STORAGE_ERROR, ) diff --git a/vm-validator/Cargo.toml b/vm-validator/Cargo.toml index 9df58dd773059..5f8d60f5ef4be 100644 --- a/vm-validator/Cargo.toml +++ b/vm-validator/Cargo.toml @@ -20,8 +20,11 @@ aptos-types = { workspace = true } aptos-vm = { workspace = true } aptos-vm-environment = { workspace = true } aptos-vm-logging = { workspace = true } -aptos-vm-types = { workspace = true } fail = { workspace = true } +move-binary-format = { workspace = true } +move-core-types = { workspace = true } +move-vm-runtime = { workspace = true } +move-vm-types = { workspace = true } rand = { workspace = true } [dev-dependencies] @@ -31,9 +34,9 @@ aptos-db = { workspace = true } aptos-executor-test-helpers = { workspace = true } aptos-gas-schedule = { workspace = true, features = ["testing"] } aptos-temppath = { workspace = true } -aptos-types = { workspace = true } +aptos-types = { workspace = true, features = ["testing"] } aptos-vm-genesis = { workspace = true } -move-core-types = { workspace = true } +move-vm-types = { workspace = true, features = ["testing"] } rand = { workspace = true } [features] diff --git a/vm-validator/src/mocks/mock_vm_validator.rs b/vm-validator/src/mocks/mock_vm_validator.rs index 1ae7e3b4118cd..c025150f71f1c 100644 --- a/vm-validator/src/mocks/mock_vm_validator.rs +++ b/vm-validator/src/mocks/mock_vm_validator.rs @@ -11,7 +11,7 @@ use aptos_types::{ vm_status::StatusCode, }; use aptos_vm::VMValidator; -use aptos_vm_types::module_and_script_storage::code_storage::AptosCodeStorage; +use move_vm_runtime::ModuleStorage; pub const ACCOUNT_DNE_TEST_ADD: AccountAddress = AccountAddress::new([0_u8; AccountAddress::LENGTH]); @@ -36,7 +36,7 @@ impl VMValidator for MockVMValidator { &self, _transaction: SignedTransaction, _state_view: &impl StateView, - _module_storage: &impl AptosCodeStorage, + _module_storage: &impl ModuleStorage, ) -> VMValidatorResult { VMValidatorResult::new(None, 0) } diff --git a/vm-validator/src/vm_validator.rs b/vm-validator/src/vm_validator.rs index aa3c90e2e8ac3..6babfb87b07e8 100644 --- a/vm-validator/src/vm_validator.rs +++ b/vm-validator/src/vm_validator.rs @@ -14,14 +14,24 @@ use aptos_storage_interface::{ use aptos_types::{ account_address::AccountAddress, account_config::AccountResource, - state_store::{MoveResourceExt, StateView}, + state_store::{state_key::StateKey, MoveResourceExt, StateView}, transaction::{SignedTransaction, VMValidatorResult}, + vm::modules::AptosModuleExtension, }; use aptos_vm::AptosVM; use aptos_vm_environment::environment::AptosEnvironment; use aptos_vm_logging::log_schema::AdapterLogSchema; -use aptos_vm_types::module_and_script_storage::{AptosCodeStorageAdapter, AsAptosCodeStorage}; use fail::fail_point; +use move_binary_format::{ + errors::{Location, PartialVMError, VMResult}, + CompiledModule, +}; +use move_core_types::{language_storage::ModuleId, vm_status::StatusCode}; +use move_vm_runtime::{Module, RuntimeEnvironment, WithRuntimeEnvironment}; +use move_vm_types::{ + code::{ModuleCache, ModuleCode, ModuleCodeBuilder, UnsyncModuleCache, WithHash}, + module_storage_error, sha3_256, +}; use rand::{thread_rng, Rng}; use std::sync::{Arc, Mutex}; @@ -42,11 +52,191 @@ pub trait TransactionValidation: Send + Sync + Clone { fn notify_commit(&mut self); } +/// Returns a new VM for validation, with configs initialized based on the provided state. +fn new_vm_for_validation(state_view: &impl StateView) -> AptosVM { + info!( + AdapterLogSchema::new(state_view.id(), 0), + "AptosVM created for Validation" + ); + let env = AptosEnvironment::new(state_view); + AptosVM::new(env, state_view) +} + +/// Represents the state used for validation. Stores raw data, module cache and the execution +/// runtime environment stored in the VM. Note that the state can get out-of-date, and it is the +/// responsibility of the owner of the struct to ensure it is up-to-date. +struct ValidationState { + /// The raw snapshot of the state used for validation. + state_view: S, + /// Versioned cache for deserialized and verified Move modules. The versioning allows to detect + /// when the version of the code is no longer up-to-date (a newer version has been committed to + /// the state view) and update the cache accordingly. + module_cache: UnsyncModuleCache, + vm: AptosVM, +} + +impl ValidationState { + /// Creates a new state based on the state view snapshot, with empty module cache and VM + /// initialized based on configs from the state. + fn new(state_view: S) -> Self { + let vm = new_vm_for_validation(&state_view); + Self { + state_view, + module_cache: UnsyncModuleCache::empty(), + vm, + } + } + + /// Resets the state view snapshot to the new one. Does not invalidate the module cache, nor + /// the VM. + fn reset_state_view(&mut self, state_view: S) { + self.state_view = state_view; + } + + /// Resets the state to the new one, empties module cache, and resets the VM based on the new + /// state view snapshot. + fn reset_all(&mut self, state_view: S) { + self.state_view = state_view; + self.module_cache = UnsyncModuleCache::empty(); + self.vm = new_vm_for_validation(&self.state_view); + } +} + +impl WithRuntimeEnvironment for ValidationState { + fn runtime_environment(&self) -> &RuntimeEnvironment { + self.vm.runtime_environment() + } +} + +impl ModuleCache for ValidationState { + type Deserialized = CompiledModule; + type Extension = AptosModuleExtension; + type Key = ModuleId; + type Verified = Module; + type Version = usize; + + fn insert_deserialized_module( + &self, + key: Self::Key, + deserialized_code: Self::Deserialized, + extension: Arc, + version: Self::Version, + ) -> VMResult>> { + self.module_cache + .insert_deserialized_module(key, deserialized_code, extension, version) + } + + fn insert_verified_module( + &self, + key: Self::Key, + verified_code: Self::Verified, + extension: Arc, + version: Self::Version, + ) -> VMResult>> { + self.module_cache + .insert_verified_module(key, verified_code, extension, version) + } + + fn get_module_or_build_with( + &self, + key: &Self::Key, + builder: &dyn ModuleCodeBuilder< + Key = Self::Key, + Deserialized = Self::Deserialized, + Verified = Self::Verified, + Extension = Self::Extension, + >, + ) -> VMResult< + Option<( + Arc>, + Self::Version, + )>, + > { + // Get the module that exists in cache. + let (module, version) = match self.module_cache.get_module_or_build_with(key, builder)? { + None => { + return Ok(None); + }, + Some(module_and_version) => module_and_version, + }; + + // Get the state value that exists in the actual state and compute the hash. + let state_value = self + .state_view + .get_state_value(&StateKey::module_id(key)) + .map_err(|err| module_storage_error!(key.address(), key.name(), err))? + .ok_or_else(|| { + PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR) + .with_message(format!( + "Module {}::{} cannot be found in storage, but exists in cache", + key.address(), + key.name() + )) + .finish(Location::Undefined) + })?; + let hash = sha3_256(state_value.bytes()); + + // If hash is the same - we can use the same module from cache. If the hash is different, + // then the state contains a newer version of the code. We deserialize the state value and + // replace the old cache entry with the new code. + // TODO(loader_v2): + // Ideally, commit notification should specify if new modules should be added to the + // cache instead of checking the state view bytes. Revisit. + Ok(if module.extension().hash() == &hash { + Some((module, version)) + } else { + let compiled_module = self + .vm + .runtime_environment() + .deserialize_into_compiled_module(state_value.bytes())?; + let extension = Arc::new(AptosModuleExtension::new(state_value)); + + let new_version = version + 1; + let new_module_code = self.module_cache.insert_deserialized_module( + key.clone(), + compiled_module, + extension, + new_version, + )?; + Some((new_module_code, new_version)) + }) + } + + fn num_modules(&self) -> usize { + self.module_cache.num_modules() + } +} + +impl ModuleCodeBuilder for ValidationState { + type Deserialized = CompiledModule; + type Extension = AptosModuleExtension; + type Key = ModuleId; + type Verified = Module; + + fn build( + &self, + key: &Self::Key, + ) -> VMResult>> { + let state_value = match self + .state_view + .get_state_value(&StateKey::module_id(key)) + .map_err(|err| module_storage_error!(key.address(), key.name(), err))? + { + Some(bytes) => bytes, + None => return Ok(None), + }; + let compiled_module = self + .runtime_environment() + .deserialize_into_compiled_module(state_value.bytes())?; + let extension = Arc::new(AptosModuleExtension::new(state_value)); + let module = ModuleCode::from_deserialized(compiled_module, extension); + Ok(Some(module)) + } +} + struct VMValidator { db_reader: Arc, - state_view: CachedDbStateView, - module_storage: AptosCodeStorageAdapter<'static, CachedDbStateView, AptosEnvironment>, - vm: AptosVM, + state: ValidationState, } impl Clone for VMValidator { @@ -56,30 +246,13 @@ impl Clone for VMValidator { } impl VMValidator { - fn new_vm_for_validation(state_view: &impl StateView) -> AptosVM { - info!( - AdapterLogSchema::new(state_view.id(), 0), - "AptosVM created for Validation" - ); - let env = AptosEnvironment::new(state_view); - AptosVM::new(env, state_view) - } - fn new(db_reader: Arc) -> Self { let db_state_view = db_reader .latest_state_checkpoint_view() .expect("Get db view cannot fail"); - - let vm = Self::new_vm_for_validation(&db_state_view); - let state_view = CachedDbStateView::from(db_state_view.clone()); - let module_storage = - CachedDbStateView::from(db_state_view).into_aptos_code_storage(vm.environment()); - VMValidator { db_reader, - state_view, - module_storage, - vm, + state: ValidationState::new(db_state_view.into()), } } @@ -91,18 +264,15 @@ impl VMValidator { fn restart(&mut self) -> Result<()> { let db_state_view = self.db_state_view(); - - self.state_view = db_state_view.clone().into(); - self.vm = Self::new_vm_for_validation(&self.state_view); - self.module_storage = - CachedDbStateView::from(db_state_view).into_aptos_code_storage(self.vm.environment()); - + self.state.reset_all(db_state_view.into()); Ok(()) } fn notify_commit(&mut self) { let db_state_view = self.db_state_view(); - self.state_view = db_state_view.into(); + + // On commit, we need to update the state view so that we can see the latest resources. + self.state.reset_state_view(db_state_view.into()); } } @@ -162,10 +332,10 @@ impl TransactionValidation for PooledVMValidator { let vm_validator_locked = vm_validator.lock().unwrap(); use aptos_vm::VMValidator; - Ok(vm_validator_locked.vm.validate_transaction( + Ok(vm_validator_locked.state.vm.validate_transaction( txn, - &vm_validator_locked.state_view, - &vm_validator_locked.module_storage, + &vm_validator_locked.state.state_view, + &vm_validator_locked.state, )) } @@ -182,3 +352,131 @@ impl TransactionValidation for PooledVMValidator { } } } + +#[cfg(test)] +mod tests { + use super::*; + use aptos_types::state_store::{state_value::StateValue, MockStateView}; + use move_binary_format::file_format::empty_module_with_dependencies_and_friends; + use move_core_types::ident_str; + use move_vm_runtime::ModuleStorage; + use std::collections::HashMap; + + fn module_state_value(module: CompiledModule) -> StateValue { + let mut bytes = vec![]; + module.serialize(&mut bytes).unwrap(); + StateValue::new_legacy(bytes.into()) + } + + #[test] + fn test_module_cache_consistency() { + // Have 3 modules in the state. + let a = empty_module_with_dependencies_and_friends("a", vec![], vec![]); + let b = empty_module_with_dependencies_and_friends("b", vec![], vec![]); + let c = empty_module_with_dependencies_and_friends("c", vec![], vec![]); + + let state_view = MockStateView::new(HashMap::from([ + ( + StateKey::module_id(&a.self_id()), + module_state_value(a.clone()), + ), + ( + StateKey::module_id(&b.self_id()), + module_state_value(b.clone()), + ), + ( + StateKey::module_id(&c.self_id()), + module_state_value(c.clone()), + ), + ])); + let mut state = ValidationState::new(state_view); + assert_eq!(state.module_cache.num_modules(), 0); + + assert!(state + .fetch_deserialized_module(&AccountAddress::ZERO, ident_str!("d")) + .unwrap() + .is_none()); + assert_eq!( + &a, + state + .fetch_deserialized_module(a.self_addr(), a.self_name()) + .unwrap() + .unwrap() + .as_ref() + ); + assert_eq!( + &c, + state + .fetch_deserialized_module(c.self_addr(), c.self_name()) + .unwrap() + .unwrap() + .as_ref() + ); + + assert_eq!(state.module_cache.num_modules(), 2); + assert_eq!(state.module_cache.get_module_version(&a.self_id()), Some(0)); + assert_eq!(state.module_cache.get_module_version(&b.self_id()), None); + assert_eq!(state.module_cache.get_module_version(&c.self_id()), Some(0)); + + // Change module "a" by adding dependencies and also add a new module "d". + let d = empty_module_with_dependencies_and_friends("d", vec![], vec![]); + let a_new = empty_module_with_dependencies_and_friends("a", vec!["b", "c"], vec![]); + assert_ne!(&a, &a_new); + + let new_state_view = MockStateView::new(HashMap::from([ + // New code: + ( + StateKey::module_id(&a_new.self_id()), + module_state_value(a_new.clone()), + ), + ( + StateKey::module_id(&d.self_id()), + module_state_value(d.clone()), + ), + // Old code: + ( + StateKey::module_id(&b.self_id()), + module_state_value(b.clone()), + ), + ( + StateKey::module_id(&c.self_id()), + module_state_value(c.clone()), + ), + ])); + state.reset_state_view(new_state_view); + + // New code version should be returned no + assert_eq!( + &a_new, + state + .fetch_deserialized_module(a_new.self_addr(), a_new.self_name()) + .unwrap() + .unwrap() + .as_ref() + ); + assert_eq!( + &d, + state + .fetch_deserialized_module(d.self_addr(), d.self_name()) + .unwrap() + .unwrap() + .as_ref() + ); + + assert_eq!(state.module_cache.num_modules(), 3); + assert_eq!(state.module_cache.get_module_version(&a.self_id()), Some(1)); + assert_eq!(state.module_cache.get_module_version(&c.self_id()), Some(0)); + assert_eq!(state.module_cache.get_module_version(&d.self_id()), Some(0)); + + // Get verified module, to load the transitive closure (modules "b" and "c") as well. + assert!(state + .fetch_verified_module(a_new.self_addr(), a_new.self_name()) + .unwrap() + .is_some()); + assert_eq!(state.module_cache.num_modules(), 4); + assert_eq!(state.module_cache.get_module_version(&a.self_id()), Some(1)); + assert_eq!(state.module_cache.get_module_version(&b.self_id()), Some(0)); + assert_eq!(state.module_cache.get_module_version(&c.self_id()), Some(0)); + assert_eq!(state.module_cache.get_module_version(&d.self_id()), Some(0)); + } +}