diff --git a/contracts b/contracts index 8670004d6daa..b2ed22024dcf 160000 --- a/contracts +++ b/contracts @@ -1 +1 @@ -Subproject commit 8670004d6daa7e8c299087d62f1451a3dec4f899 +Subproject commit b2ed22024dcf6a51fb201cf1a6b42d22ca0cbe40 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..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 @@ -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 @@ -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 73830de2759b..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 @@ -mod l2_block; +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 a97bccd661bd..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 @@ -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,12 +88,12 @@ 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())) } - 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,23 +131,48 @@ impl BootloaderState { memory } - pub(crate) fn last_l2_block(&self) -> &BootloaderL2Block { + pub(crate) fn push_tx( + &mut self, + tx: TransactionData, + predefined_overhead: u32, + predefined_refund: u64, + compressed_bytecodes: Vec, + trusted_ergs_limit: U256, + chain_id: L2ChainId, + ) -> BootloaderMemory { + self.push_tx_inner( + tx, + predefined_overhead, + predefined_refund, + compressed_bytecodes, + trusted_ergs_limit, + chain_id, + 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() .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/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/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/mod.rs b/core/lib/multivm/src/versions/era_vm/mod.rs index b595aaa86d79..378013b3a1f7 100644 --- a/core/lib/multivm/src/versions/era_vm/mod.rs +++ b/core/lib/multivm/src/versions/era_vm/mod.rs @@ -4,6 +4,7 @@ mod event; mod hook; mod initial_bootloader_memory; mod logs; +pub mod parallel_exec; mod snapshot; #[cfg(test)] mod tests; 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/parallel_exec/transaction.rs b/core/lib/multivm/src/versions/era_vm/parallel_exec/transaction.rs new file mode 100644 index 000000000000..8f8585822456 --- /dev/null +++ b/core/lib/multivm/src/versions/era_vm/parallel_exec/transaction.rs @@ -0,0 +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, + l2_block: BootloaderL2Block, + ) -> Self { + Self { + tx, + refund, + with_compression, + 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/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..67b8960afb4b --- /dev/null +++ b/core/lib/multivm/src/versions/era_vm/tests/parallel_execution.rs @@ -0,0 +1,78 @@ +use zksync_test_account::Account; +use zksync_types::{K256PrivateKey, Transaction}; + +use super::tester::VmTester; +use crate::{ + era_vm::tests::tester::{TxType, VmTesterBuilder}, + interface::{VmExecutionMode, VmInterface}, +}; + +fn prepare_test() -> (VmTester, [Transaction; 3]) { + let bytes = [1; 32]; + let account = Account::new(K256PrivateKey::from_bytes(bytes.into()).unwrap()); + + let mut vm_tester = VmTesterBuilder::new() + .with_empty_in_memory_storage() + .with_deployer() + .with_custom_account(account) + .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() + }; + println!("EXECUTION RESULT {:?}", parallel_execution.result); + + 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..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 @@ -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, U256, + AccountTreeId, Address, K256PrivateKey, L1BatchNumber, L2BlockNumber, L2ChainId, Nonce, + ProtocolVersionId, StorageKey, U256, }; use zksync_utils::{bytecode::hash_bytecode, u256_to_h256}; @@ -52,6 +51,7 @@ impl VmTester { 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( @@ -205,13 +205,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 } @@ -252,7 +259,9 @@ impl VmTesterBuilder { } pub(crate) fn default_l1_batch(number: L1BatchNumber) -> L1BatchEnv { - let timestamp = unix_timestamp_ms(); + let timestamp = 0xabcd; + let bytes = [4; 32]; + let fee_account = Account::new(K256PrivateKey::from_bytes(bytes.into()).unwrap()); L1BatchEnv { previous_batch_hash: None, number, @@ -261,7 +270,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, 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/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/vm.rs b/core/lib/multivm/src/versions/era_vm/vm.rs index 19f065088f27..723b581bc344 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, @@ -24,11 +26,14 @@ use zksync_utils::{ }; use super::{ - bootloader_state::{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, logs::IntoSystemLog, + parallel_exec::ParallelTransaction, snapshot::VmSnapshot, tracers::{ dispatcher::TracerDispatcher, manager::VmTracerManager, pubdata_tracer::PubdataTracer, @@ -68,6 +73,11 @@ pub struct Vm { pub(crate) system_env: SystemEnv, pub snapshot: Option, + + 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. @@ -143,6 +153,10 @@ impl VmFactory for Vm { batch_env, system_env, snapshot: None, + transaction_to_execute: vec![], + is_parallel: false, + current_tx: 0, + batch_has_failed: false, }; mv.write_to_bootloader_heap(bootloader_memory); @@ -165,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, @@ -217,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"); @@ -230,16 +251,33 @@ 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() + }; + memory.push((100, is_parallel)); + self.write_to_bootloader_heap(memory); + } + Hook::TxIndex => { + let mut memory: Vec<(usize, U256)> = vec![]; + memory.push((102, (self.current_tx).into())); + self.write_to_bootloader_heap(memory); + } _ => {} } 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, }; @@ -250,12 +288,12 @@ 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(), data: vec![], - }, - }; + }}; } else { break ExecutionResult::Success { output: vec![] }; } @@ -264,6 +302,7 @@ impl Vm { } }; tracer.after_bootloader_execution(self); + println!("\n\n\n ===== FINISH EXECUTION ===== \n\n\n"); result } @@ -332,10 +371,90 @@ 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_data.factory_deps.iter().map(|dep| &dep[..])); + + 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_data.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_data.trusted_ergs_limit(); + + let memory = self.bootloader_state.push_tx( + tx_data, + overhead, + refund, + compressed_bytecodes, + trusted_ergs_limit, + self.system_env.chain_id, + ); + + self.write_to_bootloader_heap(memory); + } - self.insert_bytecodes(tx.factory_deps.iter().map(|dep| &dep[..])); + /// 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_parallel_transaction( + &mut self, + tx: Transaction, + refund: u64, + with_compression: bool, + ) { + let tx_data: &TransactionData = &tx.clone().into(); + 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, + refund, + with_compression, + BootloaderL2Block { + txs: vec![], + first_tx_index: 0, + 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, + }, + )); + } + + 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(); let compressed_bytecodes = if is_l1_tx_type(tx.tx_type) || !with_compression { // L1 transactions do not need compression @@ -356,13 +475,14 @@ impl Vm { let trusted_ergs_limit = tx.trusted_ergs_limit(); - let memory = 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); @@ -402,72 +522,239 @@ 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(), + } + } + + 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), + 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 transaction and merging the state results + // and sealing the batch from the final merged state. + 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) + .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 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( + self.batch_env.clone(), + self.system_env.clone(), + self.storage.clone(), + ); + vm.bootloader_state.l2_blocks[0] = tx.l2_block.clone(); + vm.inner.state = state; + vm.is_parallel = true; + 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 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); + } + + let result: VmExecutionResultAndLogs = + vm.inspect_inner(TracerDispatcher::default(), None, VmExecutionMode::OneTx); + + state = vm.inner.state; + self.inner + .execution + .set_gas_left(vm.inner.execution.gas_left().unwrap()) + .unwrap(); + self.current_tx += 1; + + if vm.batch_has_failed { + self.inner.state = state.clone(); + return result; + } + } + + // 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; + + self.inner.state = state; + self.is_parallel = true; + + 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(), } }