From 8049eb340eadcb2e9844465d8ea15ae8c08e0ef5 Mon Sep 17 00:00:00 2001 From: Stanislav Bezkorovainyi Date: Tue, 30 Jan 2024 17:26:29 +0100 Subject: [PATCH] feat: Optimized block tip seal criterion (#968) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What ❔ ## Why ❔ ## Checklist - [ ] PR title corresponds to the body of PR (we generate changelog entries from PRs). - [ ] Tests for the changes have been added / updated. - [ ] Documentation comments have been added / updated. - [ ] Code has been formatted via `zk fmt` and `zk lint`. - [ ] Spellcheck has been run via `zk spellcheck`. - [ ] Linkcheck has been run via `zk linkcheck`. --- core/lib/multivm/src/interface/traits/vm.rs | 3 + core/lib/multivm/src/versions/vm_1_3_2/vm.rs | 6 + .../vm_boojum_integration/constants.rs | 3 + .../src/versions/vm_boojum_integration/vm.rs | 5 + .../src/versions/vm_latest/constants.rs | 3 + .../vm_latest/implementation/execution.rs | 13 +- .../src/versions/vm_latest/tests/block_tip.rs | 284 ++++++++++++++++++ .../src/versions/vm_latest/tests/mod.rs | 1 + .../versions/vm_latest/tests/tester/mod.rs | 4 +- .../src/versions/vm_latest/tests/upgrade.rs | 19 +- .../src/versions/vm_latest/tests/utils.rs | 10 + .../vm_latest/tracers/pubdata_tracer.rs | 34 ++- core/lib/multivm/src/versions/vm_latest/vm.rs | 9 +- core/lib/multivm/src/versions/vm_m5/vm.rs | 6 + core/lib/multivm/src/versions/vm_m6/vm.rs | 6 + .../src/versions/vm_refunds_enhancement/vm.rs | 6 + .../src/versions/vm_virtual_blocks/vm.rs | 6 + core/lib/multivm/src/vm_instance.rs | 4 + .../src/state_keeper/batch_executor/mod.rs | 4 + .../complex-upgrade/complex-upgrade.sol | 39 ++- 20 files changed, 433 insertions(+), 32 deletions(-) create mode 100644 core/lib/multivm/src/versions/vm_latest/tests/block_tip.rs diff --git a/core/lib/multivm/src/interface/traits/vm.rs b/core/lib/multivm/src/interface/traits/vm.rs index 1158588f849..dd31c00e98f 100644 --- a/core/lib/multivm/src/interface/traits/vm.rs +++ b/core/lib/multivm/src/interface/traits/vm.rs @@ -129,6 +129,9 @@ pub trait VmInterface { /// Record VM memory metrics. fn record_vm_memory_metrics(&self) -> VmMemoryMetrics; + /// Whether the VM still has enough gas to execute the batch tip + fn has_enough_gas_for_batch_tip(&self) -> bool; + /// Execute batch till the end and return the result, with final execution state /// and bootloader memory. fn finish_batch(&mut self) -> FinishedL1Batch { diff --git a/core/lib/multivm/src/versions/vm_1_3_2/vm.rs b/core/lib/multivm/src/versions/vm_1_3_2/vm.rs index e2d505f265e..ea5647c5636 100644 --- a/core/lib/multivm/src/versions/vm_1_3_2/vm.rs +++ b/core/lib/multivm/src/versions/vm_1_3_2/vm.rs @@ -284,6 +284,12 @@ impl VmInterface for Vm { } } + fn has_enough_gas_for_batch_tip(&self) -> bool { + // For this version this overhead has not been calculated and it has not been used with those versions. + // We return some value just in case for backwards compatibility + true + } + fn finish_batch(&mut self) -> FinishedL1Batch { self.vm .execute_till_block_end( diff --git a/core/lib/multivm/src/versions/vm_boojum_integration/constants.rs b/core/lib/multivm/src/versions/vm_boojum_integration/constants.rs index 29a67aa20a6..bf6a4947359 100644 --- a/core/lib/multivm/src/versions/vm_boojum_integration/constants.rs +++ b/core/lib/multivm/src/versions/vm_boojum_integration/constants.rs @@ -6,6 +6,9 @@ use zksync_system_constants::{L1_GAS_PER_PUBDATA_BYTE, MAX_L2_TX_GAS_LIMIT, MAX_ use crate::vm_boojum_integration::old_vm::utils::heap_page_from_base; +/// The amount of ergs to be reserved at the end of the batch to ensure that it has enough ergs to verify compression, etc. +pub(crate) const BOOTLOADER_BATCH_TIP_OVERHEAD: u32 = 80_000_000; + /// The size of the bootloader memory in bytes which is used by the protocol. /// While the maximal possible size is a lot higher, we restrict ourselves to a certain limit to reduce /// the requirements on RAM. diff --git a/core/lib/multivm/src/versions/vm_boojum_integration/vm.rs b/core/lib/multivm/src/versions/vm_boojum_integration/vm.rs index cd591088522..9e897ac3070 100644 --- a/core/lib/multivm/src/versions/vm_boojum_integration/vm.rs +++ b/core/lib/multivm/src/versions/vm_boojum_integration/vm.rs @@ -7,6 +7,7 @@ use zksync_types::{ }; use zksync_utils::bytecode::CompressedBytecodeInfo; +use super::constants::BOOTLOADER_BATCH_TIP_OVERHEAD; use crate::{ glue::GlueInto, interface::{ @@ -164,6 +165,10 @@ impl VmInterface for Vm { self.record_vm_memory_metrics_inner() } + fn has_enough_gas_for_batch_tip(&self) -> bool { + self.state.local_state.callstack.current.ergs_remaining >= BOOTLOADER_BATCH_TIP_OVERHEAD + } + fn finish_batch(&mut self) -> FinishedL1Batch { let result = self.execute(VmExecutionMode::Batch); let execution_state = self.get_current_execution_state(); diff --git a/core/lib/multivm/src/versions/vm_latest/constants.rs b/core/lib/multivm/src/versions/vm_latest/constants.rs index 1652a2f9424..5a12c0f2252 100644 --- a/core/lib/multivm/src/versions/vm_latest/constants.rs +++ b/core/lib/multivm/src/versions/vm_latest/constants.rs @@ -6,6 +6,9 @@ use zksync_system_constants::{MAX_L2_TX_GAS_LIMIT, MAX_NEW_FACTORY_DEPS}; use crate::vm_latest::old_vm::utils::heap_page_from_base; +/// The amount of ergs to be reserved at the end of the batch to ensure that it has enough ergs to verify compression, etc. +pub(crate) const BOOTLOADER_BATCH_TIP_OVERHEAD: u32 = 80_000_000; + /// The size of the bootloader memory in bytes which is used by the protocol. /// While the maximal possible size is a lot higher, we restrict ourselves to a certain limit to reduce /// the requirements on RAM. diff --git a/core/lib/multivm/src/versions/vm_latest/implementation/execution.rs b/core/lib/multivm/src/versions/vm_latest/implementation/execution.rs index 9052d01b335..21e5b16cd2d 100644 --- a/core/lib/multivm/src/versions/vm_latest/implementation/execution.rs +++ b/core/lib/multivm/src/versions/vm_latest/implementation/execution.rs @@ -22,6 +22,7 @@ impl Vm { &mut self, dispatcher: TracerDispatcher, execution_mode: VmExecutionMode, + custom_pubdata_tracer: Option>, ) -> VmExecutionResultAndLogs { let mut enable_refund_tracer = false; if let VmExecutionMode::OneTx = execution_mode { @@ -30,8 +31,12 @@ impl Vm { enable_refund_tracer = true; } - let (_, result) = - self.inspect_and_collect_results(dispatcher, execution_mode, enable_refund_tracer); + let (_, result) = self.inspect_and_collect_results( + dispatcher, + execution_mode, + enable_refund_tracer, + custom_pubdata_tracer, + ); result } @@ -42,6 +47,7 @@ impl Vm { dispatcher: TracerDispatcher, execution_mode: VmExecutionMode, with_refund_tracer: bool, + custom_pubdata_tracer: Option>, ) -> (VmExecutionStopReason, VmExecutionResultAndLogs) { let refund_tracers = with_refund_tracer.then_some(RefundsTracer::new(self.batch_env.clone())); @@ -51,7 +57,8 @@ impl Vm { dispatcher, self.storage.clone(), refund_tracers, - Some(PubdataTracer::new(self.batch_env.clone(), execution_mode)), + custom_pubdata_tracer + .or_else(|| Some(PubdataTracer::new(self.batch_env.clone(), execution_mode))), ); let timestamp_initial = Timestamp(self.state.local_state.timestamp); diff --git a/core/lib/multivm/src/versions/vm_latest/tests/block_tip.rs b/core/lib/multivm/src/versions/vm_latest/tests/block_tip.rs new file mode 100644 index 00000000000..fc6a2f26d6e --- /dev/null +++ b/core/lib/multivm/src/versions/vm_latest/tests/block_tip.rs @@ -0,0 +1,284 @@ +use std::borrow::BorrowMut; + +use ethabi::Token; +use zk_evm_1_4_1::{ + aux_structures::Timestamp, zkevm_opcode_defs::system_params::MAX_PUBDATA_PER_BLOCK, +}; +use zksync_contracts::load_sys_contract; +use zksync_system_constants::{ + CONTRACT_FORCE_DEPLOYER_ADDRESS, KNOWN_CODES_STORAGE_ADDRESS, L1_MESSENGER_ADDRESS, +}; +use zksync_types::{ + commitment::SerializeCommitment, get_code_key, l2_to_l1_log::L2ToL1Log, + writes::StateDiffRecord, Address, Execute, H256, U256, +}; +use zksync_utils::{bytecode::hash_bytecode, bytes_to_be_words, h256_to_u256, u256_to_h256}; + +use super::utils::{get_complex_upgrade_abi, read_complex_upgrade}; +use crate::{ + interface::{TxExecutionMode, VmExecutionMode, VmInterface}, + vm_latest::{ + constants::BOOTLOADER_BATCH_TIP_OVERHEAD, + tests::tester::{get_empty_storage, InMemoryStorageView, VmTesterBuilder}, + tracers::PubdataTracer, + HistoryEnabled, TracerDispatcher, + }, +}; + +#[derive(Debug, Clone, Default)] +struct L1MessengerTestData { + l2_to_l1_logs: usize, + messages: Vec>, + bytecodes: Vec>, + state_diffs: Vec, +} + +struct MimicCallInfo { + to: Address, + who_to_mimic: Address, + data: Vec, +} + +fn populate_mimic_calls(data: L1MessengerTestData) -> Vec { + let complex_upgrade = get_complex_upgrade_abi(); + let l1_messenger = load_sys_contract("L1Messenger"); + + let logs_mimic_calls = (0..data.l2_to_l1_logs).map(|_| MimicCallInfo { + to: L1_MESSENGER_ADDRESS, + who_to_mimic: KNOWN_CODES_STORAGE_ADDRESS, + data: l1_messenger + .function("sendL2ToL1Log") + .unwrap() + .encode_input(&[ + Token::Bool(false), + Token::FixedBytes(H256::random().0.to_vec()), + Token::FixedBytes(H256::random().0.to_vec()), + ]) + .unwrap(), + }); + let messages_mimic_calls = data.messages.iter().map(|message| MimicCallInfo { + to: L1_MESSENGER_ADDRESS, + who_to_mimic: KNOWN_CODES_STORAGE_ADDRESS, + data: l1_messenger + .function("sendToL1") + .unwrap() + .encode_input(&[Token::Bytes(message.clone())]) + .unwrap(), + }); + let bytecodes_mimic_calls = data.bytecodes.iter().map(|bytecode| MimicCallInfo { + to: L1_MESSENGER_ADDRESS, + who_to_mimic: KNOWN_CODES_STORAGE_ADDRESS, + data: l1_messenger + .function("requestBytecodeL1Publication") + .unwrap() + .encode_input(&[Token::FixedBytes(hash_bytecode(bytecode).0.to_vec())]) + .unwrap(), + }); + + let encoded_calls = logs_mimic_calls + .chain(messages_mimic_calls) + .chain(bytecodes_mimic_calls) + .map(|call| { + Token::Tuple(vec![ + Token::Address(call.to), + Token::Address(call.who_to_mimic), + Token::Bytes(call.data), + ]) + }) + .collect::>(); + + complex_upgrade + .function("mimicCalls") + .unwrap() + .encode_input(&[Token::Array(encoded_calls)]) + .unwrap() +} + +fn execute_test(test_data: L1MessengerTestData) -> u32 { + let mut storage = get_empty_storage(); + let complex_upgrade_code = read_complex_upgrade(); + + // For this test we'll just put the bytecode onto the force deployer address + storage.borrow_mut().set_value( + get_code_key(&CONTRACT_FORCE_DEPLOYER_ADDRESS), + hash_bytecode(&complex_upgrade_code), + ); + storage + .borrow_mut() + .store_factory_dep(hash_bytecode(&complex_upgrade_code), complex_upgrade_code); + + let mut vm = VmTesterBuilder::new(HistoryEnabled) + .with_storage(storage) + .with_execution_mode(TxExecutionMode::VerifyExecute) + .with_random_rich_accounts(1) + .build(); + + let bytecodes = test_data + .bytecodes + .iter() + .map(|bytecode| { + let hash = hash_bytecode(bytecode); + let words = bytes_to_be_words(bytecode.clone()); + (h256_to_u256(hash), words) + }) + .collect(); + vm.vm + .state + .decommittment_processor + .populate(bytecodes, Timestamp(0)); + + let data = populate_mimic_calls(test_data.clone()); + let account = &mut vm.rich_accounts[0]; + let tx = account.get_l2_tx_for_execute( + Execute { + contract_address: CONTRACT_FORCE_DEPLOYER_ADDRESS, + calldata: data, + value: U256::zero(), + factory_deps: None, + }, + None, + ); + + vm.vm.push_transaction(tx); + let result = vm.vm.execute(VmExecutionMode::OneTx); + assert!(!result.result.is_failed(), "Transaction wasn't successful"); + + // Now we count how much ergs were spent at the end of the batch + // It is assumed that the top level frame is the bootloader + + let ergs_before = vm.vm.state.local_state.callstack.current.ergs_remaining; + + // We ensure that indeed the provided state diffs are used + let pubdata_tracer = PubdataTracer::::new_with_forced_state_diffs( + vm.vm.batch_env.clone(), + VmExecutionMode::Batch, + test_data.state_diffs, + ); + + let result = vm.vm.inspect_inner( + TracerDispatcher::default(), + VmExecutionMode::Batch, + Some(pubdata_tracer), + ); + + assert!(!result.result.is_failed(), "Batch wasn't successful"); + + let ergs_after = vm.vm.state.local_state.callstack.current.ergs_remaining; + + ergs_before - ergs_after +} + +fn generate_state_diffs( + repeated_writes: bool, + small_diff: bool, + number_of_state_diffs: usize, +) -> Vec { + (0..number_of_state_diffs) + .map(|i| { + let address = Address::from_low_u64_be(i as u64); + let key = U256::from(i); + let enumeration_index = if repeated_writes { i + 1 } else { 0 }; + + let (initial_value, final_value) = if small_diff { + // As small as it gets, one byte to denote zeroing out the value + (U256::from(1), U256::from(0)) + } else { + // As large as it gets + (U256::from(0), U256::from(2).pow(255.into())) + }; + + StateDiffRecord { + address, + key, + derived_key: u256_to_h256(i.into()).0, + enumeration_index: enumeration_index as u64, + initial_value, + final_value, + } + }) + .collect() +} + +#[test] +fn test_dry_run_upper_bound() { + // We are re-using the `ComplexUpgrade` contract as it already has the `mimicCall` functionality. + // To get the upper bound, we'll try to do the following: + // 1. Max number of logs. + // 2. Lots of small L2->L1 messages / one large L2->L1 message. + // 3. Lots of small bytecodes / one large bytecode. + // 4. Lots of storage slot updates. + + let max_logs = execute_test(L1MessengerTestData { + l2_to_l1_logs: L2ToL1Log::MIN_L2_L1_LOGS_TREE_SIZE, + ..Default::default() + }); + + let max_messages = execute_test(L1MessengerTestData { + // Each L2->L1 message is accompanied by a Log, so the max number of pubdata is bound by it + messages: vec![vec![0; 0]; MAX_PUBDATA_PER_BLOCK as usize / L2ToL1Log::SERIALIZED_SIZE], + ..Default::default() + }); + + let long_message = execute_test(L1MessengerTestData { + // Each L2->L1 message is accompanied by a Log, so the max number of pubdata is bound by it + messages: vec![vec![0; MAX_PUBDATA_PER_BLOCK as usize]; 1], + ..Default::default() + }); + + let max_bytecodes = execute_test(L1MessengerTestData { + // Each bytecode must be at least 32 bytes long + bytecodes: vec![vec![0; 32]; MAX_PUBDATA_PER_BLOCK as usize / 32], + ..Default::default() + }); + + let long_bytecode = execute_test(L1MessengerTestData { + // We have to add 48 since a valid bytecode must have an odd number of 32 byte words + bytecodes: vec![vec![0; MAX_PUBDATA_PER_BLOCK as usize + 48]; 1], + ..Default::default() + }); + + let lots_of_small_repeated_writes = execute_test(L1MessengerTestData { + // In theory each state diff can require only 5 bytes to be published (enum index + 4 bytes for the key) + state_diffs: generate_state_diffs(true, true, MAX_PUBDATA_PER_BLOCK as usize / 5), + ..Default::default() + }); + + let lots_of_big_repeated_writes = execute_test(L1MessengerTestData { + // Each big write will approximately require 32 bytes to encode + state_diffs: generate_state_diffs(true, false, MAX_PUBDATA_PER_BLOCK as usize / 32), + ..Default::default() + }); + + let lots_of_small_initial_writes = execute_test(L1MessengerTestData { + // Each initial write will take at least 32 bytes for derived key + 5 bytes for value + state_diffs: generate_state_diffs(false, true, MAX_PUBDATA_PER_BLOCK as usize / 37), + ..Default::default() + }); + + let lots_of_large_initial_writes = execute_test(L1MessengerTestData { + // Each big write will take at least 32 bytes for derived key + 32 bytes for value + state_diffs: generate_state_diffs(false, false, MAX_PUBDATA_PER_BLOCK as usize / 64), + ..Default::default() + }); + + let max_used_gas = vec![ + max_logs, + max_messages, + long_message, + max_bytecodes, + long_bytecode, + lots_of_small_repeated_writes, + lots_of_big_repeated_writes, + lots_of_small_initial_writes, + lots_of_large_initial_writes, + ] + .into_iter() + .max() + .unwrap(); + + // We use 2x overhead for the batch tip compared to the worst estimated scenario. + assert!( + max_used_gas * 2 <= BOOTLOADER_BATCH_TIP_OVERHEAD, + "BOOTLOADER_BATCH_TIP_OVERHEAD is too low" + ); +} diff --git a/core/lib/multivm/src/versions/vm_latest/tests/mod.rs b/core/lib/multivm/src/versions/vm_latest/tests/mod.rs index b6c2cb654a8..a07608121bc 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/mod.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/mod.rs @@ -2,6 +2,7 @@ mod bootloader; mod default_aa; // TODO - fix this test // `mod invalid_bytecode;` +mod block_tip; mod bytecode_publishing; mod call_tracer; mod circuits; diff --git a/core/lib/multivm/src/versions/vm_latest/tests/tester/mod.rs b/core/lib/multivm/src/versions/vm_latest/tests/tester/mod.rs index dfe8905a7e0..c3cc5d8d980 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/tester/mod.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/tester/mod.rs @@ -1,5 +1,7 @@ pub(crate) use transaction_test_info::{ExpectedError, TransactionTestInfo, TxModifier}; -pub(crate) use vm_tester::{default_l1_batch, InMemoryStorageView, VmTester, VmTesterBuilder}; +pub(crate) use vm_tester::{ + default_l1_batch, get_empty_storage, InMemoryStorageView, VmTester, VmTesterBuilder, +}; pub(crate) use zksync_test_account::{Account, DeployContractsTx, TxType}; mod inner_state; diff --git a/core/lib/multivm/src/versions/vm_latest/tests/upgrade.rs b/core/lib/multivm/src/versions/vm_latest/tests/upgrade.rs index 1e2bdcb4515..8ab728e8ce3 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/upgrade.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/upgrade.rs @@ -1,5 +1,5 @@ use zk_evm_1_4_1::aux_structures::Timestamp; -use zksync_contracts::{deployer_contract, load_contract, load_sys_contract, read_bytecode}; +use zksync_contracts::{deployer_contract, load_sys_contract, read_bytecode}; use zksync_state::WriteStorage; use zksync_test_account::TxType; use zksync_types::{ @@ -12,14 +12,17 @@ use zksync_types::{ }; use zksync_utils::{bytecode::hash_bytecode, bytes_to_be_words, h256_to_u256, u256_to_h256}; -use super::utils::read_test_contract; +use super::utils::{get_complex_upgrade_abi, read_test_contract}; use crate::{ interface::{ ExecutionResult, Halt, TxExecutionMode, VmExecutionMode, VmInterface, VmInterfaceHistoryEnabled, }, vm_latest::{ - tests::{tester::VmTesterBuilder, utils::verify_required_storage}, + tests::{ + tester::VmTesterBuilder, + utils::{read_complex_upgrade, verify_required_storage}, + }, HistoryEnabled, }, }; @@ -343,20 +346,10 @@ fn get_complex_upgrade_tx( } } -fn read_complex_upgrade() -> Vec { - read_bytecode("etc/contracts-test-data/artifacts-zk/contracts/complex-upgrade/complex-upgrade.sol/ComplexUpgrade.json") -} - fn read_msg_sender_test() -> Vec { read_bytecode("etc/contracts-test-data/artifacts-zk/contracts/complex-upgrade/msg-sender.sol/MsgSenderTest.json") } -fn get_complex_upgrade_abi() -> Contract { - load_contract( - "etc/contracts-test-data/artifacts-zk/contracts/complex-upgrade/complex-upgrade.sol/ComplexUpgrade.json" - ) -} - fn get_complex_upgrader_abi() -> Contract { load_sys_contract("ComplexUpgrader") } diff --git a/core/lib/multivm/src/versions/vm_latest/tests/utils.rs b/core/lib/multivm/src/versions/vm_latest/tests/utils.rs index 7c937033a21..80d59ab709f 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/utils.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/utils.rs @@ -109,3 +109,13 @@ pub(crate) fn read_precompiles_contract() -> Vec { "etc/contracts-test-data/artifacts-zk/contracts/precompiles/precompiles.sol/Precompiles.json", ) } + +pub(crate) fn read_complex_upgrade() -> Vec { + read_bytecode("etc/contracts-test-data/artifacts-zk/contracts/complex-upgrade/complex-upgrade.sol/ComplexUpgrade.json") +} + +pub(crate) fn get_complex_upgrade_abi() -> Contract { + load_contract( + "etc/contracts-test-data/artifacts-zk/contracts/complex-upgrade/complex-upgrade.sol/ComplexUpgrade.json" + ) +} diff --git a/core/lib/multivm/src/versions/vm_latest/tracers/pubdata_tracer.rs b/core/lib/multivm/src/versions/vm_latest/tracers/pubdata_tracer.rs index 00b7c6f1056..2ff8c389608 100644 --- a/core/lib/multivm/src/versions/vm_latest/tracers/pubdata_tracer.rs +++ b/core/lib/multivm/src/versions/vm_latest/tracers/pubdata_tracer.rs @@ -40,6 +40,9 @@ pub(crate) struct PubdataTracer { l1_batch_env: L1BatchEnv, pubdata_info_requested: bool, execution_mode: VmExecutionMode, + // For testing purposes it might be helpful to supply an exact set of state diffs to be provided + // to the L1Messenger. + enforced_state_diffs: Option>, _phantom_data: PhantomData, } @@ -49,12 +52,28 @@ impl PubdataTracer { l1_batch_env, pubdata_info_requested: false, execution_mode, + enforced_state_diffs: None, + _phantom_data: Default::default(), + } + } + + // Creates the pubdata tracer with constant state diffs. + // To be used in tests only. + #[cfg(test)] + pub(crate) fn new_with_forced_state_diffs( + l1_batch_env: L1BatchEnv, + execution_mode: VmExecutionMode, + forced_state_diffs: Vec, + ) -> Self { + Self { + l1_batch_env, + pubdata_info_requested: false, + execution_mode, + enforced_state_diffs: Some(forced_state_diffs), _phantom_data: Default::default(), } } -} -impl PubdataTracer { // Packs part of L1 Messenger total pubdata that corresponds to // `L2toL1Logs` sent in the block fn get_total_user_logs( @@ -117,7 +136,14 @@ impl PubdataTracer { // Packs part of L1Messenger total pubdata that corresponds to // State diffs needed to be published on L1 - fn get_state_diffs(storage: &StorageOracle) -> Vec { + fn get_state_diffs( + &self, + storage: &StorageOracle, + ) -> Vec { + if let Some(enforced_state_diffs) = &self.enforced_state_diffs { + return enforced_state_diffs.clone(); + } + sort_storage_access_queries( storage .storage_log_queries_after_timestamp(Timestamp(0)) @@ -153,7 +179,7 @@ impl PubdataTracer { user_logs: self.get_total_user_logs(state), l2_to_l1_messages: self.get_total_l1_messenger_messages(state), published_bytecodes: self.get_total_published_bytecodes(state), - state_diffs: Self::get_state_diffs(&state.storage), + state_diffs: self.get_state_diffs(&state.storage), } } } diff --git a/core/lib/multivm/src/versions/vm_latest/vm.rs b/core/lib/multivm/src/versions/vm_latest/vm.rs index 7eaa52392a9..4fa8c58a0e1 100644 --- a/core/lib/multivm/src/versions/vm_latest/vm.rs +++ b/core/lib/multivm/src/versions/vm_latest/vm.rs @@ -7,6 +7,7 @@ use zksync_types::{ }; use zksync_utils::bytecode::CompressedBytecodeInfo; +use super::constants::BOOTLOADER_BATCH_TIP_OVERHEAD; use crate::{ glue::GlueInto, interface::{ @@ -65,7 +66,7 @@ impl VmInterface for Vm { tracer: Self::TracerDispatcher, execution_mode: VmExecutionMode, ) -> VmExecutionResultAndLogs { - self.inspect_inner(tracer, execution_mode) + self.inspect_inner(tracer, execution_mode, None) } /// Get current state of bootloader memory. @@ -149,7 +150,7 @@ impl VmInterface for Vm { VmExecutionResultAndLogs, ) { self.push_transaction_with_compression(tx, with_compression); - let result = self.inspect_inner(tracer, VmExecutionMode::OneTx); + let result = self.inspect_inner(tracer, VmExecutionMode::OneTx, None); if self.has_unpublished_bytecodes() { ( Err(BytecodeCompressionError::BytecodeCompressionFailed), @@ -164,6 +165,10 @@ impl VmInterface for Vm { self.record_vm_memory_metrics_inner() } + fn has_enough_gas_for_batch_tip(&self) -> bool { + self.state.local_state.callstack.current.ergs_remaining >= BOOTLOADER_BATCH_TIP_OVERHEAD + } + fn finish_batch(&mut self) -> FinishedL1Batch { let result = self.execute(VmExecutionMode::Batch); let execution_state = self.get_current_execution_state(); diff --git a/core/lib/multivm/src/versions/vm_m5/vm.rs b/core/lib/multivm/src/versions/vm_m5/vm.rs index fc074314ee0..472f0688248 100644 --- a/core/lib/multivm/src/versions/vm_m5/vm.rs +++ b/core/lib/multivm/src/versions/vm_m5/vm.rs @@ -224,6 +224,12 @@ impl VmInterface for Vm { } } + fn has_enough_gas_for_batch_tip(&self) -> bool { + // For this version this overhead has not been calculated and it has not been used with those versions. + // We return some value just in case for backwards compatibility + true + } + fn finish_batch(&mut self) -> FinishedL1Batch { self.vm .execute_till_block_end( diff --git a/core/lib/multivm/src/versions/vm_m6/vm.rs b/core/lib/multivm/src/versions/vm_m6/vm.rs index 11cc8183d4e..698041d6cb2 100644 --- a/core/lib/multivm/src/versions/vm_m6/vm.rs +++ b/core/lib/multivm/src/versions/vm_m6/vm.rs @@ -314,6 +314,12 @@ impl VmInterface for Vm { } } + fn has_enough_gas_for_batch_tip(&self) -> bool { + // For this version this overhead has not been calculated and it has not been used with those versions. + // We return some value just in case for backwards compatibility + true + } + fn finish_batch(&mut self) -> FinishedL1Batch { self.vm .execute_till_block_end( diff --git a/core/lib/multivm/src/versions/vm_refunds_enhancement/vm.rs b/core/lib/multivm/src/versions/vm_refunds_enhancement/vm.rs index 4e97478383e..2e4df854e5a 100644 --- a/core/lib/multivm/src/versions/vm_refunds_enhancement/vm.rs +++ b/core/lib/multivm/src/versions/vm_refunds_enhancement/vm.rs @@ -151,6 +151,12 @@ impl VmInterface for Vm { } } + fn has_enough_gas_for_batch_tip(&self) -> bool { + // For this version this overhead has not been calculated and it has not been used with those versions. + // We return some value just in case for backwards compatibility + true + } + fn record_vm_memory_metrics(&self) -> VmMemoryMetrics { self.record_vm_memory_metrics_inner() } diff --git a/core/lib/multivm/src/versions/vm_virtual_blocks/vm.rs b/core/lib/multivm/src/versions/vm_virtual_blocks/vm.rs index 98ad1d66a84..7afbaab076d 100644 --- a/core/lib/multivm/src/versions/vm_virtual_blocks/vm.rs +++ b/core/lib/multivm/src/versions/vm_virtual_blocks/vm.rs @@ -151,6 +151,12 @@ impl VmInterface for Vm { } } + fn has_enough_gas_for_batch_tip(&self) -> bool { + // For this version this overhead has not been calculated and it has not been used with those versions. + // We return some value just in case for backwards compatibility + true + } + fn record_vm_memory_metrics(&self) -> VmMemoryMetrics { self.record_vm_memory_metrics_inner() } diff --git a/core/lib/multivm/src/vm_instance.rs b/core/lib/multivm/src/vm_instance.rs index 4eaca6f44b0..44b70db07d8 100644 --- a/core/lib/multivm/src/vm_instance.rs +++ b/core/lib/multivm/src/vm_instance.rs @@ -116,6 +116,10 @@ impl VmInterface for VmInstance { dispatch_vm!(self.record_vm_memory_metrics()) } + fn has_enough_gas_for_batch_tip(&self) -> bool { + dispatch_vm!(self.has_enough_gas_for_batch_tip()) + } + /// Return the results of execution of all batch fn finish_batch(&mut self) -> FinishedL1Batch { dispatch_vm!(self.finish_batch()) diff --git a/core/lib/zksync_core/src/state_keeper/batch_executor/mod.rs b/core/lib/zksync_core/src/state_keeper/batch_executor/mod.rs index 5411a4baee1..65c71846478 100644 --- a/core/lib/zksync_core/src/state_keeper/batch_executor/mod.rs +++ b/core/lib/zksync_core/src/state_keeper/batch_executor/mod.rs @@ -395,6 +395,10 @@ impl BatchExecutor { let tx_metrics = ExecutionMetricsForCriteria::new(Some(tx), &tx_result); + if !vm.has_enough_gas_for_batch_tip() { + return TxExecutionResult::BootloaderOutOfGasForBlockTip; + } + let (bootloader_dry_run_result, bootloader_dry_run_metrics) = self.dryrun_block_tip(vm); match &bootloader_dry_run_result.result { ExecutionResult::Success { .. } => TxExecutionResult::Success { diff --git a/etc/contracts-test-data/contracts/complex-upgrade/complex-upgrade.sol b/etc/contracts-test-data/contracts/complex-upgrade/complex-upgrade.sol index 83321ec4727..e65f51d5652 100644 --- a/etc/contracts-test-data/contracts/complex-upgrade/complex-upgrade.sol +++ b/etc/contracts-test-data/contracts/complex-upgrade/complex-upgrade.sol @@ -11,18 +11,24 @@ import "./msg-sender.sol"; contract ComplexUpgrade { constructor() {} - function mimicCall( - address _address, - address _whoToMimic, - bytes memory _calldata - ) internal { + struct MimicCallInfo { + address to; + address whoToMimic; + bytes data; + } + + function _mimicCall(MimicCallInfo memory info) internal { address callAddr = MIMIC_CALL_CALL_ADDRESS; + bytes memory data = info.data; + address to = info.to; + address whoToMimic = info.whoToMimic; + uint32 dataStart; uint32 dataLength; assembly { - dataStart := add(_calldata, 0x20) - dataLength := mload(_calldata) + dataStart := add(data, 0x20) + dataLength := mload(data) } uint256 farCallAbi = SystemContractsCaller.getFarCallABI( @@ -39,7 +45,7 @@ contract ComplexUpgrade { ); assembly { - let success := call(_address, callAddr, 0, farCallAbi, _whoToMimic, 0, 0) + let success := call(to, callAddr, 0, farCallAbi, whoToMimic, 0, 0) if iszero(success) { returndatacopy(0, 0, returndatasize()) @@ -48,6 +54,14 @@ contract ComplexUpgrade { } } + function mimicCalls( + MimicCallInfo[] memory info + ) public { + for (uint256 i = 0; i < info.length; i++) { + _mimicCall(info[i]); + } + } + // This function is used to imitate some complex upgrade logic function someComplexUpgrade( address _address1, @@ -86,6 +100,13 @@ contract ComplexUpgrade { MsgSenderTest.testMsgSender.selector, toMimic ); - mimicCall(address(msgSenderTest), toMimic, _mimicCallCalldata); + + MimicCallInfo memory info = MimicCallInfo({ + to: address(msgSenderTest), + whoToMimic: toMimic, + data: _mimicCallCalldata + }); + + _mimicCall(info); } }