From 0a010f0a6f6682cedc49cb12ab9f9dfcdbccf68e Mon Sep 17 00:00:00 2001 From: perekopskiy <53865202+perekopskiy@users.noreply.github.com> Date: Mon, 8 Jan 2024 17:52:41 +0200 Subject: [PATCH] feat(state-keeper): Reject transactions that fail to publish bytecodes (#832) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What ❔ State keeper and API reject transactions that fail to publish bytecodes ## 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 `cargo spellcheck --cfg=./spellcheck/era.cfg --code 1`. --- core/bin/external_node/src/main.rs | 1 + core/lib/multivm/src/interface/traits/vm.rs | 10 ++- .../src/interface/types/errors/halt.rs | 4 ++ core/lib/multivm/src/versions/vm_1_3_2/vm.rs | 12 +++- .../src/versions/vm_boojum_integration/vm.rs | 12 +++- core/lib/multivm/src/versions/vm_latest/vm.rs | 12 +++- core/lib/multivm/src/versions/vm_m5/vm.rs | 7 +- core/lib/multivm/src/versions/vm_m6/vm.rs | 12 +++- .../src/versions/vm_refunds_enhancement/vm.rs | 12 +++- .../src/versions/vm_virtual_blocks/vm.rs | 12 +++- core/lib/multivm/src/vm_instance.rs | 14 ++-- .../src/api_server/execution_sandbox/error.rs | 3 + .../api_server/execution_sandbox/execute.rs | 17 +++-- .../src/api_server/tx_sender/mod.rs | 8 ++- .../src/api_server/tx_sender/result.rs | 3 + .../vm_interactions.rs | 2 + .../src/state_keeper/batch_executor/mod.rs | 69 ++++++++++++++++--- .../batch_executor/tests/tester.rs | 1 + .../src/state_keeper/io/mempool.rs | 2 +- core/lib/zksync_core/src/state_keeper/mod.rs | 1 + .../ts-integration/tests/contracts.test.ts | 24 +++++++ 21 files changed, 192 insertions(+), 46 deletions(-) diff --git a/core/bin/external_node/src/main.rs b/core/bin/external_node/src/main.rs index b20ab0a9fb81..7098ddcd1a6a 100644 --- a/core/bin/external_node/src/main.rs +++ b/core/bin/external_node/src/main.rs @@ -74,6 +74,7 @@ async fn build_state_keeper( save_call_traces, false, config.optional.enum_index_migration_chunk_size, + true, )); let main_node_url = config.required.main_node_url().unwrap(); diff --git a/core/lib/multivm/src/interface/traits/vm.rs b/core/lib/multivm/src/interface/traits/vm.rs index cfee13d2031b..1158588f8499 100644 --- a/core/lib/multivm/src/interface/traits/vm.rs +++ b/core/lib/multivm/src/interface/traits/vm.rs @@ -104,7 +104,10 @@ pub trait VmInterface { &mut self, tx: Transaction, with_compression: bool, - ) -> Result { + ) -> ( + Result<(), BytecodeCompressionError>, + VmExecutionResultAndLogs, + ) { self.inspect_transaction_with_bytecode_compression( Self::TracerDispatcher::default(), tx, @@ -118,7 +121,10 @@ pub trait VmInterface { tracer: Self::TracerDispatcher, tx: Transaction, with_compression: bool, - ) -> Result; + ) -> ( + Result<(), BytecodeCompressionError>, + VmExecutionResultAndLogs, + ); /// Record VM memory metrics. fn record_vm_memory_metrics(&self) -> VmMemoryMetrics; diff --git a/core/lib/multivm/src/interface/types/errors/halt.rs b/core/lib/multivm/src/interface/types/errors/halt.rs index c302467dacee..70de7548f14e 100644 --- a/core/lib/multivm/src/interface/types/errors/halt.rs +++ b/core/lib/multivm/src/interface/types/errors/halt.rs @@ -41,6 +41,7 @@ pub enum Halt { FailedToAppendTransactionToL2Block(String), VMPanic, TracerCustom(String), + FailedToPublishCompressedBytecodes, } impl Display for Halt { @@ -112,6 +113,9 @@ impl Display for Halt { Halt::ValidationOutOfGas => { write!(f, "Validation run out of gas") } + Halt::FailedToPublishCompressedBytecodes => { + write!(f, "Failed to publish compressed bytecodes") + } } } } 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 f0cf5d9c1aa2..50fa44380f58 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 @@ -162,7 +162,10 @@ impl VmInterface for Vm { _tracer: Self::TracerDispatcher, tx: Transaction, with_compression: bool, - ) -> Result { + ) -> ( + Result<(), BytecodeCompressionError>, + VmExecutionResultAndLogs, + ) { self.last_tx_compressed_bytecodes = vec![]; let bytecodes = if with_compression { let deps = tx.execute.factory_deps.as_deref().unwrap_or_default(); @@ -209,9 +212,12 @@ impl VmInterface for Vm { .iter() .any(|info| !self.vm.is_bytecode_known(info)) { - Err(crate::interface::BytecodeCompressionError::BytecodeCompressionFailed) + ( + Err(BytecodeCompressionError::BytecodeCompressionFailed), + result.glue_into(), + ) } else { - Ok(result.glue_into()) + (Ok(()), result.glue_into()) } } 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 a602ebbb9d71..425833bd910f 100644 --- a/core/lib/multivm/src/versions/vm_boojum_integration/vm.rs +++ b/core/lib/multivm/src/versions/vm_boojum_integration/vm.rs @@ -127,13 +127,19 @@ impl VmInterface for Vm { tracer: Self::TracerDispatcher, tx: Transaction, with_compression: bool, - ) -> Result { + ) -> ( + Result<(), BytecodeCompressionError>, + VmExecutionResultAndLogs, + ) { self.push_transaction_with_compression(tx, with_compression); let result = self.inspect_inner(tracer, VmExecutionMode::OneTx); if self.has_unpublished_bytecodes() { - Err(BytecodeCompressionError::BytecodeCompressionFailed) + ( + Err(BytecodeCompressionError::BytecodeCompressionFailed), + result, + ) } else { - Ok(result) + (Ok(()), result) } } diff --git a/core/lib/multivm/src/versions/vm_latest/vm.rs b/core/lib/multivm/src/versions/vm_latest/vm.rs index 920c4fefaab6..6b571cd25c40 100644 --- a/core/lib/multivm/src/versions/vm_latest/vm.rs +++ b/core/lib/multivm/src/versions/vm_latest/vm.rs @@ -127,13 +127,19 @@ impl VmInterface for Vm { tracer: Self::TracerDispatcher, tx: Transaction, with_compression: bool, - ) -> Result { + ) -> ( + Result<(), BytecodeCompressionError>, + VmExecutionResultAndLogs, + ) { self.push_transaction_with_compression(tx, with_compression); let result = self.inspect_inner(tracer, VmExecutionMode::OneTx); if self.has_unpublished_bytecodes() { - Err(BytecodeCompressionError::BytecodeCompressionFailed) + ( + Err(BytecodeCompressionError::BytecodeCompressionFailed), + result, + ) } else { - Ok(result) + (Ok(()), result) } } diff --git a/core/lib/multivm/src/versions/vm_m5/vm.rs b/core/lib/multivm/src/versions/vm_m5/vm.rs index 037388d10612..08fa783cbdbe 100644 --- a/core/lib/multivm/src/versions/vm_m5/vm.rs +++ b/core/lib/multivm/src/versions/vm_m5/vm.rs @@ -172,13 +172,16 @@ impl VmInterface for Vm { _tracer: Self::TracerDispatcher, tx: Transaction, _with_compression: bool, - ) -> Result { + ) -> ( + Result<(), BytecodeCompressionError>, + VmExecutionResultAndLogs, + ) { crate::vm_m5::vm_with_bootloader::push_transaction_to_bootloader_memory( &mut self.vm, &tx, self.system_env.execution_mode.glue_into(), ); - Ok(self.execute(VmExecutionMode::OneTx)) + (Ok(()), self.execute(VmExecutionMode::OneTx)) } fn record_vm_memory_metrics(&self) -> VmMemoryMetrics { diff --git a/core/lib/multivm/src/versions/vm_m6/vm.rs b/core/lib/multivm/src/versions/vm_m6/vm.rs index 938b47784de9..420b5c8f9bf7 100644 --- a/core/lib/multivm/src/versions/vm_m6/vm.rs +++ b/core/lib/multivm/src/versions/vm_m6/vm.rs @@ -179,7 +179,10 @@ impl VmInterface for Vm { _tracer: Self::TracerDispatcher, tx: Transaction, with_compression: bool, - ) -> Result { + ) -> ( + Result<(), BytecodeCompressionError>, + VmExecutionResultAndLogs, + ) { self.last_tx_compressed_bytecodes = vec![]; let bytecodes = if with_compression { let deps = tx.execute.factory_deps.as_deref().unwrap_or_default(); @@ -226,9 +229,12 @@ impl VmInterface for Vm { .iter() .any(|info| !self.vm.is_bytecode_exists(info)) { - Err(crate::interface::BytecodeCompressionError::BytecodeCompressionFailed) + ( + Err(BytecodeCompressionError::BytecodeCompressionFailed), + result.glue_into(), + ) } else { - Ok(result.glue_into()) + (Ok(()), result.glue_into()) } } 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 678a467d4474..f1554ee17615 100644 --- a/core/lib/multivm/src/versions/vm_refunds_enhancement/vm.rs +++ b/core/lib/multivm/src/versions/vm_refunds_enhancement/vm.rs @@ -118,13 +118,19 @@ impl VmInterface for Vm { dispatcher: Self::TracerDispatcher, tx: Transaction, with_compression: bool, - ) -> Result { + ) -> ( + Result<(), BytecodeCompressionError>, + VmExecutionResultAndLogs, + ) { self.push_transaction_with_compression(tx, with_compression); let result = self.inspect(dispatcher, VmExecutionMode::OneTx); if self.has_unpublished_bytecodes() { - Err(BytecodeCompressionError::BytecodeCompressionFailed) + ( + Err(BytecodeCompressionError::BytecodeCompressionFailed), + result, + ) } else { - Ok(result) + (Ok(()), result) } } diff --git a/core/lib/multivm/src/versions/vm_virtual_blocks/vm.rs b/core/lib/multivm/src/versions/vm_virtual_blocks/vm.rs index ed05e9514753..3bb43669f008 100644 --- a/core/lib/multivm/src/versions/vm_virtual_blocks/vm.rs +++ b/core/lib/multivm/src/versions/vm_virtual_blocks/vm.rs @@ -118,13 +118,19 @@ impl VmInterface for Vm { tracer: TracerDispatcher, tx: Transaction, with_compression: bool, - ) -> Result { + ) -> ( + Result<(), BytecodeCompressionError>, + VmExecutionResultAndLogs, + ) { self.push_transaction_with_compression(tx, with_compression); let result = self.inspect_inner(tracer, VmExecutionMode::OneTx); if self.has_unpublished_bytecodes() { - Err(BytecodeCompressionError::BytecodeCompressionFailed) + ( + Err(BytecodeCompressionError::BytecodeCompressionFailed), + result, + ) } else { - Ok(result) + (Ok(()), result) } } diff --git a/core/lib/multivm/src/vm_instance.rs b/core/lib/multivm/src/vm_instance.rs index 88b6a5275122..ea3ad74f5302 100644 --- a/core/lib/multivm/src/vm_instance.rs +++ b/core/lib/multivm/src/vm_instance.rs @@ -5,8 +5,8 @@ use zksync_utils::bytecode::CompressedBytecodeInfo; use crate::{ glue::history_mode::HistoryMode, interface::{ - BootloaderMemory, CurrentExecutionState, FinishedL1Batch, L1BatchEnv, L2BlockEnv, - SystemEnv, VmExecutionMode, VmExecutionResultAndLogs, VmInterface, + BootloaderMemory, BytecodeCompressionError, CurrentExecutionState, FinishedL1Batch, + L1BatchEnv, L2BlockEnv, SystemEnv, VmExecutionMode, VmExecutionResultAndLogs, VmInterface, VmInterfaceHistoryEnabled, VmMemoryMetrics, }, tracers::TracerDispatcher, @@ -86,7 +86,10 @@ impl VmInterface for VmInstance { &mut self, tx: zksync_types::Transaction, with_compression: bool, - ) -> Result { + ) -> ( + Result<(), BytecodeCompressionError>, + VmExecutionResultAndLogs, + ) { dispatch_vm!(self.execute_transaction_with_bytecode_compression(tx, with_compression)) } @@ -96,7 +99,10 @@ impl VmInterface for VmInstance { dispatcher: Self::TracerDispatcher, tx: zksync_types::Transaction, with_compression: bool, - ) -> Result { + ) -> ( + Result<(), BytecodeCompressionError>, + VmExecutionResultAndLogs, + ) { dispatch_vm!(self.inspect_transaction_with_bytecode_compression( dispatcher.into(), tx, diff --git a/core/lib/zksync_core/src/api_server/execution_sandbox/error.rs b/core/lib/zksync_core/src/api_server/execution_sandbox/error.rs index c5928cfd8470..9d6d635a344c 100644 --- a/core/lib/zksync_core/src/api_server/execution_sandbox/error.rs +++ b/core/lib/zksync_core/src/api_server/execution_sandbox/error.rs @@ -64,6 +64,9 @@ impl From for SandboxExecutionError { Halt::ValidationOutOfGas => Self::AccountValidationFailed( "The validation of the transaction ran out of gas".to_string(), ), + Halt::FailedToPublishCompressedBytecodes => { + Self::UnexpectedVMBehavior("Failed to publish compressed bytecodes".to_string()) + } } } } diff --git a/core/lib/zksync_core/src/api_server/execution_sandbox/execute.rs b/core/lib/zksync_core/src/api_server/execution_sandbox/execute.rs index 7e65cd9a8931..cbff25698cf7 100644 --- a/core/lib/zksync_core/src/api_server/execution_sandbox/execute.rs +++ b/core/lib/zksync_core/src/api_server/execution_sandbox/execute.rs @@ -1,7 +1,7 @@ //! Implementation of "executing" methods, e.g. `eth_call`. use multivm::{ - interface::{TxExecutionMode, VmExecutionMode, VmExecutionResultAndLogs, VmInterface}, + interface::{TxExecutionMode, VmExecutionResultAndLogs, VmInterface}, tracers::StorageInvocations, vm_latest::constants::ETH_CALL_GAS_LIMIT, MultiVMTracer, @@ -94,7 +94,7 @@ pub(crate) async fn execute_tx_eth_call( // limiting the amount of gas the call can use. // We can't use `BLOCK_ERGS_LIMIT` here since the VM itself has some overhead. tx.common_data.fee.gas_limit = ETH_CALL_GAS_LIMIT.into(); - let (vm_result, _) = execute_tx_in_sandbox( + let (vm_result, _, _) = execute_tx_in_sandbox( vm_permit, shared_args, false, @@ -125,14 +125,14 @@ pub(crate) async fn execute_tx_in_sandbox( tx: Transaction, block_args: BlockArgs, custom_tracers: Vec, -) -> (VmExecutionResultAndLogs, TransactionExecutionMetrics) { +) -> (VmExecutionResultAndLogs, TransactionExecutionMetrics, bool) { let total_factory_deps = tx .execute .factory_deps .as_ref() .map_or(0, |deps| deps.len() as u16); - let execution_result = tokio::task::spawn_blocking(move || { + let (published_bytecodes, execution_result) = tokio::task::spawn_blocking(move || { let span = span!(Level::DEBUG, "execute_in_sandbox").entered(); let result = apply::apply_vm_in_sandbox( vm_permit, @@ -143,7 +143,6 @@ pub(crate) async fn execute_tx_in_sandbox( tx, block_args, |vm, tx| { - vm.push_transaction(tx); let storage_invocation_tracer = StorageInvocations::new(execution_args.missed_storage_invocation_limit); let custom_tracers: Vec<_> = custom_tracers @@ -151,7 +150,7 @@ pub(crate) async fn execute_tx_in_sandbox( .map(|tracer| tracer.into_boxed()) .chain(vec![storage_invocation_tracer.into_tracer_pointer()]) .collect(); - vm.inspect(custom_tracers.into(), VmExecutionMode::OneTx) + vm.inspect_transaction_with_bytecode_compression(custom_tracers.into(), tx, true) }, ); span.exit(); @@ -162,5 +161,9 @@ pub(crate) async fn execute_tx_in_sandbox( let tx_execution_metrics = vm_metrics::collect_tx_execution_metrics(total_factory_deps, &execution_result); - (execution_result, tx_execution_metrics) + ( + execution_result, + tx_execution_metrics, + published_bytecodes.is_ok(), + ) } diff --git a/core/lib/zksync_core/src/api_server/tx_sender/mod.rs b/core/lib/zksync_core/src/api_server/tx_sender/mod.rs index d79daf8f2575..c2fc8beb5761 100644 --- a/core/lib/zksync_core/src/api_server/tx_sender/mod.rs +++ b/core/lib/zksync_core/src/api_server/tx_sender/mod.rs @@ -283,7 +283,7 @@ impl TxSender { let block_args = BlockArgs::pending(&mut connection).await; drop(connection); - let (_, tx_metrics) = execute_tx_in_sandbox( + let (_, tx_metrics, published_bytecodes) = execute_tx_in_sandbox( vm_permit.clone(), shared_args.clone(), true, @@ -319,6 +319,10 @@ impl TxSender { return Err(err.into()); } + if !published_bytecodes { + return Err(SubmitTxError::FailedToPublishCompressedBytecodes); + } + let stage_started_at = Instant::now(); self.ensure_tx_executable(tx.clone().into(), &tx_metrics, true)?; @@ -588,7 +592,7 @@ impl TxSender { let vm_execution_cache_misses_limit = self.0.sender_config.vm_execution_cache_misses_limit; let execution_args = TxExecutionArgs::for_gas_estimate(vm_execution_cache_misses_limit, &tx, base_fee); - let (exec_result, tx_metrics) = execute_tx_in_sandbox( + let (exec_result, tx_metrics, _) = execute_tx_in_sandbox( vm_permit, shared_args, true, diff --git a/core/lib/zksync_core/src/api_server/tx_sender/result.rs b/core/lib/zksync_core/src/api_server/tx_sender/result.rs index 2749e2a13c35..a8183c5e8ac4 100644 --- a/core/lib/zksync_core/src/api_server/tx_sender/result.rs +++ b/core/lib/zksync_core/src/api_server/tx_sender/result.rs @@ -69,6 +69,8 @@ pub enum SubmitTxError { /// Error returned from main node #[error("{0}")] ProxyError(#[from] zksync_web3_decl::jsonrpsee::core::ClientError), + #[error("not enough gas to publish compressed bytecodes")] + FailedToPublishCompressedBytecodes, } impl SubmitTxError { @@ -99,6 +101,7 @@ impl SubmitTxError { Self::InsufficientFundsForTransfer => "insufficient-funds-for-transfer", Self::IntrinsicGas => "intrinsic-gas", Self::ProxyError(_) => "proxy-error", + Self::FailedToPublishCompressedBytecodes => "failed-to-publish-compressed-bytecodes", } } diff --git a/core/lib/zksync_core/src/basic_witness_input_producer/vm_interactions.rs b/core/lib/zksync_core/src/basic_witness_input_producer/vm_interactions.rs index e655112fade2..8ad2a66155de 100644 --- a/core/lib/zksync_core/src/basic_witness_input_producer/vm_interactions.rs +++ b/core/lib/zksync_core/src/basic_witness_input_producer/vm_interactions.rs @@ -74,6 +74,7 @@ pub(super) fn execute_tx( vm.make_snapshot(); if vm .execute_transaction_with_bytecode_compression(tx.clone(), true) + .0 .is_ok() { vm.pop_snapshot_no_rollback(); @@ -84,6 +85,7 @@ pub(super) fn execute_tx( vm.rollback_to_the_latest_snapshot(); if vm .execute_transaction_with_bytecode_compression(tx.clone(), false) + .0 .is_err() { return Err(anyhow!("compression can't fail if we don't apply it")); 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 dc8a6b548b9f..8b8749c13adb 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 @@ -89,6 +89,7 @@ pub struct MainBatchExecutorBuilder { max_allowed_tx_gas_limit: U256, upload_witness_inputs_to_gcs: bool, enum_index_migration_chunk_size: usize, + optional_bytecode_compression: bool, } impl MainBatchExecutorBuilder { @@ -99,6 +100,7 @@ impl MainBatchExecutorBuilder { save_call_traces: bool, upload_witness_inputs_to_gcs: bool, enum_index_migration_chunk_size: usize, + optional_bytecode_compression: bool, ) -> Self { Self { state_keeper_db_path, @@ -107,6 +109,7 @@ impl MainBatchExecutorBuilder { max_allowed_tx_gas_limit, upload_witness_inputs_to_gcs, enum_index_migration_chunk_size, + optional_bytecode_compression, } } } @@ -135,6 +138,7 @@ impl L1BatchExecutorBuilder for MainBatchExecutorBuilder { l1_batch_params, system_env, self.upload_witness_inputs_to_gcs, + self.optional_bytecode_compression, ) } } @@ -158,6 +162,7 @@ impl BatchExecutorHandle { l1_batch_env: L1BatchEnv, system_env: SystemEnv, upload_witness_inputs_to_gcs: bool, + optional_bytecode_compression: bool, ) -> Self { // Since we process `BatchExecutor` commands one-by-one (the next command is never enqueued // until a previous command is processed), capacity 1 is enough for the commands channel. @@ -165,6 +170,7 @@ impl BatchExecutorHandle { let executor = BatchExecutor { save_call_traces, max_allowed_tx_gas_limit, + optional_bytecode_compression, commands: commands_receiver, }; @@ -285,6 +291,7 @@ pub(super) enum Command { pub(super) struct BatchExecutor { save_call_traces: bool, max_allowed_tx_gas_limit: U256, + optional_bytecode_compression: bool, commands: mpsc::Receiver, } @@ -364,7 +371,12 @@ impl BatchExecutor { // Execute the transaction. let latency = KEEPER_METRICS.tx_execution_time[&TxExecutionStage::Execution].start(); - let (tx_result, compressed_bytecodes, call_tracer_result) = self.execute_tx_in_vm(tx, vm); + let (tx_result, compressed_bytecodes, call_tracer_result) = + if self.optional_bytecode_compression { + self.execute_tx_in_vm_with_optional_compression(tx, vm) + } else { + self.execute_tx_in_vm(tx, vm) + }; latency.observe(); APP_METRICS.processed_txs[&TxStage::StateKeeper].inc(); APP_METRICS.processed_l1_txs[&TxStage::StateKeeper].inc_by(tx.is_l1().into()); @@ -432,11 +444,7 @@ impl BatchExecutor { result } - // Err when transaction is rejected. - // `Ok(TxExecutionStatus::Success)` when the transaction succeeded - // `Ok(TxExecutionStatus::Failure)` when the transaction failed. - // Note that failed transactions are considered properly processed and are included in blocks - fn execute_tx_in_vm( + fn execute_tx_in_vm_with_optional_compression( &self, tx: &Transaction, vm: &mut VmInstance, @@ -464,7 +472,7 @@ impl BatchExecutor { vec![] }; - if let Ok(result) = + if let (Ok(()), result) = vm.inspect_transaction_with_bytecode_compression(tracer.into(), tx.clone(), true) { let compressed_bytecodes = vm.get_last_tx_compressed_bytecodes(); @@ -485,8 +493,10 @@ impl BatchExecutor { vec![] }; - let result = vm - .inspect_transaction_with_bytecode_compression(tracer.into(), tx.clone(), false) + let result = + vm.inspect_transaction_with_bytecode_compression(tracer.into(), tx.clone(), false); + result + .0 .expect("Compression can't fail if we don't apply it"); let compressed_bytecodes = vm.get_last_tx_compressed_bytecodes(); @@ -496,7 +506,46 @@ impl BatchExecutor { .unwrap() .take() .unwrap_or_default(); - (result, compressed_bytecodes, trace) + (result.1, compressed_bytecodes, trace) + } + + // Err when transaction is rejected. + // `Ok(TxExecutionStatus::Success)` when the transaction succeeded + // `Ok(TxExecutionStatus::Failure)` when the transaction failed. + // Note that failed transactions are considered properly processed and are included in blocks + fn execute_tx_in_vm( + &self, + tx: &Transaction, + vm: &mut VmInstance, + ) -> ( + VmExecutionResultAndLogs, + Vec, + Vec, + ) { + let call_tracer_result = Arc::new(OnceCell::default()); + let tracer = if self.save_call_traces { + vec![CallTracer::new(call_tracer_result.clone()).into_tracer_pointer()] + } else { + vec![] + }; + + let (published_bytecodes, mut result) = + vm.inspect_transaction_with_bytecode_compression(tracer.into(), tx.clone(), true); + if published_bytecodes.is_ok() { + let compressed_bytecodes = vm.get_last_tx_compressed_bytecodes(); + + let trace = Arc::try_unwrap(call_tracer_result) + .unwrap() + .take() + .unwrap_or_default(); + (result, compressed_bytecodes, trace) + } else { + // Transaction failed to publish bytecodes, we reject it so initiator doesn't pay fee. + result.result = ExecutionResult::Halt { + reason: Halt::FailedToPublishCompressedBytecodes, + }; + (result, Default::default(), Default::default()) + } } fn dryrun_block_tip( diff --git a/core/lib/zksync_core/src/state_keeper/batch_executor/tests/tester.rs b/core/lib/zksync_core/src/state_keeper/batch_executor/tests/tester.rs index 6417c65a5f8f..6e7e5681b330 100644 --- a/core/lib/zksync_core/src/state_keeper/batch_executor/tests/tester.rs +++ b/core/lib/zksync_core/src/state_keeper/batch_executor/tests/tester.rs @@ -112,6 +112,7 @@ impl Tester { l1_batch, system_env, self.config.upload_witness_inputs_to_gcs, + false, ) } diff --git a/core/lib/zksync_core/src/state_keeper/io/mempool.rs b/core/lib/zksync_core/src/state_keeper/io/mempool.rs index 45484f645b5e..f80b57358253 100644 --- a/core/lib/zksync_core/src/state_keeper/io/mempool.rs +++ b/core/lib/zksync_core/src/state_keeper/io/mempool.rs @@ -151,7 +151,7 @@ impl StateKeeperIO for MempoolIO { ); let current_timestamp = current_timestamp.await.ok()?; - tracing::info!( + tracing::trace!( "(l1_gas_price, fair_l2_gas_price) for L1 batch #{} is ({}, {})", self.current_l1_batch_number.0, self.filter.l1_gas_price, diff --git a/core/lib/zksync_core/src/state_keeper/mod.rs b/core/lib/zksync_core/src/state_keeper/mod.rs index a17d169bc6ea..dc5016772a7b 100644 --- a/core/lib/zksync_core/src/state_keeper/mod.rs +++ b/core/lib/zksync_core/src/state_keeper/mod.rs @@ -52,6 +52,7 @@ pub(crate) async fn create_state_keeper( state_keeper_config.save_call_traces, state_keeper_config.upload_witness_inputs_to_gcs, state_keeper_config.enum_index_migration_chunk_size(), + false, ); let io = MempoolIO::new( diff --git a/core/tests/ts-integration/tests/contracts.test.ts b/core/tests/ts-integration/tests/contracts.test.ts index fea1b15845a5..e7d5fcf3a239 100644 --- a/core/tests/ts-integration/tests/contracts.test.ts +++ b/core/tests/ts-integration/tests/contracts.test.ts @@ -306,6 +306,30 @@ describe('Smart contract behavior checks', () => { ).toBeAccepted([]); }); + test('Should reject tx with not enough gas for publishing bytecode', async () => { + // Send a transaction with big unique factory dep and provide gas enough for validation but not for bytecode publishing. + // Transaction should be rejected by API. + + const BYTECODE_LEN = 50016; + const bytecode = ethers.utils.hexlify(ethers.utils.randomBytes(BYTECODE_LEN)); + + // Estimate gas for "no-op". It's a good estimate for validation gas. + const gasLimit = await alice.estimateGas({ + to: alice.address, + data: '0x' + }); + + await expect( + alice.sendTransaction({ + to: alice.address, + gasLimit, + customData: { + factoryDeps: [bytecode] + } + }) + ).toBeRejected('not enough gas to publish compressed bytecodes'); + }); + afterAll(async () => { await testMaster.deinitialize(); });