From 52143c0fcd653b01ce9ec67c1ce61430ccafffaa Mon Sep 17 00:00:00 2001 From: Marcos Nicolau Date: Wed, 28 Aug 2024 10:05:57 -0300 Subject: [PATCH 01/17] Add basic first parallel model --- core/lib/multivm/src/versions/era_vm/mod.rs | 1 + .../versions/era_vm/tracers/refunds_tracer.rs | 3 +- .../src/versions/era_vm/transaction.rs | 17 + core/lib/multivm/src/versions/era_vm/vm.rs | 292 ++++++++++++++---- 4 files changed, 251 insertions(+), 62 deletions(-) create mode 100644 core/lib/multivm/src/versions/era_vm/transaction.rs diff --git a/core/lib/multivm/src/versions/era_vm/mod.rs b/core/lib/multivm/src/versions/era_vm/mod.rs index b595aaa86d79..18257f13abd2 100644 --- a/core/lib/multivm/src/versions/era_vm/mod.rs +++ b/core/lib/multivm/src/versions/era_vm/mod.rs @@ -8,5 +8,6 @@ mod snapshot; #[cfg(test)] mod tests; pub mod tracers; +pub mod transaction; mod transaction_data; pub mod vm; diff --git a/core/lib/multivm/src/versions/era_vm/tracers/refunds_tracer.rs b/core/lib/multivm/src/versions/era_vm/tracers/refunds_tracer.rs index ee87e1ce7b90..409dca669fd0 100644 --- a/core/lib/multivm/src/versions/era_vm/tracers/refunds_tracer.rs +++ b/core/lib/multivm/src/versions/era_vm/tracers/refunds_tracer.rs @@ -3,12 +3,13 @@ use zksync_types::{H256, U256}; use zksync_utils::ceil_div_u256; use super::traits::{Tracer, VmTracer}; +pub use crate::vm_latest::Refunds; use crate::{ era_vm::hook::Hook, vm_latest::{ constants::{OPERATOR_REFUNDS_OFFSET, TX_GAS_LIMIT_OFFSET}, utils::fee::get_batch_base_fee, - L1BatchEnv, Refunds, + L1BatchEnv, }, }; diff --git a/core/lib/multivm/src/versions/era_vm/transaction.rs b/core/lib/multivm/src/versions/era_vm/transaction.rs new file mode 100644 index 000000000000..2e8ac24f3a27 --- /dev/null +++ b/core/lib/multivm/src/versions/era_vm/transaction.rs @@ -0,0 +1,17 @@ +use zksync_types::Transaction; + +pub struct ParallelTransaction { + pub tx: Transaction, + pub refund: u64, + pub with_compression: bool, +} + +impl ParallelTransaction { + pub fn new(tx: Transaction, refund: u64, with_compression: bool) -> Self { + Self { + tx, + refund, + with_compression, + } + } +} diff --git a/core/lib/multivm/src/versions/era_vm/vm.rs b/core/lib/multivm/src/versions/era_vm/vm.rs index 19f065088f27..3d04bf076372 100644 --- a/core/lib/multivm/src/versions/era_vm/vm.rs +++ b/core/lib/multivm/src/versions/era_vm/vm.rs @@ -4,8 +4,10 @@ use era_vm::{ rollbacks::Rollbackable, store::StorageKey as EraStorageKey, value::FatPointer, vm::ExecutionOutput, EraVM, Execution, }; +use itertools::Itertools; use zksync_state::{ReadStorage, StoragePtr}; use zksync_types::{ + circuit::CircuitStatistic, event::extract_l2tol1logs_from_l1_messenger, l1::is_l1_tx_type, l2_to_l1_log::UserL2ToL1Log, @@ -31,9 +33,13 @@ use super::{ logs::IntoSystemLog, snapshot::VmSnapshot, tracers::{ - dispatcher::TracerDispatcher, manager::VmTracerManager, pubdata_tracer::PubdataTracer, - refunds_tracer::RefundsTracer, traits::VmTracer, + dispatcher::TracerDispatcher, + manager::VmTracerManager, + pubdata_tracer::PubdataTracer, + refunds_tracer::{Refunds, RefundsTracer}, + traits::VmTracer, }, + transaction::ParallelTransaction, }; use crate::{ era_vm::{bytecode::compress_bytecodes, transaction_data::TransactionData}, @@ -41,6 +47,7 @@ use crate::{ tracer::{TracerExecutionStatus, TracerExecutionStopReason}, Halt, TxRevertReason, VmFactory, VmInterface, VmInterfaceHistoryEnabled, VmRevertReason, }, + vm_1_4_1::FinishedL1Batch, vm_latest::{ constants::{ get_result_success_first_slot, get_vm_hook_position, get_vm_hook_start_position_latest, @@ -68,6 +75,8 @@ pub struct Vm { pub(crate) system_env: SystemEnv, pub snapshot: Option, + + pub transaction_to_execute: Vec, } /// Encapsulates creating VM instance based on the provided environment. @@ -143,6 +152,7 @@ impl VmFactory for Vm { batch_env, system_env, snapshot: None, + transaction_to_execute: vec![], }; mv.write_to_bootloader_heap(bootloader_memory); @@ -331,6 +341,16 @@ impl Vm { } } + pub fn push_parallel_transaction( + &mut self, + tx: Transaction, + refund: u64, + with_compression: bool, + ) { + self.transaction_to_execute + .push(ParallelTransaction::new(tx, refund, with_compression)); + } + pub fn push_transaction_inner(&mut self, tx: Transaction, refund: u64, with_compression: bool) { let tx: TransactionData = tx.into(); let overhead = tx.overhead_gas(); @@ -402,75 +422,201 @@ impl Vm { let logs = if ignore_world_diff { VmExecutionLogs::default() } else { - let events = merge_events( - self.inner.state.get_events_after_snapshot(snapshot.events), - self.batch_env.number, - ); - let user_l2_to_l1_logs = extract_l2tol1logs_from_l1_messenger(&events) - .into_iter() - .map(Into::into) - .map(UserL2ToL1Log) - .collect(); - let system_l2_to_l1_logs = self - .inner - .state - .get_l2_to_l1_logs_after_snapshot(snapshot.l2_to_l1_logs) - .iter() - .map(|log| log.into_system_log()) - .collect(); - let storage_logs: Vec = self - .inner - .state - .get_storage_changes_from_snapshot(snapshot.storage_changes) - .iter() - .map(|(storage_key, previos_value, value, is_initial)| { - let key = StorageKey::new( - AccountTreeId::new(storage_key.address), - u256_to_h256(storage_key.key), - ); - - StorageLogWithPreviousValue { - log: StorageLog { - key, - value: u256_to_h256(*value), - kind: if *is_initial { - StorageLogKind::InitialWrite - } else { - StorageLogKind::RepeatedWrite - }, - }, - previous_value: u256_to_h256(previos_value.unwrap_or_default()), - } - }) - .collect(); - - VmExecutionLogs { - storage_logs, - events, - user_l2_to_l1_logs, - system_l2_to_l1_logs, - total_log_queries_count: 0, // This field is unused - } + self.get_execution_logs(snapshot) }; VmExecutionResultAndLogs { result, logs, - statistics: VmExecutionStatistics { - contracts_used: self.inner.state.decommitted_hashes().len(), - cycles_used: self.inner.statistics.monotonic_counter - monotonic_counter_before, - gas_used: (ergs_before - ergs_after) as u64, - gas_remaining: ergs_after, - computational_gas_used: ergs_before - ergs_after, - total_log_queries: 0, - pubdata_published: tracer.pubdata_tracer.pubdata_published, - circuit_statistic: tracer + statistics: self.get_execution_statistics( + monotonic_counter_before, + tracer.pubdata_tracer.pubdata_published, + ergs_before, + ergs_after, + tracer + .circuits_tracer + .circuit_statistics(&self.inner.statistics), + ), + refunds: tracer.refund_tracer.unwrap_or_default().into(), + } + } + + fn get_execution_logs(&self, snapshot: era_vm::state::StateSnapshot) -> VmExecutionLogs { + dbg!(&snapshot); + let events = merge_events( + self.inner.state.get_events_after_snapshot(snapshot.events), + self.batch_env.number, + ); + let user_l2_to_l1_logs = extract_l2tol1logs_from_l1_messenger(&events) + .into_iter() + .map(Into::into) + .map(UserL2ToL1Log) + .collect(); + let system_l2_to_l1_logs = self + .inner + .state + .get_l2_to_l1_logs_after_snapshot(snapshot.l2_to_l1_logs) + .iter() + .map(|log| log.into_system_log()) + .collect(); + let storage_logs: Vec = self + .inner + .state + .get_storage_changes_from_snapshot(snapshot.storage_changes) + .iter() + .map(|(storage_key, previos_value, value, is_initial)| { + let key = StorageKey::new( + AccountTreeId::new(storage_key.address), + u256_to_h256(storage_key.key), + ); + + StorageLogWithPreviousValue { + log: StorageLog { + key, + value: u256_to_h256(*value), + kind: if *is_initial { + StorageLogKind::InitialWrite + } else { + StorageLogKind::RepeatedWrite + }, + }, + previous_value: u256_to_h256(previos_value.unwrap_or_default()), + } + }) + .sorted_by(|a, b| { + a.log + .key + .address() + .cmp(&b.log.key.address()) + .then_with(|| a.log.key.key().cmp(&b.log.key.key())) + }) + .collect(); + + VmExecutionLogs { + storage_logs, + events, + user_l2_to_l1_logs, + system_l2_to_l1_logs, + total_log_queries_count: 0, // This field is unused + } + } + + fn get_execution_statistics( + &self, + monotonic_counter_before: u32, + pubdata_published: u32, + ergs_before: u32, + ergs_after: u32, + circuit_statistic: CircuitStatistic, + ) -> VmExecutionStatistics { + VmExecutionStatistics { + contracts_used: self.inner.state.decommitted_hashes().len(), + cycles_used: self.inner.statistics.monotonic_counter - monotonic_counter_before, + gas_used: (ergs_before - ergs_after) as u64, + gas_remaining: ergs_after, + computational_gas_used: ergs_before - ergs_after, + total_log_queries: 0, + pubdata_published, + circuit_statistic, + } + } + + // Very basic parallel execution model, basically, we are spawning a new vm per transactions and then mergint the results + // and creating the batch from the final merged state. We are not accounting for gas sharing, transactions that depend upon each other, etc + // in the future, this would become a new mode of execution (i.e VmExecutionMode::Parallel) + pub fn execute_parallel(&mut self) -> VmExecutionResultAndLogs { + let transactions: Vec = + self.transaction_to_execute.drain(..).collect(); + + // we only care about the final VMState, since that is where the pubdata and L2 changes reside + // we will merge this results later + let mut final_states: Vec = vec![self.inner.state.clone()]; + + // to run in parallel, we spin up new vms to process and run the transaction in their own bootloader + for tx in transactions { + let mut vm = Vm::new( + self.batch_env.clone(), + self.system_env.clone(), + self.storage.clone(), + ); + + vm.push_transaction_inner(tx.tx, tx.refund, tx.with_compression); + + // in the future we don't want to call the bootloader for this, and instead crate a new era_vm and pass the + // transaction bytecode directly, that would require to build all the validation logic for processing the transaction + // in rust + let result = + vm.inspect_inner(TracerDispatcher::default(), None, VmExecutionMode::OneTx); + + // if one transaction fails, the whole batch fails + if result.result.is_failed() { + return result; + } + + final_states.push(vm.inner.state); + } + + // since no transactions have been pushed onto the bootloader, here it will only call the SetFictiveBlock and request the pubdata + let mut tracer = VmTracerManager::new( + VmExecutionMode::Batch, + TracerDispatcher::default(), + None, + None, + ); + let ergs_before = self.inner.execution.gas_left().unwrap(); + let monotonic_counter_before = self.inner.statistics.monotonic_counter; + + let snapshot = self.inner.state.snapshot(); + // finally, we need to merge the results to the current vm + self.inner.state = self.merge_vm_states(final_states); + let result = self.run(VmExecutionMode::Batch, &mut tracer); + let ergs_after = self.inner.execution.gas_left().unwrap(); + + VmExecutionResultAndLogs { + result, + logs: self.get_execution_logs(snapshot), + // we should handle the statistics merge properly + // for this first model, it isn't that important + statistics: self.get_execution_statistics( + monotonic_counter_before, + tracer.pubdata_tracer.pubdata_published, + ergs_before, + ergs_after, + tracer .circuits_tracer .circuit_statistics(&self.inner.statistics), - }, + ), refunds: tracer.refund_tracer.unwrap_or_default().into(), } } + + pub fn merge_vm_states( + &self, + vm_states: Vec, + ) -> era_vm::state::VMState { + let mut final_state = era_vm::state::VMState::new(self.inner.state.storage.clone()); + + for state in vm_states { + for log in state.l2_to_l1_logs() { + final_state.record_l2_to_l1_log(log.clone()); + } + for event in state.events() { + final_state.record_event(event.clone()); + } + for (key, value) in state.storage_changes().iter() { + final_state.storage_write(key.clone(), value.clone()); + } + final_state.add_pubdata(state.pubdata()); + for hash in state.decommitted_hashes().iter() { + // this is to add the hash to the decommited hashes + final_state.decommit(hash.clone()); + } + } + + final_state + } + + pub fn merge_statistics(&self) {} } impl VmInterface for Vm { @@ -555,6 +701,30 @@ impl VmInterface for Vm { fn gas_remaining(&self) -> u32 { self.inner.execution.current_frame().unwrap().gas_left.0 } + + fn finish_batch(&mut self) -> FinishedL1Batch { + let result = self.execute(VmExecutionMode::Batch); + let execution_state = self.get_current_execution_state(); + let bootloader_memory = self.get_bootloader_memory(); + + FinishedL1Batch { + block_tip_execution_result: result, + final_execution_state: execution_state, + final_bootloader_memory: Some(bootloader_memory), + pubdata_input: Some( + self.bootloader_state + .get_pubdata_information() + .clone() + .build_pubdata(false), + ), + state_diffs: Some( + self.bootloader_state + .get_pubdata_information() + .state_diffs + .to_vec(), + ), + } + } } impl VmInterfaceHistoryEnabled for Vm { From 19cc90772bbd7933f5badd084bc8c80847672cd9 Mon Sep 17 00:00:00 2001 From: Marcos Nicolau Date: Wed, 28 Aug 2024 10:08:31 -0300 Subject: [PATCH 02/17] Add parallel execution test --- .../multivm/src/versions/era_vm/tests/mod.rs | 1 + .../era_vm/tests/parallel_execution.rs | 73 +++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 core/lib/multivm/src/versions/era_vm/tests/parallel_execution.rs diff --git a/core/lib/multivm/src/versions/era_vm/tests/mod.rs b/core/lib/multivm/src/versions/era_vm/tests/mod.rs index 801b6450ef85..a248eda064d7 100644 --- a/core/lib/multivm/src/versions/era_vm/tests/mod.rs +++ b/core/lib/multivm/src/versions/era_vm/tests/mod.rs @@ -11,6 +11,7 @@ mod is_write_initial; mod l1_tx_execution; mod l2_blocks; mod nonce_holder; +mod parallel_execution; mod precompiles; mod refunds; mod require_eip712; diff --git a/core/lib/multivm/src/versions/era_vm/tests/parallel_execution.rs b/core/lib/multivm/src/versions/era_vm/tests/parallel_execution.rs new file mode 100644 index 000000000000..876bea4fb0d1 --- /dev/null +++ b/core/lib/multivm/src/versions/era_vm/tests/parallel_execution.rs @@ -0,0 +1,73 @@ +use zksync_types::Transaction; + +use super::tester::VmTester; +use crate::{ + era_vm::tests::tester::{TxType, VmTesterBuilder}, + interface::{VmExecutionMode, VmInterface}, +}; + +fn prepare_test() -> (VmTester, [Transaction; 3]) { + let mut vm_tester = VmTesterBuilder::new() + .with_empty_in_memory_storage() + .with_deployer() + .with_random_rich_accounts(1) + .build(); + + vm_tester.deploy_test_contract(); + + let account = &mut vm_tester.rich_accounts[0]; + + let txs = [ + account.get_test_contract_transaction( + vm_tester.test_contract.unwrap(), + false, + Default::default(), + false, + TxType::L1 { serial_id: 1 }, + ), + account.get_test_contract_transaction( + vm_tester.test_contract.unwrap(), + true, + Default::default(), + false, + TxType::L1 { serial_id: 1 }, + ), + account.get_test_contract_transaction( + vm_tester.test_contract.unwrap(), + false, + Default::default(), + false, + TxType::L1 { serial_id: 1 }, + ), + ]; + + (vm_tester, txs) +} + +#[test] +fn parallel_execution() { + let normal_execution = { + let (mut vm, txs) = prepare_test(); + let vm = &mut vm.vm; + for tx in &txs { + vm.push_transaction_inner(tx.clone(), 0, true); + } + vm.execute(VmExecutionMode::Batch) + }; + + let parallel_execution = { + let (mut vm, txs) = prepare_test(); + let vm = &mut vm.vm; + for tx in txs { + vm.push_parallel_transaction(tx, 0, true); + } + vm.execute_parallel() + }; + + // we don't assert if statistics are equal since that would require + // sharing the gas in parallel, + // assert!(1 == 2); + assert_eq!(normal_execution.logs, parallel_execution.logs); + // assert_eq!(normal_execution.result, parallel_execution.result); + // assert_eq!(normal_execution.refunds, parallel_execution.refunds); +} From b0365ca0d0a3552733cca12c419db54d481f7191 Mon Sep 17 00:00:00 2001 From: Marcos Nicolau Date: Wed, 28 Aug 2024 10:14:29 -0300 Subject: [PATCH 03/17] Remove finish_batch As it was not related to this pr --- core/lib/multivm/src/versions/era_vm/vm.rs | 32 ++-------------------- 1 file changed, 2 insertions(+), 30 deletions(-) diff --git a/core/lib/multivm/src/versions/era_vm/vm.rs b/core/lib/multivm/src/versions/era_vm/vm.rs index 3d04bf076372..ebeb2ee331b1 100644 --- a/core/lib/multivm/src/versions/era_vm/vm.rs +++ b/core/lib/multivm/src/versions/era_vm/vm.rs @@ -33,11 +33,8 @@ use super::{ logs::IntoSystemLog, snapshot::VmSnapshot, tracers::{ - dispatcher::TracerDispatcher, - manager::VmTracerManager, - pubdata_tracer::PubdataTracer, - refunds_tracer::{Refunds, RefundsTracer}, - traits::VmTracer, + dispatcher::TracerDispatcher, manager::VmTracerManager, pubdata_tracer::PubdataTracer, + refunds_tracer::RefundsTracer, traits::VmTracer, }, transaction::ParallelTransaction, }; @@ -47,7 +44,6 @@ use crate::{ tracer::{TracerExecutionStatus, TracerExecutionStopReason}, Halt, TxRevertReason, VmFactory, VmInterface, VmInterfaceHistoryEnabled, VmRevertReason, }, - vm_1_4_1::FinishedL1Batch, vm_latest::{ constants::{ get_result_success_first_slot, get_vm_hook_position, get_vm_hook_start_position_latest, @@ -701,30 +697,6 @@ impl VmInterface for Vm { fn gas_remaining(&self) -> u32 { self.inner.execution.current_frame().unwrap().gas_left.0 } - - fn finish_batch(&mut self) -> FinishedL1Batch { - let result = self.execute(VmExecutionMode::Batch); - let execution_state = self.get_current_execution_state(); - let bootloader_memory = self.get_bootloader_memory(); - - FinishedL1Batch { - block_tip_execution_result: result, - final_execution_state: execution_state, - final_bootloader_memory: Some(bootloader_memory), - pubdata_input: Some( - self.bootloader_state - .get_pubdata_information() - .clone() - .build_pubdata(false), - ), - state_diffs: Some( - self.bootloader_state - .get_pubdata_information() - .state_diffs - .to_vec(), - ), - } - } } impl VmInterfaceHistoryEnabled for Vm { From 32983e496cde27607367e02adcb3020126d662e1 Mon Sep 17 00:00:00 2001 From: Marcos Nicolau Date: Fri, 30 Aug 2024 11:51:46 -0300 Subject: [PATCH 04/17] Add parallel executor --- core/lib/multivm/src/versions/era_vm/mod.rs | 2 +- .../versions/era_vm/parallel_exec/executor.rs | 32 +++++++++++++++++++ .../src/versions/era_vm/parallel_exec/mod.rs | 4 +++ .../era_vm/{ => parallel_exec}/transaction.rs | 1 + 4 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 core/lib/multivm/src/versions/era_vm/parallel_exec/executor.rs create mode 100644 core/lib/multivm/src/versions/era_vm/parallel_exec/mod.rs rename core/lib/multivm/src/versions/era_vm/{ => parallel_exec}/transaction.rs (95%) diff --git a/core/lib/multivm/src/versions/era_vm/mod.rs b/core/lib/multivm/src/versions/era_vm/mod.rs index 18257f13abd2..378013b3a1f7 100644 --- a/core/lib/multivm/src/versions/era_vm/mod.rs +++ b/core/lib/multivm/src/versions/era_vm/mod.rs @@ -4,10 +4,10 @@ mod event; mod hook; mod initial_bootloader_memory; mod logs; +pub mod parallel_exec; mod snapshot; #[cfg(test)] mod tests; pub mod tracers; -pub mod transaction; mod transaction_data; pub mod vm; diff --git a/core/lib/multivm/src/versions/era_vm/parallel_exec/executor.rs b/core/lib/multivm/src/versions/era_vm/parallel_exec/executor.rs new file mode 100644 index 000000000000..fd2f4e8c2b33 --- /dev/null +++ b/core/lib/multivm/src/versions/era_vm/parallel_exec/executor.rs @@ -0,0 +1,32 @@ +use zksync_state::{ReadStorage, StoragePtr}; + +use super::ParallelTransaction; + +// this struct is a partial replacemnt of the bootloader transaction processing and system context contract +pub struct ParallelExecutor { + storage: StoragePtr, +} + +impl ParallelExecutor { + pub fn new(storage: StoragePtr) -> Self { + Self { storage } + } + + pub fn append_transaction(&self, txs: Vec) {} + + pub fn process_transaction(&self, tx: ParallelTransaction) { + self.set_l2_block(); + // bla bla + } + + fn process_l2_transaction(&self, tx: ParallelTransaction) {} + + fn process_l1_transaction(&self, tx: ParallelTransaction) { + todo!(); + } + + fn set_l2_block(&self) {} + + /// finalizes transaction processing by commiting final state changes to storage + pub fn finalize(&self) {} +} diff --git a/core/lib/multivm/src/versions/era_vm/parallel_exec/mod.rs b/core/lib/multivm/src/versions/era_vm/parallel_exec/mod.rs new file mode 100644 index 000000000000..e6d07d0513e8 --- /dev/null +++ b/core/lib/multivm/src/versions/era_vm/parallel_exec/mod.rs @@ -0,0 +1,4 @@ +pub use executor::ParallelExecutor; +pub use transaction::ParallelTransaction; +mod executor; +mod transaction; diff --git a/core/lib/multivm/src/versions/era_vm/transaction.rs b/core/lib/multivm/src/versions/era_vm/parallel_exec/transaction.rs similarity index 95% rename from core/lib/multivm/src/versions/era_vm/transaction.rs rename to core/lib/multivm/src/versions/era_vm/parallel_exec/transaction.rs index 2e8ac24f3a27..3395f0aa3ed5 100644 --- a/core/lib/multivm/src/versions/era_vm/transaction.rs +++ b/core/lib/multivm/src/versions/era_vm/parallel_exec/transaction.rs @@ -1,5 +1,6 @@ use zksync_types::Transaction; +#[derive(Clone)] pub struct ParallelTransaction { pub tx: Transaction, pub refund: u64, From dc56357864206c058139efdd9098295666377903 Mon Sep 17 00:00:00 2001 From: Marcos Nicolau Date: Fri, 30 Aug 2024 11:53:43 -0300 Subject: [PATCH 05/17] [WIP] Execute parallel --- .../versions/era_vm/bootloader_state/state.rs | 42 ++++- core/lib/multivm/src/versions/era_vm/hook.rs | 4 + .../era_vm/tests/parallel_execution.rs | 43 ++++-- .../versions/era_vm/tests/tester/vm_tester.rs | 14 +- core/lib/multivm/src/versions/era_vm/vm.rs | 144 +++++++++++++++++- 5 files changed, 224 insertions(+), 23 deletions(-) diff --git a/core/lib/multivm/src/versions/era_vm/bootloader_state/state.rs b/core/lib/multivm/src/versions/era_vm/bootloader_state/state.rs index a97bccd661bd..7edd61a098f8 100644 --- a/core/lib/multivm/src/versions/era_vm/bootloader_state/state.rs +++ b/core/lib/multivm/src/versions/era_vm/bootloader_state/state.rs @@ -130,23 +130,61 @@ impl BootloaderState { memory } + pub(crate) fn push_tx_parallel( + &mut self, + tx: TransactionData, + predefined_overhead: u32, + predefined_refund: u64, + compressed_bytecodes: Vec, + trusted_ergs_limit: U256, + chain_id: L2ChainId, + ) -> BootloaderMemory { + let tx_offset = self.free_tx_offset(); + let bootloader_tx = BootloaderTx::new( + tx, + predefined_refund, + predefined_overhead, + trusted_ergs_limit, + compressed_bytecodes, + tx_offset, + chain_id, + ); + + let mut memory = vec![]; + let compressed_bytecode_size = apply_tx_to_memory( + &mut memory, + &bootloader_tx, + self.last_l2_block(), + self.free_tx_index(), + self.free_tx_offset(), + self.compressed_bytecodes_encoding, + self.execution_mode, + false, + ); + self.compressed_bytecodes_encoding += compressed_bytecode_size; + self.free_tx_offset = tx_offset + bootloader_tx.encoded_len(); + self.last_mut_l2_block().push_tx(bootloader_tx); + memory + } + pub(crate) fn last_l2_block(&self) -> &BootloaderL2Block { self.l2_blocks.last().unwrap() } + pub(crate) fn get_pubdata_information(&self) -> &PubdataInput { self.pubdata_information .get() .expect("Pubdata information is not set") } - fn last_mut_l2_block(&mut self) -> &mut BootloaderL2Block { + pub(crate) fn last_mut_l2_block(&mut self) -> &mut BootloaderL2Block { self.l2_blocks.last_mut().unwrap() } /// Apply all bootloader transaction to the initial memory pub(crate) fn bootloader_memory(&self) -> BootloaderMemory { let mut initial_memory = self.initial_memory.clone(); - let mut offset = 0; + let mut offset: usize = 0; let mut compressed_bytecodes_offset = 0; let mut tx_index = 0; for l2_block in &self.l2_blocks { diff --git a/core/lib/multivm/src/versions/era_vm/hook.rs b/core/lib/multivm/src/versions/era_vm/hook.rs index 348ae11c5324..dd85ee0ba376 100644 --- a/core/lib/multivm/src/versions/era_vm/hook.rs +++ b/core/lib/multivm/src/versions/era_vm/hook.rs @@ -14,6 +14,8 @@ pub enum Hook { PostResult, FinalBatchInfo, PubdataRequested, + LoadParallel, + TxIndex, } impl Hook { @@ -34,6 +36,8 @@ impl Hook { 10 => Hook::PostResult, 11 => Hook::FinalBatchInfo, 12 => Hook::PubdataRequested, + 13 => Hook::LoadParallel, + 14 => Hook::TxIndex, _ => panic!("Unknown hook {}", hook), } } diff --git a/core/lib/multivm/src/versions/era_vm/tests/parallel_execution.rs b/core/lib/multivm/src/versions/era_vm/tests/parallel_execution.rs index 876bea4fb0d1..6c05df597d9b 100644 --- a/core/lib/multivm/src/versions/era_vm/tests/parallel_execution.rs +++ b/core/lib/multivm/src/versions/era_vm/tests/parallel_execution.rs @@ -6,14 +6,27 @@ use crate::{ interface::{VmExecutionMode, VmInterface}, }; -fn prepare_test() -> (VmTester, [Transaction; 3]) { +fn prepare_test(is_parallel: bool) -> (VmTester, [Transaction; 3]) { let mut vm_tester = VmTesterBuilder::new() .with_empty_in_memory_storage() .with_deployer() .with_random_rich_accounts(1) .build(); - vm_tester.deploy_test_contract(); + if is_parallel { + let mut vm_tester_2 = VmTesterBuilder::new() + .with_empty_in_memory_storage() + .with_deployer() + .with_random_rich_accounts(1) + .build(); + vm_tester_2.deploy_test_contract(); + + vm_tester.test_contract = vm_tester_2.test_contract; + + vm_tester.deploy_test_contract_parallel() + } else { + vm_tester.deploy_test_contract(); + } let account = &mut vm_tester.rich_accounts[0]; @@ -46,28 +59,32 @@ fn prepare_test() -> (VmTester, [Transaction; 3]) { #[test] fn parallel_execution() { - let normal_execution = { - let (mut vm, txs) = prepare_test(); - let vm = &mut vm.vm; - for tx in &txs { - vm.push_transaction_inner(tx.clone(), 0, true); - } - vm.execute(VmExecutionMode::Batch) - }; + // let normal_execution = { + // let (mut vm, txs) = prepare_test(false); + // let vm = &mut vm.vm; + // for tx in &txs { + // vm.push_transaction_inner(tx.clone(), 0, true); + // } + // vm.execute(VmExecutionMode::Batch) + // }; let parallel_execution = { - let (mut vm, txs) = prepare_test(); + let (mut vm, txs) = prepare_test(true); let vm = &mut vm.vm; for tx in txs { vm.push_parallel_transaction(tx, 0, true); } vm.execute_parallel() }; + println!("RESULT {:?}", parallel_execution.result); // we don't assert if statistics are equal since that would require // sharing the gas in parallel, - // assert!(1 == 2); - assert_eq!(normal_execution.logs, parallel_execution.logs); + assert!(1 == 2); + // assert_eq!( + // normal_execution.logs.storage_logs, + // parallel_execution.logs.storage_logs + // ); // assert_eq!(normal_execution.result, parallel_execution.result); // assert_eq!(normal_execution.refunds, parallel_execution.refunds); } diff --git a/core/lib/multivm/src/versions/era_vm/tests/tester/vm_tester.rs b/core/lib/multivm/src/versions/era_vm/tests/tester/vm_tester.rs index 59fb18bfcab1..3d96198d4476 100644 --- a/core/lib/multivm/src/versions/era_vm/tests/tester/vm_tester.rs +++ b/core/lib/multivm/src/versions/era_vm/tests/tester/vm_tester.rs @@ -10,7 +10,7 @@ use zksync_types::{ helpers::unix_timestamp_ms, utils::{deployed_address_create, storage_key_for_eth_balance}, AccountTreeId, Address, L1BatchNumber, L2BlockNumber, L2ChainId, Nonce, ProtocolVersionId, - StorageKey, U256, + StorageKey, H160, U256, }; use zksync_utils::{bytecode::hash_bytecode, u256_to_h256}; @@ -52,6 +52,18 @@ impl VmTester { deployed_address_create(self.deployer.as_ref().unwrap().address, nonce); self.test_contract = Some(deployed_address); } + + pub(crate) fn deploy_test_contract_parallel(&mut self) { + let contract = read_test_contract(); + let tx = self + .deployer + .as_mut() + .expect("You have to initialize builder with deployer") + .get_deploy_tx(&contract, None, TxType::L2) + .tx; + self.vm.push_parallel_transaction(tx, 0, true); + } + pub(crate) fn reset_with_empty_storage(&mut self) { self.storage = Rc::new(RefCell::new(get_empty_storage())); let world_storage = Rc::new(RefCell::new(World::new( diff --git a/core/lib/multivm/src/versions/era_vm/vm.rs b/core/lib/multivm/src/versions/era_vm/vm.rs index ebeb2ee331b1..54e43b7a88e9 100644 --- a/core/lib/multivm/src/versions/era_vm/vm.rs +++ b/core/lib/multivm/src/versions/era_vm/vm.rs @@ -31,12 +31,12 @@ use super::{ hook::Hook, initial_bootloader_memory::bootloader_initial_memory, logs::IntoSystemLog, + parallel_exec::ParallelTransaction, snapshot::VmSnapshot, tracers::{ dispatcher::TracerDispatcher, manager::VmTracerManager, pubdata_tracer::PubdataTracer, refunds_tracer::RefundsTracer, traits::VmTracer, }, - transaction::ParallelTransaction, }; use crate::{ era_vm::{bytecode::compress_bytecodes, transaction_data::TransactionData}, @@ -73,6 +73,9 @@ pub struct Vm { pub snapshot: Option, pub transaction_to_execute: Vec, + + pub current_tx: usize, + pub is_parallel: bool, } /// Encapsulates creating VM instance based on the provided environment. @@ -149,6 +152,8 @@ impl VmFactory for Vm { system_env, snapshot: None, transaction_to_execute: vec![], + is_parallel: false, + current_tx: 0, }; mv.write_to_bootloader_heap(bootloader_memory); @@ -236,6 +241,23 @@ impl Vm { apply_l2_block(&mut memory, l2_block, txs_index); self.write_to_bootloader_heap(memory); } + Hook::LoadParallel => { + let mut memory: Vec<(usize, U256)> = vec![]; + let is_parallel = if self.is_parallel { + U256::one() + } else { + U256::zero() + }; + println!("IS PARALLEL {}", self.is_parallel); + memory.push((100, is_parallel)); + self.write_to_bootloader_heap(memory); + } + Hook::TxIndex => { + println!("CURRENT TX {}", self.current_tx); + let mut memory: Vec<(usize, U256)> = vec![]; + memory.push((102, (self.current_tx).into())); + self.write_to_bootloader_heap(memory); + } _ => {} } @@ -270,6 +292,7 @@ impl Vm { } }; tracer.after_bootloader_execution(self); + println!("\n\n\n ===== FINISH EXECUTION ===== \n\n\n"); result } @@ -384,6 +407,87 @@ impl Vm { self.write_to_bootloader_heap(memory); } + pub fn push_transaction_inner_parallel( + &mut self, + tx: Transaction, + refund: u64, + with_compression: bool, + ) { + let tx: TransactionData = tx.into(); + let overhead = tx.overhead_gas(); + + self.insert_bytecodes(tx.factory_deps.iter().map(|dep| &dep[..])); + + let compressed_bytecodes = if is_l1_tx_type(tx.tx_type) || !with_compression { + // L1 transactions do not need compression + vec![] + } else { + compress_bytecodes(&tx.factory_deps, |hash| { + self.inner + .state + .storage_changes() + .get(&EraStorageKey::new( + KNOWN_CODES_STORAGE_ADDRESS, + h256_to_u256(hash), + )) + .map(|x| !x.is_zero()) + .unwrap_or_else(|| self.storage.is_bytecode_known(&hash)) + }) + }; + + let trusted_ergs_limit = tx.trusted_ergs_limit(); + + let memory = self.bootloader_state.push_tx_parallel( + tx, + overhead, + refund, + compressed_bytecodes, + trusted_ergs_limit, + self.system_env.chain_id, + ); + + self.write_to_bootloader_heap(memory); + } + + /// pushes transaction to the current l2 block but doesn't write to the bootloader memory + /// this is used in the context of parallel execution, since we wan't to update the L2 rolling hash + /// but we don't necessary want to write the transaction to the bootloader memory to execute them + pub fn write_block_to_mem(&mut self, tx: Transaction, refund: u64, with_compression: bool) { + let tx: TransactionData = tx.into(); + let overhead = tx.overhead_gas(); + + self.insert_bytecodes(tx.factory_deps.iter().map(|dep| &dep[..])); + + let compressed_bytecodes = if is_l1_tx_type(tx.tx_type) || !with_compression { + // L1 transactions do not need compression + vec![] + } else { + compress_bytecodes(&tx.factory_deps, |hash| { + self.inner + .state + .storage_changes() + .get(&EraStorageKey::new( + KNOWN_CODES_STORAGE_ADDRESS, + h256_to_u256(hash), + )) + .map(|x| !x.is_zero()) + .unwrap_or_else(|| self.storage.is_bytecode_known(&hash)) + }) + }; + + let trusted_ergs_limit = tx.trusted_ergs_limit(); + + // this sets the rolling hash + self.bootloader_state.push_tx( + tx, + overhead, + refund, + compressed_bytecodes, + trusted_ergs_limit, + self.system_env.chain_id, + ); + } + pub fn inspect_inner( &mut self, tracer: TracerDispatcher, @@ -438,7 +542,6 @@ impl Vm { } fn get_execution_logs(&self, snapshot: era_vm::state::StateSnapshot) -> VmExecutionLogs { - dbg!(&snapshot); let events = merge_events( self.inner.state.get_events_after_snapshot(snapshot.events), self.batch_env.number, @@ -517,39 +620,65 @@ impl Vm { } } - // Very basic parallel execution model, basically, we are spawning a new vm per transactions and then mergint the results + // Very basic parallel execution model, basically, we are spawning a new vm per transactions and then merging the results // and creating the batch from the final merged state. We are not accounting for gas sharing, transactions that depend upon each other, etc // in the future, this would become a new mode of execution (i.e VmExecutionMode::Parallel) pub fn execute_parallel(&mut self) -> VmExecutionResultAndLogs { let transactions: Vec = self.transaction_to_execute.drain(..).collect(); + let last_tx_number = transactions.len(); + self.current_tx = last_tx_number; + self.is_parallel = true; // we only care about the final VMState, since that is where the pubdata and L2 changes reside // we will merge this results later let mut final_states: Vec = vec![self.inner.state.clone()]; + let mut final_execution: era_vm::execution::Execution = self.inner.execution.clone(); + + // push the transactions to the main vm, which holds the actual L2 block + for tx in transactions.iter() { + self.write_block_to_mem(tx.tx.clone(), tx.refund, tx.with_compression); + } // to run in parallel, we spin up new vms to process and run the transaction in their own bootloader - for tx in transactions { + for (idx, tx) in transactions.iter().enumerate() { + // the idea now is to spawn an era_vm and pass the transaction bytecode and let the rest be handled by the parallel executor + // let mut vm = EraVM::new(); let mut vm = Vm::new( self.batch_env.clone(), self.system_env.clone(), self.storage.clone(), ); - vm.push_transaction_inner(tx.tx, tx.refund, tx.with_compression); + // storage writes are necessary + for (key, value) in final_states.last().unwrap().storage_changes().iter() { + vm.inner.state.storage_write(key.clone(), value.clone()); + } + + vm.is_parallel = true; + vm.current_tx = idx; + vm.inner.execution.tx_number = idx as u64; + + if idx == 0 { + // since the first transaction starts the batch, we want to push it normally so that + // it create the virtual block at the beginnig + vm.push_transaction_inner(tx.tx.clone(), tx.refund, tx.with_compression); + } else { + vm.push_transaction_inner_parallel(tx.tx.clone(), tx.refund, tx.with_compression); + } // in the future we don't want to call the bootloader for this, and instead crate a new era_vm and pass the // transaction bytecode directly, that would require to build all the validation logic for processing the transaction // in rust - let result = + let result: VmExecutionResultAndLogs = vm.inspect_inner(TracerDispatcher::default(), None, VmExecutionMode::OneTx); // if one transaction fails, the whole batch fails if result.result.is_failed() { return result; } - final_states.push(vm.inner.state); + final_execution = vm.inner.execution; } // since no transactions have been pushed onto the bootloader, here it will only call the SetFictiveBlock and request the pubdata @@ -563,6 +692,7 @@ impl Vm { let monotonic_counter_before = self.inner.statistics.monotonic_counter; let snapshot = self.inner.state.snapshot(); + // finally, we need to merge the results to the current vm self.inner.state = self.merge_vm_states(final_states); let result = self.run(VmExecutionMode::Batch, &mut tracer); From 49b94ad865fdcfb8800bb9610c03295a9adc7cf5 Mon Sep 17 00:00:00 2001 From: Marcos Nicolau Date: Fri, 30 Aug 2024 11:54:04 -0300 Subject: [PATCH 06/17] Add debug tracers --- .../src/versions/era_vm/tracers/debug_tracer.rs | 10 ++++++++-- .../lib/multivm/src/versions/era_vm/tracers/manager.rs | 2 +- .../src/versions/era_vm/tracers/pubdata_tracer.rs | 2 ++ 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/core/lib/multivm/src/versions/era_vm/tracers/debug_tracer.rs b/core/lib/multivm/src/versions/era_vm/tracers/debug_tracer.rs index 5585745e05a2..c608f26e9283 100644 --- a/core/lib/multivm/src/versions/era_vm/tracers/debug_tracer.rs +++ b/core/lib/multivm/src/versions/era_vm/tracers/debug_tracer.rs @@ -9,10 +9,16 @@ pub struct DebugTracer {} impl Tracer for DebugTracer {} -impl VmTracer for DebugTracer { +impl DebugTracer { + pub fn new() -> Self { + Self {} + } +} + +impl VmTracer for DebugTracer { fn bootloader_hook_call( &mut self, - _vm: &mut super::traits::Vm, + vm: &mut super::traits::Vm, hook: crate::era_vm::hook::Hook, hook_params: &[U256; 3], ) { diff --git a/core/lib/multivm/src/versions/era_vm/tracers/manager.rs b/core/lib/multivm/src/versions/era_vm/tracers/manager.rs index 5ffc6ea58bd6..f4eaf0d67b2f 100644 --- a/core/lib/multivm/src/versions/era_vm/tracers/manager.rs +++ b/core/lib/multivm/src/versions/era_vm/tracers/manager.rs @@ -40,7 +40,7 @@ impl VmTracerManager { refund_tracer, circuits_tracer: CircuitsTracer::new(), pubdata_tracer: pubdata_tracer.unwrap_or(PubdataTracer::new(execution_mode)), - debug_tracer: None, // or Some(DebugTracer) to enable debugger + debug_tracer: Some(DebugTracer::new()), // or Some(DebugTracer) to enable debugger } } } diff --git a/core/lib/multivm/src/versions/era_vm/tracers/pubdata_tracer.rs b/core/lib/multivm/src/versions/era_vm/tracers/pubdata_tracer.rs index ac1098256a57..c9f4df535ba5 100644 --- a/core/lib/multivm/src/versions/era_vm/tracers/pubdata_tracer.rs +++ b/core/lib/multivm/src/versions/era_vm/tracers/pubdata_tracer.rs @@ -159,6 +159,8 @@ impl VmTracer for PubdataTracer { state_diffs, }; + println!("PUBDATA INPUT {:?}", pubdata_input); + // Save the pubdata for the future initial bootloader memory building vm.bootloader_state.set_pubdata_input(pubdata_input.clone()); From 99e350e8e796e9c3acd8965268150c2d61f57a29 Mon Sep 17 00:00:00 2001 From: Marcos Nicolau Date: Fri, 30 Aug 2024 19:17:44 -0300 Subject: [PATCH 07/17] Add tx number to main parallel vm --- core/lib/multivm/src/versions/era_vm/vm.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/lib/multivm/src/versions/era_vm/vm.rs b/core/lib/multivm/src/versions/era_vm/vm.rs index 54e43b7a88e9..84832b765ec7 100644 --- a/core/lib/multivm/src/versions/era_vm/vm.rs +++ b/core/lib/multivm/src/versions/era_vm/vm.rs @@ -633,7 +633,6 @@ impl Vm { // we only care about the final VMState, since that is where the pubdata and L2 changes reside // we will merge this results later let mut final_states: Vec = vec![self.inner.state.clone()]; - let mut final_execution: era_vm::execution::Execution = self.inner.execution.clone(); // push the transactions to the main vm, which holds the actual L2 block for tx in transactions.iter() { @@ -678,7 +677,6 @@ impl Vm { return result; } final_states.push(vm.inner.state); - final_execution = vm.inner.execution; } // since no transactions have been pushed onto the bootloader, here it will only call the SetFictiveBlock and request the pubdata @@ -695,6 +693,7 @@ impl Vm { // finally, we need to merge the results to the current vm self.inner.state = self.merge_vm_states(final_states); + self.inner.execution.tx_number = last_tx_number as u64; let result = self.run(VmExecutionMode::Batch, &mut tracer); let ergs_after = self.inner.execution.gas_left().unwrap(); From 672abd7a51e9319be0854074b1586739f79d7b98 Mon Sep 17 00:00:00 2001 From: Marcos Nicolau Date: Fri, 30 Aug 2024 19:21:21 -0300 Subject: [PATCH 08/17] Update era-contracts submodule --- contracts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts b/contracts index 8670004d6daa..84cc900ebcf1 160000 --- a/contracts +++ b/contracts @@ -1 +1 @@ -Subproject commit 8670004d6daa7e8c299087d62f1451a3dec4f899 +Subproject commit 84cc900ebcf1d897e75eb2a91efa934d68f5a270 From d6ba396e664b11c43d8373fefaca167545c33f3b Mon Sep 17 00:00:00 2001 From: Marcos Nicolau Date: Mon, 2 Sep 2024 16:44:40 -0300 Subject: [PATCH 09/17] Fix execute parallel final result --- .../era_vm/bootloader_state/l2_block.rs | 3 +- .../versions/era_vm/bootloader_state/mod.rs | 2 +- .../versions/era_vm/bootloader_state/state.rs | 8 +- core/lib/multivm/src/versions/era_vm/vm.rs | 179 +++++++++++------- 4 files changed, 113 insertions(+), 79 deletions(-) diff --git a/core/lib/multivm/src/versions/era_vm/bootloader_state/l2_block.rs b/core/lib/multivm/src/versions/era_vm/bootloader_state/l2_block.rs index 009f2040d20b..48dcb1d3cae4 100644 --- a/core/lib/multivm/src/versions/era_vm/bootloader_state/l2_block.rs +++ b/core/lib/multivm/src/versions/era_vm/bootloader_state/l2_block.rs @@ -12,7 +12,7 @@ use crate::{ const EMPTY_TXS_ROLLING_HASH: H256 = H256::zero(); #[derive(Debug, Clone)] -pub(crate) struct BootloaderL2Block { +pub struct BootloaderL2Block { pub(crate) number: u32, pub(crate) timestamp: u64, pub(crate) txs_rolling_hash: H256, // The rolling hash of all the transactions in the miniblock @@ -57,6 +57,7 @@ impl BootloaderL2Block { pub(crate) fn interim_version(&self) -> BootloaderL2Block { let mut interim = self.clone(); interim.max_virtual_blocks_to_create = 0; + println!("IN INTERIM VERSION"); interim } diff --git a/core/lib/multivm/src/versions/era_vm/bootloader_state/mod.rs b/core/lib/multivm/src/versions/era_vm/bootloader_state/mod.rs index 73830de2759b..f0be30c07ea1 100644 --- a/core/lib/multivm/src/versions/era_vm/bootloader_state/mod.rs +++ b/core/lib/multivm/src/versions/era_vm/bootloader_state/mod.rs @@ -1,4 +1,4 @@ -mod l2_block; +pub mod l2_block; mod snapshot; mod state; mod tx; diff --git a/core/lib/multivm/src/versions/era_vm/bootloader_state/state.rs b/core/lib/multivm/src/versions/era_vm/bootloader_state/state.rs index 7edd61a098f8..12fe3ba345bc 100644 --- a/core/lib/multivm/src/versions/era_vm/bootloader_state/state.rs +++ b/core/lib/multivm/src/versions/era_vm/bootloader_state/state.rs @@ -32,11 +32,11 @@ pub struct BootloaderState { /// See the structure doc-comment for a better explanation of purpose. tx_to_execute: usize, /// Stored txs in bootloader memory - l2_blocks: Vec, + pub l2_blocks: Vec, /// The number of 32-byte words spent on the already included compressed bytecodes. compressed_bytecodes_encoding: usize, /// Initial memory of bootloader - initial_memory: BootloaderMemory, + pub initial_memory: BootloaderMemory, /// Mode of txs for execution, it can be changed once per vm lunch execution_mode: TxExecutionMode, /// Current offset of the free space in the bootloader memory. @@ -88,7 +88,7 @@ impl BootloaderState { } /// This method bypass sanity checks and should be used carefully. - pub(crate) fn push_l2_block(&mut self, l2_block: L2BlockEnv) { + pub fn push_l2_block(&mut self, l2_block: L2BlockEnv) { self.l2_blocks .push(BootloaderL2Block::new(l2_block, self.free_tx_index())) } @@ -167,7 +167,7 @@ impl BootloaderState { memory } - pub(crate) fn last_l2_block(&self) -> &BootloaderL2Block { + pub fn last_l2_block(&self) -> &BootloaderL2Block { self.l2_blocks.last().unwrap() } diff --git a/core/lib/multivm/src/versions/era_vm/vm.rs b/core/lib/multivm/src/versions/era_vm/vm.rs index 84832b765ec7..5a769674b97b 100644 --- a/core/lib/multivm/src/versions/era_vm/vm.rs +++ b/core/lib/multivm/src/versions/era_vm/vm.rs @@ -26,7 +26,7 @@ use zksync_utils::{ }; use super::{ - bootloader_state::{utils::apply_l2_block, BootloaderState}, + bootloader_state::{l2_block::BootloaderL2Block, utils::apply_l2_block, BootloaderState}, event::merge_events, hook::Hook, initial_bootloader_memory::bootloader_initial_memory, @@ -73,7 +73,6 @@ pub struct Vm { pub snapshot: Option, pub transaction_to_execute: Vec, - pub current_tx: usize, pub is_parallel: bool, } @@ -248,12 +247,10 @@ impl Vm { } else { U256::zero() }; - println!("IS PARALLEL {}", self.is_parallel); memory.push((100, is_parallel)); self.write_to_bootloader_heap(memory); } Hook::TxIndex => { - println!("CURRENT TX {}", self.current_tx); let mut memory: Vec<(usize, U256)> = vec![]; memory.push((102, (self.current_tx).into())); self.write_to_bootloader_heap(memory); @@ -282,8 +279,7 @@ impl Vm { output: crate::interface::VmRevertReason::General { msg: "Transaction reverted with empty reason. Possibly out of gas".to_string(), data: vec![], - }, - }; + }}; } else { break ExecutionResult::Success { output: vec![] }; } @@ -366,6 +362,8 @@ impl Vm { refund: u64, with_compression: bool, ) { + // push the transactions to the main vm, which holds the actual L2 block + self.write_block_to_mem(tx.clone(), refund, with_compression); self.transaction_to_execute .push(ParallelTransaction::new(tx, refund, with_compression)); } @@ -407,7 +405,7 @@ impl Vm { self.write_to_bootloader_heap(memory); } - pub fn push_transaction_inner_parallel( + pub fn push_transaction_inner_no_bytecode( &mut self, tx: Transaction, refund: u64, @@ -416,7 +414,45 @@ impl Vm { let tx: TransactionData = tx.into(); let overhead = tx.overhead_gas(); - self.insert_bytecodes(tx.factory_deps.iter().map(|dep| &dep[..])); + let compressed_bytecodes = if is_l1_tx_type(tx.tx_type) || !with_compression { + // L1 transactions do not need compression + vec![] + } else { + compress_bytecodes(&tx.factory_deps, |hash| { + self.inner + .state + .storage_changes() + .get(&EraStorageKey::new( + KNOWN_CODES_STORAGE_ADDRESS, + h256_to_u256(hash), + )) + .map(|x| !x.is_zero()) + .unwrap_or_else(|| self.storage.is_bytecode_known(&hash)) + }) + }; + + let trusted_ergs_limit = tx.trusted_ergs_limit(); + + let memory = self.bootloader_state.push_tx( + tx, + overhead, + refund, + compressed_bytecodes, + trusted_ergs_limit, + self.system_env.chain_id, + ); + + self.write_to_bootloader_heap(memory); + } + + pub fn push_transaction_inner_parallel( + &mut self, + tx: Transaction, + refund: u64, + with_compression: bool, + ) { + let tx: TransactionData = tx.into(); + let overhead = tx.overhead_gas(); let compressed_bytecodes = if is_l1_tx_type(tx.tx_type) || !with_compression { // L1 transactions do not need compression @@ -620,63 +656,78 @@ impl Vm { } } - // Very basic parallel execution model, basically, we are spawning a new vm per transactions and then merging the results - // and creating the batch from the final merged state. We are not accounting for gas sharing, transactions that depend upon each other, etc - // in the future, this would become a new mode of execution (i.e VmExecutionMode::Parallel) - pub fn execute_parallel(&mut self) -> VmExecutionResultAndLogs { - let transactions: Vec = - self.transaction_to_execute.drain(..).collect(); - let last_tx_number = transactions.len(); - self.current_tx = last_tx_number; - self.is_parallel = true; + // Very basic parallel execution model, basically, we are spawning a new vm per transaction and merging the state results + // and sealing the batch from the final merged state. + pub fn execute_parallel_inner(&mut self, one_tx: bool) -> VmExecutionResultAndLogs { + let txs_to_process = if one_tx { + 1 + } else { + self.transaction_to_execute.len() + }; + let transactions: Vec = self + .transaction_to_execute + .drain(..txs_to_process) + .collect(); // we only care about the final VMState, since that is where the pubdata and L2 changes reside // we will merge this results later - let mut final_states: Vec = vec![self.inner.state.clone()]; - - // push the transactions to the main vm, which holds the actual L2 block - for tx in transactions.iter() { - self.write_block_to_mem(tx.tx.clone(), tx.refund, tx.with_compression); - } + let mut state: era_vm::state::VMState = self.inner.state.clone(); // to run in parallel, we spin up new vms to process and run the transaction in their own bootloader - for (idx, tx) in transactions.iter().enumerate() { - // the idea now is to spawn an era_vm and pass the transaction bytecode and let the rest be handled by the parallel executor - // let mut vm = EraVM::new(); + for tx in transactions.iter() { let mut vm = Vm::new( self.batch_env.clone(), self.system_env.clone(), self.storage.clone(), ); - - // storage writes are necessary - for (key, value) in final_states.last().unwrap().storage_changes().iter() { - vm.inner.state.storage_write(key.clone(), value.clone()); - } - + // set the last block to what the main vm has + vm.bootloader_state.l2_blocks.push(BootloaderL2Block { + number: self.bootloader_state.last_l2_block().number, + timestamp: self.bootloader_state.last_l2_block().timestamp, + txs_rolling_hash: self.bootloader_state.last_l2_block().txs_rolling_hash, + prev_block_hash: self.bootloader_state.last_l2_block().prev_block_hash, + first_tx_index: 0, + max_virtual_blocks_to_create: self + .bootloader_state + .last_l2_block() + .max_virtual_blocks_to_create, + txs: vec![], + }); + vm.inner.state = state; vm.is_parallel = true; - vm.current_tx = idx; - vm.inner.execution.tx_number = idx as u64; - - if idx == 0 { + vm.current_tx = self.current_tx; + vm.inner.execution.tx_number = vm.current_tx as u64; + vm.inner + .execution + .set_gas_left(self.inner.execution.gas_left().unwrap()) + .unwrap(); + + if vm.current_tx == 0 { // since the first transaction starts the batch, we want to push it normally so that - // it create the virtual block at the beginnig - vm.push_transaction_inner(tx.tx.clone(), tx.refund, tx.with_compression); + // it create the virtual block at the beginning + vm.push_transaction_inner_no_bytecode( + tx.tx.clone(), + tx.refund, + tx.with_compression, + ); } else { vm.push_transaction_inner_parallel(tx.tx.clone(), tx.refund, tx.with_compression); } - // in the future we don't want to call the bootloader for this, and instead crate a new era_vm and pass the - // transaction bytecode directly, that would require to build all the validation logic for processing the transaction - // in rust let result: VmExecutionResultAndLogs = vm.inspect_inner(TracerDispatcher::default(), None, VmExecutionMode::OneTx); - // if one transaction fails, the whole batch fails - if result.result.is_failed() { + state = vm.inner.state; + self.inner + .execution + .set_gas_left(vm.inner.execution.gas_left().unwrap()) + .unwrap(); + self.current_tx += 1; + + if one_tx { + self.inner.state = state.clone(); return result; } - final_states.push(vm.inner.state); } // since no transactions have been pushed onto the bootloader, here it will only call the SetFictiveBlock and request the pubdata @@ -690,11 +741,10 @@ impl Vm { let monotonic_counter_before = self.inner.statistics.monotonic_counter; let snapshot = self.inner.state.snapshot(); + self.inner.state = state; + self.is_parallel = true; - // finally, we need to merge the results to the current vm - self.inner.state = self.merge_vm_states(final_states); - self.inner.execution.tx_number = last_tx_number as u64; - let result = self.run(VmExecutionMode::Batch, &mut tracer); + let result: ExecutionResult = self.run(VmExecutionMode::Batch, &mut tracer); let ergs_after = self.inner.execution.gas_left().unwrap(); VmExecutionResultAndLogs { @@ -715,33 +765,16 @@ impl Vm { } } - pub fn merge_vm_states( - &self, - vm_states: Vec, - ) -> era_vm::state::VMState { - let mut final_state = era_vm::state::VMState::new(self.inner.state.storage.clone()); - - for state in vm_states { - for log in state.l2_to_l1_logs() { - final_state.record_l2_to_l1_log(log.clone()); - } - for event in state.events() { - final_state.record_event(event.clone()); - } - for (key, value) in state.storage_changes().iter() { - final_state.storage_write(key.clone(), value.clone()); - } - final_state.add_pubdata(state.pubdata()); - for hash in state.decommitted_hashes().iter() { - // this is to add the hash to the decommited hashes - final_state.decommit(hash.clone()); - } + pub fn execute_parallel( + &mut self, + execution_mode: VmExecutionMode, + ) -> VmExecutionResultAndLogs { + if let VmExecutionMode::OneTx = execution_mode { + self.execute_parallel_inner(true) + } else { + self.execute_parallel_inner(false) } - - final_state } - - pub fn merge_statistics(&self) {} } impl VmInterface for Vm { From b23b3ffc10b0d0760390a19da90cb95c4351ad39 Mon Sep 17 00:00:00 2001 From: Marcos Nicolau Date: Mon, 2 Sep 2024 16:46:24 -0300 Subject: [PATCH 10/17] Add custom account and enforced deployer account address --- .../era_vm/parallel_exec/transaction.rs | 2 +- .../era_vm/tests/parallel_execution.rs | 53 ++++++++----------- .../versions/era_vm/tests/tester/vm_tester.rs | 41 ++++++++------ 3 files changed, 49 insertions(+), 47 deletions(-) diff --git a/core/lib/multivm/src/versions/era_vm/parallel_exec/transaction.rs b/core/lib/multivm/src/versions/era_vm/parallel_exec/transaction.rs index 3395f0aa3ed5..d063ccfe5d12 100644 --- a/core/lib/multivm/src/versions/era_vm/parallel_exec/transaction.rs +++ b/core/lib/multivm/src/versions/era_vm/parallel_exec/transaction.rs @@ -1,6 +1,6 @@ use zksync_types::Transaction; -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct ParallelTransaction { pub tx: Transaction, pub refund: u64, diff --git a/core/lib/multivm/src/versions/era_vm/tests/parallel_execution.rs b/core/lib/multivm/src/versions/era_vm/tests/parallel_execution.rs index 6c05df597d9b..7f1f75cdabcf 100644 --- a/core/lib/multivm/src/versions/era_vm/tests/parallel_execution.rs +++ b/core/lib/multivm/src/versions/era_vm/tests/parallel_execution.rs @@ -1,4 +1,5 @@ -use zksync_types::Transaction; +use zksync_test_account::Account; +use zksync_types::{K256PrivateKey, Transaction}; use super::tester::VmTester; use crate::{ @@ -7,23 +8,17 @@ use crate::{ }; fn prepare_test(is_parallel: bool) -> (VmTester, [Transaction; 3]) { + let bytes = [1; 32]; + let account = Account::new(K256PrivateKey::from_bytes(bytes.into()).unwrap()); + dbg!(&account); let mut vm_tester = VmTesterBuilder::new() .with_empty_in_memory_storage() .with_deployer() - .with_random_rich_accounts(1) + .with_custom_account(account) .build(); if is_parallel { - let mut vm_tester_2 = VmTesterBuilder::new() - .with_empty_in_memory_storage() - .with_deployer() - .with_random_rich_accounts(1) - .build(); - vm_tester_2.deploy_test_contract(); - - vm_tester.test_contract = vm_tester_2.test_contract; - - vm_tester.deploy_test_contract_parallel() + vm_tester.deploy_test_contract(); } else { vm_tester.deploy_test_contract(); } @@ -59,14 +54,14 @@ fn prepare_test(is_parallel: bool) -> (VmTester, [Transaction; 3]) { #[test] fn parallel_execution() { - // let normal_execution = { - // let (mut vm, txs) = prepare_test(false); - // let vm = &mut vm.vm; - // for tx in &txs { - // vm.push_transaction_inner(tx.clone(), 0, true); - // } - // vm.execute(VmExecutionMode::Batch) - // }; + let normal_execution = { + let (mut vm, txs) = prepare_test(false); + let vm = &mut vm.vm; + for tx in &txs { + vm.push_transaction_inner(tx.clone(), 0, true); + } + vm.execute(VmExecutionMode::Batch) + }; let parallel_execution = { let (mut vm, txs) = prepare_test(true); @@ -74,17 +69,13 @@ fn parallel_execution() { for tx in txs { vm.push_parallel_transaction(tx, 0, true); } - vm.execute_parallel() + vm.execute_parallel(VmExecutionMode::Batch) }; - println!("RESULT {:?}", parallel_execution.result); - // we don't assert if statistics are equal since that would require - // sharing the gas in parallel, - assert!(1 == 2); - // assert_eq!( - // normal_execution.logs.storage_logs, - // parallel_execution.logs.storage_logs - // ); - // assert_eq!(normal_execution.result, parallel_execution.result); - // assert_eq!(normal_execution.refunds, parallel_execution.refunds); + assert_eq!( + normal_execution.logs.storage_logs, + parallel_execution.logs.storage_logs + ); + assert_eq!(normal_execution.result, parallel_execution.result); + assert_eq!(normal_execution.refunds, parallel_execution.refunds); } diff --git a/core/lib/multivm/src/versions/era_vm/tests/tester/vm_tester.rs b/core/lib/multivm/src/versions/era_vm/tests/tester/vm_tester.rs index 3d96198d4476..51c2ec9b1803 100644 --- a/core/lib/multivm/src/versions/era_vm/tests/tester/vm_tester.rs +++ b/core/lib/multivm/src/versions/era_vm/tests/tester/vm_tester.rs @@ -7,10 +7,9 @@ use zksync_types::{ block::L2BlockHasher, fee_model::BatchFeeInput, get_code_key, get_is_account_key, - helpers::unix_timestamp_ms, utils::{deployed_address_create, storage_key_for_eth_balance}, - AccountTreeId, Address, L1BatchNumber, L2BlockNumber, L2ChainId, Nonce, ProtocolVersionId, - StorageKey, H160, U256, + AccountTreeId, Address, K256PrivateKey, L1BatchNumber, L2BlockNumber, L2ChainId, Nonce, + ProtocolVersionId, StorageKey, U256, }; use zksync_utils::{bytecode::hash_bytecode, u256_to_h256}; @@ -53,16 +52,21 @@ impl VmTester { self.test_contract = Some(deployed_address); } - pub(crate) fn deploy_test_contract_parallel(&mut self) { - let contract = read_test_contract(); - let tx = self - .deployer - .as_mut() - .expect("You have to initialize builder with deployer") - .get_deploy_tx(&contract, None, TxType::L2) - .tx; - self.vm.push_parallel_transaction(tx, 0, true); - } + // pub(crate) fn deploy_test_contract_parallel(&mut self) { + // let contract = read_test_contract(); + // let tx = self + // .deployer + // .as_mut() + // .expect("You have to initialize builder with deployer") + // .get_deploy_tx(&contract, None, TxType::L2) + // .tx; + // let nonce = tx.nonce().unwrap().0.into(); + // self.vm.push_parallel_transaction(tx, 0, true); + // self.vm.execute_parallel(VmExecutionMode::OneTx); + // let deployed_address = + // deployed_address_create(self.deployer.as_ref().unwrap().address, nonce); + // self.test_contract = Some(deployed_address); + // } pub(crate) fn reset_with_empty_storage(&mut self) { self.storage = Rc::new(RefCell::new(get_empty_storage())); @@ -217,13 +221,20 @@ impl VmTesterBuilder { self } + pub fn with_custom_account(mut self, account: Account) -> Self { + self.rich_accounts.push(account); + self + } + pub(crate) fn with_rich_accounts(mut self, accounts: Vec) -> Self { self.rich_accounts.extend(accounts); self } pub(crate) fn with_deployer(mut self) -> Self { - let deployer = Account::random(); + let bytes = [2; 32]; + let account = Account::new(K256PrivateKey::from_bytes(bytes.into()).unwrap()); + let deployer = account; self.deployer = Some(deployer); self } @@ -264,7 +275,7 @@ impl VmTesterBuilder { } pub(crate) fn default_l1_batch(number: L1BatchNumber) -> L1BatchEnv { - let timestamp = unix_timestamp_ms(); + let timestamp = 0xabcd; L1BatchEnv { previous_batch_hash: None, number, From 2bb912a1d5e17b7d25afae6e6c05bc9aa8c72670 Mon Sep 17 00:00:00 2001 From: Marcos Nicolau Date: Mon, 2 Sep 2024 18:46:57 -0300 Subject: [PATCH 11/17] Fix parallel execution logs --- .../era_vm/bootloader_state/l2_block.rs | 1 - .../era_vm/tests/parallel_execution.rs | 4 +- .../versions/era_vm/tests/tester/vm_tester.rs | 30 ++++----- core/lib/multivm/src/versions/era_vm/vm.rs | 61 +++++++++++++++++-- 4 files changed, 74 insertions(+), 22 deletions(-) diff --git a/core/lib/multivm/src/versions/era_vm/bootloader_state/l2_block.rs b/core/lib/multivm/src/versions/era_vm/bootloader_state/l2_block.rs index 48dcb1d3cae4..23704ed2a08f 100644 --- a/core/lib/multivm/src/versions/era_vm/bootloader_state/l2_block.rs +++ b/core/lib/multivm/src/versions/era_vm/bootloader_state/l2_block.rs @@ -57,7 +57,6 @@ impl BootloaderL2Block { pub(crate) fn interim_version(&self) -> BootloaderL2Block { let mut interim = self.clone(); interim.max_virtual_blocks_to_create = 0; - println!("IN INTERIM VERSION"); interim } diff --git a/core/lib/multivm/src/versions/era_vm/tests/parallel_execution.rs b/core/lib/multivm/src/versions/era_vm/tests/parallel_execution.rs index 7f1f75cdabcf..7748f786c250 100644 --- a/core/lib/multivm/src/versions/era_vm/tests/parallel_execution.rs +++ b/core/lib/multivm/src/versions/era_vm/tests/parallel_execution.rs @@ -10,7 +10,7 @@ use crate::{ fn prepare_test(is_parallel: bool) -> (VmTester, [Transaction; 3]) { let bytes = [1; 32]; let account = Account::new(K256PrivateKey::from_bytes(bytes.into()).unwrap()); - dbg!(&account); + let mut vm_tester = VmTesterBuilder::new() .with_empty_in_memory_storage() .with_deployer() @@ -18,7 +18,7 @@ fn prepare_test(is_parallel: bool) -> (VmTester, [Transaction; 3]) { .build(); if is_parallel { - vm_tester.deploy_test_contract(); + vm_tester.deploy_test_contract_parallel(); } else { vm_tester.deploy_test_contract(); } diff --git a/core/lib/multivm/src/versions/era_vm/tests/tester/vm_tester.rs b/core/lib/multivm/src/versions/era_vm/tests/tester/vm_tester.rs index 51c2ec9b1803..a9be55109f14 100644 --- a/core/lib/multivm/src/versions/era_vm/tests/tester/vm_tester.rs +++ b/core/lib/multivm/src/versions/era_vm/tests/tester/vm_tester.rs @@ -52,21 +52,21 @@ impl VmTester { self.test_contract = Some(deployed_address); } - // pub(crate) fn deploy_test_contract_parallel(&mut self) { - // let contract = read_test_contract(); - // let tx = self - // .deployer - // .as_mut() - // .expect("You have to initialize builder with deployer") - // .get_deploy_tx(&contract, None, TxType::L2) - // .tx; - // let nonce = tx.nonce().unwrap().0.into(); - // self.vm.push_parallel_transaction(tx, 0, true); - // self.vm.execute_parallel(VmExecutionMode::OneTx); - // let deployed_address = - // deployed_address_create(self.deployer.as_ref().unwrap().address, nonce); - // self.test_contract = Some(deployed_address); - // } + pub(crate) fn deploy_test_contract_parallel(&mut self) { + let contract = read_test_contract(); + let tx = self + .deployer + .as_mut() + .expect("You have to initialize builder with deployer") + .get_deploy_tx(&contract, None, TxType::L2) + .tx; + let nonce = tx.nonce().unwrap().0.into(); + self.vm.push_parallel_transaction(tx, 0, true); + self.vm.execute_parallel(VmExecutionMode::OneTx); + let deployed_address = + deployed_address_create(self.deployer.as_ref().unwrap().address, nonce); + self.test_contract = Some(deployed_address); + } pub(crate) fn reset_with_empty_storage(&mut self) { self.storage = Rc::new(RefCell::new(get_empty_storage())); diff --git a/core/lib/multivm/src/versions/era_vm/vm.rs b/core/lib/multivm/src/versions/era_vm/vm.rs index 5a769674b97b..deb2880a6b8b 100644 --- a/core/lib/multivm/src/versions/era_vm/vm.rs +++ b/core/lib/multivm/src/versions/era_vm/vm.rs @@ -577,6 +577,58 @@ impl Vm { } } + pub fn inspect_inner_with_custom_snapshot( + &mut self, + tracer: TracerDispatcher, + custom_pubdata_tracer: Option, + execution_mode: VmExecutionMode, + snapshot: era_vm::state::StateSnapshot, + ) -> VmExecutionResultAndLogs { + let mut track_refunds = false; + if let VmExecutionMode::OneTx = execution_mode { + // Move the pointer to the next transaction + self.bootloader_state.move_tx_to_execute_pointer(); + track_refunds = true; + } + + let refund_tracer = if track_refunds { + Some(RefundsTracer::new()) + } else { + None + }; + let mut tracer = + VmTracerManager::new(execution_mode, tracer, refund_tracer, custom_pubdata_tracer); + let ergs_before = self.inner.execution.gas_left().unwrap(); + let monotonic_counter_before = self.inner.statistics.monotonic_counter; + + let result = self.run(execution_mode, &mut tracer); + let ergs_after = self.inner.execution.gas_left().unwrap(); + + let ignore_world_diff = matches!(execution_mode, VmExecutionMode::OneTx) + && matches!(result, ExecutionResult::Halt { .. }); + + let logs = if ignore_world_diff { + VmExecutionLogs::default() + } else { + self.get_execution_logs(snapshot) + }; + + VmExecutionResultAndLogs { + result, + logs, + statistics: self.get_execution_statistics( + monotonic_counter_before, + tracer.pubdata_tracer.pubdata_published, + ergs_before, + ergs_after, + tracer + .circuits_tracer + .circuit_statistics(&self.inner.statistics), + ), + refunds: tracer.refund_tracer.unwrap_or_default().into(), + } + } + fn get_execution_logs(&self, snapshot: era_vm::state::StateSnapshot) -> VmExecutionLogs { let events = merge_events( self.inner.state.get_events_after_snapshot(snapshot.events), @@ -660,7 +712,7 @@ impl Vm { // and sealing the batch from the final merged state. pub fn execute_parallel_inner(&mut self, one_tx: bool) -> VmExecutionResultAndLogs { let txs_to_process = if one_tx { - 1 + self.transaction_to_execute.len() } else { self.transaction_to_execute.len() }; @@ -671,6 +723,7 @@ impl Vm { // we only care about the final VMState, since that is where the pubdata and L2 changes reside // we will merge this results later + let snapshot = self.inner.state.snapshot(); let mut state: era_vm::state::VMState = self.inner.state.clone(); // to run in parallel, we spin up new vms to process and run the transaction in their own bootloader @@ -681,7 +734,7 @@ impl Vm { self.storage.clone(), ); // set the last block to what the main vm has - vm.bootloader_state.l2_blocks.push(BootloaderL2Block { + vm.bootloader_state.l2_blocks[0] = BootloaderL2Block { number: self.bootloader_state.last_l2_block().number, timestamp: self.bootloader_state.last_l2_block().timestamp, txs_rolling_hash: self.bootloader_state.last_l2_block().txs_rolling_hash, @@ -692,7 +745,7 @@ impl Vm { .last_l2_block() .max_virtual_blocks_to_create, txs: vec![], - }); + }; vm.inner.state = state; vm.is_parallel = true; vm.current_tx = self.current_tx; @@ -705,6 +758,7 @@ impl Vm { if vm.current_tx == 0 { // since the first transaction starts the batch, we want to push it normally so that // it create the virtual block at the beginning + vm.is_parallel = false; vm.push_transaction_inner_no_bytecode( tx.tx.clone(), tx.refund, @@ -740,7 +794,6 @@ impl Vm { let ergs_before = self.inner.execution.gas_left().unwrap(); let monotonic_counter_before = self.inner.statistics.monotonic_counter; - let snapshot = self.inner.state.snapshot(); self.inner.state = state; self.is_parallel = true; From 57ac2c031d045c689920095df1cfeb7b58bb394e Mon Sep 17 00:00:00 2001 From: Marcos Nicolau Date: Tue, 3 Sep 2024 18:38:48 -0300 Subject: [PATCH 12/17] Enforce default_l1_batch fee account --- .../lib/multivm/src/versions/era_vm/tests/tester/vm_tester.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/lib/multivm/src/versions/era_vm/tests/tester/vm_tester.rs b/core/lib/multivm/src/versions/era_vm/tests/tester/vm_tester.rs index a9be55109f14..903c02dbbd84 100644 --- a/core/lib/multivm/src/versions/era_vm/tests/tester/vm_tester.rs +++ b/core/lib/multivm/src/versions/era_vm/tests/tester/vm_tester.rs @@ -276,6 +276,8 @@ impl VmTesterBuilder { pub(crate) fn default_l1_batch(number: L1BatchNumber) -> L1BatchEnv { let timestamp = 0xabcd; + let bytes = [4; 32]; + let fee_account = Account::new(K256PrivateKey::from_bytes(bytes.into()).unwrap()); L1BatchEnv { previous_batch_hash: None, number, @@ -284,7 +286,7 @@ pub(crate) fn default_l1_batch(number: L1BatchNumber) -> L1BatchEnv { 50_000_000_000, // 50 gwei 250_000_000, // 0.25 gwei ), - fee_account: Address::random(), + fee_account: fee_account.address(), enforced_base_fee: None, first_l2_block: L2BlockEnv { number: 1, From ac33c3dd35ce95b7629d1b728bd521ab5b79cac4 Mon Sep 17 00:00:00 2001 From: Marcos Nicolau Date: Wed, 4 Sep 2024 13:04:15 -0300 Subject: [PATCH 13/17] Refactor execute_parallel and parallel bootloader state --- .../era_vm/bootloader_state/l2_block.rs | 4 +- .../versions/era_vm/bootloader_state/mod.rs | 2 +- .../versions/era_vm/bootloader_state/state.rs | 54 +++---- .../versions/era_vm/bootloader_state/tx.rs | 4 +- .../era_vm/parallel_exec/transaction.rs | 12 +- .../versions/era_vm/tracers/pubdata_tracer.rs | 2 - core/lib/multivm/src/versions/era_vm/vm.rs | 153 +++++------------- 7 files changed, 79 insertions(+), 152 deletions(-) diff --git a/core/lib/multivm/src/versions/era_vm/bootloader_state/l2_block.rs b/core/lib/multivm/src/versions/era_vm/bootloader_state/l2_block.rs index 23704ed2a08f..d43d5e60c8c0 100644 --- a/core/lib/multivm/src/versions/era_vm/bootloader_state/l2_block.rs +++ b/core/lib/multivm/src/versions/era_vm/bootloader_state/l2_block.rs @@ -36,7 +36,7 @@ impl BootloaderL2Block { } } - pub(super) fn push_tx(&mut self, tx: BootloaderTx) { + pub(crate) fn push_tx(&mut self, tx: BootloaderTx) { self.update_rolling_hash(tx.hash); self.txs.push(tx) } @@ -50,7 +50,7 @@ impl BootloaderL2Block { ) } - fn update_rolling_hash(&mut self, tx_hash: H256) { + pub(crate) fn update_rolling_hash(&mut self, tx_hash: H256) { self.txs_rolling_hash = concat_and_hash(self.txs_rolling_hash, tx_hash) } diff --git a/core/lib/multivm/src/versions/era_vm/bootloader_state/mod.rs b/core/lib/multivm/src/versions/era_vm/bootloader_state/mod.rs index f0be30c07ea1..c6bf062b498b 100644 --- a/core/lib/multivm/src/versions/era_vm/bootloader_state/mod.rs +++ b/core/lib/multivm/src/versions/era_vm/bootloader_state/mod.rs @@ -1,7 +1,7 @@ pub mod l2_block; mod snapshot; mod state; -mod tx; +pub mod tx; pub(crate) mod utils; pub(crate) use snapshot::BootloaderStateSnapshot; diff --git a/core/lib/multivm/src/versions/era_vm/bootloader_state/state.rs b/core/lib/multivm/src/versions/era_vm/bootloader_state/state.rs index 12fe3ba345bc..ff01c5190cc8 100644 --- a/core/lib/multivm/src/versions/era_vm/bootloader_state/state.rs +++ b/core/lib/multivm/src/versions/era_vm/bootloader_state/state.rs @@ -93,7 +93,7 @@ impl BootloaderState { .push(BootloaderL2Block::new(l2_block, self.free_tx_index())) } - pub(crate) fn push_tx( + pub(crate) fn push_tx_inner( &mut self, tx: TransactionData, predefined_overhead: u32, @@ -101,6 +101,7 @@ impl BootloaderState { compressed_bytecodes: Vec, trusted_ergs_limit: U256, chain_id: L2ChainId, + start_new_l2_block: bool, ) -> BootloaderMemory { let tx_offset = self.free_tx_offset(); let bootloader_tx = BootloaderTx::new( @@ -122,7 +123,7 @@ impl BootloaderState { self.free_tx_offset(), self.compressed_bytecodes_encoding, self.execution_mode, - self.last_l2_block().txs.is_empty(), + start_new_l2_block, ); self.compressed_bytecodes_encoding += compressed_bytecode_size; self.free_tx_offset = tx_offset + bootloader_tx.encoded_len(); @@ -130,7 +131,7 @@ impl BootloaderState { memory } - pub(crate) fn push_tx_parallel( + pub(crate) fn push_tx( &mut self, tx: TransactionData, predefined_overhead: u32, @@ -139,38 +140,25 @@ impl BootloaderState { trusted_ergs_limit: U256, chain_id: L2ChainId, ) -> BootloaderMemory { - let tx_offset = self.free_tx_offset(); - let bootloader_tx = BootloaderTx::new( + self.push_tx_inner( tx, - predefined_refund, predefined_overhead, - trusted_ergs_limit, + predefined_refund, compressed_bytecodes, - tx_offset, + trusted_ergs_limit, chain_id, - ); - - let mut memory = vec![]; - let compressed_bytecode_size = apply_tx_to_memory( - &mut memory, - &bootloader_tx, - self.last_l2_block(), - self.free_tx_index(), - self.free_tx_offset(), - self.compressed_bytecodes_encoding, - self.execution_mode, - false, - ); - self.compressed_bytecodes_encoding += compressed_bytecode_size; - self.free_tx_offset = tx_offset + bootloader_tx.encoded_len(); - self.last_mut_l2_block().push_tx(bootloader_tx); - memory + self.last_l2_block().txs.is_empty(), + ) } pub fn last_l2_block(&self) -> &BootloaderL2Block { self.l2_blocks.last().unwrap() } + pub fn last_l2_block_mut(&mut self) -> &mut BootloaderL2Block { + self.l2_blocks.last_mut().unwrap() + } + pub(crate) fn get_pubdata_information(&self) -> &PubdataInput { self.pubdata_information .get() @@ -263,14 +251,14 @@ impl BootloaderState { pub(crate) fn insert_fictive_l2_block(&mut self) -> &BootloaderL2Block { let block = self.last_l2_block(); - if !block.txs.is_empty() { - self.start_new_l2_block(L2BlockEnv { - timestamp: block.timestamp + 1, - number: block.number + 1, - prev_block_hash: block.get_hash(), - max_virtual_blocks_to_create: 1, - }); - } + // if !block.txs.is_empty() { + self.start_new_l2_block(L2BlockEnv { + timestamp: block.timestamp + 1, + number: block.number + 1, + prev_block_hash: block.get_hash(), + max_virtual_blocks_to_create: 1, + }); + // } self.last_l2_block() } diff --git a/core/lib/multivm/src/versions/era_vm/bootloader_state/tx.rs b/core/lib/multivm/src/versions/era_vm/bootloader_state/tx.rs index 211661384143..de58a0d3e30c 100644 --- a/core/lib/multivm/src/versions/era_vm/bootloader_state/tx.rs +++ b/core/lib/multivm/src/versions/era_vm/bootloader_state/tx.rs @@ -4,7 +4,7 @@ use zksync_utils::bytecode::CompressedBytecodeInfo; use crate::era_vm::transaction_data::TransactionData; /// Information about tx necessary for execution in bootloader. -#[derive(Debug, Clone)] +#[derive(Debug, Default, Clone)] pub(crate) struct BootloaderTx { pub(crate) hash: H256, /// Encoded transaction @@ -22,7 +22,7 @@ pub(crate) struct BootloaderTx { } impl BootloaderTx { - pub(super) fn new( + pub fn new( tx: TransactionData, predefined_refund: u64, predefined_overhead: u32, diff --git a/core/lib/multivm/src/versions/era_vm/parallel_exec/transaction.rs b/core/lib/multivm/src/versions/era_vm/parallel_exec/transaction.rs index d063ccfe5d12..8f8585822456 100644 --- a/core/lib/multivm/src/versions/era_vm/parallel_exec/transaction.rs +++ b/core/lib/multivm/src/versions/era_vm/parallel_exec/transaction.rs @@ -1,18 +1,28 @@ use zksync_types::Transaction; +use crate::era_vm::bootloader_state::l2_block::BootloaderL2Block; + #[derive(Clone, Debug)] pub struct ParallelTransaction { pub tx: Transaction, pub refund: u64, pub with_compression: bool, + // the l2 block this transaction belongs to + pub l2_block: BootloaderL2Block, } impl ParallelTransaction { - pub fn new(tx: Transaction, refund: u64, with_compression: bool) -> Self { + pub fn new( + tx: Transaction, + refund: u64, + with_compression: bool, + l2_block: BootloaderL2Block, + ) -> Self { Self { tx, refund, with_compression, + l2_block, } } } diff --git a/core/lib/multivm/src/versions/era_vm/tracers/pubdata_tracer.rs b/core/lib/multivm/src/versions/era_vm/tracers/pubdata_tracer.rs index c9f4df535ba5..ac1098256a57 100644 --- a/core/lib/multivm/src/versions/era_vm/tracers/pubdata_tracer.rs +++ b/core/lib/multivm/src/versions/era_vm/tracers/pubdata_tracer.rs @@ -159,8 +159,6 @@ impl VmTracer for PubdataTracer { state_diffs, }; - println!("PUBDATA INPUT {:?}", pubdata_input); - // Save the pubdata for the future initial bootloader memory building vm.bootloader_state.set_pubdata_input(pubdata_input.clone()); diff --git a/core/lib/multivm/src/versions/era_vm/vm.rs b/core/lib/multivm/src/versions/era_vm/vm.rs index deb2880a6b8b..125fc99f26d9 100644 --- a/core/lib/multivm/src/versions/era_vm/vm.rs +++ b/core/lib/multivm/src/versions/era_vm/vm.rs @@ -26,7 +26,9 @@ use zksync_utils::{ }; use super::{ - bootloader_state::{l2_block::BootloaderL2Block, utils::apply_l2_block, BootloaderState}, + bootloader_state::{ + l2_block::BootloaderL2Block, tx::BootloaderTx, utils::apply_l2_block, BootloaderState, + }, event::merge_events, hook::Hook, initial_bootloader_memory::bootloader_initial_memory, @@ -356,18 +358,6 @@ impl Vm { } } - pub fn push_parallel_transaction( - &mut self, - tx: Transaction, - refund: u64, - with_compression: bool, - ) { - // push the transactions to the main vm, which holds the actual L2 block - self.write_block_to_mem(tx.clone(), refund, with_compression); - self.transaction_to_execute - .push(ParallelTransaction::new(tx, refund, with_compression)); - } - pub fn push_transaction_inner(&mut self, tx: Transaction, refund: u64, with_compression: bool) { let tx: TransactionData = tx.into(); let overhead = tx.overhead_gas(); @@ -405,95 +395,50 @@ impl Vm { self.write_to_bootloader_heap(memory); } - pub fn push_transaction_inner_no_bytecode( - &mut self, - tx: Transaction, - refund: u64, - with_compression: bool, - ) { - let tx: TransactionData = tx.into(); - let overhead = tx.overhead_gas(); - - let compressed_bytecodes = if is_l1_tx_type(tx.tx_type) || !with_compression { - // L1 transactions do not need compression - vec![] - } else { - compress_bytecodes(&tx.factory_deps, |hash| { - self.inner - .state - .storage_changes() - .get(&EraStorageKey::new( - KNOWN_CODES_STORAGE_ADDRESS, - h256_to_u256(hash), - )) - .map(|x| !x.is_zero()) - .unwrap_or_else(|| self.storage.is_bytecode_known(&hash)) - }) - }; - - let trusted_ergs_limit = tx.trusted_ergs_limit(); - - let memory = self.bootloader_state.push_tx( - tx, - overhead, - refund, - compressed_bytecodes, - trusted_ergs_limit, - self.system_env.chain_id, - ); - - self.write_to_bootloader_heap(memory); + /// updates the rolling hash of the current l2 block + /// this functions would be used in the context of parallel execution + fn update_l2_block(&mut self, tx: &TransactionData) { + self.bootloader_state + .last_l2_block_mut() + .update_rolling_hash(tx.tx_hash(self.system_env.chain_id)); + // finally, push a default transaction to pass empty block assertions when setting the final fictive block + self.bootloader_state + .last_l2_block_mut() + .txs + .push(BootloaderTx::default()); } - pub fn push_transaction_inner_parallel( + pub fn push_parallel_transaction( &mut self, tx: Transaction, refund: u64, with_compression: bool, ) { - let tx: TransactionData = tx.into(); - let overhead = tx.overhead_gas(); - - let compressed_bytecodes = if is_l1_tx_type(tx.tx_type) || !with_compression { - // L1 transactions do not need compression - vec![] - } else { - compress_bytecodes(&tx.factory_deps, |hash| { - self.inner - .state - .storage_changes() - .get(&EraStorageKey::new( - KNOWN_CODES_STORAGE_ADDRESS, - h256_to_u256(hash), - )) - .map(|x| !x.is_zero()) - .unwrap_or_else(|| self.storage.is_bytecode_known(&hash)) - }) - }; - - let trusted_ergs_limit = tx.trusted_ergs_limit(); - - let memory = self.bootloader_state.push_tx_parallel( + let tx_data: &TransactionData = &tx.clone().into(); + self.update_l2_block(tx_data); + self.insert_bytecodes(tx_data.factory_deps.iter().map(|dep| &dep[..])); + self.transaction_to_execute.push(ParallelTransaction::new( tx, - overhead, refund, - compressed_bytecodes, - trusted_ergs_limit, - self.system_env.chain_id, - ); - - self.write_to_bootloader_heap(memory); + with_compression, + BootloaderL2Block { + txs: vec![], + first_tx_index: 0, + ..self.bootloader_state.last_l2_block().clone() + }, + )); } - /// pushes transaction to the current l2 block but doesn't write to the bootloader memory - /// this is used in the context of parallel execution, since we wan't to update the L2 rolling hash - /// but we don't necessary want to write the transaction to the bootloader memory to execute them - pub fn write_block_to_mem(&mut self, tx: Transaction, refund: u64, with_compression: bool) { - let tx: TransactionData = tx.into(); + pub fn write_parallel_tx_to_mem(&mut self, tx: &ParallelTransaction, is_first_in_block: bool) { + let ParallelTransaction { + tx, + refund, + with_compression, + .. + } = tx; + let tx: TransactionData = tx.clone().into(); let overhead = tx.overhead_gas(); - self.insert_bytecodes(tx.factory_deps.iter().map(|dep| &dep[..])); - let compressed_bytecodes = if is_l1_tx_type(tx.tx_type) || !with_compression { // L1 transactions do not need compression vec![] @@ -513,15 +458,17 @@ impl Vm { let trusted_ergs_limit = tx.trusted_ergs_limit(); - // this sets the rolling hash - self.bootloader_state.push_tx( + let memory = self.bootloader_state.push_tx_inner( tx, overhead, - refund, + *refund, compressed_bytecodes, trusted_ergs_limit, self.system_env.chain_id, + is_first_in_block, ); + + self.write_to_bootloader_heap(memory); } pub fn inspect_inner( @@ -733,19 +680,7 @@ impl Vm { self.system_env.clone(), self.storage.clone(), ); - // set the last block to what the main vm has - vm.bootloader_state.l2_blocks[0] = BootloaderL2Block { - number: self.bootloader_state.last_l2_block().number, - timestamp: self.bootloader_state.last_l2_block().timestamp, - txs_rolling_hash: self.bootloader_state.last_l2_block().txs_rolling_hash, - prev_block_hash: self.bootloader_state.last_l2_block().prev_block_hash, - first_tx_index: 0, - max_virtual_blocks_to_create: self - .bootloader_state - .last_l2_block() - .max_virtual_blocks_to_create, - txs: vec![], - }; + vm.bootloader_state.l2_blocks[0] = tx.l2_block.clone(); vm.inner.state = state; vm.is_parallel = true; vm.current_tx = self.current_tx; @@ -759,13 +694,9 @@ impl Vm { // since the first transaction starts the batch, we want to push it normally so that // it create the virtual block at the beginning vm.is_parallel = false; - vm.push_transaction_inner_no_bytecode( - tx.tx.clone(), - tx.refund, - tx.with_compression, - ); + vm.write_parallel_tx_to_mem(tx, true); } else { - vm.push_transaction_inner_parallel(tx.tx.clone(), tx.refund, tx.with_compression); + vm.write_parallel_tx_to_mem(tx, false); } let result: VmExecutionResultAndLogs = From d89a9f5fa4d0c9b3f5e2c982dd52c51f2c6a9c9b Mon Sep 17 00:00:00 2001 From: Marcos Nicolau Date: Wed, 4 Sep 2024 13:08:40 -0300 Subject: [PATCH 14/17] Update era-contracts submodule --- contracts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts b/contracts index 84cc900ebcf1..4b828ab306ea 160000 --- a/contracts +++ b/contracts @@ -1 +1 @@ -Subproject commit 84cc900ebcf1d897e75eb2a91efa934d68f5a270 +Subproject commit 4b828ab306ea63c847a828815388cfa6b64c695a From 963149e2045b61c078a9475838025d1a55e69d4d Mon Sep 17 00:00:00 2001 From: Marcos Nicolau Date: Wed, 4 Sep 2024 13:30:50 -0300 Subject: [PATCH 15/17] Avoid cloning whole l2 block --- core/lib/multivm/src/versions/era_vm/vm.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/core/lib/multivm/src/versions/era_vm/vm.rs b/core/lib/multivm/src/versions/era_vm/vm.rs index 125fc99f26d9..ccf2d27d26ff 100644 --- a/core/lib/multivm/src/versions/era_vm/vm.rs +++ b/core/lib/multivm/src/versions/era_vm/vm.rs @@ -417,6 +417,7 @@ impl Vm { let tx_data: &TransactionData = &tx.clone().into(); self.update_l2_block(tx_data); self.insert_bytecodes(tx_data.factory_deps.iter().map(|dep| &dep[..])); + let l2_block = self.bootloader_state.last_l2_block(); self.transaction_to_execute.push(ParallelTransaction::new( tx, refund, @@ -424,7 +425,11 @@ impl Vm { BootloaderL2Block { txs: vec![], first_tx_index: 0, - ..self.bootloader_state.last_l2_block().clone() + number: l2_block.number, + max_virtual_blocks_to_create: l2_block.max_virtual_blocks_to_create, + prev_block_hash: l2_block.prev_block_hash, + timestamp: l2_block.timestamp, + txs_rolling_hash: l2_block.txs_rolling_hash, }, )); } From 45f33da72e876043be18310b2454e5771c293f60 Mon Sep 17 00:00:00 2001 From: Marcos Nicolau Date: Wed, 4 Sep 2024 16:38:55 -0300 Subject: [PATCH 16/17] Add posibility to change parallel execution mode betweem executions also fixes an err when checking the err of the individual transactions --- .../versions/era_vm/bootloader_state/state.rs | 16 ++--- .../era_vm/tests/get_used_contracts.rs | 1 - .../era_vm/tests/parallel_execution.rs | 15 ++-- .../versions/era_vm/tests/tester/vm_tester.rs | 16 ----- core/lib/multivm/src/versions/era_vm/vm.rs | 72 +++++++++---------- 5 files changed, 47 insertions(+), 73 deletions(-) diff --git a/core/lib/multivm/src/versions/era_vm/bootloader_state/state.rs b/core/lib/multivm/src/versions/era_vm/bootloader_state/state.rs index ff01c5190cc8..57f6bde65d86 100644 --- a/core/lib/multivm/src/versions/era_vm/bootloader_state/state.rs +++ b/core/lib/multivm/src/versions/era_vm/bootloader_state/state.rs @@ -251,14 +251,14 @@ impl BootloaderState { pub(crate) fn insert_fictive_l2_block(&mut self) -> &BootloaderL2Block { let block = self.last_l2_block(); - // if !block.txs.is_empty() { - self.start_new_l2_block(L2BlockEnv { - timestamp: block.timestamp + 1, - number: block.number + 1, - prev_block_hash: block.get_hash(), - max_virtual_blocks_to_create: 1, - }); - // } + if !block.txs.is_empty() { + self.start_new_l2_block(L2BlockEnv { + timestamp: block.timestamp + 1, + number: block.number + 1, + prev_block_hash: block.get_hash(), + max_virtual_blocks_to_create: 1, + }); + } self.last_l2_block() } diff --git a/core/lib/multivm/src/versions/era_vm/tests/get_used_contracts.rs b/core/lib/multivm/src/versions/era_vm/tests/get_used_contracts.rs index 8ec01ae8d0ae..494336be1a1f 100644 --- a/core/lib/multivm/src/versions/era_vm/tests/get_used_contracts.rs +++ b/core/lib/multivm/src/versions/era_vm/tests/get_used_contracts.rs @@ -1,6 +1,5 @@ use std::collections::HashSet; -use itertools::Itertools; use zksync_state::ReadStorage; use zksync_system_constants::CONTRACT_DEPLOYER_ADDRESS; use zksync_test_account::Account; diff --git a/core/lib/multivm/src/versions/era_vm/tests/parallel_execution.rs b/core/lib/multivm/src/versions/era_vm/tests/parallel_execution.rs index 7748f786c250..67b8960afb4b 100644 --- a/core/lib/multivm/src/versions/era_vm/tests/parallel_execution.rs +++ b/core/lib/multivm/src/versions/era_vm/tests/parallel_execution.rs @@ -7,7 +7,7 @@ use crate::{ interface::{VmExecutionMode, VmInterface}, }; -fn prepare_test(is_parallel: bool) -> (VmTester, [Transaction; 3]) { +fn prepare_test() -> (VmTester, [Transaction; 3]) { let bytes = [1; 32]; let account = Account::new(K256PrivateKey::from_bytes(bytes.into()).unwrap()); @@ -17,11 +17,7 @@ fn prepare_test(is_parallel: bool) -> (VmTester, [Transaction; 3]) { .with_custom_account(account) .build(); - if is_parallel { - vm_tester.deploy_test_contract_parallel(); - } else { - vm_tester.deploy_test_contract(); - } + vm_tester.deploy_test_contract(); let account = &mut vm_tester.rich_accounts[0]; @@ -55,7 +51,7 @@ fn prepare_test(is_parallel: bool) -> (VmTester, [Transaction; 3]) { #[test] fn parallel_execution() { let normal_execution = { - let (mut vm, txs) = prepare_test(false); + let (mut vm, txs) = prepare_test(); let vm = &mut vm.vm; for tx in &txs { vm.push_transaction_inner(tx.clone(), 0, true); @@ -64,13 +60,14 @@ fn parallel_execution() { }; let parallel_execution = { - let (mut vm, txs) = prepare_test(true); + let (mut vm, txs) = prepare_test(); let vm = &mut vm.vm; for tx in txs { vm.push_parallel_transaction(tx, 0, true); } - vm.execute_parallel(VmExecutionMode::Batch) + vm.execute_parallel() }; + println!("EXECUTION RESULT {:?}", parallel_execution.result); assert_eq!( normal_execution.logs.storage_logs, diff --git a/core/lib/multivm/src/versions/era_vm/tests/tester/vm_tester.rs b/core/lib/multivm/src/versions/era_vm/tests/tester/vm_tester.rs index 903c02dbbd84..f4d060d3dfd2 100644 --- a/core/lib/multivm/src/versions/era_vm/tests/tester/vm_tester.rs +++ b/core/lib/multivm/src/versions/era_vm/tests/tester/vm_tester.rs @@ -52,22 +52,6 @@ impl VmTester { self.test_contract = Some(deployed_address); } - pub(crate) fn deploy_test_contract_parallel(&mut self) { - let contract = read_test_contract(); - let tx = self - .deployer - .as_mut() - .expect("You have to initialize builder with deployer") - .get_deploy_tx(&contract, None, TxType::L2) - .tx; - let nonce = tx.nonce().unwrap().0.into(); - self.vm.push_parallel_transaction(tx, 0, true); - self.vm.execute_parallel(VmExecutionMode::OneTx); - let deployed_address = - deployed_address_create(self.deployer.as_ref().unwrap().address, nonce); - self.test_contract = Some(deployed_address); - } - pub(crate) fn reset_with_empty_storage(&mut self) { self.storage = Rc::new(RefCell::new(get_empty_storage())); let world_storage = Rc::new(RefCell::new(World::new( diff --git a/core/lib/multivm/src/versions/era_vm/vm.rs b/core/lib/multivm/src/versions/era_vm/vm.rs index ccf2d27d26ff..723b581bc344 100644 --- a/core/lib/multivm/src/versions/era_vm/vm.rs +++ b/core/lib/multivm/src/versions/era_vm/vm.rs @@ -77,6 +77,7 @@ pub struct Vm { pub transaction_to_execute: Vec, pub current_tx: usize, pub is_parallel: bool, + pub batch_has_failed: bool, } /// Encapsulates creating VM instance based on the provided environment. @@ -155,6 +156,7 @@ impl VmFactory for Vm { transaction_to_execute: vec![], is_parallel: false, current_tx: 0, + batch_has_failed: false, }; mv.write_to_bootloader_heap(bootloader_memory); @@ -177,18 +179,24 @@ impl Vm { let status = tracer.after_vm_run(self, output.clone()); let (hook, hook_params) = match output { ExecutionOutput::Ok(output) => break ExecutionResult::Success { output }, - ExecutionOutput::Revert(output) => match TxRevertReason::parse_error(&output) { - TxRevertReason::TxReverted(output) => break ExecutionResult::Revert { output }, - TxRevertReason::Halt(reason) => break ExecutionResult::Halt { reason }, - }, + ExecutionOutput::Revert(output) => { + self.batch_has_failed = true; + match TxRevertReason::parse_error(&output) { + TxRevertReason::TxReverted(output) => { + break ExecutionResult::Revert { output } + } + TxRevertReason::Halt(reason) => break ExecutionResult::Halt { reason }, + } + } ExecutionOutput::Panic => { + self.batch_has_failed = true; break ExecutionResult::Halt { reason: if self.inner.execution.gas_left().unwrap() == 0 { Halt::BootloaderOutOfGas } else { Halt::VMPanic }, - } + }; } ExecutionOutput::SuspendedOnHook { hook, @@ -229,6 +237,7 @@ impl Vm { }); } Hook::TxHasEnded => { + self.current_tx += 1; if let VmExecutionMode::OneTx = execution_mode { break last_tx_result .expect("There should always be a result if we got this hook"); @@ -263,10 +272,12 @@ impl Vm { if let TracerExecutionStatus::Stop(reason) = status { match reason { TracerExecutionStopReason::Abort(halt) => { - break ExecutionResult::Halt { reason: halt } + self.batch_has_failed = true; + break ExecutionResult::Halt { reason: halt }; } TracerExecutionStopReason::Finish => { if self.inner.execution.gas_left().unwrap() == 0 { + self.batch_has_failed = true; break ExecutionResult::Halt { reason: Halt::BootloaderOutOfGas, }; @@ -277,6 +288,7 @@ impl Vm { let has_failed = self.tx_has_failed(self.bootloader_state.current_tx() as u32); if has_failed { + self.batch_has_failed = true; break ExecutionResult::Revert { output: crate::interface::VmRevertReason::General { msg: "Transaction reverted with empty reason. Possibly out of gas".to_string(), @@ -359,16 +371,16 @@ impl Vm { } pub fn push_transaction_inner(&mut self, tx: Transaction, refund: u64, with_compression: bool) { - let tx: TransactionData = tx.into(); - let overhead = tx.overhead_gas(); + let tx_data: TransactionData = tx.clone().into(); + let overhead = tx_data.overhead_gas(); - self.insert_bytecodes(tx.factory_deps.iter().map(|dep| &dep[..])); + self.insert_bytecodes(tx_data.factory_deps.iter().map(|dep| &dep[..])); - let compressed_bytecodes = if is_l1_tx_type(tx.tx_type) || !with_compression { + let compressed_bytecodes = if is_l1_tx_type(tx_data.tx_type) || !with_compression { // L1 transactions do not need compression vec![] } else { - compress_bytecodes(&tx.factory_deps, |hash| { + compress_bytecodes(&tx_data.factory_deps, |hash| { self.inner .state .storage_changes() @@ -381,10 +393,10 @@ impl Vm { }) }; - let trusted_ergs_limit = tx.trusted_ergs_limit(); + let trusted_ergs_limit = tx_data.trusted_ergs_limit(); let memory = self.bootloader_state.push_tx( - tx, + tx_data, overhead, refund, compressed_bytecodes, @@ -415,8 +427,8 @@ impl Vm { with_compression: bool, ) { let tx_data: &TransactionData = &tx.clone().into(); - self.update_l2_block(tx_data); self.insert_bytecodes(tx_data.factory_deps.iter().map(|dep| &dep[..])); + self.update_l2_block(tx_data); let l2_block = self.bootloader_state.last_l2_block(); self.transaction_to_execute.push(ParallelTransaction::new( tx, @@ -662,12 +674,8 @@ impl Vm { // Very basic parallel execution model, basically, we are spawning a new vm per transaction and merging the state results // and sealing the batch from the final merged state. - pub fn execute_parallel_inner(&mut self, one_tx: bool) -> VmExecutionResultAndLogs { - let txs_to_process = if one_tx { - self.transaction_to_execute.len() - } else { - self.transaction_to_execute.len() - }; + pub fn execute_parallel(&mut self) -> VmExecutionResultAndLogs { + let txs_to_process = self.transaction_to_execute.len(); let transactions: Vec = self .transaction_to_execute .drain(..txs_to_process) @@ -677,7 +685,6 @@ impl Vm { // we will merge this results later let snapshot = self.inner.state.snapshot(); let mut state: era_vm::state::VMState = self.inner.state.clone(); - // to run in parallel, we spin up new vms to process and run the transaction in their own bootloader for tx in transactions.iter() { let mut vm = Vm::new( @@ -694,11 +701,9 @@ impl Vm { .execution .set_gas_left(self.inner.execution.gas_left().unwrap()) .unwrap(); - - if vm.current_tx == 0 { - // since the first transaction starts the batch, we want to push it normally so that - // it create the virtual block at the beginning - vm.is_parallel = false; + if self.current_tx == 0 { + // since the first transaction starts the batch, we want to push as first in block + // so that it creates the batch and the virtual block at the beginning vm.write_parallel_tx_to_mem(tx, true); } else { vm.write_parallel_tx_to_mem(tx, false); @@ -714,7 +719,7 @@ impl Vm { .unwrap(); self.current_tx += 1; - if one_tx { + if vm.batch_has_failed { self.inner.state = state.clone(); return result; } @@ -733,7 +738,7 @@ impl Vm { self.inner.state = state; self.is_parallel = true; - let result: ExecutionResult = self.run(VmExecutionMode::Batch, &mut tracer); + let result = self.run(VmExecutionMode::Batch, &mut tracer); let ergs_after = self.inner.execution.gas_left().unwrap(); VmExecutionResultAndLogs { @@ -753,17 +758,6 @@ impl Vm { refunds: tracer.refund_tracer.unwrap_or_default().into(), } } - - pub fn execute_parallel( - &mut self, - execution_mode: VmExecutionMode, - ) -> VmExecutionResultAndLogs { - if let VmExecutionMode::OneTx = execution_mode { - self.execute_parallel_inner(true) - } else { - self.execute_parallel_inner(false) - } - } } impl VmInterface for Vm { From 82f846b9dd903482cc86cc630d46e163f27cfc95 Mon Sep 17 00:00:00 2001 From: Marcos Nicolau Date: Wed, 4 Sep 2024 16:41:38 -0300 Subject: [PATCH 17/17] Update era-contracts submodule --- contracts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts b/contracts index 4b828ab306ea..b2ed22024dcf 160000 --- a/contracts +++ b/contracts @@ -1 +1 @@ -Subproject commit 4b828ab306ea63c847a828815388cfa6b64c695a +Subproject commit b2ed22024dcf6a51fb201cf1a6b42d22ca0cbe40