From 93bc66f21f9f67a440f06f1c4402e0d687698741 Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Mon, 23 Sep 2024 16:58:42 +0300 Subject: [PATCH] feat(vm): Split old and new VM implementations (#2915) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What ❔ Splits old and new VM implementations in the `multivm` crate: - Old VMs are encapsulated in the `LegacyVmInstance` enum, while new ones in the `FastVmInstance` enum (which includes plain and shadowed VM variants). - Fast VM and `FastVmInstance` now expose a tracer type. - Usage of the Fast VM in the batch executor are updated correspondingly. ## Why ❔ It seems infeasible to unify the tracer model for old and new VMs, so keeping them all in a single enum makes little sense. ## Checklist - [x] PR title corresponds to the body of PR (we generate changelog entries from PRs). - [x] Tests for the changes have been added / updated. - [x] Documentation comments have been added / updated. - [x] Code has been formatted via `zk fmt` and `zk lint`. --- Cargo.lock | 1 + .../system-constants-generator/src/utils.rs | 2 +- core/lib/multivm/src/lib.rs | 2 +- core/lib/multivm/src/versions/tests.rs | 6 +- core/lib/multivm/src/versions/vm_1_3_2/vm.rs | 4 +- .../vm_1_4_1/implementation/execution.rs | 9 +- core/lib/multivm/src/versions/vm_1_4_1/vm.rs | 6 +- core/lib/multivm/src/versions/vm_1_4_2/vm.rs | 12 +- .../implementation/execution.rs | 9 +- .../src/versions/vm_boojum_integration/vm.rs | 6 +- .../multivm/src/versions/vm_fast/bytecode.rs | 2 +- .../src/versions/vm_fast/circuits_tracer.rs | 7 +- core/lib/multivm/src/versions/vm_fast/mod.rs | 4 +- .../src/versions/vm_fast/tests/circuits.rs | 5 +- .../src/versions/vm_fast/tests/code_oracle.rs | 4 +- .../vm_fast/tests/get_used_contracts.rs | 2 +- .../src/versions/vm_fast/tests/precompiles.rs | 11 +- .../versions/vm_fast/tests/require_eip712.rs | 2 +- .../tests/tester/transaction_test_info.rs | 2 +- .../vm_fast/tests/tester/vm_tester.rs | 13 +- core/lib/multivm/src/versions/vm_fast/vm.rs | 50 ++-- .../vm_latest/implementation/execution.rs | 9 +- .../src/versions/vm_latest/tests/block_tip.rs | 2 +- .../versions/vm_latest/tests/call_tracer.rs | 8 +- .../src/versions/vm_latest/tests/circuits.rs | 4 +- .../versions/vm_latest/tests/precompiles.rs | 12 +- .../vm_latest/tests/prestate_tracer.rs | 5 +- .../src/versions/vm_latest/tests/rollbacks.rs | 13 +- core/lib/multivm/src/versions/vm_latest/vm.rs | 6 +- core/lib/multivm/src/versions/vm_m5/vm.rs | 9 +- core/lib/multivm/src/versions/vm_m6/vm.rs | 4 +- .../implementation/execution.rs | 9 +- .../src/versions/vm_refunds_enhancement/vm.rs | 6 +- .../implementation/execution.rs | 9 +- .../src/versions/vm_virtual_blocks/vm.rs | 6 +- core/lib/multivm/src/vm_instance.rs | 238 +++++++++++----- core/lib/tee_verifier/src/lib.rs | 15 +- core/lib/vm_executor/src/batch/factory.rs | 267 ++++++++++++------ core/lib/vm_executor/src/batch/mod.rs | 5 +- core/lib/vm_executor/src/oneshot/mod.rs | 12 +- core/lib/vm_executor/src/shared.rs | 3 + .../src/types/outputs/execution_result.rs | 6 +- core/lib/vm_interface/src/utils/dump.rs | 9 +- core/lib/vm_interface/src/utils/shadow.rs | 26 +- core/lib/vm_interface/src/vm.rs | 13 +- core/node/consensus/src/testonly.rs | 2 +- .../state_keeper/main_batch_executor.rs | 19 +- .../implementations/layers/vm_runner/bwip.rs | 2 +- core/node/state_keeper/Cargo.toml | 1 + .../state_keeper/src/executor/tests/mod.rs | 67 ++++- .../state_keeper/src/executor/tests/tester.rs | 34 ++- core/node/vm_runner/src/impls/playground.rs | 4 +- .../vm_runner/src/impls/protective_reads.rs | 2 +- core/node/vm_runner/src/tests/process.rs | 2 +- .../vm_runner/src/tests/storage_writer.rs | 4 +- core/tests/vm-benchmark/src/vm.rs | 10 +- 56 files changed, 662 insertions(+), 340 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c2d01881fcd..50f0784d9fa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10752,6 +10752,7 @@ dependencies = [ "hex", "itertools 0.10.5", "once_cell", + "rand 0.8.5", "tempfile", "test-casing", "thiserror", diff --git a/core/bin/system-constants-generator/src/utils.rs b/core/bin/system-constants-generator/src/utils.rs index 3269fc96204..43ac9841c40 100644 --- a/core/bin/system-constants-generator/src/utils.rs +++ b/core/bin/system-constants-generator/src/utils.rs @@ -262,7 +262,7 @@ pub(super) fn execute_internal_transfer_test() -> u32 { } .into_tracer_pointer(); let mut vm: Vm<_, HistoryEnabled> = Vm::new(l1_batch, system_env, storage_view.to_rc_ptr()); - let result = vm.inspect(tracer.into(), VmExecutionMode::Bootloader); + let result = vm.inspect(&mut tracer.into(), VmExecutionMode::Bootloader); assert!(!result.result.is_failed(), "The internal call has reverted"); tracer_result.take() diff --git a/core/lib/multivm/src/lib.rs b/core/lib/multivm/src/lib.rs index be740d6b378..e171a78e179 100644 --- a/core/lib/multivm/src/lib.rs +++ b/core/lib/multivm/src/lib.rs @@ -16,7 +16,7 @@ pub use crate::{ vm_1_3_2, vm_1_4_1, vm_1_4_2, vm_boojum_integration, vm_fast, vm_latest, vm_m5, vm_m6, vm_refunds_enhancement, vm_virtual_blocks, }, - vm_instance::VmInstance, + vm_instance::{FastVmInstance, LegacyVmInstance}, }; mod glue; diff --git a/core/lib/multivm/src/versions/tests.rs b/core/lib/multivm/src/versions/tests.rs index 96a85c86816..c2a04c155fe 100644 --- a/core/lib/multivm/src/versions/tests.rs +++ b/core/lib/multivm/src/versions/tests.rs @@ -29,7 +29,7 @@ use crate::{ }; type ReferenceVm = vm_latest::Vm, HistoryEnabled>; -type ShadowedVmFast = crate::vm_instance::ShadowedVmFast; +type ShadowedFastVm = crate::vm_instance::ShadowedFastVm; fn hash_block(block_env: L2BlockEnv, tx_hashes: &[H256]) -> H256 { let mut hasher = L2BlockHasher::new( @@ -248,12 +248,12 @@ fn sanity_check_shadow_vm() { #[test] fn shadow_vm_basics() { - let (vm, harness) = sanity_check_vm::(); + let (vm, harness) = sanity_check_vm::(); let mut dump = vm.dump_state(); Harness::assert_dump(&mut dump); // Test standard playback functionality. - let replayed_dump = dump.clone().play_back::>().dump_state(); + let replayed_dump = dump.clone().play_back::>().dump_state(); pretty_assertions::assert_eq!(replayed_dump, dump); // Check that the VM executes identically when reading from the original storage and one restored from the dump. diff --git a/core/lib/multivm/src/versions/vm_1_3_2/vm.rs b/core/lib/multivm/src/versions/vm_1_3_2/vm.rs index 8068e4847b8..5692f103da3 100644 --- a/core/lib/multivm/src/versions/vm_1_3_2/vm.rs +++ b/core/lib/multivm/src/versions/vm_1_3_2/vm.rs @@ -36,7 +36,7 @@ impl VmInterface for Vm { fn inspect( &mut self, - tracer: Self::TracerDispatcher, + tracer: &mut Self::TracerDispatcher, execution_mode: VmExecutionMode, ) -> VmExecutionResultAndLogs { if let Some(storage_invocations) = tracer.storage_invocations { @@ -80,7 +80,7 @@ impl VmInterface for Vm { fn inspect_transaction_with_bytecode_compression( &mut self, - tracer: Self::TracerDispatcher, + tracer: &mut Self::TracerDispatcher, tx: Transaction, with_compression: bool, ) -> (BytecodeCompressionResult<'_>, VmExecutionResultAndLogs) { diff --git a/core/lib/multivm/src/versions/vm_1_4_1/implementation/execution.rs b/core/lib/multivm/src/versions/vm_1_4_1/implementation/execution.rs index db5aaa783df..2160c4b56a0 100644 --- a/core/lib/multivm/src/versions/vm_1_4_1/implementation/execution.rs +++ b/core/lib/multivm/src/versions/vm_1_4_1/implementation/execution.rs @@ -1,3 +1,5 @@ +use std::mem; + use zk_evm_1_4_1::aux_structures::Timestamp; use crate::{ @@ -20,7 +22,7 @@ use crate::{ impl Vm { pub(crate) fn inspect_inner( &mut self, - dispatcher: TracerDispatcher, + dispatcher: &mut TracerDispatcher, execution_mode: VmExecutionMode, custom_pubdata_tracer: Option>, ) -> VmExecutionResultAndLogs { @@ -44,7 +46,7 @@ impl Vm { /// Collect the result from the default tracers. fn inspect_and_collect_results( &mut self, - dispatcher: TracerDispatcher, + dispatcher: &mut TracerDispatcher, execution_mode: VmExecutionMode, with_refund_tracer: bool, custom_pubdata_tracer: Option>, @@ -54,7 +56,7 @@ impl Vm { let mut tx_tracer: DefaultExecutionTracer = DefaultExecutionTracer::new( self.system_env.default_validation_computational_gas_limit, execution_mode, - dispatcher, + mem::take(dispatcher), self.storage.clone(), refund_tracers, custom_pubdata_tracer @@ -90,6 +92,7 @@ impl Vm { circuit_statistic_from_cycles(tx_tracer.circuits_tracer.statistics), ); let result = tx_tracer.result_tracer.into_result(); + *dispatcher = tx_tracer.dispatcher; let result = VmExecutionResultAndLogs { result, diff --git a/core/lib/multivm/src/versions/vm_1_4_1/vm.rs b/core/lib/multivm/src/versions/vm_1_4_1/vm.rs index 2c1a4ba5e36..68c8e92a03a 100644 --- a/core/lib/multivm/src/versions/vm_1_4_1/vm.rs +++ b/core/lib/multivm/src/versions/vm_1_4_1/vm.rs @@ -90,7 +90,7 @@ impl VmInterface for Vm { /// Execute VM with custom tracers. fn inspect( &mut self, - tracer: Self::TracerDispatcher, + tracer: &mut Self::TracerDispatcher, execution_mode: VmExecutionMode, ) -> VmExecutionResultAndLogs { self.inspect_inner(tracer, execution_mode, None) @@ -102,7 +102,7 @@ impl VmInterface for Vm { fn inspect_transaction_with_bytecode_compression( &mut self, - tracer: Self::TracerDispatcher, + tracer: &mut Self::TracerDispatcher, tx: Transaction, with_compression: bool, ) -> (BytecodeCompressionResult<'_>, VmExecutionResultAndLogs) { @@ -129,7 +129,7 @@ impl VmInterface for Vm { } fn finish_batch(&mut self) -> FinishedL1Batch { - let result = self.inspect(TracerDispatcher::default(), VmExecutionMode::Batch); + let result = self.inspect(&mut TracerDispatcher::default(), VmExecutionMode::Batch); let execution_state = self.get_current_execution_state(); let bootloader_memory = self.bootloader_state.bootloader_memory(); FinishedL1Batch { diff --git a/core/lib/multivm/src/versions/vm_1_4_2/vm.rs b/core/lib/multivm/src/versions/vm_1_4_2/vm.rs index 71633dd3fca..d6e1fbc68a8 100644 --- a/core/lib/multivm/src/versions/vm_1_4_2/vm.rs +++ b/core/lib/multivm/src/versions/vm_1_4_2/vm.rs @@ -1,3 +1,5 @@ +use std::mem; + use circuit_sequencer_api_1_4_2::sort_storage_access::sort_storage_access_queries; use zksync_types::{ l2_to_l1_log::{SystemL2ToL1Log, UserL2ToL1Log}, @@ -90,10 +92,10 @@ impl VmInterface for Vm { /// Execute VM with custom tracers. fn inspect( &mut self, - tracer: Self::TracerDispatcher, + tracer: &mut Self::TracerDispatcher, execution_mode: VmExecutionMode, ) -> VmExecutionResultAndLogs { - self.inspect_inner(tracer, execution_mode, None) + self.inspect_inner(mem::take(tracer), execution_mode, None) } fn start_new_l2_block(&mut self, l2_block_env: L2BlockEnv) { @@ -102,12 +104,12 @@ impl VmInterface for Vm { fn inspect_transaction_with_bytecode_compression( &mut self, - tracer: Self::TracerDispatcher, + tracer: &mut Self::TracerDispatcher, tx: Transaction, with_compression: bool, ) -> (BytecodeCompressionResult<'_>, VmExecutionResultAndLogs) { self.push_transaction_with_compression(tx, with_compression); - let result = self.inspect_inner(tracer, VmExecutionMode::OneTx, None); + let result = self.inspect_inner(mem::take(tracer), VmExecutionMode::OneTx, None); if self.has_unpublished_bytecodes() { ( Err(BytecodeCompressionError::BytecodeCompressionFailed), @@ -129,7 +131,7 @@ impl VmInterface for Vm { } fn finish_batch(&mut self) -> FinishedL1Batch { - let result = self.inspect(TracerDispatcher::default(), VmExecutionMode::Batch); + let result = self.inspect(&mut TracerDispatcher::default(), VmExecutionMode::Batch); let execution_state = self.get_current_execution_state(); let bootloader_memory = self.bootloader_state.bootloader_memory(); FinishedL1Batch { diff --git a/core/lib/multivm/src/versions/vm_boojum_integration/implementation/execution.rs b/core/lib/multivm/src/versions/vm_boojum_integration/implementation/execution.rs index a7c790a4bc3..79669eddd56 100644 --- a/core/lib/multivm/src/versions/vm_boojum_integration/implementation/execution.rs +++ b/core/lib/multivm/src/versions/vm_boojum_integration/implementation/execution.rs @@ -1,3 +1,5 @@ +use std::mem; + use zk_evm_1_4_0::aux_structures::Timestamp; use crate::{ @@ -20,7 +22,7 @@ use crate::{ impl Vm { pub(crate) fn inspect_inner( &mut self, - dispatcher: TracerDispatcher, + dispatcher: &mut TracerDispatcher, execution_mode: VmExecutionMode, ) -> VmExecutionResultAndLogs { let mut enable_refund_tracer = false; @@ -39,7 +41,7 @@ impl Vm { /// Collect the result from the default tracers. fn inspect_and_collect_results( &mut self, - dispatcher: TracerDispatcher, + dispatcher: &mut TracerDispatcher, execution_mode: VmExecutionMode, with_refund_tracer: bool, ) -> (VmExecutionStopReason, VmExecutionResultAndLogs) { @@ -49,7 +51,7 @@ impl Vm { DefaultExecutionTracer::new( self.system_env.default_validation_computational_gas_limit, execution_mode, - dispatcher, + mem::take(dispatcher), self.storage.clone(), refund_tracers, Some(PubdataTracer::new(self.batch_env.clone(), execution_mode)), @@ -84,6 +86,7 @@ impl Vm { circuit_statistic_from_cycles(tx_tracer.circuits_tracer.statistics), ); let result = tx_tracer.result_tracer.into_result(); + *dispatcher = tx_tracer.dispatcher; // return the dispatcher back let result = VmExecutionResultAndLogs { result, diff --git a/core/lib/multivm/src/versions/vm_boojum_integration/vm.rs b/core/lib/multivm/src/versions/vm_boojum_integration/vm.rs index c7b4a5537ac..17ce8365a0a 100644 --- a/core/lib/multivm/src/versions/vm_boojum_integration/vm.rs +++ b/core/lib/multivm/src/versions/vm_boojum_integration/vm.rs @@ -90,7 +90,7 @@ impl VmInterface for Vm { /// Execute VM with custom tracers. fn inspect( &mut self, - tracer: Self::TracerDispatcher, + tracer: &mut Self::TracerDispatcher, execution_mode: VmExecutionMode, ) -> VmExecutionResultAndLogs { self.inspect_inner(tracer, execution_mode) @@ -103,7 +103,7 @@ impl VmInterface for Vm { /// Inspect transaction with optional bytecode compression. fn inspect_transaction_with_bytecode_compression( &mut self, - tracer: Self::TracerDispatcher, + tracer: &mut Self::TracerDispatcher, tx: Transaction, with_compression: bool, ) -> (BytecodeCompressionResult<'_>, VmExecutionResultAndLogs) { @@ -130,7 +130,7 @@ impl VmInterface for Vm { } fn finish_batch(&mut self) -> FinishedL1Batch { - let result = self.inspect(TracerDispatcher::default(), VmExecutionMode::Batch); + let result = self.inspect(&mut TracerDispatcher::default(), VmExecutionMode::Batch); let execution_state = self.get_current_execution_state(); let bootloader_memory = self.bootloader_state.bootloader_memory(); FinishedL1Batch { diff --git a/core/lib/multivm/src/versions/vm_fast/bytecode.rs b/core/lib/multivm/src/versions/vm_fast/bytecode.rs index 02122e5f29c..b75e33a21b0 100644 --- a/core/lib/multivm/src/versions/vm_fast/bytecode.rs +++ b/core/lib/multivm/src/versions/vm_fast/bytecode.rs @@ -8,7 +8,7 @@ use crate::{ utils::bytecode, }; -impl Vm { +impl Vm { /// Checks the last transaction has successfully published compressed bytecodes and returns `true` if there is at least one is still unknown. pub(crate) fn has_unpublished_bytecodes(&mut self) -> bool { self.bootloader_state diff --git a/core/lib/multivm/src/versions/vm_fast/circuits_tracer.rs b/core/lib/multivm/src/versions/vm_fast/circuits_tracer.rs index e6b1a53e9d0..b48ec7eacb0 100644 --- a/core/lib/multivm/src/versions/vm_fast/circuits_tracer.rs +++ b/core/lib/multivm/src/versions/vm_fast/circuits_tracer.rs @@ -4,8 +4,10 @@ use zksync_vm_interface::CircuitStatistic; use crate::vm_latest::tracers::circuits_capacity::*; +/// VM tracer tracking [`CircuitStatistic`]s. Statistics generally depend on the number of time some opcodes were invoked, +/// and, for precompiles, invocation complexity (e.g., how many hashing cycles `keccak256` required). #[derive(Debug, Default, Clone, PartialEq)] -pub(crate) struct CircuitsTracer { +pub struct CircuitsTracer { main_vm_cycles: u32, ram_permutation_cycles: u32, storage_application_cycles: u32, @@ -124,7 +126,8 @@ impl Tracer for CircuitsTracer { } impl CircuitsTracer { - pub(crate) fn circuit_statistic(&self) -> CircuitStatistic { + /// Obtains the current circuit stats from this tracer. + pub fn circuit_statistic(&self) -> CircuitStatistic { CircuitStatistic { main_vm: self.main_vm_cycles as f32 / GEOMETRY_CONFIG.cycles_per_vm_snapshot as f32, ram_permutation: self.ram_permutation_cycles as f32 diff --git a/core/lib/multivm/src/versions/vm_fast/mod.rs b/core/lib/multivm/src/versions/vm_fast/mod.rs index f0d8bafe69e..bb5a342bff2 100644 --- a/core/lib/multivm/src/versions/vm_fast/mod.rs +++ b/core/lib/multivm/src/versions/vm_fast/mod.rs @@ -1,4 +1,6 @@ -pub use self::vm::Vm; +pub use zksync_vm2::interface::Tracer; + +pub use self::{circuits_tracer::CircuitsTracer, vm::Vm}; mod bootloader_state; mod bytecode; diff --git a/core/lib/multivm/src/versions/vm_fast/tests/circuits.rs b/core/lib/multivm/src/versions/vm_fast/tests/circuits.rs index a119a31618e..f40e5336eb3 100644 --- a/core/lib/multivm/src/versions/vm_fast/tests/circuits.rs +++ b/core/lib/multivm/src/versions/vm_fast/tests/circuits.rs @@ -2,7 +2,7 @@ use zksync_types::{Address, Execute, U256}; use super::tester::VmTesterBuilder; use crate::{ - interface::{TxExecutionMode, VmExecutionMode, VmInterface}, + interface::{TxExecutionMode, VmExecutionMode, VmInterface, VmInterfaceExt}, vm_latest::constants::BATCH_COMPUTATIONAL_GAS_LIMIT, }; @@ -29,7 +29,8 @@ fn test_circuits() { None, ); vm.vm.push_transaction(tx); - let res = vm.vm.inspect((), VmExecutionMode::OneTx); + let res = vm.vm.execute(VmExecutionMode::OneTx); + assert!(!res.result.is_failed(), "{res:#?}"); let s = res.statistics.circuit_statistic; // Check `circuit_statistic`. diff --git a/core/lib/multivm/src/versions/vm_fast/tests/code_oracle.rs b/core/lib/multivm/src/versions/vm_fast/tests/code_oracle.rs index 156af43dcf2..34342d7f3b8 100644 --- a/core/lib/multivm/src/versions/vm_fast/tests/code_oracle.rs +++ b/core/lib/multivm/src/versions/vm_fast/tests/code_oracle.rs @@ -8,11 +8,11 @@ use crate::{ interface::{TxExecutionMode, VmExecutionMode, VmInterface, VmInterfaceExt}, versions::testonly::ContractToDeploy, vm_fast::{ - circuits_tracer::CircuitsTracer, tests::{ tester::{get_empty_storage, VmTesterBuilder}, utils::{load_precompiles_contract, read_precompiles_contract, read_test_contract}, }, + CircuitsTracer, }, }; @@ -210,7 +210,7 @@ fn refunds_in_code_oracle() { if decommit { let (_, is_fresh) = vm.vm.inner.world_diff_mut().decommit_opcode( &mut vm.vm.world, - &mut CircuitsTracer::default(), + &mut ((), CircuitsTracer::default()), h256_to_u256(normal_zkevm_bytecode_hash), ); assert!(is_fresh); diff --git a/core/lib/multivm/src/versions/vm_fast/tests/get_used_contracts.rs b/core/lib/multivm/src/versions/vm_fast/tests/get_used_contracts.rs index b8942dcbb6a..62fa82f52f2 100644 --- a/core/lib/multivm/src/versions/vm_fast/tests/get_used_contracts.rs +++ b/core/lib/multivm/src/versions/vm_fast/tests/get_used_contracts.rs @@ -117,7 +117,7 @@ struct ProxyCounterData { counter_bytecode_hash: U256, } -fn execute_proxy_counter(gas: u32) -> (VmTester, ProxyCounterData, VmExecutionResultAndLogs) { +fn execute_proxy_counter(gas: u32) -> (VmTester<()>, ProxyCounterData, VmExecutionResultAndLogs) { let counter_bytecode = inflated_counter_bytecode(); let counter_bytecode_hash = h256_to_u256(hash_bytecode(&counter_bytecode)); let counter_address = Address::repeat_byte(0x23); diff --git a/core/lib/multivm/src/versions/vm_fast/tests/precompiles.rs b/core/lib/multivm/src/versions/vm_fast/tests/precompiles.rs index 5bc3f614d61..b3ca1596217 100644 --- a/core/lib/multivm/src/versions/vm_fast/tests/precompiles.rs +++ b/core/lib/multivm/src/versions/vm_fast/tests/precompiles.rs @@ -3,7 +3,7 @@ use zksync_types::{Address, Execute}; use super::{tester::VmTesterBuilder, utils::read_precompiles_contract}; use crate::{ - interface::{TxExecutionMode, VmExecutionMode, VmInterface}, + interface::{TxExecutionMode, VmExecutionMode, VmInterface, VmInterfaceExt}, versions::testonly::ContractToDeploy, vm_latest::constants::BATCH_COMPUTATIONAL_GAS_LIMIT, }; @@ -37,7 +37,8 @@ fn test_keccak() { None, ); vm.vm.push_transaction(tx); - let exec_result = vm.vm.inspect((), VmExecutionMode::OneTx); + + let exec_result = vm.vm.execute(VmExecutionMode::OneTx); assert!(!exec_result.result.is_failed(), "{exec_result:#?}"); let keccak_count = exec_result.statistics.circuit_statistic.keccak256 @@ -74,7 +75,8 @@ fn test_sha256() { None, ); vm.vm.push_transaction(tx); - let exec_result = vm.vm.inspect((), VmExecutionMode::OneTx); + + let exec_result = vm.vm.execute(VmExecutionMode::OneTx); assert!(!exec_result.result.is_failed(), "{exec_result:#?}"); let sha_count = exec_result.statistics.circuit_statistic.sha256 @@ -104,7 +106,8 @@ fn test_ecrecover() { None, ); vm.vm.push_transaction(tx); - let exec_result = vm.vm.inspect((), VmExecutionMode::OneTx); + + let exec_result = vm.vm.execute(VmExecutionMode::OneTx); assert!(!exec_result.result.is_failed(), "{exec_result:#?}"); let ecrecover_count = exec_result.statistics.circuit_statistic.ecrecover diff --git a/core/lib/multivm/src/versions/vm_fast/tests/require_eip712.rs b/core/lib/multivm/src/versions/vm_fast/tests/require_eip712.rs index 1fd2ebd523b..e119cea0114 100644 --- a/core/lib/multivm/src/versions/vm_fast/tests/require_eip712.rs +++ b/core/lib/multivm/src/versions/vm_fast/tests/require_eip712.rs @@ -19,7 +19,7 @@ use crate::{ }, }; -impl VmTester { +impl VmTester<()> { pub(crate) fn get_eth_balance(&mut self, address: Address) -> U256 { let key = storage_key_for_standard_token_balance( AccountTreeId::new(L2_BASE_TOKEN_ADDRESS), diff --git a/core/lib/multivm/src/versions/vm_fast/tests/tester/transaction_test_info.rs b/core/lib/multivm/src/versions/vm_fast/tests/tester/transaction_test_info.rs index b84af0481d7..e6506ff225b 100644 --- a/core/lib/multivm/src/versions/vm_fast/tests/tester/transaction_test_info.rs +++ b/core/lib/multivm/src/versions/vm_fast/tests/tester/transaction_test_info.rs @@ -212,7 +212,7 @@ impl Vm { } } -impl VmTester { +impl VmTester<()> { pub(crate) fn execute_and_verify_txs( &mut self, txs: &[TransactionTestInfo], diff --git a/core/lib/multivm/src/versions/vm_fast/tests/tester/vm_tester.rs b/core/lib/multivm/src/versions/vm_fast/tests/tester/vm_tester.rs index dd82b73839b..9549b32c4f1 100644 --- a/core/lib/multivm/src/versions/vm_fast/tests/tester/vm_tester.rs +++ b/core/lib/multivm/src/versions/vm_fast/tests/tester/vm_tester.rs @@ -7,13 +7,12 @@ use zksync_types::{ L2BlockNumber, Nonce, StorageKey, }; use zksync_utils::{bytecode::hash_bytecode, u256_to_h256}; -use zksync_vm2::WorldDiff; +use zksync_vm2::{interface::Tracer, WorldDiff}; use crate::{ interface::{ storage::{InMemoryStorage, StoragePtr}, L1BatchEnv, L2Block, L2BlockEnv, SystemEnv, TxExecutionMode, VmExecutionMode, VmInterface, - VmInterfaceExt, }, versions::{ testonly::{default_l1_batch, default_system_env, make_account_rich, ContractToDeploy}, @@ -22,8 +21,8 @@ use crate::{ vm_latest::utils::l2_blocks::load_last_l2_block, }; -pub(crate) struct VmTester { - pub(crate) vm: Vm>, +pub(crate) struct VmTester { + pub(crate) vm: Vm, Tr>, pub(crate) storage: StoragePtr, pub(crate) deployer: Option, pub(crate) test_contract: Option
, @@ -32,7 +31,7 @@ pub(crate) struct VmTester { pub(crate) custom_contracts: Vec, } -impl VmTester { +impl VmTester { pub(crate) fn deploy_test_contract(&mut self) { let contract = read_test_contract(); let tx = self @@ -43,7 +42,7 @@ impl VmTester { .tx; let nonce = tx.nonce().unwrap().0.into(); self.vm.push_transaction(tx); - self.vm.execute(VmExecutionMode::OneTx); + self.vm.inspect(&mut Tr::default(), VmExecutionMode::OneTx); let deployed_address = deployed_address_create(self.deployer.as_ref().unwrap().address, nonce); self.test_contract = Some(deployed_address); @@ -197,7 +196,7 @@ impl VmTesterBuilder { self } - pub(crate) fn build(self) -> VmTester { + pub(crate) fn build(self) -> VmTester<()> { let l1_batch_env = self .l1_batch_env .unwrap_or_else(|| default_l1_batch(L1BatchNumber(1))); diff --git a/core/lib/multivm/src/versions/vm_fast/vm.rs b/core/lib/multivm/src/versions/vm_fast/vm.rs index 0b6172a4d8a..36698de105c 100644 --- a/core/lib/multivm/src/versions/vm_fast/vm.rs +++ b/core/lib/multivm/src/versions/vm_fast/vm.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, fmt}; +use std::{collections::HashMap, fmt, mem}; use zk_evm_1_5_0::zkevm_opcode_defs::system_params::INITIAL_FRAME_FORMAL_EH_LOCATION; use zksync_contracts::SystemContractCode; @@ -56,9 +56,16 @@ use crate::{ const VM_VERSION: MultiVMSubversion = MultiVMSubversion::IncreasedBootloaderMemory; -pub struct Vm { - pub(crate) world: World, - pub(crate) inner: VirtualMachine>, +type FullTracer = (Tr, CircuitsTracer); + +/// Fast VM wrapper. +/// +/// The wrapper is parametric by the storage and tracer types. Besides the [`Tracer`] trait, a tracer must have `'static` lifetime +/// and implement [`Default`] (the latter is necessary to complete batches). [`CircuitsTracer`] is currently always enabled; +/// you don't need to specify it explicitly. +pub struct Vm { + pub(crate) world: World>, + pub(crate) inner: VirtualMachine, World>>, gas_for_account_validation: u32, pub(crate) bootloader_state: BootloaderState, pub(crate) batch_env: L1BatchEnv, @@ -68,7 +75,7 @@ pub struct Vm { enforced_state_diffs: Option>, } -impl Vm { +impl Vm { pub fn custom(batch_env: L1BatchEnv, system_env: SystemEnv, storage: S) -> Self { let default_aa_code_hash = system_env .base_system_smart_contracts @@ -131,7 +138,7 @@ impl Vm { fn run( &mut self, execution_mode: VmExecutionMode, - tracer: &mut CircuitsTracer, + tracer: &mut (Tr, CircuitsTracer), track_refunds: bool, ) -> (ExecutionResult, Refunds) { let mut refunds = Refunds { @@ -485,7 +492,11 @@ impl Vm { } } -impl VmFactory> for Vm> { +impl VmFactory> for Vm, Tr> +where + S: ReadStorage, + Tr: Tracer + Default + 'static, +{ fn new( batch_env: L1BatchEnv, system_env: SystemEnv, @@ -496,8 +507,8 @@ impl VmFactory> for Vm> { } } -impl VmInterface for Vm { - type TracerDispatcher = (); +impl VmInterface for Vm { + type TracerDispatcher = Tr; fn push_transaction(&mut self, tx: zksync_types::Transaction) { self.push_transaction_inner(tx, 0, true); @@ -505,7 +516,7 @@ impl VmInterface for Vm { fn inspect( &mut self, - (): Self::TracerDispatcher, + tracer: &mut Self::TracerDispatcher, execution_mode: VmExecutionMode, ) -> VmExecutionResultAndLogs { let mut track_refunds = false; @@ -515,12 +526,14 @@ impl VmInterface for Vm { track_refunds = true; } - let mut tracer = CircuitsTracer::default(); let start = self.inner.world_diff().snapshot(); let pubdata_before = self.inner.pubdata(); let gas_before = self.gas_remaining(); - let (result, refunds) = self.run(execution_mode, &mut tracer, track_refunds); + let mut full_tracer = (mem::take(tracer), CircuitsTracer::default()); + let (result, refunds) = self.run(execution_mode, &mut full_tracer, track_refunds); + *tracer = full_tracer.0; // place the tracer back + let ignore_world_diff = matches!(execution_mode, VmExecutionMode::OneTx) && matches!(result, ExecutionResult::Halt { .. }); @@ -572,7 +585,6 @@ impl VmInterface for Vm { }; let pubdata_after = self.inner.pubdata(); - let circuit_statistic = tracer.circuit_statistic(); let gas_remaining = self.gas_remaining(); VmExecutionResultAndLogs { result, @@ -586,7 +598,7 @@ impl VmInterface for Vm { computational_gas_used: 0, total_log_queries: 0, pubdata_published: (pubdata_after - pubdata_before).max(0) as u32, - circuit_statistic, + circuit_statistic: full_tracer.1.circuit_statistic(), }, refunds, } @@ -594,12 +606,12 @@ impl VmInterface for Vm { fn inspect_transaction_with_bytecode_compression( &mut self, - (): Self::TracerDispatcher, + tracer: &mut Self::TracerDispatcher, tx: zksync_types::Transaction, with_compression: bool, ) -> (BytecodeCompressionResult<'_>, VmExecutionResultAndLogs) { self.push_transaction_inner(tx, 0, with_compression); - let result = self.inspect((), VmExecutionMode::OneTx); + let result = self.inspect(tracer, VmExecutionMode::OneTx); let compression_result = if self.has_unpublished_bytecodes() { Err(BytecodeCompressionError::BytecodeCompressionFailed) @@ -621,7 +633,7 @@ impl VmInterface for Vm { } fn finish_batch(&mut self) -> FinishedL1Batch { - let result = self.inspect((), VmExecutionMode::Batch); + let result = self.inspect(&mut Tr::default(), VmExecutionMode::Batch); let execution_state = self.get_current_execution_state(); let bootloader_memory = self.bootloader_state.bootloader_memory(); FinishedL1Batch { @@ -650,7 +662,7 @@ struct VmSnapshot { gas_for_account_validation: u32, } -impl VmInterfaceHistoryEnabled for Vm { +impl VmInterfaceHistoryEnabled for Vm { fn make_snapshot(&mut self) { assert!( self.snapshot.is_none(), @@ -687,7 +699,7 @@ impl VmTrackingContracts for Vm { } } -impl fmt::Debug for Vm { +impl fmt::Debug for Vm { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Vm") .field( diff --git a/core/lib/multivm/src/versions/vm_latest/implementation/execution.rs b/core/lib/multivm/src/versions/vm_latest/implementation/execution.rs index 66fc1a8bfd7..b8242fa7ca8 100644 --- a/core/lib/multivm/src/versions/vm_latest/implementation/execution.rs +++ b/core/lib/multivm/src/versions/vm_latest/implementation/execution.rs @@ -1,3 +1,5 @@ +use std::mem; + use zk_evm_1_5_0::aux_structures::Timestamp; use crate::{ @@ -20,7 +22,7 @@ use crate::{ impl Vm { pub(crate) fn inspect_inner( &mut self, - dispatcher: TracerDispatcher, + dispatcher: &mut TracerDispatcher, execution_mode: VmExecutionMode, custom_pubdata_tracer: Option>, ) -> VmExecutionResultAndLogs { @@ -44,7 +46,7 @@ impl Vm { /// Collect the result from the default tracers. fn inspect_and_collect_results( &mut self, - dispatcher: TracerDispatcher, + dispatcher: &mut TracerDispatcher, execution_mode: VmExecutionMode, with_refund_tracer: bool, custom_pubdata_tracer: Option>, @@ -54,7 +56,7 @@ impl Vm { let mut tx_tracer: DefaultExecutionTracer = DefaultExecutionTracer::new( self.system_env.default_validation_computational_gas_limit, execution_mode, - dispatcher, + mem::take(dispatcher), self.storage.clone(), refund_tracers, custom_pubdata_tracer.or_else(|| { @@ -93,6 +95,7 @@ impl Vm { circuit_statistic_from_cycles(tx_tracer.circuits_tracer.statistics), ); let result = tx_tracer.result_tracer.into_result(); + *dispatcher = tx_tracer.dispatcher; let result = VmExecutionResultAndLogs { result, diff --git a/core/lib/multivm/src/versions/vm_latest/tests/block_tip.rs b/core/lib/multivm/src/versions/vm_latest/tests/block_tip.rs index 02c73344a54..9909ca24937 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/block_tip.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/block_tip.rs @@ -196,7 +196,7 @@ fn execute_test(test_data: L1MessengerTestData) -> TestStatistics { ); let result = vm.vm.inspect_inner( - TracerDispatcher::default(), + &mut TracerDispatcher::default(), VmExecutionMode::Batch, Some(pubdata_tracer), ); diff --git a/core/lib/multivm/src/versions/vm_latest/tests/call_tracer.rs b/core/lib/multivm/src/versions/vm_latest/tests/call_tracer.rs index df7a7885542..e7f26b7faf8 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/call_tracer.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/call_tracer.rs @@ -45,7 +45,9 @@ fn test_max_depth() { let result = Arc::new(OnceCell::new()); let call_tracer = CallTracer::new(result.clone()).into_tracer_pointer(); vm.vm.push_transaction(tx); - let res = vm.vm.inspect(call_tracer.into(), VmExecutionMode::OneTx); + let res = vm + .vm + .inspect(&mut call_tracer.into(), VmExecutionMode::OneTx); assert!(result.get().is_some()); assert!(res.result.is_failed()); } @@ -80,7 +82,9 @@ fn test_basic_behavior() { let result = Arc::new(OnceCell::new()); let call_tracer = CallTracer::new(result.clone()).into_tracer_pointer(); vm.vm.push_transaction(tx); - let res = vm.vm.inspect(call_tracer.into(), VmExecutionMode::OneTx); + let res = vm + .vm + .inspect(&mut call_tracer.into(), VmExecutionMode::OneTx); let call_tracer_result = result.get().unwrap(); diff --git a/core/lib/multivm/src/versions/vm_latest/tests/circuits.rs b/core/lib/multivm/src/versions/vm_latest/tests/circuits.rs index 35412ee4d1b..c3c6816cbd8 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/circuits.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/circuits.rs @@ -30,7 +30,9 @@ fn test_circuits() { None, ); vm.vm.push_transaction(tx); - let res = vm.vm.inspect(Default::default(), VmExecutionMode::OneTx); + let res = vm + .vm + .inspect(&mut Default::default(), VmExecutionMode::OneTx); let s = res.statistics.circuit_statistic; // Check `circuit_statistic`. diff --git a/core/lib/multivm/src/versions/vm_latest/tests/precompiles.rs b/core/lib/multivm/src/versions/vm_latest/tests/precompiles.rs index 9388d016184..110b14146c7 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/precompiles.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/precompiles.rs @@ -39,7 +39,9 @@ fn test_keccak() { None, ); vm.vm.push_transaction(tx); - let _ = vm.vm.inspect(Default::default(), VmExecutionMode::OneTx); + let _ = vm + .vm + .inspect(&mut Default::default(), VmExecutionMode::OneTx); let keccak_count = vm .vm @@ -83,7 +85,9 @@ fn test_sha256() { None, ); vm.vm.push_transaction(tx); - let _ = vm.vm.inspect(Default::default(), VmExecutionMode::OneTx); + let _ = vm + .vm + .inspect(&mut Default::default(), VmExecutionMode::OneTx); let sha_count = vm .vm @@ -120,7 +124,9 @@ fn test_ecrecover() { None, ); vm.vm.push_transaction(tx); - let _ = vm.vm.inspect(Default::default(), VmExecutionMode::OneTx); + let _ = vm + .vm + .inspect(&mut Default::default(), VmExecutionMode::OneTx); let ecrecover_count = vm .vm diff --git a/core/lib/multivm/src/versions/vm_latest/tests/prestate_tracer.rs b/core/lib/multivm/src/versions/vm_latest/tests/prestate_tracer.rs index 8bf5e991988..230b1d0ad87 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/prestate_tracer.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/prestate_tracer.rs @@ -40,7 +40,8 @@ fn test_prestate_tracer() { let prestate_tracer_result = Arc::new(OnceCell::default()); let prestate_tracer = PrestateTracer::new(false, prestate_tracer_result.clone()); let tracer_ptr = prestate_tracer.into_tracer_pointer(); - vm.vm.inspect(tracer_ptr.into(), VmExecutionMode::Batch); + vm.vm + .inspect(&mut tracer_ptr.into(), VmExecutionMode::Batch); let prestate_result = Arc::try_unwrap(prestate_tracer_result) .unwrap() @@ -110,7 +111,7 @@ fn test_prestate_tracer_diff_mode() { let prestate_tracer = PrestateTracer::new(true, prestate_tracer_result.clone()); let tracer_ptr = prestate_tracer.into_tracer_pointer(); vm.vm - .inspect(tracer_ptr.into(), VmExecutionMode::Bootloader); + .inspect(&mut tracer_ptr.into(), VmExecutionMode::Bootloader); let prestate_result = Arc::try_unwrap(prestate_tracer_result) .unwrap() diff --git a/core/lib/multivm/src/versions/vm_latest/tests/rollbacks.rs b/core/lib/multivm/src/versions/vm_latest/tests/rollbacks.rs index 52e4d24bc0b..880f189fd89 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/rollbacks.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/rollbacks.rs @@ -224,14 +224,11 @@ fn test_layered_rollback() { vm.vm.make_snapshot(); vm.vm.push_transaction(loadnext_transaction.clone()); - vm.vm.inspect( - MaxRecursionTracer { - max_recursion_depth: 15, - } - .into_tracer_pointer() - .into(), - VmExecutionMode::OneTx, - ); + let tracer = MaxRecursionTracer { + max_recursion_depth: 15, + } + .into_tracer_pointer(); + vm.vm.inspect(&mut tracer.into(), VmExecutionMode::OneTx); let nonce_val2 = vm .vm diff --git a/core/lib/multivm/src/versions/vm_latest/vm.rs b/core/lib/multivm/src/versions/vm_latest/vm.rs index 71f7a635212..506b6666ecd 100644 --- a/core/lib/multivm/src/versions/vm_latest/vm.rs +++ b/core/lib/multivm/src/versions/vm_latest/vm.rs @@ -126,7 +126,7 @@ impl VmInterface for Vm { /// Execute VM with custom tracers. fn inspect( &mut self, - tracer: Self::TracerDispatcher, + tracer: &mut Self::TracerDispatcher, execution_mode: VmExecutionMode, ) -> VmExecutionResultAndLogs { self.inspect_inner(tracer, execution_mode, None) @@ -139,7 +139,7 @@ impl VmInterface for Vm { /// Inspect transaction with optional bytecode compression. fn inspect_transaction_with_bytecode_compression( &mut self, - tracer: Self::TracerDispatcher, + tracer: &mut Self::TracerDispatcher, tx: Transaction, with_compression: bool, ) -> (BytecodeCompressionResult<'_>, VmExecutionResultAndLogs) { @@ -166,7 +166,7 @@ impl VmInterface for Vm { } fn finish_batch(&mut self) -> FinishedL1Batch { - let result = self.inspect(TracerDispatcher::default(), VmExecutionMode::Batch); + let result = self.inspect(&mut TracerDispatcher::default(), VmExecutionMode::Batch); let execution_state = self.get_current_execution_state(); let bootloader_memory = self.bootloader_state.bootloader_memory(); FinishedL1Batch { diff --git a/core/lib/multivm/src/versions/vm_m5/vm.rs b/core/lib/multivm/src/versions/vm_m5/vm.rs index df4baccaf15..40f66659f29 100644 --- a/core/lib/multivm/src/versions/vm_m5/vm.rs +++ b/core/lib/multivm/src/versions/vm_m5/vm.rs @@ -66,7 +66,7 @@ impl VmInterface for Vm { fn inspect( &mut self, - _tracer: Self::TracerDispatcher, + _tracer: &mut Self::TracerDispatcher, execution_mode: VmExecutionMode, ) -> VmExecutionResultAndLogs { match execution_mode { @@ -90,7 +90,7 @@ impl VmInterface for Vm { fn inspect_transaction_with_bytecode_compression( &mut self, - _tracer: Self::TracerDispatcher, + _tracer: &mut Self::TracerDispatcher, tx: Transaction, _with_compression: bool, ) -> (BytecodeCompressionResult<'_>, VmExecutionResultAndLogs) { @@ -100,7 +100,10 @@ impl VmInterface for Vm { self.system_env.execution_mode.glue_into(), ); // Bytecode compression isn't supported - (Ok(vec![].into()), self.inspect((), VmExecutionMode::OneTx)) + ( + Ok(vec![].into()), + self.inspect(&mut (), VmExecutionMode::OneTx), + ) } fn record_vm_memory_metrics(&self) -> VmMemoryMetrics { diff --git a/core/lib/multivm/src/versions/vm_m6/vm.rs b/core/lib/multivm/src/versions/vm_m6/vm.rs index 7e19076a520..627687a5524 100644 --- a/core/lib/multivm/src/versions/vm_m6/vm.rs +++ b/core/lib/multivm/src/versions/vm_m6/vm.rs @@ -66,7 +66,7 @@ impl VmInterface for Vm { fn inspect( &mut self, - tracer: Self::TracerDispatcher, + tracer: &mut Self::TracerDispatcher, execution_mode: VmExecutionMode, ) -> VmExecutionResultAndLogs { if let Some(storage_invocations) = tracer.storage_invocations { @@ -106,7 +106,7 @@ impl VmInterface for Vm { fn inspect_transaction_with_bytecode_compression( &mut self, - tracer: Self::TracerDispatcher, + tracer: &mut Self::TracerDispatcher, tx: Transaction, with_compression: bool, ) -> (BytecodeCompressionResult<'_>, VmExecutionResultAndLogs) { diff --git a/core/lib/multivm/src/versions/vm_refunds_enhancement/implementation/execution.rs b/core/lib/multivm/src/versions/vm_refunds_enhancement/implementation/execution.rs index cadd183735e..8196760a621 100644 --- a/core/lib/multivm/src/versions/vm_refunds_enhancement/implementation/execution.rs +++ b/core/lib/multivm/src/versions/vm_refunds_enhancement/implementation/execution.rs @@ -1,3 +1,5 @@ +use std::mem; + use zk_evm_1_3_3::aux_structures::Timestamp; use crate::{ @@ -19,7 +21,7 @@ use crate::{ impl Vm { pub(crate) fn inspect_inner( &mut self, - dispatcher: TracerDispatcher, + dispatcher: &mut TracerDispatcher, execution_mode: VmExecutionMode, ) -> VmExecutionResultAndLogs { let mut enable_refund_tracer = false; @@ -37,7 +39,7 @@ impl Vm { /// Collect the result from the default tracers. fn inspect_and_collect_results( &mut self, - dispatcher: TracerDispatcher, + dispatcher: &mut TracerDispatcher, execution_mode: VmExecutionMode, with_refund_tracer: bool, ) -> (VmExecutionStopReason, VmExecutionResultAndLogs) { @@ -47,7 +49,7 @@ impl Vm { DefaultExecutionTracer::new( self.system_env.default_validation_computational_gas_limit, execution_mode, - dispatcher, + mem::take(dispatcher), self.storage.clone(), refund_tracers, ); @@ -81,6 +83,7 @@ impl Vm { ); let result = tx_tracer.result_tracer.into_result(); + *dispatcher = tx_tracer.dispatcher; let result = VmExecutionResultAndLogs { result, diff --git a/core/lib/multivm/src/versions/vm_refunds_enhancement/vm.rs b/core/lib/multivm/src/versions/vm_refunds_enhancement/vm.rs index 119abf052b9..735bd29c3b0 100644 --- a/core/lib/multivm/src/versions/vm_refunds_enhancement/vm.rs +++ b/core/lib/multivm/src/versions/vm_refunds_enhancement/vm.rs @@ -83,7 +83,7 @@ impl VmInterface for Vm { /// Execute VM with custom tracers. fn inspect( &mut self, - dispatcher: Self::TracerDispatcher, + dispatcher: &mut Self::TracerDispatcher, execution_mode: VmExecutionMode, ) -> VmExecutionResultAndLogs { self.inspect_inner(dispatcher, execution_mode) @@ -96,7 +96,7 @@ impl VmInterface for Vm { /// Inspect transaction with optional bytecode compression. fn inspect_transaction_with_bytecode_compression( &mut self, - dispatcher: Self::TracerDispatcher, + dispatcher: &mut Self::TracerDispatcher, tx: Transaction, with_compression: bool, ) -> (BytecodeCompressionResult<'_>, VmExecutionResultAndLogs) { @@ -123,7 +123,7 @@ impl VmInterface for Vm { } fn finish_batch(&mut self) -> FinishedL1Batch { - let result = self.inspect(TracerDispatcher::default(), VmExecutionMode::Batch); + let result = self.inspect(&mut TracerDispatcher::default(), VmExecutionMode::Batch); let execution_state = self.get_current_execution_state(); let bootloader_memory = self.bootloader_state.bootloader_memory(); FinishedL1Batch { diff --git a/core/lib/multivm/src/versions/vm_virtual_blocks/implementation/execution.rs b/core/lib/multivm/src/versions/vm_virtual_blocks/implementation/execution.rs index 42709c345ea..c48d48edd3b 100644 --- a/core/lib/multivm/src/versions/vm_virtual_blocks/implementation/execution.rs +++ b/core/lib/multivm/src/versions/vm_virtual_blocks/implementation/execution.rs @@ -1,3 +1,5 @@ +use std::mem; + use zk_evm_1_3_3::aux_structures::Timestamp; use crate::{ @@ -21,7 +23,7 @@ use crate::{ impl Vm { pub(crate) fn inspect_inner( &mut self, - tracer: TracerDispatcher, + tracer: &mut TracerDispatcher, execution_mode: VmExecutionMode, ) -> VmExecutionResultAndLogs { let mut enable_refund_tracer = false; @@ -40,7 +42,7 @@ impl Vm { /// Collect the result from the default tracers. fn inspect_and_collect_results( &mut self, - dispatcher: TracerDispatcher, + dispatcher: &mut TracerDispatcher, execution_mode: VmExecutionMode, enable_refund_tracer: bool, ) -> (VmExecutionStopReason, VmExecutionResultAndLogs) { @@ -50,7 +52,7 @@ impl Vm { DefaultExecutionTracer::new( self.system_env.default_validation_computational_gas_limit, execution_mode, - dispatcher, + mem::take(dispatcher), refund_tracer, self.storage.clone(), ); @@ -89,6 +91,7 @@ impl Vm { }; tx_tracer.dispatcher.save_results(&mut result); + *dispatcher = tx_tracer.dispatcher; (stop_reason, result) } diff --git a/core/lib/multivm/src/versions/vm_virtual_blocks/vm.rs b/core/lib/multivm/src/versions/vm_virtual_blocks/vm.rs index 0ecdd6797f4..2a9d6eed6c7 100644 --- a/core/lib/multivm/src/versions/vm_virtual_blocks/vm.rs +++ b/core/lib/multivm/src/versions/vm_virtual_blocks/vm.rs @@ -83,7 +83,7 @@ impl VmInterface for Vm { /// Execute VM with custom tracers. fn inspect( &mut self, - tracer: TracerDispatcher, + tracer: &mut TracerDispatcher, execution_mode: VmExecutionMode, ) -> VmExecutionResultAndLogs { self.inspect_inner(tracer, execution_mode) @@ -96,7 +96,7 @@ impl VmInterface for Vm { /// Inspect transaction with optional bytecode compression. fn inspect_transaction_with_bytecode_compression( &mut self, - tracer: TracerDispatcher, + tracer: &mut TracerDispatcher, tx: Transaction, with_compression: bool, ) -> (BytecodeCompressionResult<'_>, VmExecutionResultAndLogs) { @@ -123,7 +123,7 @@ impl VmInterface for Vm { } fn finish_batch(&mut self) -> FinishedL1Batch { - let result = self.inspect(TracerDispatcher::default(), VmExecutionMode::Batch); + let result = self.inspect(&mut TracerDispatcher::default(), VmExecutionMode::Batch); let execution_state = self.get_current_execution_state(); let bootloader_memory = self.bootloader_state.bootloader_memory(); FinishedL1Batch { diff --git a/core/lib/multivm/src/vm_instance.rs b/core/lib/multivm/src/vm_instance.rs index 5e254b09b30..8dd67e1ac4e 100644 --- a/core/lib/multivm/src/vm_instance.rs +++ b/core/lib/multivm/src/vm_instance.rs @@ -1,4 +1,7 @@ -use zksync_types::vm::{FastVmMode, VmVersion}; +use std::mem; + +use zksync_types::{vm::VmVersion, Transaction}; +use zksync_vm2::interface::Tracer; use crate::{ glue::history_mode::HistoryMode, @@ -10,16 +13,18 @@ use crate::{ VmInterfaceHistoryEnabled, VmMemoryMetrics, }, tracers::TracerDispatcher, + vm_latest::HistoryEnabled, }; -pub(crate) type ShadowedVmFast = ShadowVm< - S, - crate::vm_latest::Vm, H>, - crate::vm_fast::Vm>, ->; - +/// Enumeration encompassing all supported legacy VM versions. +/// +/// # Important +/// +/// Methods with a tracer arg take the provided tracer, replacing it with the default value. Legacy tracers +/// are adapted for this workflow (previously, tracers were passed by value), so they provide means to extract state after execution +/// if necessary (e.g., using `Arc>`). #[derive(Debug)] -pub enum VmInstance { +pub enum LegacyVmInstance { VmM5(crate::vm_m5::Vm, H>), VmM6(crate::vm_m6::Vm, H>), Vm1_3_2(crate::vm_1_3_2::Vm, H>), @@ -29,74 +34,70 @@ pub enum VmInstance { Vm1_4_1(crate::vm_1_4_1::Vm, H>), Vm1_4_2(crate::vm_1_4_2::Vm, H>), Vm1_5_0(crate::vm_latest::Vm, H>), - VmFast(crate::vm_fast::Vm>), - ShadowedVmFast(ShadowedVmFast), } -macro_rules! dispatch_vm { +macro_rules! dispatch_legacy_vm { ($self:ident.$function:ident($($params:tt)*)) => { match $self { - VmInstance::VmM5(vm) => vm.$function($($params)*), - VmInstance::VmM6(vm) => vm.$function($($params)*), - VmInstance::Vm1_3_2(vm) => vm.$function($($params)*), - VmInstance::VmVirtualBlocks(vm) => vm.$function($($params)*), - VmInstance::VmVirtualBlocksRefundsEnhancement(vm) => vm.$function($($params)*), - VmInstance::VmBoojumIntegration(vm) => vm.$function($($params)*), - VmInstance::Vm1_4_1(vm) => vm.$function($($params)*), - VmInstance::Vm1_4_2(vm) => vm.$function($($params)*), - VmInstance::Vm1_5_0(vm) => vm.$function($($params)*), - VmInstance::VmFast(vm) => vm.$function($($params)*), - VmInstance::ShadowedVmFast(vm) => vm.$function($($params)*), + Self::VmM5(vm) => vm.$function($($params)*), + Self::VmM6(vm) => vm.$function($($params)*), + Self::Vm1_3_2(vm) => vm.$function($($params)*), + Self::VmVirtualBlocks(vm) => vm.$function($($params)*), + Self::VmVirtualBlocksRefundsEnhancement(vm) => vm.$function($($params)*), + Self::VmBoojumIntegration(vm) => vm.$function($($params)*), + Self::Vm1_4_1(vm) => vm.$function($($params)*), + Self::Vm1_4_2(vm) => vm.$function($($params)*), + Self::Vm1_5_0(vm) => vm.$function($($params)*), } }; } -impl VmInterface for VmInstance { +impl VmInterface for LegacyVmInstance { type TracerDispatcher = TracerDispatcher, H>; /// Push tx into memory for the future execution - fn push_transaction(&mut self, tx: zksync_types::Transaction) { - dispatch_vm!(self.push_transaction(tx)) + fn push_transaction(&mut self, tx: Transaction) { + dispatch_legacy_vm!(self.push_transaction(tx)) } /// Execute next transaction with custom tracers fn inspect( &mut self, - dispatcher: Self::TracerDispatcher, + dispatcher: &mut Self::TracerDispatcher, execution_mode: VmExecutionMode, ) -> VmExecutionResultAndLogs { - dispatch_vm!(self.inspect(dispatcher.into(), execution_mode)) + dispatch_legacy_vm!(self.inspect(&mut mem::take(dispatcher).into(), execution_mode)) } fn start_new_l2_block(&mut self, l2_block_env: L2BlockEnv) { - dispatch_vm!(self.start_new_l2_block(l2_block_env)) + dispatch_legacy_vm!(self.start_new_l2_block(l2_block_env)) } /// Inspect transaction with optional bytecode compression. fn inspect_transaction_with_bytecode_compression( &mut self, - dispatcher: Self::TracerDispatcher, - tx: zksync_types::Transaction, + dispatcher: &mut Self::TracerDispatcher, + tx: Transaction, with_compression: bool, ) -> (BytecodeCompressionResult<'_>, VmExecutionResultAndLogs) { - dispatch_vm!(self.inspect_transaction_with_bytecode_compression( - dispatcher.into(), + dispatch_legacy_vm!(self.inspect_transaction_with_bytecode_compression( + &mut mem::take(dispatcher).into(), tx, with_compression )) } fn record_vm_memory_metrics(&self) -> VmMemoryMetrics { - dispatch_vm!(self.record_vm_memory_metrics()) + dispatch_legacy_vm!(self.record_vm_memory_metrics()) } /// Return the results of execution of all batch fn finish_batch(&mut self) -> FinishedL1Batch { - dispatch_vm!(self.finish_batch()) + dispatch_legacy_vm!(self.finish_batch()) } } -impl VmFactory> for VmInstance { +impl VmFactory> for LegacyVmInstance { fn new( batch_env: L1BatchEnv, system_env: SystemEnv, @@ -108,21 +109,21 @@ impl VmFactory> for VmInstance VmInterfaceHistoryEnabled for VmInstance { +impl VmInterfaceHistoryEnabled for LegacyVmInstance { fn make_snapshot(&mut self) { - dispatch_vm!(self.make_snapshot()) + dispatch_legacy_vm!(self.make_snapshot()); } fn rollback_to_the_latest_snapshot(&mut self) { - dispatch_vm!(self.rollback_to_the_latest_snapshot()) + dispatch_legacy_vm!(self.rollback_to_the_latest_snapshot()); } fn pop_snapshot_no_rollback(&mut self) { - dispatch_vm!(self.pop_snapshot_no_rollback()) + dispatch_legacy_vm!(self.pop_snapshot_no_rollback()); } } -impl VmInstance { +impl LegacyVmInstance { pub fn new_with_specific_version( l1_batch_env: L1BatchEnv, system_env: SystemEnv, @@ -137,7 +138,7 @@ impl VmInstance { storage_view, crate::vm_m5::vm_instance::MultiVMSubversion::V1, ); - VmInstance::VmM5(vm) + Self::VmM5(vm) } VmVersion::M5WithRefunds => { let vm = crate::vm_m5::Vm::new_with_subversion( @@ -146,7 +147,7 @@ impl VmInstance { storage_view, crate::vm_m5::vm_instance::MultiVMSubversion::V2, ); - VmInstance::VmM5(vm) + Self::VmM5(vm) } VmVersion::M6Initial => { let vm = crate::vm_m6::Vm::new_with_subversion( @@ -155,7 +156,7 @@ impl VmInstance { storage_view, crate::vm_m6::vm_instance::MultiVMSubversion::V1, ); - VmInstance::VmM6(vm) + Self::VmM6(vm) } VmVersion::M6BugWithCompressionFixed => { let vm = crate::vm_m6::Vm::new_with_subversion( @@ -164,33 +165,33 @@ impl VmInstance { storage_view, crate::vm_m6::vm_instance::MultiVMSubversion::V2, ); - VmInstance::VmM6(vm) + Self::VmM6(vm) } VmVersion::Vm1_3_2 => { let vm = crate::vm_1_3_2::Vm::new(l1_batch_env, system_env, storage_view); - VmInstance::Vm1_3_2(vm) + Self::Vm1_3_2(vm) } VmVersion::VmVirtualBlocks => { let vm = crate::vm_virtual_blocks::Vm::new(l1_batch_env, system_env, storage_view); - VmInstance::VmVirtualBlocks(vm) + Self::VmVirtualBlocks(vm) } VmVersion::VmVirtualBlocksRefundsEnhancement => { let vm = crate::vm_refunds_enhancement::Vm::new(l1_batch_env, system_env, storage_view); - VmInstance::VmVirtualBlocksRefundsEnhancement(vm) + Self::VmVirtualBlocksRefundsEnhancement(vm) } VmVersion::VmBoojumIntegration => { let vm = crate::vm_boojum_integration::Vm::new(l1_batch_env, system_env, storage_view); - VmInstance::VmBoojumIntegration(vm) + Self::VmBoojumIntegration(vm) } VmVersion::Vm1_4_1 => { let vm = crate::vm_1_4_1::Vm::new(l1_batch_env, system_env, storage_view); - VmInstance::Vm1_4_1(vm) + Self::Vm1_4_1(vm) } VmVersion::Vm1_4_2 => { let vm = crate::vm_1_4_2::Vm::new(l1_batch_env, system_env, storage_view); - VmInstance::Vm1_4_2(vm) + Self::Vm1_4_2(vm) } VmVersion::Vm1_5_0SmallBootloaderMemory => { let vm = crate::vm_latest::Vm::new_with_subversion( @@ -199,7 +200,7 @@ impl VmInstance { storage_view, crate::vm_latest::MultiVMSubversion::SmallBootloaderMemory, ); - VmInstance::Vm1_5_0(vm) + Self::Vm1_5_0(vm) } VmVersion::Vm1_5_0IncreasedBootloaderMemory => { let vm = crate::vm_latest::Vm::new_with_subversion( @@ -208,36 +209,125 @@ impl VmInstance { storage_view, crate::vm_latest::MultiVMSubversion::IncreasedBootloaderMemory, ); - VmInstance::Vm1_5_0(vm) + Self::Vm1_5_0(vm) + } + } + } +} + +/// Fast VM shadowed by the latest legacy VM. +pub type ShadowedFastVm = ShadowVm< + S, + crate::vm_latest::Vm, HistoryEnabled>, + crate::vm_fast::Vm, Tr>, +>; + +/// Fast VM variants. +#[derive(Debug)] +pub enum FastVmInstance { + /// Fast VM running in isolation. + Fast(crate::vm_fast::Vm, Tr>), + /// Fast VM shadowed by the latest legacy VM. + Shadowed(ShadowedFastVm), +} + +macro_rules! dispatch_fast_vm { + ($self:ident.$function:ident($($params:tt)*)) => { + match $self { + Self::Fast(vm) => vm.$function($($params)*), + Self::Shadowed(vm) => vm.$function($($params)*), + } + }; +} + +impl VmInterface for FastVmInstance { + type TracerDispatcher = ( + crate::vm_latest::TracerDispatcher, HistoryEnabled>, + Tr, + ); + + fn push_transaction(&mut self, tx: Transaction) { + dispatch_fast_vm!(self.push_transaction(tx)); + } + + fn inspect( + &mut self, + tracer: &mut Self::TracerDispatcher, + execution_mode: VmExecutionMode, + ) -> VmExecutionResultAndLogs { + match self { + Self::Fast(vm) => vm.inspect(&mut tracer.1, execution_mode), + Self::Shadowed(vm) => vm.inspect(tracer, execution_mode), + } + } + + fn start_new_l2_block(&mut self, l2_block_env: L2BlockEnv) { + dispatch_fast_vm!(self.start_new_l2_block(l2_block_env)); + } + + fn inspect_transaction_with_bytecode_compression( + &mut self, + tracer: &mut Self::TracerDispatcher, + tx: Transaction, + with_compression: bool, + ) -> (BytecodeCompressionResult<'_>, VmExecutionResultAndLogs) { + match self { + Self::Fast(vm) => vm.inspect_transaction_with_bytecode_compression( + &mut tracer.1, + tx, + with_compression, + ), + Self::Shadowed(vm) => { + vm.inspect_transaction_with_bytecode_compression(tracer, tx, with_compression) } } } - /// Creates a VM that may use the fast VM depending on the protocol version in `system_env` and `mode`. - pub fn maybe_fast( + fn record_vm_memory_metrics(&self) -> VmMemoryMetrics { + dispatch_fast_vm!(self.record_vm_memory_metrics()) + } + + fn finish_batch(&mut self) -> FinishedL1Batch { + dispatch_fast_vm!(self.finish_batch()) + } +} + +impl VmInterfaceHistoryEnabled + for FastVmInstance +{ + fn make_snapshot(&mut self) { + dispatch_fast_vm!(self.make_snapshot()); + } + + fn rollback_to_the_latest_snapshot(&mut self) { + dispatch_fast_vm!(self.rollback_to_the_latest_snapshot()); + } + + fn pop_snapshot_no_rollback(&mut self) { + dispatch_fast_vm!(self.pop_snapshot_no_rollback()); + } +} + +impl FastVmInstance { + /// Creates an isolated fast VM. + pub fn fast( l1_batch_env: L1BatchEnv, system_env: SystemEnv, storage_view: StoragePtr>, - mode: FastVmMode, ) -> Self { - let vm_version = system_env.version.into(); - match vm_version { - VmVersion::Vm1_5_0IncreasedBootloaderMemory => match mode { - FastVmMode::Old => Self::new(l1_batch_env, system_env, storage_view), - FastVmMode::New => { - let storage = ImmutableStorageView::new(storage_view); - Self::VmFast(crate::vm_fast::Vm::custom( - l1_batch_env, - system_env, - storage, - )) - } - FastVmMode::Shadow => { - let vm = ShadowVm::new(l1_batch_env, system_env, storage_view); - Self::ShadowedVmFast(vm) - } - }, - _ => Self::new(l1_batch_env, system_env, storage_view), - } + Self::Fast(crate::vm_fast::Vm::new( + l1_batch_env, + system_env, + storage_view, + )) + } + + /// Creates a shadowed fast VM. + pub fn shadowed( + l1_batch_env: L1BatchEnv, + system_env: SystemEnv, + storage_view: StoragePtr>, + ) -> Self { + Self::Shadowed(ShadowedFastVm::new(l1_batch_env, system_env, storage_view)) } } diff --git a/core/lib/tee_verifier/src/lib.rs b/core/lib/tee_verifier/src/lib.rs index 8728a4e5274..68b25416d66 100644 --- a/core/lib/tee_verifier/src/lib.rs +++ b/core/lib/tee_verifier/src/lib.rs @@ -14,10 +14,11 @@ use zksync_merkle_tree::{ use zksync_multivm::{ interface::{ storage::{InMemoryStorage, ReadStorage, StorageView}, - FinishedL1Batch, L2BlockEnv, VmFactory, VmInterface, VmInterfaceHistoryEnabled, + FinishedL1Batch, L2BlockEnv, VmFactory, VmInterface, VmInterfaceExt, + VmInterfaceHistoryEnabled, }, vm_latest::HistoryEnabled, - VmInstance, + LegacyVmInstance, }; use zksync_prover_interface::inputs::{ StorageLogMetadata, V1TeeVerifierInput, WitnessInputMerklePaths, @@ -69,7 +70,7 @@ impl Verify for V1TeeVerifierInput { let storage_view = Rc::new(RefCell::new(StorageView::new(&raw_storage))); let batch_number = self.l1_batch_env.number; - let vm = VmInstance::new(self.l1_batch_env, self.system_env, storage_view); + let vm = LegacyVmInstance::new(self.l1_batch_env, self.system_env, storage_view); let vm_out = execute_vm(self.l2_blocks_execution_data, vm)?; @@ -157,7 +158,7 @@ fn get_bowp_and_set_initial_values( /// Executes the VM and returns `FinishedL1Batch` on success. fn execute_vm( l2_blocks_execution_data: Vec, - mut vm: VmInstance, + mut vm: LegacyVmInstance, ) -> anyhow::Result { let next_l2_blocks_data = l2_blocks_execution_data.iter().skip(1); @@ -239,12 +240,12 @@ fn generate_tree_instructions( fn execute_tx( tx: &Transaction, - vm: &mut VmInstance, + vm: &mut LegacyVmInstance, ) -> anyhow::Result<()> { // Attempt to run VM with bytecode compression on. vm.make_snapshot(); if vm - .inspect_transaction_with_bytecode_compression(Default::default(), tx.clone(), true) + .execute_transaction_with_bytecode_compression(tx.clone(), true) .0 .is_ok() { @@ -255,7 +256,7 @@ fn execute_tx( // If failed with bytecode compression, attempt to run without bytecode compression. vm.rollback_to_the_latest_snapshot(); if vm - .inspect_transaction_with_bytecode_compression(Default::default(), tx.clone(), false) + .execute_transaction_with_bytecode_compression(tx.clone(), false) .0 .is_err() { diff --git a/core/lib/vm_executor/src/batch/factory.rs b/core/lib/vm_executor/src/batch/factory.rs index 62bab29fea8..146f0bb4e5c 100644 --- a/core/lib/vm_executor/src/batch/factory.rs +++ b/core/lib/vm_executor/src/batch/factory.rs @@ -1,4 +1,4 @@ -use std::{marker::PhantomData, rc::Rc, sync::Arc, time::Duration}; +use std::{borrow::Cow, fmt, marker::PhantomData, rc::Rc, sync::Arc, time::Duration}; use anyhow::Context as _; use once_cell::sync::OnceCell; @@ -6,14 +6,16 @@ use tokio::sync::mpsc; use zksync_multivm::{ interface::{ executor::{BatchExecutor, BatchExecutorFactory}, - storage::{ReadStorage, StorageView, StorageViewStats}, + storage::{ReadStorage, StoragePtr, StorageView, StorageViewStats}, utils::DivergenceHandler, - BatchTransactionExecutionResult, ExecutionResult, FinishedL1Batch, Halt, L1BatchEnv, - L2BlockEnv, SystemEnv, VmInterface, VmInterfaceHistoryEnabled, + BatchTransactionExecutionResult, BytecodeCompressionError, CompressedBytecodeInfo, + ExecutionResult, FinishedL1Batch, Halt, L1BatchEnv, L2BlockEnv, SystemEnv, VmFactory, + VmInterface, VmInterfaceHistoryEnabled, }, tracers::CallTracer, + vm_fast, vm_latest::HistoryEnabled, - MultiVMTracer, VmInstance, + FastVmInstance, LegacyVmInstance, MultiVMTracer, }; use zksync_types::{vm::FastVmMode, Transaction}; @@ -21,13 +23,43 @@ use super::{ executor::{Command, MainBatchExecutor}, metrics::{TxExecutionStage, BATCH_TIP_METRICS, EXECUTOR_METRICS, KEEPER_METRICS}, }; -use crate::shared::{InteractionType, STORAGE_METRICS}; +use crate::shared::{InteractionType, Sealed, STORAGE_METRICS}; + +/// Encapsulates a tracer used during batch processing. Currently supported tracers are `()` (no-op) and [`TraceCalls`]. +/// +/// All members of this trait are implementation details. +pub trait BatchTracer: fmt::Debug + 'static + Send + Sealed { + /// True if call tracing is enabled. Used by legacy VMs which enable / disable call tracing dynamically. + #[doc(hidden)] + const TRACE_CALLS: bool; + /// Tracer for the fast VM. + #[doc(hidden)] + type Fast: vm_fast::Tracer + Default + 'static; +} + +impl Sealed for () {} + +/// No-op implementation that doesn't trace anything. +impl BatchTracer for () { + const TRACE_CALLS: bool = false; + type Fast = (); +} + +/// [`BatchTracer`] implementation tracing calls (returned in [`BatchTransactionExecutionResult`]s). +#[derive(Debug)] +pub struct TraceCalls(()); + +impl Sealed for TraceCalls {} + +impl BatchTracer for TraceCalls { + const TRACE_CALLS: bool = true; + type Fast = (); // TODO: change once call tracing is implemented in fast VM +} /// The default implementation of [`BatchExecutorFactory`]. /// Creates real batch executors which maintain the VM (as opposed to the test factories which don't use the VM). #[derive(Debug, Clone)] -pub struct MainBatchExecutorFactory { - save_call_traces: bool, +pub struct MainBatchExecutorFactory { /// Whether batch executor would allow transactions with bytecode that cannot be compressed. /// For new blocks, bytecode compression is mandatory -- if bytecode compression is not supported, /// the transaction will be rejected. @@ -38,16 +70,17 @@ pub struct MainBatchExecutorFactory { fast_vm_mode: FastVmMode, observe_storage_metrics: bool, divergence_handler: Option, + _tracer: PhantomData, } -impl MainBatchExecutorFactory { - pub fn new(save_call_traces: bool, optional_bytecode_compression: bool) -> Self { +impl MainBatchExecutorFactory { + pub fn new(optional_bytecode_compression: bool) -> Self { Self { - save_call_traces, optional_bytecode_compression, fast_vm_mode: FastVmMode::Old, observe_storage_metrics: false, divergence_handler: None, + _tracer: PhantomData, } } @@ -74,7 +107,9 @@ impl MainBatchExecutorFactory { } } -impl BatchExecutorFactory for MainBatchExecutorFactory { +impl BatchExecutorFactory + for MainBatchExecutorFactory +{ fn init_batch( &mut self, storage: S, @@ -85,13 +120,13 @@ impl BatchExecutorFactory for MainBatchExecu // until a previous command is processed), capacity 1 is enough for the commands channel. let (commands_sender, commands_receiver) = mpsc::channel(1); let executor = CommandReceiver { - save_call_traces: self.save_call_traces, optional_bytecode_compression: self.optional_bytecode_compression, fast_vm_mode: self.fast_vm_mode, observe_storage_metrics: self.observe_storage_metrics, divergence_handler: self.divergence_handler.clone(), commands: commands_receiver, _storage: PhantomData, + _tracer: PhantomData::, }; let handle = @@ -100,6 +135,103 @@ impl BatchExecutorFactory for MainBatchExecu } } +type BytecodeResult = Result, BytecodeCompressionError>; + +#[derive(Debug)] +enum BatchVm { + Legacy(LegacyVmInstance), + Fast(FastVmInstance), +} + +macro_rules! dispatch_batch_vm { + ($self:ident.$function:ident($($params:tt)*)) => { + match $self { + Self::Legacy(vm) => vm.$function($($params)*), + Self::Fast(vm) => vm.$function($($params)*), + } + }; +} + +impl BatchVm { + fn new( + l1_batch_env: L1BatchEnv, + system_env: SystemEnv, + storage_ptr: StoragePtr>, + mode: FastVmMode, + ) -> Self { + match mode { + FastVmMode::Old => { + Self::Legacy(LegacyVmInstance::new(l1_batch_env, system_env, storage_ptr)) + } + FastVmMode::New => { + Self::Fast(FastVmInstance::fast(l1_batch_env, system_env, storage_ptr)) + } + FastVmMode::Shadow => Self::Fast(FastVmInstance::shadowed( + l1_batch_env, + system_env, + storage_ptr, + )), + } + } + + fn start_new_l2_block(&mut self, l2_block: L2BlockEnv) { + dispatch_batch_vm!(self.start_new_l2_block(l2_block)); + } + + fn finish_batch(&mut self) -> FinishedL1Batch { + dispatch_batch_vm!(self.finish_batch()) + } + + fn make_snapshot(&mut self) { + dispatch_batch_vm!(self.make_snapshot()); + } + + fn rollback_to_the_latest_snapshot(&mut self) { + dispatch_batch_vm!(self.rollback_to_the_latest_snapshot()); + } + + fn pop_snapshot_no_rollback(&mut self) { + dispatch_batch_vm!(self.pop_snapshot_no_rollback()); + } + + fn inspect_transaction( + &mut self, + tx: Transaction, + with_compression: bool, + ) -> BatchTransactionExecutionResult { + let call_tracer_result = Arc::new(OnceCell::default()); + let legacy_tracer = if Tr::TRACE_CALLS { + vec![CallTracer::new(call_tracer_result.clone()).into_tracer_pointer()] + } else { + vec![] + }; + let mut legacy_tracer = legacy_tracer.into(); + + let (compression_result, tx_result) = match self { + Self::Legacy(vm) => vm.inspect_transaction_with_bytecode_compression( + &mut legacy_tracer, + tx, + with_compression, + ), + Self::Fast(vm) => { + let mut tracer = (legacy_tracer.into(), ::default()); + vm.inspect_transaction_with_bytecode_compression(&mut tracer, tx, with_compression) + } + }; + + let compressed_bytecodes = compression_result.map(Cow::into_owned); + let call_traces = Arc::try_unwrap(call_tracer_result) + .expect("failed extracting call traces") + .take() + .unwrap_or_default(); + BatchTransactionExecutionResult { + tx_result: Box::new(tx_result), + compressed_bytecodes, + call_traces, + } + } +} + /// Implementation of the "primary" (non-test) batch executor. /// Upon launch, it initializes the VM object with provided block context and properties, and keeps invoking the commands /// sent to it one by one until the batch is finished. @@ -107,17 +239,17 @@ impl BatchExecutorFactory for MainBatchExecu /// One `CommandReceiver` can execute exactly one batch, so once the batch is sealed, a new `CommandReceiver` object must /// be constructed. #[derive(Debug)] -struct CommandReceiver { - save_call_traces: bool, +struct CommandReceiver { optional_bytecode_compression: bool, fast_vm_mode: FastVmMode, observe_storage_metrics: bool, divergence_handler: Option, commands: mpsc::Receiver, _storage: PhantomData, + _tracer: PhantomData, } -impl CommandReceiver { +impl CommandReceiver { pub(super) fn run( mut self, storage: S, @@ -127,7 +259,7 @@ impl CommandReceiver { tracing::info!("Starting executing L1 batch #{}", &l1_batch_params.number); let storage_view = StorageView::new(storage).to_rc_ptr(); - let mut vm = VmInstance::maybe_fast( + let mut vm = BatchVm::::new( l1_batch_params, system_env, storage_view.clone(), @@ -136,9 +268,9 @@ impl CommandReceiver { let mut batch_finished = false; let mut prev_storage_stats = StorageViewStats::default(); - if let VmInstance::ShadowedVmFast(vm) = &mut vm { + if let BatchVm::Fast(FastVmInstance::Shadowed(shadowed)) = &mut vm { if let Some(handler) = self.divergence_handler.take() { - vm.set_divergence_handler(handler); + shadowed.set_divergence_handler(handler); } } @@ -167,7 +299,7 @@ impl CommandReceiver { } } Command::StartNextL2Block(l2_block_env, resp) => { - self.start_next_l2_block(l2_block_env, &mut vm); + vm.start_new_l2_block(l2_block_env); if resp.send(()).is_err() { break; } @@ -203,7 +335,7 @@ impl CommandReceiver { fn execute_tx( &self, transaction: Transaction, - vm: &mut VmInstance, + vm: &mut BatchVm, ) -> anyhow::Result<(BatchTransactionExecutionResult, Duration)> { // Executing a next transaction means that a previous transaction was either rolled back (in which case its snapshot // was already removed), or that we build on top of it (in which case, it can be removed now). @@ -222,24 +354,13 @@ impl CommandReceiver { Ok((result, latency.observe())) } - fn rollback_last_tx(&self, vm: &mut VmInstance) { + fn rollback_last_tx(&self, vm: &mut BatchVm) { let latency = KEEPER_METRICS.tx_execution_time[&TxExecutionStage::TxRollback].start(); vm.rollback_to_the_latest_snapshot(); latency.observe(); } - fn start_next_l2_block( - &self, - l2_block_env: L2BlockEnv, - vm: &mut VmInstance, - ) { - vm.start_new_l2_block(l2_block_env); - } - - fn finish_batch( - &self, - vm: &mut VmInstance, - ) -> anyhow::Result { + fn finish_batch(&self, vm: &mut BatchVm) -> anyhow::Result { // The vm execution was paused right after the last transaction was executed. // There is some post-processing work that the VM needs to do before the block is fully processed. let result = vm.finish_batch(); @@ -258,7 +379,7 @@ impl CommandReceiver { fn execute_tx_in_vm_with_optional_compression( &self, tx: &Transaction, - vm: &mut VmInstance, + vm: &mut BatchVm, ) -> anyhow::Result { // Note, that the space where we can put the calldata for compressing transactions // is limited and the transactions do not pay for taking it. @@ -269,24 +390,12 @@ impl CommandReceiver { // it means that there is no sense in polluting the space of compressed bytecodes, // and so we re-execute the transaction, but without compression. - let call_tracer_result = Arc::new(OnceCell::default()); - let tracer = if self.save_call_traces { - vec![CallTracer::new(call_tracer_result.clone()).into_tracer_pointer()] - } else { - vec![] - }; - - if let (Ok(compressed_bytecodes), tx_result) = - vm.inspect_transaction_with_bytecode_compression(tracer.into(), tx.clone(), true) - { - let call_traces = Arc::try_unwrap(call_tracer_result) - .map_err(|_| anyhow::anyhow!("failed extracting call traces"))? - .take() - .unwrap_or_default(); + let res = vm.inspect_transaction(tx.clone(), true); + if let Ok(compressed_bytecodes) = res.compressed_bytecodes { return Ok(BatchTransactionExecutionResult { - tx_result: Box::new(tx_result), - compressed_bytecodes: compressed_bytecodes.into_owned(), - call_traces, + tx_result: res.tx_result, + compressed_bytecodes, + call_traces: res.call_traces, }); } @@ -295,29 +404,14 @@ impl CommandReceiver { vm.rollback_to_the_latest_snapshot(); vm.make_snapshot(); - let call_tracer_result = Arc::new(OnceCell::default()); - let tracer = if self.save_call_traces { - vec![CallTracer::new(call_tracer_result.clone()).into_tracer_pointer()] - } else { - vec![] - }; - - let (compression_result, tx_result) = - vm.inspect_transaction_with_bytecode_compression(tracer.into(), tx.clone(), false); - let compressed_bytecodes = compression_result - .context("compression failed when it wasn't applied")? - .into_owned(); - - // TODO implement tracer manager which will be responsible - // for collecting result from all tracers and save it to the database - let call_traces = Arc::try_unwrap(call_tracer_result) - .map_err(|_| anyhow::anyhow!("failed extracting call traces"))? - .take() - .unwrap_or_default(); + let res = vm.inspect_transaction(tx.clone(), false); + let compressed_bytecodes = res + .compressed_bytecodes + .context("compression failed when it wasn't applied")?; Ok(BatchTransactionExecutionResult { - tx_result: Box::new(tx_result), + tx_result: res.tx_result, compressed_bytecodes, - call_traces, + call_traces: res.call_traces, }) } @@ -326,34 +420,23 @@ impl CommandReceiver { fn execute_tx_in_vm( &self, tx: &Transaction, - vm: &mut VmInstance, + vm: &mut BatchVm, ) -> anyhow::Result { - let call_tracer_result = Arc::new(OnceCell::default()); - let tracer = if self.save_call_traces { - vec![CallTracer::new(call_tracer_result.clone()).into_tracer_pointer()] - } else { - vec![] - }; - - let (bytecodes_result, mut tx_result) = - vm.inspect_transaction_with_bytecode_compression(tracer.into(), tx.clone(), true); - if let Ok(compressed_bytecodes) = bytecodes_result { - let call_traces = Arc::try_unwrap(call_tracer_result) - .map_err(|_| anyhow::anyhow!("failed extracting call traces"))? - .take() - .unwrap_or_default(); + let res = vm.inspect_transaction(tx.clone(), true); + if let Ok(compressed_bytecodes) = res.compressed_bytecodes { Ok(BatchTransactionExecutionResult { - tx_result: Box::new(tx_result), - compressed_bytecodes: compressed_bytecodes.into_owned(), - call_traces, + tx_result: res.tx_result, + compressed_bytecodes, + call_traces: res.call_traces, }) } else { // Transaction failed to publish bytecodes, we reject it so initiator doesn't pay fee. + let mut tx_result = res.tx_result; tx_result.result = ExecutionResult::Halt { reason: Halt::FailedToPublishCompressedBytecodes, }; Ok(BatchTransactionExecutionResult { - tx_result: Box::new(tx_result), + tx_result, compressed_bytecodes: vec![], call_traces: vec![], }) diff --git a/core/lib/vm_executor/src/batch/mod.rs b/core/lib/vm_executor/src/batch/mod.rs index 2407d2daba2..82882e24c59 100644 --- a/core/lib/vm_executor/src/batch/mod.rs +++ b/core/lib/vm_executor/src/batch/mod.rs @@ -2,7 +2,10 @@ //! //! This implementation is used by various ZKsync components, like the state keeper and components based on the VM runner. -pub use self::{executor::MainBatchExecutor, factory::MainBatchExecutorFactory}; +pub use self::{ + executor::MainBatchExecutor, + factory::{BatchTracer, MainBatchExecutorFactory, TraceCalls}, +}; mod executor; mod factory; diff --git a/core/lib/vm_executor/src/oneshot/mod.rs b/core/lib/vm_executor/src/oneshot/mod.rs index 11a7252dc7f..cb75f396b5d 100644 --- a/core/lib/vm_executor/src/oneshot/mod.rs +++ b/core/lib/vm_executor/src/oneshot/mod.rs @@ -26,7 +26,7 @@ use zksync_multivm::{ utils::adjust_pubdata_price_for_tx, vm_latest::HistoryDisabled, zk_evm_latest::ethereum_types::U256, - MultiVMTracer, VmInstance, + LegacyVmInstance, MultiVMTracer, }; use zksync_types::{ block::pack_block_info, @@ -111,7 +111,7 @@ where let mut result = executor.apply(|vm, transaction| { let (compression_result, tx_result) = vm .inspect_transaction_with_bytecode_compression( - tracers.into(), + &mut tracers.into(), transaction, true, ); @@ -165,7 +165,7 @@ where ); let exec_result = executor.apply(|vm, transaction| { vm.push_transaction(transaction); - vm.inspect(tracers.into(), VmExecutionMode::OneTx) + vm.inspect(&mut tracers.into(), VmExecutionMode::OneTx) }); let validation_result = Arc::make_mut(&mut validation_result) .take() @@ -184,7 +184,7 @@ where #[derive(Debug)] struct VmSandbox { - vm: Box>, + vm: Box>, storage_view: StoragePtr>, transaction: Transaction, execution_latency_histogram: Option<&'static vise::Histogram>, @@ -212,7 +212,7 @@ impl VmSandbox { }; let storage_view = storage_view.to_rc_ptr(); - let vm = Box::new(VmInstance::new_with_specific_version( + let vm = Box::new(LegacyVmInstance::new_with_specific_version( env.l1_batch, env.system, storage_view.clone(), @@ -277,7 +277,7 @@ impl VmSandbox { pub(super) fn apply(mut self, apply_fn: F) -> T where - F: FnOnce(&mut VmInstance, Transaction) -> T, + F: FnOnce(&mut LegacyVmInstance, Transaction) -> T, { let tx_id = format!( "{:?}-{}", diff --git a/core/lib/vm_executor/src/shared.rs b/core/lib/vm_executor/src/shared.rs index 8ac4dce2e01..382fd50e23a 100644 --- a/core/lib/vm_executor/src/shared.rs +++ b/core/lib/vm_executor/src/shared.rs @@ -5,6 +5,9 @@ use std::time::Duration; use vise::{Buckets, EncodeLabelSet, EncodeLabelValue, Family, Histogram, Metrics}; use zksync_multivm::interface::storage::StorageViewStats; +/// Marker for sealed traits. Intentionally not exported from the crate. +pub trait Sealed {} + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EncodeLabelValue, EncodeLabelSet)] #[metrics(label = "interaction", rename_all = "snake_case")] pub(crate) enum InteractionType { diff --git a/core/lib/vm_interface/src/types/outputs/execution_result.rs b/core/lib/vm_interface/src/types/outputs/execution_result.rs index 6f9c02f0b58..3e53aad85f1 100644 --- a/core/lib/vm_interface/src/types/outputs/execution_result.rs +++ b/core/lib/vm_interface/src/types/outputs/execution_result.rs @@ -300,16 +300,16 @@ impl Call { /// Mid-level transaction execution output returned by a [batch executor](crate::executor::BatchExecutor). #[derive(Debug, Clone)] -pub struct BatchTransactionExecutionResult { +pub struct BatchTransactionExecutionResult> { /// VM result. pub tx_result: Box, /// Compressed bytecodes used by the transaction. - pub compressed_bytecodes: Vec, + pub compressed_bytecodes: C, /// Call traces (if requested; otherwise, empty). pub call_traces: Vec, } -impl BatchTransactionExecutionResult { +impl BatchTransactionExecutionResult { pub fn was_halted(&self) -> bool { matches!(self.tx_result.result, ExecutionResult::Halt { .. }) } diff --git a/core/lib/vm_interface/src/utils/dump.rs b/core/lib/vm_interface/src/utils/dump.rs index f7dce38ee89..5dc2351dcf7 100644 --- a/core/lib/vm_interface/src/utils/dump.rs +++ b/core/lib/vm_interface/src/utils/dump.rs @@ -62,7 +62,10 @@ impl VmDump { } /// Plays back this dump on the specified VM. - pub fn play_back>>(self) -> Vm { + pub fn play_back(self) -> Vm + where + Vm: VmFactory>, + { self.play_back_custom(Vm::new) } @@ -146,7 +149,7 @@ impl VmInterface for DumpingVm { fn inspect( &mut self, - dispatcher: Self::TracerDispatcher, + dispatcher: &mut Self::TracerDispatcher, execution_mode: VmExecutionMode, ) -> VmExecutionResultAndLogs { self.inner.inspect(dispatcher, execution_mode) @@ -165,7 +168,7 @@ impl VmInterface for DumpingVm { fn inspect_transaction_with_bytecode_compression( &mut self, - tracer: Self::TracerDispatcher, + tracer: &mut Self::TracerDispatcher, tx: Transaction, with_compression: bool, ) -> (BytecodeCompressionResult, VmExecutionResultAndLogs) { diff --git a/core/lib/vm_interface/src/utils/shadow.rs b/core/lib/vm_interface/src/utils/shadow.rs index 7dfe31f6b68..2819e54e9a7 100644 --- a/core/lib/vm_interface/src/utils/shadow.rs +++ b/core/lib/vm_interface/src/utils/shadow.rs @@ -158,7 +158,10 @@ where Main: VmTrackingContracts, Shadow: VmInterface, { - type TracerDispatcher =
::TracerDispatcher; + type TracerDispatcher = ( +
::TracerDispatcher, + ::TracerDispatcher, + ); fn push_transaction(&mut self, tx: Transaction) { if let Some(shadow) = self.shadow.get_mut() { @@ -169,14 +172,12 @@ where fn inspect( &mut self, - dispatcher: Self::TracerDispatcher, + (main_tracer, shadow_tracer): &mut Self::TracerDispatcher, execution_mode: VmExecutionMode, ) -> VmExecutionResultAndLogs { - let main_result = self.main.inspect(dispatcher, execution_mode); + let main_result = self.main.inspect(main_tracer, execution_mode); if let Some(shadow) = self.shadow.get_mut() { - let shadow_result = shadow - .vm - .inspect(Shadow::TracerDispatcher::default(), execution_mode); + let shadow_result = shadow.vm.inspect(shadow_tracer, execution_mode); let mut errors = DivergenceErrors::new(); errors.check_results_match(&main_result, &shadow_result); @@ -197,14 +198,17 @@ where fn inspect_transaction_with_bytecode_compression( &mut self, - tracer: Self::TracerDispatcher, + (main_tracer, shadow_tracer): &mut Self::TracerDispatcher, tx: Transaction, with_compression: bool, ) -> (BytecodeCompressionResult<'_>, VmExecutionResultAndLogs) { let tx_hash = tx.hash(); - let (main_bytecodes_result, main_tx_result) = self - .main - .inspect_transaction_with_bytecode_compression(tracer, tx.clone(), with_compression); + let (main_bytecodes_result, main_tx_result) = + self.main.inspect_transaction_with_bytecode_compression( + main_tracer, + tx.clone(), + with_compression, + ); // Extend lifetime to `'static` so that the result isn't mutably borrowed from the main VM. // Unfortunately, there's no way to express that this borrow is actually immutable, which would allow not extending the lifetime unless there's a divergence. let main_bytecodes_result = @@ -212,7 +216,7 @@ where if let Some(shadow) = self.shadow.get_mut() { let shadow_result = shadow.vm.inspect_transaction_with_bytecode_compression( - Shadow::TracerDispatcher::default(), + shadow_tracer, tx, with_compression, ); diff --git a/core/lib/vm_interface/src/vm.rs b/core/lib/vm_interface/src/vm.rs index a380f0659e6..90ae76be805 100644 --- a/core/lib/vm_interface/src/vm.rs +++ b/core/lib/vm_interface/src/vm.rs @@ -19,26 +19,27 @@ use crate::{ }; pub trait VmInterface { + /// Lifetime is used to be able to define `Option<&mut _>` as a dispatcher. type TracerDispatcher: Default; /// Push transaction to bootloader memory. fn push_transaction(&mut self, tx: Transaction); - /// Execute next VM step (either next transaction or bootloader or the whole batch) + /// Executes the next VM step (either next transaction or bootloader or the whole batch) /// with custom tracers. fn inspect( &mut self, - dispatcher: Self::TracerDispatcher, + dispatcher: &mut Self::TracerDispatcher, execution_mode: VmExecutionMode, ) -> VmExecutionResultAndLogs; /// Start a new L2 block. fn start_new_l2_block(&mut self, l2_block_env: L2BlockEnv); - /// Execute transaction with optional bytecode compression using custom tracers. + /// Executes the provided transaction with optional bytecode compression using custom tracers. fn inspect_transaction_with_bytecode_compression( &mut self, - tracer: Self::TracerDispatcher, + tracer: &mut Self::TracerDispatcher, tx: Transaction, with_compression: bool, ) -> (BytecodeCompressionResult<'_>, VmExecutionResultAndLogs); @@ -55,7 +56,7 @@ pub trait VmInterface { pub trait VmInterfaceExt: VmInterface { /// Executes the next VM step (either next transaction or bootloader or the whole batch). fn execute(&mut self, execution_mode: VmExecutionMode) -> VmExecutionResultAndLogs { - self.inspect(Self::TracerDispatcher::default(), execution_mode) + self.inspect(&mut ::default(), execution_mode) } /// Executes a transaction with optional bytecode compression. @@ -65,7 +66,7 @@ pub trait VmInterfaceExt: VmInterface { with_compression: bool, ) -> (BytecodeCompressionResult<'_>, VmExecutionResultAndLogs) { self.inspect_transaction_with_bytecode_compression( - Self::TracerDispatcher::default(), + &mut ::default(), tx, with_compression, ) diff --git a/core/node/consensus/src/testonly.rs b/core/node/consensus/src/testonly.rs index bcd22186a4b..1a486b3fc64 100644 --- a/core/node/consensus/src/testonly.rs +++ b/core/node/consensus/src/testonly.rs @@ -621,7 +621,7 @@ impl StateKeeperRunner { }); s.spawn_bg({ - let executor_factory = MainBatchExecutorFactory::new(false, false); + let executor_factory = MainBatchExecutorFactory::<()>::new(false); let stop_recv = stop_recv.clone(); async { ZkSyncStateKeeper::new( diff --git a/core/node/node_framework/src/implementations/layers/state_keeper/main_batch_executor.rs b/core/node/node_framework/src/implementations/layers/state_keeper/main_batch_executor.rs index f369db2bbf0..920b4fab1d0 100644 --- a/core/node/node_framework/src/implementations/layers/state_keeper/main_batch_executor.rs +++ b/core/node/node_framework/src/implementations/layers/state_keeper/main_batch_executor.rs @@ -1,5 +1,5 @@ use zksync_types::vm::FastVmMode; -use zksync_vm_executor::batch::MainBatchExecutorFactory; +use zksync_vm_executor::batch::{BatchTracer, MainBatchExecutorFactory, TraceCalls}; use crate::{ implementations::resources::state_keeper::BatchExecutorResource, @@ -27,6 +27,12 @@ impl MainBatchExecutorLayer { self.fast_vm_mode = mode; self } + + fn create_executor(&self) -> BatchExecutorResource { + let mut executor = MainBatchExecutorFactory::::new(self.optional_bytecode_compression); + executor.set_fast_vm_mode(self.fast_vm_mode); + executor.into() + } } #[async_trait::async_trait] @@ -39,11 +45,10 @@ impl WiringLayer for MainBatchExecutorLayer { } async fn wire(self, (): Self::Input) -> Result { - let mut executor = MainBatchExecutorFactory::new( - self.save_call_traces, - self.optional_bytecode_compression, - ); - executor.set_fast_vm_mode(self.fast_vm_mode); - Ok(executor.into()) + Ok(if self.save_call_traces { + self.create_executor::() + } else { + self.create_executor::<()>() + }) } } diff --git a/core/node/node_framework/src/implementations/layers/vm_runner/bwip.rs b/core/node/node_framework/src/implementations/layers/vm_runner/bwip.rs index 858692d3c85..35c26d909c9 100644 --- a/core/node/node_framework/src/implementations/layers/vm_runner/bwip.rs +++ b/core/node/node_framework/src/implementations/layers/vm_runner/bwip.rs @@ -76,7 +76,7 @@ impl WiringLayer for BasicWitnessInputProducerLayer { let connection_pool = master_pool.get_custom(self.config.window_size + 2).await?; // We don't get the executor from the context because it would contain state keeper-specific settings. - let batch_executor = MainBatchExecutorFactory::new(false, false); + let batch_executor = MainBatchExecutorFactory::<()>::new(false); let (basic_witness_input_producer, tasks) = BasicWitnessInputProducer::new( connection_pool, diff --git a/core/node/state_keeper/Cargo.toml b/core/node/state_keeper/Cargo.toml index 1810cc00de5..0e924b9f066 100644 --- a/core/node/state_keeper/Cargo.toml +++ b/core/node/state_keeper/Cargo.toml @@ -44,6 +44,7 @@ hex.workspace = true [dev-dependencies] assert_matches.workspace = true +rand.workspace = true tempfile.workspace = true test-casing.workspace = true futures.workspace = true diff --git a/core/node/state_keeper/src/executor/tests/mod.rs b/core/node/state_keeper/src/executor/tests/mod.rs index 6fa4522d43f..04fb016ab63 100644 --- a/core/node/state_keeper/src/executor/tests/mod.rs +++ b/core/node/state_keeper/src/executor/tests/mod.rs @@ -1,8 +1,8 @@ // FIXME: move storage-agnostic tests to VM executor crate use assert_matches::assert_matches; +use rand::{thread_rng, Rng}; use test_casing::{test_casing, Product}; -use tester::AccountFailedCall; use zksync_dal::{ConnectionPool, Core}; use zksync_multivm::interface::{BatchTransactionExecutionResult, ExecutionResult, Halt}; use zksync_test_account::Account; @@ -10,7 +10,9 @@ use zksync_types::{ get_nonce_key, utils::storage_key_for_eth_balance, vm::FastVmMode, PriorityOpId, }; -use self::tester::{AccountLoadNextExecutable, StorageSnapshot, TestConfig, Tester}; +use self::tester::{ + AccountFailedCall, AccountLoadNextExecutable, StorageSnapshot, TestConfig, Tester, +}; mod read_storage_factory; mod tester; @@ -425,7 +427,7 @@ async fn bootloader_out_of_gas_for_any_tx(vm_mode: FastVmMode) { let mut tester = Tester::with_config( connection_pool, TestConfig { - save_call_traces: false, + trace_calls: false, vm_gas_limit: Some(10), validation_computational_gas_limit: u32::MAX, fast_vm_mode: vm_mode, @@ -470,7 +472,7 @@ async fn bootloader_tip_out_of_gas() { // Just a bit below the gas used for the previous batch execution should be fine to execute the tx // but not enough to execute the block tip. tester.set_config(TestConfig { - save_call_traces: false, + trace_calls: false, vm_gas_limit: Some( finished_batch .block_tip_execution_result @@ -538,3 +540,60 @@ async fn catchup_rocksdb_cache() { let res = executor.execute_tx(tx).await.unwrap(); assert_rejected(&res); } + +#[test_casing(3, FAST_VM_MODES)] +#[tokio::test] +async fn execute_tx_with_large_packable_bytecode(vm_mode: FastVmMode) { + // The rough length of the packed bytecode should be 350_000 / 4 = 87500, + // which should fit into a batch + const BYTECODE_LEN: usize = 350_016 + 32; // +32 to ensure validity of the bytecode + + let connection_pool = ConnectionPool::::constrained_test_pool(1).await; + let mut alice = Account::random(); + let mut tester = Tester::new(connection_pool, vm_mode); + let mut rng = thread_rng(); + + tester.genesis().await; + tester.fund(&[alice.address()]).await; + let mut executor = tester + .create_batch_executor(StorageType::AsyncRocksdbCache) + .await; + + let mut packable_bytecode = vec![]; + while packable_bytecode.len() < BYTECODE_LEN { + packable_bytecode.extend_from_slice(&if rng.gen() { [0_u8; 8] } else { [0xff_u8; 8] }); + } + let tx = alice.execute_with_factory_deps(vec![packable_bytecode.clone()]); + + let res = executor.execute_tx(tx).await.unwrap(); + assert_matches!(res.tx_result.result, ExecutionResult::Success { .. }); + assert_eq!(res.compressed_bytecodes.len(), 1); + assert_eq!(res.compressed_bytecodes[0].original, packable_bytecode); + assert!(res.compressed_bytecodes[0].compressed.len() < BYTECODE_LEN / 2); + + executor.finish_batch().await.unwrap(); +} + +#[test_casing(2, [FastVmMode::Old, FastVmMode::Shadow])] // new VM doesn't support call tracing yet +#[tokio::test] +async fn execute_tx_with_call_traces(vm_mode: FastVmMode) { + let connection_pool = ConnectionPool::::constrained_test_pool(1).await; + let mut alice = Account::random(); + let mut tester = Tester::with_config( + connection_pool, + TestConfig { + trace_calls: true, + ..TestConfig::new(vm_mode) + }, + ); + + tester.genesis().await; + tester.fund(&[alice.address()]).await; + let mut executor = tester + .create_batch_executor(StorageType::AsyncRocksdbCache) + .await; + let res = executor.execute_tx(alice.execute()).await.unwrap(); + + assert_matches!(res.tx_result.result, ExecutionResult::Success { .. }); + assert!(!res.call_traces.is_empty()); +} diff --git a/core/node/state_keeper/src/executor/tests/tester.rs b/core/node/state_keeper/src/executor/tests/tester.rs index d524d1a20dd..7a1871dbfea 100644 --- a/core/node/state_keeper/src/executor/tests/tester.rs +++ b/core/node/state_keeper/src/executor/tests/tester.rs @@ -35,7 +35,7 @@ use zksync_types::{ StorageLog, Transaction, H256, L2_BASE_TOKEN_ADDRESS, U256, }; use zksync_utils::u256_to_h256; -use zksync_vm_executor::batch::MainBatchExecutorFactory; +use zksync_vm_executor::batch::{MainBatchExecutorFactory, TraceCalls}; use super::{read_storage_factory::RocksdbStorageFactory, StorageType}; use crate::{ @@ -49,7 +49,7 @@ use crate::{ /// Has sensible defaults for most tests, each of which can be overridden. #[derive(Debug)] pub(super) struct TestConfig { - pub(super) save_call_traces: bool, + pub(super) trace_calls: bool, pub(super) vm_gas_limit: Option, pub(super) validation_computational_gas_limit: u32, pub(super) fast_vm_mode: FastVmMode, @@ -60,8 +60,8 @@ impl TestConfig { let config = StateKeeperConfig::for_tests(); Self { + trace_calls: false, vm_gas_limit: None, - save_call_traces: false, validation_computational_gas_limit: config.validation_computational_gas_limit, fast_vm_mode, } @@ -149,16 +149,21 @@ impl Tester { l1_batch_env: L1BatchEnv, system_env: SystemEnv, ) -> Box> { - let mut batch_executor = MainBatchExecutorFactory::new(self.config.save_call_traces, false); - batch_executor.set_fast_vm_mode(self.config.fast_vm_mode); - let (_stop_sender, stop_receiver) = watch::channel(false); let storage = storage_factory .access_storage(&stop_receiver, l1_batch_env.number - 1) .await .expect("failed creating VM storage") .unwrap(); - batch_executor.init_batch(storage, l1_batch_env, system_env) + if self.config.trace_calls { + let mut executor = MainBatchExecutorFactory::::new(false); + executor.set_fast_vm_mode(self.config.fast_vm_mode); + executor.init_batch(storage, l1_batch_env, system_env) + } else { + let mut executor = MainBatchExecutorFactory::<()>::new(false); + executor.set_fast_vm_mode(self.config.fast_vm_mode); + executor.init_batch(storage, l1_batch_env, system_env) + } } pub(super) async fn recover_batch_executor( @@ -319,6 +324,9 @@ pub trait AccountLoadNextExecutable { /// Returns a valid `execute` transaction. /// Automatically increments nonce of the account. fn execute(&mut self) -> Transaction; + /// Returns an `execute` transaction with custom factory deps (which aren't used in a transaction, + /// so they are mostly useful to test bytecode compression). + fn execute_with_factory_deps(&mut self, factory_deps: Vec>) -> Transaction; fn loadnext_custom_writes_call( &mut self, address: Address, @@ -377,6 +385,18 @@ impl AccountLoadNextExecutable for Account { self.execute_with_gas_limit(1_000_000) } + fn execute_with_factory_deps(&mut self, factory_deps: Vec>) -> Transaction { + self.get_l2_tx_for_execute( + Execute { + contract_address: Some(Address::random()), + calldata: vec![], + value: Default::default(), + factory_deps, + }, + Some(testonly::fee(30_000_000)), + ) + } + /// Returns a transaction to the loadnext contract with custom amount of write requests. /// Increments the account nonce. fn loadnext_custom_writes_call( diff --git a/core/node/vm_runner/src/impls/playground.rs b/core/node/vm_runner/src/impls/playground.rs index 4bab43d1d0f..618a3a86c82 100644 --- a/core/node/vm_runner/src/impls/playground.rs +++ b/core/node/vm_runner/src/impls/playground.rs @@ -87,7 +87,7 @@ enum VmPlaygroundStorage { #[derive(Debug)] pub struct VmPlayground { pool: ConnectionPool, - batch_executor_factory: MainBatchExecutorFactory, + batch_executor_factory: MainBatchExecutorFactory<()>, storage: VmPlaygroundStorage, chain_id: L2ChainId, io: VmPlaygroundIo, @@ -133,7 +133,7 @@ impl VmPlayground { latest_processed_batch.unwrap_or(cursor.first_processed_batch) }; - let mut batch_executor_factory = MainBatchExecutorFactory::new(false, false); + let mut batch_executor_factory = MainBatchExecutorFactory::new(false); batch_executor_factory.set_fast_vm_mode(vm_mode); batch_executor_factory.observe_storage_metrics(); let handle = tokio::runtime::Handle::current(); diff --git a/core/node/vm_runner/src/impls/protective_reads.rs b/core/node/vm_runner/src/impls/protective_reads.rs index b1aff9fe382..7f968ee24bb 100644 --- a/core/node/vm_runner/src/impls/protective_reads.rs +++ b/core/node/vm_runner/src/impls/protective_reads.rs @@ -38,7 +38,7 @@ impl ProtectiveReadsWriter { let output_handler_factory = ProtectiveReadsOutputHandlerFactory { pool: pool.clone() }; let (output_handler_factory, output_handler_factory_task) = ConcurrentOutputHandlerFactory::new(pool.clone(), io.clone(), output_handler_factory); - let batch_processor = MainBatchExecutorFactory::new(false, false); + let batch_processor = MainBatchExecutorFactory::<()>::new(false); let vm_runner = VmRunner::new( pool, Arc::new(io), diff --git a/core/node/vm_runner/src/tests/process.rs b/core/node/vm_runner/src/tests/process.rs index 115410ce8fb..8e9bd66f3c9 100644 --- a/core/node/vm_runner/src/tests/process.rs +++ b/core/node/vm_runner/src/tests/process.rs @@ -54,7 +54,7 @@ async fn process_batches((batch_count, window): (u32, u32)) -> anyhow::Result<() tokio::task::spawn(async move { task.run(output_stop_receiver).await.unwrap() }); let storage = Arc::new(storage); - let batch_executor = MainBatchExecutorFactory::new(false, false); + let batch_executor = MainBatchExecutorFactory::<()>::new(false); let vm_runner = VmRunner::new( connection_pool, io.clone(), diff --git a/core/node/vm_runner/src/tests/storage_writer.rs b/core/node/vm_runner/src/tests/storage_writer.rs index c377cf95b5a..28a244308fe 100644 --- a/core/node/vm_runner/src/tests/storage_writer.rs +++ b/core/node/vm_runner/src/tests/storage_writer.rs @@ -181,7 +181,7 @@ pub(super) async fn write_storage_logs(pool: ConnectionPool, insert_protec .await .unwrap(); let loader = Arc::new(loader); - let batch_executor = MainBatchExecutorFactory::new(false, false); + let batch_executor = MainBatchExecutorFactory::<()>::new(false); let vm_runner = VmRunner::new(pool, io.clone(), loader, io, Box::new(batch_executor)); let (stop_sender, stop_receiver) = watch::channel(false); let vm_runner_handle = tokio::spawn(async move { vm_runner.run(&stop_receiver).await }); @@ -239,7 +239,7 @@ async fn storage_writer_works(insert_protective_reads: bool) { let (output_factory, output_factory_task) = ConcurrentOutputHandlerFactory::new(pool.clone(), io.clone(), TestOutputFactory::default()); let output_factory_handle = tokio::spawn(output_factory_task.run(stop_receiver.clone())); - let batch_executor = MainBatchExecutorFactory::new(false, false); + let batch_executor = MainBatchExecutorFactory::<()>::new(false); let vm_runner = VmRunner::new( pool, io.clone(), diff --git a/core/tests/vm-benchmark/src/vm.rs b/core/tests/vm-benchmark/src/vm.rs index 55196413de8..f4a0010f29e 100644 --- a/core/tests/vm-benchmark/src/vm.rs +++ b/core/tests/vm-benchmark/src/vm.rs @@ -157,11 +157,9 @@ impl BenchmarkingVm { pub fn run_transaction_full(&mut self, tx: &Transaction) -> VmExecutionResultAndLogs { self.0.make_snapshot(); - let (compression_result, tx_result) = self.0.inspect_transaction_with_bytecode_compression( - Default::default(), - tx.clone(), - true, - ); + let (compression_result, tx_result) = self + .0 + .execute_transaction_with_bytecode_compression(tx.clone(), true); compression_result.expect("compressing bytecodes failed"); if matches!(tx_result.result, ExecutionResult::Halt { .. }) { @@ -175,7 +173,7 @@ impl BenchmarkingVm { pub fn instruction_count(&mut self, tx: &Transaction) -> usize { self.0.push_transaction(tx.clone()); let count = Rc::new(RefCell::new(0)); - self.0.inspect(Default::default(), VmExecutionMode::OneTx); // FIXME: re-enable instruction counting once new tracers are merged + self.0.execute(VmExecutionMode::OneTx); // FIXME: re-enable instruction counting once new tracers are merged count.take() } }