From 6d39715145cc9b9c9ab137eb327506ec45aa2186 Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Wed, 11 Sep 2024 21:20:50 +0300 Subject: [PATCH 01/12] Move `MultiVMBaseSystemContracts` to executor --- core/lib/vm_executor/src/oneshot/contracts.rs | 91 +++++++++++++++++++ core/lib/vm_executor/src/oneshot/mod.rs | 3 +- .../api_server/src/execution_sandbox/mod.rs | 2 +- core/node/api_server/src/tx_sender/mod.rs | 89 +----------------- 4 files changed, 95 insertions(+), 90 deletions(-) create mode 100644 core/lib/vm_executor/src/oneshot/contracts.rs diff --git a/core/lib/vm_executor/src/oneshot/contracts.rs b/core/lib/vm_executor/src/oneshot/contracts.rs new file mode 100644 index 000000000000..a328e48fb7ec --- /dev/null +++ b/core/lib/vm_executor/src/oneshot/contracts.rs @@ -0,0 +1,91 @@ +use zksync_contracts::BaseSystemContracts; +use zksync_types::ProtocolVersionId; + +/// System contracts (bootloader and default account abstraction) for all VM versions. +#[derive(Debug, Clone)] +pub struct MultiVMBaseSystemContracts { + /// Contracts to be used for pre-virtual-blocks protocol versions. + pub(crate) pre_virtual_blocks: BaseSystemContracts, + /// Contracts to be used for post-virtual-blocks protocol versions. + pub(crate) post_virtual_blocks: BaseSystemContracts, + /// Contracts to be used for protocol versions after virtual block upgrade fix. + pub(crate) post_virtual_blocks_finish_upgrade_fix: BaseSystemContracts, + /// Contracts to be used for post-boojum protocol versions. + pub(crate) post_boojum: BaseSystemContracts, + /// Contracts to be used after the allow-list removal upgrade + pub(crate) post_allowlist_removal: BaseSystemContracts, + /// Contracts to be used after the 1.4.1 upgrade + pub(crate) post_1_4_1: BaseSystemContracts, + /// Contracts to be used after the 1.4.2 upgrade + pub(crate) post_1_4_2: BaseSystemContracts, + /// Contracts to be used during the `v23` upgrade. This upgrade was done on an internal staging environment only. + pub(crate) vm_1_5_0_small_memory: BaseSystemContracts, + /// Contracts to be used after the 1.5.0 upgrade + pub(crate) vm_1_5_0_increased_memory: BaseSystemContracts, +} + +impl MultiVMBaseSystemContracts { + /// Gets contracts for a certain version. + pub fn get_by_protocol_version(self, version: ProtocolVersionId) -> BaseSystemContracts { + match version { + ProtocolVersionId::Version0 + | ProtocolVersionId::Version1 + | ProtocolVersionId::Version2 + | ProtocolVersionId::Version3 + | ProtocolVersionId::Version4 + | ProtocolVersionId::Version5 + | ProtocolVersionId::Version6 + | ProtocolVersionId::Version7 + | ProtocolVersionId::Version8 + | ProtocolVersionId::Version9 + | ProtocolVersionId::Version10 + | ProtocolVersionId::Version11 + | ProtocolVersionId::Version12 => self.pre_virtual_blocks, + ProtocolVersionId::Version13 => self.post_virtual_blocks, + ProtocolVersionId::Version14 + | ProtocolVersionId::Version15 + | ProtocolVersionId::Version16 + | ProtocolVersionId::Version17 => self.post_virtual_blocks_finish_upgrade_fix, + ProtocolVersionId::Version18 => self.post_boojum, + ProtocolVersionId::Version19 => self.post_allowlist_removal, + ProtocolVersionId::Version20 => self.post_1_4_1, + ProtocolVersionId::Version21 | ProtocolVersionId::Version22 => self.post_1_4_2, + ProtocolVersionId::Version23 => self.vm_1_5_0_small_memory, + ProtocolVersionId::Version24 | ProtocolVersionId::Version25 => { + self.vm_1_5_0_increased_memory + } + } + } + + pub fn load_estimate_gas_blocking() -> Self { + Self { + pre_virtual_blocks: BaseSystemContracts::estimate_gas_pre_virtual_blocks(), + post_virtual_blocks: BaseSystemContracts::estimate_gas_post_virtual_blocks(), + post_virtual_blocks_finish_upgrade_fix: + BaseSystemContracts::estimate_gas_post_virtual_blocks_finish_upgrade_fix(), + post_boojum: BaseSystemContracts::estimate_gas_post_boojum(), + post_allowlist_removal: BaseSystemContracts::estimate_gas_post_allowlist_removal(), + post_1_4_1: BaseSystemContracts::estimate_gas_post_1_4_1(), + post_1_4_2: BaseSystemContracts::estimate_gas_post_1_4_2(), + vm_1_5_0_small_memory: BaseSystemContracts::estimate_gas_1_5_0_small_memory(), + vm_1_5_0_increased_memory: + BaseSystemContracts::estimate_gas_post_1_5_0_increased_memory(), + } + } + + pub fn load_eth_call_blocking() -> Self { + Self { + pre_virtual_blocks: BaseSystemContracts::playground_pre_virtual_blocks(), + post_virtual_blocks: BaseSystemContracts::playground_post_virtual_blocks(), + post_virtual_blocks_finish_upgrade_fix: + BaseSystemContracts::playground_post_virtual_blocks_finish_upgrade_fix(), + post_boojum: BaseSystemContracts::playground_post_boojum(), + post_allowlist_removal: BaseSystemContracts::playground_post_allowlist_removal(), + post_1_4_1: BaseSystemContracts::playground_post_1_4_1(), + post_1_4_2: BaseSystemContracts::playground_post_1_4_2(), + vm_1_5_0_small_memory: BaseSystemContracts::playground_1_5_0_small_memory(), + vm_1_5_0_increased_memory: BaseSystemContracts::playground_post_1_5_0_increased_memory( + ), + } + } +} diff --git a/core/lib/vm_executor/src/oneshot/mod.rs b/core/lib/vm_executor/src/oneshot/mod.rs index cac8edfdfdf8..b90ebc7ad05f 100644 --- a/core/lib/vm_executor/src/oneshot/mod.rs +++ b/core/lib/vm_executor/src/oneshot/mod.rs @@ -32,8 +32,9 @@ use zksync_types::{ }; use zksync_utils::{h256_to_u256, u256_to_h256}; -pub use self::mock::MockOneshotExecutor; +pub use self::{contracts::MultiVMBaseSystemContracts, mock::MockOneshotExecutor}; +mod contracts; mod metrics; mod mock; diff --git a/core/node/api_server/src/execution_sandbox/mod.rs b/core/node/api_server/src/execution_sandbox/mod.rs index 79c6123642cc..6706f909b6fe 100644 --- a/core/node/api_server/src/execution_sandbox/mod.rs +++ b/core/node/api_server/src/execution_sandbox/mod.rs @@ -11,6 +11,7 @@ use zksync_state::PostgresStorageCaches; use zksync_types::{ api, fee_model::BatchFeeInput, AccountTreeId, Address, L1BatchNumber, L2BlockNumber, L2ChainId, }; +use zksync_vm_executor::oneshot::MultiVMBaseSystemContracts; pub use self::execute::TransactionExecutor; // FIXME (PLA-1018): remove use self::vm_metrics::SandboxStage; @@ -19,7 +20,6 @@ pub(super) use self::{ validate::ValidationError, vm_metrics::{SubmitTxStage, SANDBOX_METRICS}, }; -use super::tx_sender::MultiVMBaseSystemContracts; // Note: keep the modules private, and instead re-export functions that make public interface. mod apply; diff --git a/core/node/api_server/src/tx_sender/mod.rs b/core/node/api_server/src/tx_sender/mod.rs index 44eaae2e3eee..1d18853a1376 100644 --- a/core/node/api_server/src/tx_sender/mod.rs +++ b/core/node/api_server/src/tx_sender/mod.rs @@ -5,7 +5,6 @@ use std::{sync::Arc, time::Instant}; use anyhow::Context as _; use tokio::sync::RwLock; use zksync_config::configs::{api::Web3JsonRpcConfig, chain::StateKeeperConfig}; -use zksync_contracts::BaseSystemContracts; use zksync_dal::{ transactions_dal::L2TxSubmissionResult, Connection, ConnectionPool, Core, CoreDal, }; @@ -39,6 +38,7 @@ use zksync_types::{ ProtocolVersionId, Transaction, H160, H256, MAX_L2_TX_GAS_LIMIT, MAX_NEW_FACTORY_DEPS, U256, }; use zksync_utils::h256_to_u256; +use zksync_vm_executor::oneshot::MultiVMBaseSystemContracts; pub(super) use self::result::SubmitTxError; use self::{master_pool_sink::MasterPoolSink, tx_sink::TxSink}; @@ -90,93 +90,6 @@ pub async fn build_tx_sender( Ok((tx_sender, vm_barrier)) } -#[derive(Debug, Clone)] -pub struct MultiVMBaseSystemContracts { - /// Contracts to be used for pre-virtual-blocks protocol versions. - pub(crate) pre_virtual_blocks: BaseSystemContracts, - /// Contracts to be used for post-virtual-blocks protocol versions. - pub(crate) post_virtual_blocks: BaseSystemContracts, - /// Contracts to be used for protocol versions after virtual block upgrade fix. - pub(crate) post_virtual_blocks_finish_upgrade_fix: BaseSystemContracts, - /// Contracts to be used for post-boojum protocol versions. - pub(crate) post_boojum: BaseSystemContracts, - /// Contracts to be used after the allow-list removal upgrade - pub(crate) post_allowlist_removal: BaseSystemContracts, - /// Contracts to be used after the 1.4.1 upgrade - pub(crate) post_1_4_1: BaseSystemContracts, - /// Contracts to be used after the 1.4.2 upgrade - pub(crate) post_1_4_2: BaseSystemContracts, - /// Contracts to be used during the `v23` upgrade. This upgrade was done on an internal staging environment only. - pub(crate) vm_1_5_0_small_memory: BaseSystemContracts, - /// Contracts to be used after the 1.5.0 upgrade - pub(crate) vm_1_5_0_increased_memory: BaseSystemContracts, -} - -impl MultiVMBaseSystemContracts { - pub fn get_by_protocol_version(self, version: ProtocolVersionId) -> BaseSystemContracts { - match version { - ProtocolVersionId::Version0 - | ProtocolVersionId::Version1 - | ProtocolVersionId::Version2 - | ProtocolVersionId::Version3 - | ProtocolVersionId::Version4 - | ProtocolVersionId::Version5 - | ProtocolVersionId::Version6 - | ProtocolVersionId::Version7 - | ProtocolVersionId::Version8 - | ProtocolVersionId::Version9 - | ProtocolVersionId::Version10 - | ProtocolVersionId::Version11 - | ProtocolVersionId::Version12 => self.pre_virtual_blocks, - ProtocolVersionId::Version13 => self.post_virtual_blocks, - ProtocolVersionId::Version14 - | ProtocolVersionId::Version15 - | ProtocolVersionId::Version16 - | ProtocolVersionId::Version17 => self.post_virtual_blocks_finish_upgrade_fix, - ProtocolVersionId::Version18 => self.post_boojum, - ProtocolVersionId::Version19 => self.post_allowlist_removal, - ProtocolVersionId::Version20 => self.post_1_4_1, - ProtocolVersionId::Version21 | ProtocolVersionId::Version22 => self.post_1_4_2, - ProtocolVersionId::Version23 => self.vm_1_5_0_small_memory, - ProtocolVersionId::Version24 | ProtocolVersionId::Version25 => { - self.vm_1_5_0_increased_memory - } - } - } - - pub fn load_estimate_gas_blocking() -> Self { - Self { - pre_virtual_blocks: BaseSystemContracts::estimate_gas_pre_virtual_blocks(), - post_virtual_blocks: BaseSystemContracts::estimate_gas_post_virtual_blocks(), - post_virtual_blocks_finish_upgrade_fix: - BaseSystemContracts::estimate_gas_post_virtual_blocks_finish_upgrade_fix(), - post_boojum: BaseSystemContracts::estimate_gas_post_boojum(), - post_allowlist_removal: BaseSystemContracts::estimate_gas_post_allowlist_removal(), - post_1_4_1: BaseSystemContracts::estimate_gas_post_1_4_1(), - post_1_4_2: BaseSystemContracts::estimate_gas_post_1_4_2(), - vm_1_5_0_small_memory: BaseSystemContracts::estimate_gas_1_5_0_small_memory(), - vm_1_5_0_increased_memory: - BaseSystemContracts::estimate_gas_post_1_5_0_increased_memory(), - } - } - - pub fn load_eth_call_blocking() -> Self { - Self { - pre_virtual_blocks: BaseSystemContracts::playground_pre_virtual_blocks(), - post_virtual_blocks: BaseSystemContracts::playground_post_virtual_blocks(), - post_virtual_blocks_finish_upgrade_fix: - BaseSystemContracts::playground_post_virtual_blocks_finish_upgrade_fix(), - post_boojum: BaseSystemContracts::playground_post_boojum(), - post_allowlist_removal: BaseSystemContracts::playground_post_allowlist_removal(), - post_1_4_1: BaseSystemContracts::playground_post_1_4_1(), - post_1_4_2: BaseSystemContracts::playground_post_1_4_2(), - vm_1_5_0_small_memory: BaseSystemContracts::playground_1_5_0_small_memory(), - vm_1_5_0_increased_memory: BaseSystemContracts::playground_post_1_5_0_increased_memory( - ), - } - } -} - /// Smart contracts to be used in the API sandbox requests, e.g. for estimating gas and /// performing `eth_call` requests. #[derive(Debug, Clone)] From ffc6cc555c9544fc2baa8f6cb1fe06447c261765 Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Thu, 12 Sep 2024 09:22:18 +0300 Subject: [PATCH 02/12] Move block types to executor --- core/lib/vm_executor/src/oneshot/block.rs | 353 ++++++++++++++++++ core/lib/vm_executor/src/oneshot/mod.rs | 7 +- .../api_server/src/execution_sandbox/apply.rs | 313 +--------------- .../src/execution_sandbox/execute.rs | 37 +- .../api_server/src/execution_sandbox/mod.rs | 114 ++---- .../api_server/src/execution_sandbox/tests.rs | 34 +- .../src/execution_sandbox/validate.rs | 18 +- core/node/api_server/src/tx_sender/mod.rs | 28 +- .../api_server/src/web3/namespaces/debug.rs | 10 +- 9 files changed, 483 insertions(+), 431 deletions(-) create mode 100644 core/lib/vm_executor/src/oneshot/block.rs diff --git a/core/lib/vm_executor/src/oneshot/block.rs b/core/lib/vm_executor/src/oneshot/block.rs new file mode 100644 index 000000000000..33a46e3d710e --- /dev/null +++ b/core/lib/vm_executor/src/oneshot/block.rs @@ -0,0 +1,353 @@ +use anyhow::Context; +use zksync_dal::{Connection, Core, CoreDal, DalError}; +use zksync_multivm::{ + interface::{L1BatchEnv, L2BlockEnv, OneshotEnv, StoredL2BlockEnv, SystemEnv, TxExecutionMode}, + vm_latest::constants::BATCH_COMPUTATIONAL_GAS_LIMIT, +}; +use zksync_types::{ + api, + block::{unpack_block_info, L2BlockHasher}, + fee_model::BatchFeeInput, + AccountTreeId, L1BatchNumber, L2BlockNumber, L2ChainId, ProtocolVersionId, StorageKey, H256, + SYSTEM_CONTEXT_ADDRESS, SYSTEM_CONTEXT_CURRENT_L2_BLOCK_INFO_POSITION, + SYSTEM_CONTEXT_CURRENT_TX_ROLLING_HASH_POSITION, ZKPORTER_IS_AVAILABLE, +}; +use zksync_utils::{h256_to_u256, time::seconds_since_epoch}; + +use super::MultiVMBaseSystemContracts; + +/// Block information necessary to execute a transaction / call. +// FIXME: consider combining with `ResolvedBlockInfo`? +#[derive(Debug, Clone, Copy)] +pub struct BlockInfo { + resolved_block_number: L2BlockNumber, + l1_batch_timestamp_s: Option, +} + +impl BlockInfo { + /// Fetches information for a pending block. + pub async fn pending(connection: &mut Connection<'_, Core>) -> anyhow::Result { + let resolved_block_number = connection + .blocks_web3_dal() + .resolve_block_id(api::BlockId::Number(api::BlockNumber::Pending)) + .await + .map_err(DalError::generalize)? + .context("pending block should always be present in Postgres")?; + Ok(Self { + resolved_block_number, + l1_batch_timestamp_s: None, + }) + } + + /// Fetches information for an existing block. + pub async fn for_existing_block( + connection: &mut Connection<'_, Core>, + number: L2BlockNumber, + ) -> anyhow::Result { + let l1_batch = connection + .storage_web3_dal() + .resolve_l1_batch_number_of_l2_block(number) + .await + .with_context(|| format!("failed resolving L1 batch number of L2 block #{number}"))?; + let l1_batch_timestamp = connection + .blocks_web3_dal() + .get_expected_l1_batch_timestamp(&l1_batch) + .await + .map_err(DalError::generalize)? + .context("missing timestamp for non-pending block")?; + Ok(Self { + resolved_block_number: number, + l1_batch_timestamp_s: Some(l1_batch_timestamp), + }) + } + + /// Returns L2 block number. + pub fn block_number(&self) -> L2BlockNumber { + self.resolved_block_number + } + + /// Returns L1 batch timestamp in seconds, or `None` for a pending block. + pub fn l1_batch_timestamp(&self) -> Option { + self.l1_batch_timestamp_s + } + + fn is_pending_l2_block(&self) -> bool { + self.l1_batch_timestamp_s.is_none() + } + + /// Loads historical fee input for this block. If the block is not present in the DB, returns an error. + pub async fn historical_fee_input( + &self, + connection: &mut Connection<'_, Core>, + ) -> anyhow::Result { + let header = connection + .blocks_dal() + .get_l2_block_header(self.resolved_block_number) + .await? + .context("resolved L2 block is not in storage")?; + Ok(header.batch_fee_input) + } + + /// Resolves this information into [`ResolvedBlockInfo`]. + pub async fn resolve( + &self, + connection: &mut Connection<'_, Core>, + ) -> anyhow::Result { + let (state_l2_block_number, vm_l1_batch_number, l1_batch_timestamp); + + let l2_block_header = if let Some(l1_batch_timestamp_s) = self.l1_batch_timestamp_s { + vm_l1_batch_number = connection + .storage_web3_dal() + .resolve_l1_batch_number_of_l2_block(self.resolved_block_number) + .await + .context("failed resolving L1 batch for L2 block")? + .expected_l1_batch(); + l1_batch_timestamp = l1_batch_timestamp_s; + state_l2_block_number = self.resolved_block_number; + + connection + .blocks_dal() + .get_l2_block_header(self.resolved_block_number) + .await? + .context("resolved L2 block disappeared from storage")? + } else { + vm_l1_batch_number = connection + .blocks_dal() + .get_sealed_l1_batch_number() + .await? + .context("no L1 batches in storage")?; + let sealed_l2_block_header = connection + .blocks_dal() + .get_last_sealed_l2_block_header() + .await? + .context("no L2 blocks in storage")?; + + state_l2_block_number = sealed_l2_block_header.number; + // Timestamp of the next L1 batch must be greater than the timestamp of the last L2 block. + l1_batch_timestamp = seconds_since_epoch().max(sealed_l2_block_header.timestamp + 1); + sealed_l2_block_header + }; + + // Blocks without version specified are considered to be of `Version9`. + // TODO: remove `unwrap_or` when protocol version ID will be assigned for each block. + let protocol_version = l2_block_header + .protocol_version + .unwrap_or(ProtocolVersionId::last_potentially_undefined()); + + Ok(ResolvedBlockInfo { + state_l2_block_number, + state_l2_block_hash: l2_block_header.hash, + vm_l1_batch_number, + l1_batch_timestamp, + protocol_version, + is_pending: self.is_pending_l2_block(), + }) + } +} + +/// Resolved [`BlockInfo`] containing additional data from VM state. +#[derive(Debug)] +pub struct ResolvedBlockInfo { + state_l2_block_number: L2BlockNumber, + state_l2_block_hash: H256, + vm_l1_batch_number: L1BatchNumber, + l1_batch_timestamp: u64, + protocol_version: ProtocolVersionId, + is_pending: bool, +} + +impl ResolvedBlockInfo { + /// L2 block number (as stored in Postgres). This number may differ from `block.number` provided to the VM. + pub fn state_l2_block_number(&self) -> L2BlockNumber { + self.state_l2_block_number + } +} + +/// Arguments for VM execution necessary to set up storage and environment. +// FIXME: rename +#[derive(Debug, Clone)] +pub struct TxSetupArgs { + // FIXME: revise fields vs args for `into_env` + pub execution_mode: TxExecutionMode, // changes each call + pub operator_account: AccountTreeId, // rarely + pub fee_input: BatchFeeInput, // each call + pub base_system_contracts: MultiVMBaseSystemContracts, // never + pub validation_computational_gas_limit: u32, // rarely + pub chain_id: L2ChainId, // never + pub enforced_base_fee: Option, // ??? +} + +impl TxSetupArgs { + #[doc(hidden)] // Useful only for tests + pub fn mock( + execution_mode: TxExecutionMode, + base_system_contracts: MultiVMBaseSystemContracts, + ) -> Self { + Self { + execution_mode, + operator_account: AccountTreeId::default(), + fee_input: BatchFeeInput::l1_pegged(55, 555), + base_system_contracts, + validation_computational_gas_limit: u32::MAX, + chain_id: L2ChainId::default(), + enforced_base_fee: None, + } + } + + pub async fn into_env( + self, + connection: &mut Connection<'_, Core>, + resolved_block_info: &ResolvedBlockInfo, + ) -> anyhow::Result { + let (next_block, current_block) = load_l2_block_info( + connection, + resolved_block_info.is_pending, + resolved_block_info, + ) + .await?; + + let (system, l1_batch) = self.prepare_env(resolved_block_info, next_block); + let env = OneshotEnv { + system, + l1_batch, + current_block, + }; + Ok(env) + } + + fn prepare_env( + self, + resolved_block_info: &ResolvedBlockInfo, + next_block: L2BlockEnv, + ) -> (SystemEnv, L1BatchEnv) { + let Self { + execution_mode, + operator_account, + fee_input, + base_system_contracts, + validation_computational_gas_limit, + chain_id, + enforced_base_fee, + } = self; + + let system_env = SystemEnv { + zk_porter_available: ZKPORTER_IS_AVAILABLE, + version: resolved_block_info.protocol_version, + base_system_smart_contracts: base_system_contracts + .get_by_protocol_version(resolved_block_info.protocol_version), + bootloader_gas_limit: BATCH_COMPUTATIONAL_GAS_LIMIT, + execution_mode, + default_validation_computational_gas_limit: validation_computational_gas_limit, + chain_id, + }; + let l1_batch_env = L1BatchEnv { + previous_batch_hash: None, + number: resolved_block_info.vm_l1_batch_number, + timestamp: resolved_block_info.l1_batch_timestamp, + fee_input, + fee_account: *operator_account.address(), + enforced_base_fee, + first_l2_block: next_block, + }; + (system_env, l1_batch_env) + } +} + +async fn load_l2_block_info( + connection: &mut Connection<'_, Core>, + is_pending_block: bool, + resolved_block_info: &ResolvedBlockInfo, +) -> anyhow::Result<(L2BlockEnv, Option)> { + let mut current_block = None; + let next_block = read_stored_l2_block(connection, resolved_block_info.state_l2_block_number) + .await + .context("failed reading L2 block info")?; + + let next_block = if is_pending_block { + L2BlockEnv { + number: next_block.number + 1, + timestamp: resolved_block_info.l1_batch_timestamp, + prev_block_hash: resolved_block_info.state_l2_block_hash, + // For simplicity, we assume each L2 block create one virtual block. + // This may be wrong only during transition period. + max_virtual_blocks_to_create: 1, + } + } else if next_block.number == 0 { + // Special case: + // - For environments, where genesis block was created before virtual block upgrade it doesn't matter what we put here. + // - Otherwise, we need to put actual values here. We cannot create next L2 block with block_number=0 and `max_virtual_blocks_to_create=0` + // because of SystemContext requirements. But, due to intrinsics of SystemContext, block.number still will be resolved to 0. + L2BlockEnv { + number: 1, + timestamp: 0, + prev_block_hash: L2BlockHasher::legacy_hash(L2BlockNumber(0)), + max_virtual_blocks_to_create: 1, + } + } else { + // We need to reset L2 block info in storage to process transaction in the current block context. + // Actual resetting will be done after `storage_view` is created. + let prev_block_number = resolved_block_info.state_l2_block_number - 1; + let prev_l2_block = read_stored_l2_block(connection, prev_block_number) + .await + .context("failed reading previous L2 block info")?; + + let mut prev_block_hash = connection + .blocks_web3_dal() + .get_l2_block_hash(prev_block_number) + .await + .map_err(DalError::generalize)?; + if prev_block_hash.is_none() { + // We might need to load the previous block hash from the snapshot recovery metadata + let snapshot_recovery = connection + .snapshot_recovery_dal() + .get_applied_snapshot_status() + .await + .map_err(DalError::generalize)?; + prev_block_hash = snapshot_recovery.and_then(|recovery| { + (recovery.l2_block_number == prev_block_number).then_some(recovery.l2_block_hash) + }); + } + + current_block = Some(prev_l2_block); + L2BlockEnv { + number: next_block.number, + timestamp: next_block.timestamp, + prev_block_hash: prev_block_hash.with_context(|| { + format!("missing hash for previous L2 block #{prev_block_number}") + })?, + max_virtual_blocks_to_create: 1, + } + }; + + Ok((next_block, current_block)) +} + +async fn read_stored_l2_block( + connection: &mut Connection<'_, Core>, + l2_block_number: L2BlockNumber, +) -> anyhow::Result { + let l2_block_info_key = StorageKey::new( + AccountTreeId::new(SYSTEM_CONTEXT_ADDRESS), + SYSTEM_CONTEXT_CURRENT_L2_BLOCK_INFO_POSITION, + ); + let l2_block_info = connection + .storage_web3_dal() + .get_historical_value_unchecked(l2_block_info_key.hashed_key(), l2_block_number) + .await?; + let (l2_block_number_from_state, timestamp) = unpack_block_info(h256_to_u256(l2_block_info)); + + let l2_block_txs_rolling_hash_key = StorageKey::new( + AccountTreeId::new(SYSTEM_CONTEXT_ADDRESS), + SYSTEM_CONTEXT_CURRENT_TX_ROLLING_HASH_POSITION, + ); + let txs_rolling_hash = connection + .storage_web3_dal() + .get_historical_value_unchecked(l2_block_txs_rolling_hash_key.hashed_key(), l2_block_number) + .await?; + + Ok(StoredL2BlockEnv { + number: l2_block_number_from_state as u32, + timestamp, + txs_rolling_hash, + }) +} diff --git a/core/lib/vm_executor/src/oneshot/mod.rs b/core/lib/vm_executor/src/oneshot/mod.rs index b90ebc7ad05f..2b922943960d 100644 --- a/core/lib/vm_executor/src/oneshot/mod.rs +++ b/core/lib/vm_executor/src/oneshot/mod.rs @@ -32,8 +32,13 @@ use zksync_types::{ }; use zksync_utils::{h256_to_u256, u256_to_h256}; -pub use self::{contracts::MultiVMBaseSystemContracts, mock::MockOneshotExecutor}; +pub use self::{ + block::{BlockInfo, TxSetupArgs}, + contracts::MultiVMBaseSystemContracts, + mock::MockOneshotExecutor, +}; +mod block; mod contracts; mod metrics; mod mock; diff --git a/core/node/api_server/src/execution_sandbox/apply.rs b/core/node/api_server/src/execution_sandbox/apply.rs index 0fbf8abc3dd4..2b3497cc975f 100644 --- a/core/node/api_server/src/execution_sandbox/apply.rs +++ b/core/node/api_server/src/execution_sandbox/apply.rs @@ -1,327 +1,54 @@ -//! This module provides primitives focusing on the VM instantiation and execution for different use cases. -//! It is rather generic and low-level, so it's not supposed to be a part of public API. -//! -//! Instead, we expect people to write wrappers in the `execution_sandbox` module with a more high-level API -//! that would, in its turn, be used by the actual API method handlers. -//! -//! This module is intended to be blocking. - use std::time::{Duration, Instant}; use anyhow::Context as _; use tokio::runtime::Handle; -use zksync_dal::{Connection, Core, CoreDal, DalError}; -use zksync_multivm::{ - interface::{L1BatchEnv, L2BlockEnv, OneshotEnv, StoredL2BlockEnv, SystemEnv}, - utils::get_eth_call_gas_limit, - vm_latest::constants::BATCH_COMPUTATIONAL_GAS_LIMIT, -}; -use zksync_state::PostgresStorage; -use zksync_system_constants::{ - SYSTEM_CONTEXT_ADDRESS, SYSTEM_CONTEXT_CURRENT_L2_BLOCK_INFO_POSITION, - SYSTEM_CONTEXT_CURRENT_TX_ROLLING_HASH_POSITION, ZKPORTER_IS_AVAILABLE, -}; -use zksync_types::{ - api, - block::{unpack_block_info, L2BlockHasher}, - fee_model::BatchFeeInput, - AccountTreeId, L1BatchNumber, L2BlockNumber, ProtocolVersionId, StorageKey, H256, U256, -}; -use zksync_utils::{h256_to_u256, time::seconds_since_epoch}; +use zksync_dal::{Connection, Core}; +use zksync_multivm::interface::OneshotEnv; +use zksync_state::{PostgresStorage, PostgresStorageCaches}; +use zksync_vm_executor::oneshot::TxSetupArgs; use super::{ vm_metrics::{SandboxStage, SANDBOX_METRICS}, - BlockArgs, TxSetupArgs, + BlockArgs, }; pub(super) async fn prepare_env_and_storage( mut connection: Connection<'static, Core>, setup_args: TxSetupArgs, block_args: &BlockArgs, + storage_caches: Option, ) -> anyhow::Result<(OneshotEnv, PostgresStorage<'static>)> { let initialization_stage = SANDBOX_METRICS.sandbox[&SandboxStage::Initialization].start(); - let resolve_started_at = Instant::now(); - let resolved_block_info = block_args - .resolve_block_info(&mut connection) - .await - .with_context(|| format!("cannot resolve block numbers for {block_args:?}"))?; let resolve_time = resolve_started_at.elapsed(); + let resolved_block_info = block_args.inner.resolve(&mut connection).await?; // We don't want to emit too many logs. if resolve_time > Duration::from_millis(10) { tracing::debug!("Resolved block numbers (took {resolve_time:?})"); } + let env = setup_args + .into_env(&mut connection, &resolved_block_info) + .await?; + if block_args.resolves_to_latest_sealed_l2_block() { - setup_args - .caches - .schedule_values_update(resolved_block_info.state_l2_block_number); + if let Some(caches) = &storage_caches { + caches.schedule_values_update(resolved_block_info.state_l2_block_number()); + } } - let (next_block, current_block) = load_l2_block_info( - &mut connection, - block_args.is_pending_l2_block(), - &resolved_block_info, - ) - .await?; - - let storage = PostgresStorage::new_async( + let mut storage = PostgresStorage::new_async( Handle::current(), connection, - resolved_block_info.state_l2_block_number, + resolved_block_info.state_l2_block_number(), false, ) .await - .context("cannot create `PostgresStorage`")? - .with_caches(setup_args.caches.clone()); - - let (system, l1_batch) = prepare_env(setup_args, &resolved_block_info, next_block); + .context("cannot create `PostgresStorage`")?; - let env = OneshotEnv { - system, - l1_batch, - current_block, - }; + if let Some(caches) = storage_caches { + storage = storage.with_caches(caches); + } initialization_stage.observe(); Ok((env, storage)) } - -async fn load_l2_block_info( - connection: &mut Connection<'_, Core>, - is_pending_block: bool, - resolved_block_info: &ResolvedBlockInfo, -) -> anyhow::Result<(L2BlockEnv, Option)> { - let mut current_block = None; - let next_block = read_stored_l2_block(connection, resolved_block_info.state_l2_block_number) - .await - .context("failed reading L2 block info")?; - - let next_block = if is_pending_block { - L2BlockEnv { - number: next_block.number + 1, - timestamp: resolved_block_info.l1_batch_timestamp, - prev_block_hash: resolved_block_info.state_l2_block_hash, - // For simplicity, we assume each L2 block create one virtual block. - // This may be wrong only during transition period. - max_virtual_blocks_to_create: 1, - } - } else if next_block.number == 0 { - // Special case: - // - For environments, where genesis block was created before virtual block upgrade it doesn't matter what we put here. - // - Otherwise, we need to put actual values here. We cannot create next L2 block with block_number=0 and `max_virtual_blocks_to_create=0` - // because of SystemContext requirements. But, due to intrinsics of SystemContext, block.number still will be resolved to 0. - L2BlockEnv { - number: 1, - timestamp: 0, - prev_block_hash: L2BlockHasher::legacy_hash(L2BlockNumber(0)), - max_virtual_blocks_to_create: 1, - } - } else { - // We need to reset L2 block info in storage to process transaction in the current block context. - // Actual resetting will be done after `storage_view` is created. - let prev_block_number = resolved_block_info.state_l2_block_number - 1; - let prev_l2_block = read_stored_l2_block(connection, prev_block_number) - .await - .context("failed reading previous L2 block info")?; - - let mut prev_block_hash = connection - .blocks_web3_dal() - .get_l2_block_hash(prev_block_number) - .await - .map_err(DalError::generalize)?; - if prev_block_hash.is_none() { - // We might need to load the previous block hash from the snapshot recovery metadata - let snapshot_recovery = connection - .snapshot_recovery_dal() - .get_applied_snapshot_status() - .await - .map_err(DalError::generalize)?; - prev_block_hash = snapshot_recovery.and_then(|recovery| { - (recovery.l2_block_number == prev_block_number).then_some(recovery.l2_block_hash) - }); - } - - current_block = Some(prev_l2_block); - L2BlockEnv { - number: next_block.number, - timestamp: next_block.timestamp, - prev_block_hash: prev_block_hash.with_context(|| { - format!("missing hash for previous L2 block #{prev_block_number}") - })?, - max_virtual_blocks_to_create: 1, - } - }; - - Ok((next_block, current_block)) -} - -fn prepare_env( - setup_args: TxSetupArgs, - resolved_block_info: &ResolvedBlockInfo, - next_block: L2BlockEnv, -) -> (SystemEnv, L1BatchEnv) { - let TxSetupArgs { - execution_mode, - operator_account, - fee_input, - base_system_contracts, - validation_computational_gas_limit, - chain_id, - enforced_base_fee, - .. - } = setup_args; - - // In case we are executing in a past block, we'll use the historical fee data. - let fee_input = resolved_block_info - .historical_fee_input - .unwrap_or(fee_input); - let system_env = SystemEnv { - zk_porter_available: ZKPORTER_IS_AVAILABLE, - version: resolved_block_info.protocol_version, - base_system_smart_contracts: base_system_contracts - .get_by_protocol_version(resolved_block_info.protocol_version), - bootloader_gas_limit: BATCH_COMPUTATIONAL_GAS_LIMIT, - execution_mode, - default_validation_computational_gas_limit: validation_computational_gas_limit, - chain_id, - }; - let l1_batch_env = L1BatchEnv { - previous_batch_hash: None, - number: resolved_block_info.vm_l1_batch_number, - timestamp: resolved_block_info.l1_batch_timestamp, - fee_input, - fee_account: *operator_account.address(), - enforced_base_fee, - first_l2_block: next_block, - }; - (system_env, l1_batch_env) -} - -async fn read_stored_l2_block( - connection: &mut Connection<'_, Core>, - l2_block_number: L2BlockNumber, -) -> anyhow::Result { - let l2_block_info_key = StorageKey::new( - AccountTreeId::new(SYSTEM_CONTEXT_ADDRESS), - SYSTEM_CONTEXT_CURRENT_L2_BLOCK_INFO_POSITION, - ); - let l2_block_info = connection - .storage_web3_dal() - .get_historical_value_unchecked(l2_block_info_key.hashed_key(), l2_block_number) - .await?; - let (l2_block_number_from_state, timestamp) = unpack_block_info(h256_to_u256(l2_block_info)); - - let l2_block_txs_rolling_hash_key = StorageKey::new( - AccountTreeId::new(SYSTEM_CONTEXT_ADDRESS), - SYSTEM_CONTEXT_CURRENT_TX_ROLLING_HASH_POSITION, - ); - let txs_rolling_hash = connection - .storage_web3_dal() - .get_historical_value_unchecked(l2_block_txs_rolling_hash_key.hashed_key(), l2_block_number) - .await?; - - Ok(StoredL2BlockEnv { - number: l2_block_number_from_state as u32, - timestamp, - txs_rolling_hash, - }) -} - -#[derive(Debug)] -pub(crate) struct ResolvedBlockInfo { - state_l2_block_number: L2BlockNumber, - state_l2_block_hash: H256, - vm_l1_batch_number: L1BatchNumber, - l1_batch_timestamp: u64, - pub(crate) protocol_version: ProtocolVersionId, - historical_fee_input: Option, -} - -impl BlockArgs { - fn is_pending_l2_block(&self) -> bool { - matches!( - self.block_id, - api::BlockId::Number(api::BlockNumber::Pending) - ) - } - - pub(crate) async fn default_eth_call_gas( - &self, - connection: &mut Connection<'_, Core>, - ) -> anyhow::Result { - let protocol_version = self - .resolve_block_info(connection) - .await - .context("failed to resolve block info")? - .protocol_version; - Ok(get_eth_call_gas_limit(protocol_version.into()).into()) - } - - async fn resolve_block_info( - &self, - connection: &mut Connection<'_, Core>, - ) -> anyhow::Result { - let (state_l2_block_number, vm_l1_batch_number, l1_batch_timestamp); - - let l2_block_header = if self.is_pending_l2_block() { - vm_l1_batch_number = connection - .blocks_dal() - .get_sealed_l1_batch_number() - .await? - .context("no L1 batches in storage")?; - let sealed_l2_block_header = connection - .blocks_dal() - .get_last_sealed_l2_block_header() - .await? - .context("no L2 blocks in storage")?; - - state_l2_block_number = sealed_l2_block_header.number; - // Timestamp of the next L1 batch must be greater than the timestamp of the last L2 block. - l1_batch_timestamp = seconds_since_epoch().max(sealed_l2_block_header.timestamp + 1); - sealed_l2_block_header - } else { - vm_l1_batch_number = connection - .storage_web3_dal() - .resolve_l1_batch_number_of_l2_block(self.resolved_block_number) - .await - .context("failed resolving L1 batch for L2 block")? - .expected_l1_batch(); - l1_batch_timestamp = self - .l1_batch_timestamp_s - .context("L1 batch timestamp is `None` for non-pending block args")?; - state_l2_block_number = self.resolved_block_number; - - connection - .blocks_dal() - .get_l2_block_header(self.resolved_block_number) - .await? - .context("resolved L2 block disappeared from storage")? - }; - - let historical_fee_input = if !self.resolves_to_latest_sealed_l2_block() { - let l2_block_header = connection - .blocks_dal() - .get_l2_block_header(self.resolved_block_number) - .await? - .context("resolved L2 block is not in storage")?; - Some(l2_block_header.batch_fee_input) - } else { - None - }; - - // Blocks without version specified are considered to be of `Version9`. - // TODO: remove `unwrap_or` when protocol version ID will be assigned for each block. - let protocol_version = l2_block_header - .protocol_version - .unwrap_or(ProtocolVersionId::last_potentially_undefined()); - - Ok(ResolvedBlockInfo { - state_l2_block_number, - state_l2_block_hash: l2_block_header.hash, - vm_l1_batch_number, - l1_batch_timestamp, - protocol_version, - historical_fee_input, - }) - } -} diff --git a/core/node/api_server/src/execution_sandbox/execute.rs b/core/node/api_server/src/execution_sandbox/execute.rs index d22d7de47d0f..a19cdd48b1f0 100644 --- a/core/node/api_server/src/execution_sandbox/execute.rs +++ b/core/node/api_server/src/execution_sandbox/execute.rs @@ -9,12 +9,12 @@ use zksync_multivm::interface::{ Call, OneshotEnv, OneshotTracingParams, OneshotTransactionExecutionResult, TransactionExecutionMetrics, TxExecutionArgs, VmExecutionResultAndLogs, }; +use zksync_state::PostgresStorageCaches; use zksync_types::{api::state_override::StateOverride, l2::L2Tx}; -use zksync_vm_executor::oneshot::{MainOneshotExecutor, MockOneshotExecutor}; +use zksync_vm_executor::oneshot::{MainOneshotExecutor, MockOneshotExecutor, TxSetupArgs}; use super::{ - apply, storage::StorageWithOverrides, vm_metrics, BlockArgs, TxSetupArgs, VmPermit, - SANDBOX_METRICS, + apply, storage::StorageWithOverrides, vm_metrics, BlockArgs, VmPermit, SANDBOX_METRICS, }; use crate::execution_sandbox::vm_metrics::SandboxStage; @@ -33,17 +33,27 @@ pub struct TransactionExecutionOutput { /// Executor of transactions. #[derive(Debug)] pub enum TransactionExecutor { - Real(MainOneshotExecutor), + Real { + executor: MainOneshotExecutor, + caches: PostgresStorageCaches, + }, #[doc(hidden)] // Intended for tests only Mock(MockOneshotExecutor), } impl TransactionExecutor { - pub fn real(missed_storage_invocation_limit: usize) -> Self { + pub fn real(missed_storage_invocation_limit: usize, caches: PostgresStorageCaches) -> Self { let mut executor = MainOneshotExecutor::new(missed_storage_invocation_limit); executor .set_execution_latency_histogram(&SANDBOX_METRICS.sandbox[&SandboxStage::Execution]); - Self::Real(executor) + Self::Real { executor, caches } + } + + pub(super) fn storage_caches(&self) -> Option { + match self { + Self::Real { caches, .. } => Some(caches.clone()), + Self::Mock(_) => None, + } } /// This method assumes that (block with number `resolved_block_number` is present in DB) @@ -56,13 +66,18 @@ impl TransactionExecutor { setup_args: TxSetupArgs, execution_args: TxExecutionArgs, connection: Connection<'static, Core>, - block_args: BlockArgs, + block_args: &BlockArgs, state_override: Option, tracing_params: OneshotTracingParams, ) -> anyhow::Result { let total_factory_deps = execution_args.transaction.execute.factory_deps.len() as u16; - let (env, storage) = - apply::prepare_env_and_storage(connection, setup_args, &block_args).await?; + let (env, storage) = apply::prepare_env_and_storage( + connection, + setup_args, + block_args, + self.storage_caches(), + ) + .await?; let state_override = state_override.unwrap_or_default(); let storage = StorageWithOverrides::new(storage, &state_override); @@ -106,7 +121,7 @@ where tracing_params: OneshotTracingParams, ) -> anyhow::Result { match self { - Self::Real(executor) => { + Self::Real { executor, .. } => { executor .inspect_transaction_with_bytecode_compression( storage, @@ -143,7 +158,7 @@ where validation_params: ValidationParams, ) -> anyhow::Result> { match self { - Self::Real(executor) => { + Self::Real { executor, .. } => { executor .validate_transaction(storage, env, tx, validation_params) .await diff --git a/core/node/api_server/src/execution_sandbox/mod.rs b/core/node/api_server/src/execution_sandbox/mod.rs index 6706f909b6fe..a2c7ac2b389c 100644 --- a/core/node/api_server/src/execution_sandbox/mod.rs +++ b/core/node/api_server/src/execution_sandbox/mod.rs @@ -6,12 +6,9 @@ use std::{ use anyhow::Context as _; use rand::{thread_rng, Rng}; use zksync_dal::{pruning_dal::PruningInfo, Connection, Core, CoreDal, DalError}; -use zksync_multivm::interface::TxExecutionMode; -use zksync_state::PostgresStorageCaches; -use zksync_types::{ - api, fee_model::BatchFeeInput, AccountTreeId, Address, L1BatchNumber, L2BlockNumber, L2ChainId, -}; -use zksync_vm_executor::oneshot::MultiVMBaseSystemContracts; +use zksync_multivm::utils::get_eth_call_gas_limit; +use zksync_types::{api, L1BatchNumber, L2BlockNumber, ProtocolVersionId, U256}; +use zksync_vm_executor::oneshot::BlockInfo; pub use self::execute::TransactionExecutor; // FIXME (PLA-1018): remove use self::vm_metrics::SandboxStage; @@ -136,53 +133,6 @@ impl VmConcurrencyLimiter { } } -async fn get_pending_state( - connection: &mut Connection<'_, Core>, -) -> anyhow::Result<(api::BlockId, L2BlockNumber)> { - let block_id = api::BlockId::Number(api::BlockNumber::Pending); - let resolved_block_number = connection - .blocks_web3_dal() - .resolve_block_id(block_id) - .await - .map_err(DalError::generalize)? - .context("pending block should always be present in Postgres")?; - Ok((block_id, resolved_block_number)) -} - -/// Arguments for VM execution necessary to set up storage and environment. -#[derive(Debug, Clone)] -pub struct TxSetupArgs { - pub execution_mode: TxExecutionMode, - pub operator_account: AccountTreeId, - pub fee_input: BatchFeeInput, - pub base_system_contracts: MultiVMBaseSystemContracts, - pub caches: PostgresStorageCaches, - pub validation_computational_gas_limit: u32, - pub chain_id: L2ChainId, - pub whitelisted_tokens_for_aa: Vec
, - pub enforced_base_fee: Option, -} - -impl TxSetupArgs { - #[cfg(test)] - pub fn mock( - execution_mode: TxExecutionMode, - base_system_contracts: MultiVMBaseSystemContracts, - ) -> Self { - Self { - execution_mode, - operator_account: AccountTreeId::default(), - fee_input: BatchFeeInput::l1_pegged(55, 555), - base_system_contracts, - caches: PostgresStorageCaches::new(1, 1), - validation_computational_gas_limit: u32::MAX, - chain_id: L2ChainId::default(), - whitelisted_tokens_for_aa: vec![], - enforced_base_fee: None, - } - } -} - #[derive(Debug, Clone, Copy)] struct BlockStartInfoInner { info: PruningInfo, @@ -336,18 +286,16 @@ pub enum BlockArgsError { /// Information about a block provided to VM. #[derive(Debug, Clone, Copy)] pub struct BlockArgs { + inner: BlockInfo, block_id: api::BlockId, - resolved_block_number: L2BlockNumber, - l1_batch_timestamp_s: Option, } impl BlockArgs { pub(crate) async fn pending(connection: &mut Connection<'_, Core>) -> anyhow::Result { - let (block_id, resolved_block_number) = get_pending_state(connection).await?; + let inner = BlockInfo::pending(connection).await?; Ok(Self { - block_id, - resolved_block_number, - l1_batch_timestamp_s: None, + inner, + block_id: api::BlockId::Number(api::BlockNumber::Pending), }) } @@ -365,7 +313,7 @@ impl BlockArgs { .await?; if block_id == api::BlockId::Number(api::BlockNumber::Pending) { - return Ok(BlockArgs::pending(connection).await?); + return Ok(Self::pending(connection).await?); } let resolved_block_number = connection @@ -373,32 +321,25 @@ impl BlockArgs { .resolve_block_id(block_id) .await .map_err(DalError::generalize)?; - let Some(resolved_block_number) = resolved_block_number else { + let Some(block_number) = resolved_block_number else { return Err(BlockArgsError::Missing); }; - let l1_batch = connection - .storage_web3_dal() - .resolve_l1_batch_number_of_l2_block(resolved_block_number) - .await - .with_context(|| { - format!("failed resolving L1 batch number of L2 block #{resolved_block_number}") - })?; - let l1_batch_timestamp = connection - .blocks_web3_dal() - .get_expected_l1_batch_timestamp(&l1_batch) - .await - .map_err(DalError::generalize)? - .context("missing timestamp for non-pending block")?; Ok(Self { + inner: BlockInfo::for_existing_block(connection, block_number).await?, block_id, - resolved_block_number, - l1_batch_timestamp_s: Some(l1_batch_timestamp), }) } pub fn resolved_block_number(&self) -> L2BlockNumber { - self.resolved_block_number + self.inner.block_number() + } + + fn is_pending(&self) -> bool { + matches!( + self.block_id, + api::BlockId::Number(api::BlockNumber::Pending) + ) } pub fn resolves_to_latest_sealed_l2_block(&self) -> bool { @@ -409,4 +350,23 @@ impl BlockArgs { ) ) } + + pub(crate) async fn default_eth_call_gas( + &self, + connection: &mut Connection<'_, Core>, + ) -> anyhow::Result { + let protocol_version = if self.is_pending() { + connection.blocks_dal().pending_protocol_version().await? + } else { + let block_number = self.inner.block_number(); + connection + .blocks_dal() + .get_l2_block_header(block_number) + .await? + .with_context(|| format!("missing header for resolved block #{block_number}"))? + .protocol_version + .unwrap_or_else(ProtocolVersionId::last_potentially_undefined) + }; + Ok(get_eth_call_gas_limit(protocol_version.into()).into()) + } } diff --git a/core/node/api_server/src/execution_sandbox/tests.rs b/core/node/api_server/src/execution_sandbox/tests.rs index 35103779a49e..5a9dc8a63f7d 100644 --- a/core/node/api_server/src/execution_sandbox/tests.rs +++ b/core/node/api_server/src/execution_sandbox/tests.rs @@ -9,7 +9,7 @@ use zksync_multivm::{ interface::{ executor::{OneshotExecutor, TransactionValidator}, tracer::ValidationError, - Halt, OneshotTracingParams, TxExecutionArgs, + Halt, OneshotTracingParams, TxExecutionArgs, TxExecutionMode, }, utils::derive_base_fee_and_gas_per_pubdata, }; @@ -20,9 +20,9 @@ use zksync_types::{ fee::Fee, l2::L2Tx, transaction_request::PaymasterParams, - K256PrivateKey, Nonce, ProtocolVersionId, Transaction, U256, + Address, K256PrivateKey, L2ChainId, Nonce, ProtocolVersionId, Transaction, U256, }; -use zksync_vm_executor::oneshot::MainOneshotExecutor; +use zksync_vm_executor::oneshot::{MainOneshotExecutor, TxSetupArgs}; use super::{storage::StorageWithOverrides, *}; use crate::tx_sender::ApiContracts; @@ -46,8 +46,9 @@ async fn creating_block_args() { pending_block_args.block_id, api::BlockId::Number(api::BlockNumber::Pending) ); - assert_eq!(pending_block_args.resolved_block_number, L2BlockNumber(2)); - assert_eq!(pending_block_args.l1_batch_timestamp_s, None); + assert_eq!(pending_block_args.resolved_block_number(), L2BlockNumber(2)); + assert_eq!(pending_block_args.inner.l1_batch_timestamp(), None); + assert!(pending_block_args.is_pending()); let start_info = BlockStartInfo::new(&mut storage, Duration::MAX) .await @@ -66,9 +67,9 @@ async fn creating_block_args() { .await .unwrap(); assert_eq!(latest_block_args.block_id, latest_block); - assert_eq!(latest_block_args.resolved_block_number, L2BlockNumber(1)); + assert_eq!(latest_block_args.resolved_block_number(), L2BlockNumber(1)); assert_eq!( - latest_block_args.l1_batch_timestamp_s, + latest_block_args.inner.l1_batch_timestamp(), Some(l2_block.timestamp) ); @@ -77,8 +78,11 @@ async fn creating_block_args() { .await .unwrap(); assert_eq!(earliest_block_args.block_id, earliest_block); - assert_eq!(earliest_block_args.resolved_block_number, L2BlockNumber(0)); - assert_eq!(earliest_block_args.l1_batch_timestamp_s, Some(0)); + assert_eq!( + earliest_block_args.resolved_block_number(), + L2BlockNumber(0) + ); + assert_eq!(earliest_block_args.inner.l1_batch_timestamp(), Some(0)); let missing_block = api::BlockId::Number(100.into()); let err = BlockArgs::new(&mut storage, missing_block, &start_info) @@ -100,10 +104,10 @@ async fn creating_block_args_after_snapshot_recovery() { api::BlockId::Number(api::BlockNumber::Pending) ); assert_eq!( - pending_block_args.resolved_block_number, + pending_block_args.resolved_block_number(), snapshot_recovery.l2_block_number + 1 ); - assert_eq!(pending_block_args.l1_batch_timestamp_s, None); + assert!(pending_block_args.is_pending()); let start_info = BlockStartInfo::new(&mut storage, Duration::MAX) .await @@ -159,9 +163,9 @@ async fn creating_block_args_after_snapshot_recovery() { .await .unwrap(); assert_eq!(latest_block_args.block_id, latest_block); - assert_eq!(latest_block_args.resolved_block_number, l2_block.number); + assert_eq!(latest_block_args.resolved_block_number(), l2_block.number); assert_eq!( - latest_block_args.l1_batch_timestamp_s, + latest_block_args.inner.l1_batch_timestamp(), Some(l2_block.timestamp) ); @@ -213,7 +217,7 @@ async fn test_instantiating_vm(connection: Connection<'static, Core>, block_args let transaction = Transaction::from(create_transfer(base_fee, gas_per_pubdata)); let execution_args = TxExecutionArgs::for_gas_estimate(transaction.clone()); - let (env, storage) = apply::prepare_env_and_storage(connection, setup_args, &block_args) + let (env, storage) = apply::prepare_env_and_storage(connection, setup_args, &block_args, None) .await .unwrap(); let storage = StorageWithOverrides::new(storage, &StateOverride::default()); @@ -273,7 +277,7 @@ async fn validating_transaction(set_balance: bool) { validate::get_validation_params(&mut connection, &transaction, u32::MAX, &[]) .await .unwrap(); - let (env, storage) = apply::prepare_env_and_storage(connection, setup_args, &block_args) + let (env, storage) = apply::prepare_env_and_storage(connection, setup_args, &block_args, None) .await .unwrap(); let state_override = if set_balance { diff --git a/core/node/api_server/src/execution_sandbox/validate.rs b/core/node/api_server/src/execution_sandbox/validate.rs index e9087e608eeb..7cfa46797753 100644 --- a/core/node/api_server/src/execution_sandbox/validate.rs +++ b/core/node/api_server/src/execution_sandbox/validate.rs @@ -11,13 +11,14 @@ use zksync_types::{ api::state_override::StateOverride, l2::L2Tx, Address, TRUSTED_ADDRESS_SLOTS, TRUSTED_TOKEN_SLOTS, }; +use zksync_vm_executor::oneshot::TxSetupArgs; use super::{ apply, execute::TransactionExecutor, storage::StorageWithOverrides, vm_metrics::{SandboxStage, EXECUTION_METRICS, SANDBOX_METRICS}, - BlockArgs, TxSetupArgs, VmPermit, + BlockArgs, VmPermit, }; /// Validation error used by the sandbox. Besides validation errors returned by VM, it also includes an internal error @@ -39,20 +40,25 @@ impl TransactionExecutor { tx: L2Tx, setup_args: TxSetupArgs, block_args: BlockArgs, - computational_gas_limit: u32, + whitelisted_tokens_for_aa: &[Address], ) -> Result<(), ValidationError> { let total_latency = SANDBOX_METRICS.sandbox[&SandboxStage::ValidateInSandbox].start(); let validation_params = get_validation_params( &mut connection, &tx, - computational_gas_limit, - &setup_args.whitelisted_tokens_for_aa, + setup_args.validation_computational_gas_limit, + whitelisted_tokens_for_aa, ) .await .context("failed getting validation params")?; - let (env, storage) = - apply::prepare_env_and_storage(connection, setup_args, &block_args).await?; + let (env, storage) = apply::prepare_env_and_storage( + connection, + setup_args, + &block_args, + self.storage_caches(), + ) + .await?; let storage = StorageWithOverrides::new(storage, &StateOverride::default()); let stage_latency = SANDBOX_METRICS.sandbox[&SandboxStage::Validation].start(); diff --git a/core/node/api_server/src/tx_sender/mod.rs b/core/node/api_server/src/tx_sender/mod.rs index 1d18853a1376..07aeb8aec9c9 100644 --- a/core/node/api_server/src/tx_sender/mod.rs +++ b/core/node/api_server/src/tx_sender/mod.rs @@ -38,14 +38,14 @@ use zksync_types::{ ProtocolVersionId, Transaction, H160, H256, MAX_L2_TX_GAS_LIMIT, MAX_NEW_FACTORY_DEPS, U256, }; use zksync_utils::h256_to_u256; -use zksync_vm_executor::oneshot::MultiVMBaseSystemContracts; +use zksync_vm_executor::oneshot::{MultiVMBaseSystemContracts, TxSetupArgs}; pub(super) use self::result::SubmitTxError; use self::{master_pool_sink::MasterPoolSink, tx_sink::TxSink}; use crate::{ execution_sandbox::{ - BlockArgs, SubmitTxStage, TransactionExecutor, TxSetupArgs, VmConcurrencyBarrier, - VmConcurrencyLimiter, VmPermit, SANDBOX_METRICS, + BlockArgs, SubmitTxStage, TransactionExecutor, VmConcurrencyBarrier, VmConcurrencyLimiter, + VmPermit, SANDBOX_METRICS, }, tx_sender::result::ApiCallResult, }; @@ -188,10 +188,9 @@ impl TxSenderBuilder { batch_fee_input_provider, api_contracts, vm_concurrency_limiter, - storage_caches, whitelisted_tokens_for_aa_cache, sealer, - executor: TransactionExecutor::real(missed_storage_invocation_limit), + executor: TransactionExecutor::real(missed_storage_invocation_limit, storage_caches), })) } } @@ -243,8 +242,6 @@ pub struct TxSenderInner { pub(super) api_contracts: ApiContracts, /// Used to limit the amount of VMs that can be executed simultaneously. pub(super) vm_concurrency_limiter: Arc, - // Caches used in VM execution. - storage_caches: PostgresStorageCaches, // Cache for white-listed tokens. pub(super) whitelisted_tokens_for_aa_cache: Arc>>, /// Batch sealer used to check whether transaction can be executed by the sequencer. @@ -266,10 +263,6 @@ impl TxSender { Arc::clone(&self.0.vm_concurrency_limiter) } - pub(crate) fn storage_caches(&self) -> PostgresStorageCaches { - self.0.storage_caches.clone() - } - pub(crate) async fn read_whitelisted_tokens_for_aa_cache(&self) -> Vec
{ self.0.whitelisted_tokens_for_aa_cache.read().await.clone() } @@ -310,7 +303,7 @@ impl TxSender { setup_args.clone(), TxExecutionArgs::for_validation(tx.clone()), connection, - block_args, + &block_args, None, OneshotTracingParams::default(), ) @@ -324,7 +317,6 @@ impl TxSender { let stage_latency = SANDBOX_METRICS.start_tx_submit_stage(tx_hash, SubmitTxStage::VerifyExecute); let connection = self.acquire_replica_connection().await?; - let computational_gas_limit = self.0.sender_config.validation_computational_gas_limit; let validation_result = self .0 .executor @@ -334,7 +326,7 @@ impl TxSender { tx.clone(), setup_args, block_args, - computational_gas_limit, + &self.read_whitelisted_tokens_for_aa_cache().await, ) .await; stage_latency.observe(); @@ -408,13 +400,11 @@ impl TxSender { operator_account: AccountTreeId::new(self.0.sender_config.fee_account_addr), fee_input, base_system_contracts: self.0.api_contracts.eth_call.clone(), - caches: self.storage_caches(), validation_computational_gas_limit: self .0 .sender_config .validation_computational_gas_limit, chain_id: self.0.sender_config.chain_id, - whitelisted_tokens_for_aa: self.read_whitelisted_tokens_for_aa_cache().await, enforced_base_fee: if let Some(overrides) = call_overrides { overrides.enforced_base_fee } else { @@ -647,7 +637,7 @@ impl TxSender { setup_args, execution_args, connection, - block_args, + &block_args, state_override, OneshotTracingParams::default(), ) @@ -664,9 +654,7 @@ impl TxSender { // We want to bypass the computation gas limit check for gas estimation validation_computational_gas_limit: BATCH_COMPUTATIONAL_GAS_LIMIT, base_system_contracts: self.0.api_contracts.estimate_gas.clone(), - caches: self.storage_caches(), chain_id: config.chain_id, - whitelisted_tokens_for_aa: self.read_whitelisted_tokens_for_aa_cache().await, enforced_base_fee: Some(base_fee), } } @@ -947,7 +935,7 @@ impl TxSender { setup_args, TxExecutionArgs::for_eth_call(tx), connection, - block_args, + &block_args, state_override, OneshotTracingParams::default(), ) diff --git a/core/node/api_server/src/web3/namespaces/debug.rs b/core/node/api_server/src/web3/namespaces/debug.rs index ad00f6a878b9..6b91e3a96cee 100644 --- a/core/node/api_server/src/web3/namespaces/debug.rs +++ b/core/node/api_server/src/web3/namespaces/debug.rs @@ -15,10 +15,10 @@ use zksync_types::{ transaction_request::CallRequest, web3, AccountTreeId, H256, U256, }; +use zksync_vm_executor::oneshot::TxSetupArgs; use zksync_web3_decl::error::Web3Error; use crate::{ - execution_sandbox::TxSetupArgs, tx_sender::{ApiContracts, TxSenderConfig}, web3::{backend_jsonrpsee::MethodTracer, state::RpcState}, }; @@ -201,7 +201,7 @@ impl DebugNamespace { setup_args, TxExecutionArgs::for_eth_call(tx.clone()), connection, - block_args, + &block_args, None, tracing_params, ) @@ -237,14 +237,8 @@ impl DebugNamespace { operator_account: AccountTreeId::default(), fee_input: self.batch_fee_input, base_system_contracts: self.api_contracts.eth_call.clone(), - caches: self.state.tx_sender.storage_caches().clone(), validation_computational_gas_limit: BATCH_COMPUTATIONAL_GAS_LIMIT, chain_id: sender_config.chain_id, - whitelisted_tokens_for_aa: self - .state - .tx_sender - .read_whitelisted_tokens_for_aa_cache() - .await, enforced_base_fee, } } From cc6acfb17eaddb0ae5f851b9cef110d0fa9e0ab2 Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Thu, 12 Sep 2024 09:46:52 +0300 Subject: [PATCH 03/12] Use oneshot executor in consensus --- Cargo.lock | 2 +- core/lib/vm_executor/src/oneshot/block.rs | 16 +++--- core/lib/vm_executor/src/oneshot/contracts.rs | 20 +++---- core/lib/vm_executor/src/oneshot/mod.rs | 2 +- .../api_server/src/execution_sandbox/apply.rs | 3 +- core/node/consensus/Cargo.toml | 3 +- core/node/consensus/src/storage/connection.rs | 24 ++++----- core/node/consensus/src/vm.rs | 53 +++++++++++-------- 8 files changed, 65 insertions(+), 58 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 59b464f8501d..a87add2a22df 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10209,7 +10209,6 @@ version = "0.1.0" dependencies = [ "anyhow", "async-trait", - "hex", "rand 0.8.5", "secrecy", "semver", @@ -10243,6 +10242,7 @@ dependencies = [ "zksync_test_account", "zksync_types", "zksync_utils", + "zksync_vm_executor", "zksync_vm_interface", "zksync_web3_decl", ] diff --git a/core/lib/vm_executor/src/oneshot/block.rs b/core/lib/vm_executor/src/oneshot/block.rs index 33a46e3d710e..8052cfafe19f 100644 --- a/core/lib/vm_executor/src/oneshot/block.rs +++ b/core/lib/vm_executor/src/oneshot/block.rs @@ -194,8 +194,8 @@ impl TxSetupArgs { } } - pub async fn into_env( - self, + pub async fn to_env( + &self, connection: &mut Connection<'_, Core>, resolved_block_info: &ResolvedBlockInfo, ) -> anyhow::Result { @@ -216,25 +216,27 @@ impl TxSetupArgs { } fn prepare_env( - self, + &self, resolved_block_info: &ResolvedBlockInfo, next_block: L2BlockEnv, ) -> (SystemEnv, L1BatchEnv) { - let Self { + let &Self { execution_mode, operator_account, fee_input, - base_system_contracts, validation_computational_gas_limit, chain_id, enforced_base_fee, + .. } = self; let system_env = SystemEnv { zk_porter_available: ZKPORTER_IS_AVAILABLE, version: resolved_block_info.protocol_version, - base_system_smart_contracts: base_system_contracts - .get_by_protocol_version(resolved_block_info.protocol_version), + base_system_smart_contracts: self + .base_system_contracts + .get_by_protocol_version(resolved_block_info.protocol_version) + .clone(), bootloader_gas_limit: BATCH_COMPUTATIONAL_GAS_LIMIT, execution_mode, default_validation_computational_gas_limit: validation_computational_gas_limit, diff --git a/core/lib/vm_executor/src/oneshot/contracts.rs b/core/lib/vm_executor/src/oneshot/contracts.rs index a328e48fb7ec..57635ff3ef57 100644 --- a/core/lib/vm_executor/src/oneshot/contracts.rs +++ b/core/lib/vm_executor/src/oneshot/contracts.rs @@ -26,7 +26,7 @@ pub struct MultiVMBaseSystemContracts { impl MultiVMBaseSystemContracts { /// Gets contracts for a certain version. - pub fn get_by_protocol_version(self, version: ProtocolVersionId) -> BaseSystemContracts { + pub fn get_by_protocol_version(&self, version: ProtocolVersionId) -> &BaseSystemContracts { match version { ProtocolVersionId::Version0 | ProtocolVersionId::Version1 @@ -40,19 +40,19 @@ impl MultiVMBaseSystemContracts { | ProtocolVersionId::Version9 | ProtocolVersionId::Version10 | ProtocolVersionId::Version11 - | ProtocolVersionId::Version12 => self.pre_virtual_blocks, - ProtocolVersionId::Version13 => self.post_virtual_blocks, + | ProtocolVersionId::Version12 => &self.pre_virtual_blocks, + ProtocolVersionId::Version13 => &self.post_virtual_blocks, ProtocolVersionId::Version14 | ProtocolVersionId::Version15 | ProtocolVersionId::Version16 - | ProtocolVersionId::Version17 => self.post_virtual_blocks_finish_upgrade_fix, - ProtocolVersionId::Version18 => self.post_boojum, - ProtocolVersionId::Version19 => self.post_allowlist_removal, - ProtocolVersionId::Version20 => self.post_1_4_1, - ProtocolVersionId::Version21 | ProtocolVersionId::Version22 => self.post_1_4_2, - ProtocolVersionId::Version23 => self.vm_1_5_0_small_memory, + | ProtocolVersionId::Version17 => &self.post_virtual_blocks_finish_upgrade_fix, + ProtocolVersionId::Version18 => &self.post_boojum, + ProtocolVersionId::Version19 => &self.post_allowlist_removal, + ProtocolVersionId::Version20 => &self.post_1_4_1, + ProtocolVersionId::Version21 | ProtocolVersionId::Version22 => &self.post_1_4_2, + ProtocolVersionId::Version23 => &self.vm_1_5_0_small_memory, ProtocolVersionId::Version24 | ProtocolVersionId::Version25 => { - self.vm_1_5_0_increased_memory + &self.vm_1_5_0_increased_memory } } } diff --git a/core/lib/vm_executor/src/oneshot/mod.rs b/core/lib/vm_executor/src/oneshot/mod.rs index 2b922943960d..6b8c83276fd6 100644 --- a/core/lib/vm_executor/src/oneshot/mod.rs +++ b/core/lib/vm_executor/src/oneshot/mod.rs @@ -33,7 +33,7 @@ use zksync_types::{ use zksync_utils::{h256_to_u256, u256_to_h256}; pub use self::{ - block::{BlockInfo, TxSetupArgs}, + block::{BlockInfo, ResolvedBlockInfo, TxSetupArgs}, contracts::MultiVMBaseSystemContracts, mock::MockOneshotExecutor, }; diff --git a/core/node/api_server/src/execution_sandbox/apply.rs b/core/node/api_server/src/execution_sandbox/apply.rs index 2b3497cc975f..47f7b0887866 100644 --- a/core/node/api_server/src/execution_sandbox/apply.rs +++ b/core/node/api_server/src/execution_sandbox/apply.rs @@ -12,6 +12,7 @@ use super::{ BlockArgs, }; +// FIXME: pass args by ref pub(super) async fn prepare_env_and_storage( mut connection: Connection<'static, Core>, setup_args: TxSetupArgs, @@ -28,7 +29,7 @@ pub(super) async fn prepare_env_and_storage( } let env = setup_args - .into_env(&mut connection, &resolved_block_info) + .to_env(&mut connection, &resolved_block_info) .await?; if block_args.resolves_to_latest_sealed_l2_block() { diff --git a/core/node/consensus/Cargo.toml b/core/node/consensus/Cargo.toml index 707bd957d810..fdcc9089e339 100644 --- a/core/node/consensus/Cargo.toml +++ b/core/node/consensus/Cargo.toml @@ -32,8 +32,8 @@ zksync_system_constants.workspace = true zksync_types.workspace = true zksync_utils.workspace = true zksync_web3_decl.workspace = true -zksync_node_api_server.workspace = true zksync_state.workspace = true +zksync_vm_executor.workspace = true zksync_vm_interface.workspace = true anyhow.workspace = true async-trait.workspace = true @@ -41,7 +41,6 @@ secrecy.workspace = true tempfile.workspace = true thiserror.workspace = true tracing.workspace = true -hex.workspace = true tokio.workspace = true semver.workspace = true diff --git a/core/node/consensus/src/storage/connection.rs b/core/node/consensus/src/storage/connection.rs index 512b37e81a11..228a78c5bdf7 100644 --- a/core/node/consensus/src/storage/connection.rs +++ b/core/node/consensus/src/storage/connection.rs @@ -5,10 +5,10 @@ use zksync_consensus_roles::{attester, attester::BatchNumber, validator}; use zksync_consensus_storage::{self as storage, BatchStoreState}; use zksync_dal::{consensus_dal, consensus_dal::Payload, Core, CoreDal, DalError}; use zksync_l1_contract_interface::i_executor::structures::StoredBatchInfo; -use zksync_node_api_server::execution_sandbox::{BlockArgs, BlockStartInfo}; use zksync_node_sync::{fetcher::IoCursorExt as _, ActionQueueSender, SyncState}; use zksync_state_keeper::io::common::IoCursor; -use zksync_types::{api, commitment::L1BatchWithMetadata, L1BatchNumber}; +use zksync_types::{commitment::L1BatchWithMetadata, L1BatchNumber, L2BlockNumber}; +use zksync_vm_executor::oneshot::{BlockInfo, ResolvedBlockInfo}; use super::{InsertCertificateError, PayloadQueue}; use crate::config; @@ -469,27 +469,25 @@ impl<'a> Connection<'a> { } /// Constructs `BlockArgs` for the last block of the batch. - pub async fn vm_block_args( + pub async fn vm_block_info( &mut self, ctx: &ctx::Ctx, batch: attester::BatchNumber, - ) -> ctx::Result { + ) -> ctx::Result { let (_, block) = self .get_l2_block_range_of_l1_batch(ctx, batch) .await .wrap("get_l2_block_range_of_l1_batch()")? .context("batch not sealed")?; - let block = api::BlockId::Number(api::BlockNumber::Number(block.0.into())); - let start_info = ctx - .wait(BlockStartInfo::new( - &mut self.0, - /*max_cache_age=*/ std::time::Duration::from_secs(10), - )) + // `unwrap()` is safe: the block range is returned as `L2BlockNumber`s + let block = L2BlockNumber(u32::try_from(block.0).unwrap()); + let block_info = ctx + .wait(BlockInfo::for_existing_block(&mut self.0, block)) .await? - .context("BlockStartInfo::new()")?; + .context("BlockInfo")?; Ok(ctx - .wait(BlockArgs::new(&mut self.0, block, &start_info)) + .wait(block_info.resolve(&mut self.0)) .await? - .context("BlockArgs::new")?) + .context("resolve()")?) } } diff --git a/core/node/consensus/src/vm.rs b/core/node/consensus/src/vm.rs index 11b6b5c67e3b..632381608e95 100644 --- a/core/node/consensus/src/vm.rs +++ b/core/node/consensus/src/vm.rs @@ -1,17 +1,16 @@ use anyhow::Context as _; +use tokio::runtime::Handle; use zksync_concurrency::{ctx, error::Wrap as _, scope}; use zksync_consensus_roles::attester; -use zksync_node_api_server::{ - execution_sandbox::{TransactionExecutor, TxSetupArgs, VmConcurrencyLimiter}, - tx_sender::MultiVMBaseSystemContracts, -}; -use zksync_state::PostgresStorageCaches; +use zksync_state::PostgresStorage; use zksync_system_constants::DEFAULT_L2_TX_GAS_PER_PUBDATA_BYTE; use zksync_types::{ ethabi, fee::Fee, fee_model::BatchFeeInput, l2::L2Tx, AccountTreeId, L2ChainId, Nonce, U256, }; +use zksync_vm_executor::oneshot::{MainOneshotExecutor, MultiVMBaseSystemContracts, TxSetupArgs}; use zksync_vm_interface::{ - ExecutionResult, OneshotTracingParams, TxExecutionArgs, TxExecutionMode, + executor::OneshotExecutor, ExecutionResult, OneshotTracingParams, TxExecutionArgs, + TxExecutionMode, }; use crate::{abi, storage::ConnectionPool}; @@ -21,7 +20,7 @@ use crate::{abi, storage::ConnectionPool}; pub(crate) struct VM { pool: ConnectionPool, setup_args: TxSetupArgs, - limiter: VmConcurrencyLimiter, + executor: MainOneshotExecutor, } impl VM { @@ -37,17 +36,14 @@ impl VM { MultiVMBaseSystemContracts::load_eth_call_blocking, ) .await, - caches: PostgresStorageCaches::new(1, 1), validation_computational_gas_limit: u32::MAX, chain_id: L2ChainId::default(), - whitelisted_tokens_for_aa: vec![], enforced_base_fee: None, }, - limiter: VmConcurrencyLimiter::new(1).0, + executor: MainOneshotExecutor::new(usize::MAX), } } - // FIXME (PLA-1018): switch to oneshot executor pub async fn call( &self, ctx: &ctx::Ctx, @@ -70,25 +66,36 @@ impl VM { vec![], Default::default(), ); - let permit = ctx.wait(self.limiter.acquire()).await?.unwrap(); + let mut conn = self.pool.connection(ctx).await.wrap("connection()")?; - let args = conn - .vm_block_args(ctx, batch) + let block_info = conn + .vm_block_info(ctx, batch) .await - .wrap("vm_block_args()")?; - let output = ctx - .wait(TransactionExecutor::real(usize::MAX).execute_tx_in_sandbox( - permit, - self.setup_args.clone(), - TxExecutionArgs::for_eth_call(tx.clone()), + .wrap("vm_block_info()")?; + let env = ctx + .wait(self.setup_args.to_env(&mut conn.0, &block_info)) + .await? + .context("to_env()")?; + let storage = ctx + .wait(PostgresStorage::new_async( + Handle::current(), conn.0, - args, - None, + block_info.state_l2_block_number(), + false, + )) + .await? + .context("PostgresStorage")?; + + let output = ctx + .wait(self.executor.inspect_transaction_with_bytecode_compression( + storage, + env, + TxExecutionArgs::for_eth_call(tx), OneshotTracingParams::default(), )) .await? .context("execute_tx_in_sandbox()")?; - match output.vm.result { + match output.tx_result.result { ExecutionResult::Success { output } => { Ok(call.decode_outputs(&output).context("decode_output()")?) } From dc5b570160762b7fe9e5b5bc82a24a557898d36a Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Thu, 12 Sep 2024 16:21:12 +0300 Subject: [PATCH 04/12] Make API server work --- core/bin/external_node/src/node_builder.rs | 4 +- core/bin/zksync_server/src/node_builder.rs | 3 +- core/lib/vm_executor/src/oneshot/block.rs | 60 ++--- core/lib/vm_executor/src/oneshot/contracts.rs | 24 +- core/lib/vm_executor/src/oneshot/mod.rs | 5 +- core/lib/vm_executor/src/oneshot/options.rs | 121 ++++++++++ .../api_server/src/execution_sandbox/apply.rs | 55 ----- .../src/execution_sandbox/execute.rs | 227 ++++++++++++++---- .../api_server/src/execution_sandbox/mod.rs | 20 +- .../api_server/src/execution_sandbox/tests.rs | 105 ++++---- .../src/execution_sandbox/validate.rs | 30 ++- core/node/api_server/src/tx_sender/mod.rs | 211 +++++++--------- core/node/api_server/src/tx_sender/tests.rs | 10 +- .../api_server/src/web3/namespaces/debug.rs | 53 ++-- core/node/api_server/src/web3/testonly.rs | 7 +- core/node/consensus/src/vm.rs | 32 ++- .../layers/web3_api/tx_sender.rs | 20 +- 17 files changed, 561 insertions(+), 426 deletions(-) create mode 100644 core/lib/vm_executor/src/oneshot/options.rs delete mode 100644 core/node/api_server/src/execution_sandbox/apply.rs diff --git a/core/bin/external_node/src/node_builder.rs b/core/bin/external_node/src/node_builder.rs index 98e286c253a2..d0055896d42e 100644 --- a/core/bin/external_node/src/node_builder.rs +++ b/core/bin/external_node/src/node_builder.rs @@ -12,7 +12,7 @@ use zksync_config::{ PostgresConfig, }; use zksync_metadata_calculator::{MetadataCalculatorConfig, MetadataCalculatorRecoveryConfig}; -use zksync_node_api_server::{tx_sender::ApiContracts, web3::Namespace}; +use zksync_node_api_server::web3::Namespace; use zksync_node_framework::{ implementations::layers::{ batch_status_updater::BatchStatusUpdaterLayer, @@ -380,12 +380,10 @@ impl ExternalNodeBuilder { latest_values_cache_size: self.config.optional.latest_values_cache_size() as u64, }; let max_vm_concurrency = self.config.optional.vm_concurrency_limit; - let api_contracts = ApiContracts::load_from_disk_blocking(); // TODO (BFT-138): Allow to dynamically reload API contracts; let tx_sender_layer = TxSenderLayer::new( (&self.config).into(), postgres_storage_config, max_vm_concurrency, - api_contracts, ) .with_whitelisted_tokens_for_aa_cache(true); diff --git a/core/bin/zksync_server/src/node_builder.rs b/core/bin/zksync_server/src/node_builder.rs index 069a7a799ab5..14db83b9f25a 100644 --- a/core/bin/zksync_server/src/node_builder.rs +++ b/core/bin/zksync_server/src/node_builder.rs @@ -12,7 +12,7 @@ use zksync_config::{ use zksync_core_leftovers::Component; use zksync_metadata_calculator::MetadataCalculatorConfig; use zksync_node_api_server::{ - tx_sender::{ApiContracts, TxSenderConfig}, + tx_sender::TxSenderConfig, web3::{state::InternalApiConfig, Namespace}, }; use zksync_node_framework::{ @@ -322,7 +322,6 @@ impl MainNodeBuilder { ), postgres_storage_caches_config, rpc_config.vm_concurrency_limit(), - ApiContracts::load_from_disk_blocking(), // TODO (BFT-138): Allow to dynamically reload API contracts )); Ok(self) } diff --git a/core/lib/vm_executor/src/oneshot/block.rs b/core/lib/vm_executor/src/oneshot/block.rs index 8052cfafe19f..f28aa4b6786b 100644 --- a/core/lib/vm_executor/src/oneshot/block.rs +++ b/core/lib/vm_executor/src/oneshot/block.rs @@ -8,13 +8,13 @@ use zksync_types::{ api, block::{unpack_block_info, L2BlockHasher}, fee_model::BatchFeeInput, - AccountTreeId, L1BatchNumber, L2BlockNumber, L2ChainId, ProtocolVersionId, StorageKey, H256, + AccountTreeId, L1BatchNumber, L2BlockNumber, ProtocolVersionId, StorageKey, H256, SYSTEM_CONTEXT_ADDRESS, SYSTEM_CONTEXT_CURRENT_L2_BLOCK_INFO_POSITION, SYSTEM_CONTEXT_CURRENT_TX_ROLLING_HASH_POSITION, ZKPORTER_IS_AVAILABLE, }; use zksync_utils::{h256_to_u256, time::seconds_since_epoch}; -use super::MultiVMBaseSystemContracts; +use super::options::OneshotExecutorOptions; /// Block information necessary to execute a transaction / call. // FIXME: consider combining with `ResolvedBlockInfo`? @@ -163,41 +163,14 @@ impl ResolvedBlockInfo { } } -/// Arguments for VM execution necessary to set up storage and environment. -// FIXME: rename -#[derive(Debug, Clone)] -pub struct TxSetupArgs { - // FIXME: revise fields vs args for `into_env` - pub execution_mode: TxExecutionMode, // changes each call - pub operator_account: AccountTreeId, // rarely - pub fee_input: BatchFeeInput, // each call - pub base_system_contracts: MultiVMBaseSystemContracts, // never - pub validation_computational_gas_limit: u32, // rarely - pub chain_id: L2ChainId, // never - pub enforced_base_fee: Option, // ??? -} - -impl TxSetupArgs { - #[doc(hidden)] // Useful only for tests - pub fn mock( - execution_mode: TxExecutionMode, - base_system_contracts: MultiVMBaseSystemContracts, - ) -> Self { - Self { - execution_mode, - operator_account: AccountTreeId::default(), - fee_input: BatchFeeInput::l1_pegged(55, 555), - base_system_contracts, - validation_computational_gas_limit: u32::MAX, - chain_id: L2ChainId::default(), - enforced_base_fee: None, - } - } - - pub async fn to_env( +impl OneshotExecutorOptions { + pub(super) async fn to_env_inner( &self, connection: &mut Connection<'_, Core>, + execution_mode: TxExecutionMode, resolved_block_info: &ResolvedBlockInfo, + fee_input: BatchFeeInput, + enforced_base_fee: Option, ) -> anyhow::Result { let (next_block, current_block) = load_l2_block_info( connection, @@ -206,27 +179,32 @@ impl TxSetupArgs { ) .await?; - let (system, l1_batch) = self.prepare_env(resolved_block_info, next_block); - let env = OneshotEnv { + let (system, l1_batch) = self.prepare_env( + execution_mode, + resolved_block_info, + next_block, + fee_input, + enforced_base_fee, + ); + Ok(OneshotEnv { system, l1_batch, current_block, - }; - Ok(env) + }) } fn prepare_env( &self, + execution_mode: TxExecutionMode, resolved_block_info: &ResolvedBlockInfo, next_block: L2BlockEnv, + fee_input: BatchFeeInput, + enforced_base_fee: Option, ) -> (SystemEnv, L1BatchEnv) { let &Self { - execution_mode, operator_account, - fee_input, validation_computational_gas_limit, chain_id, - enforced_base_fee, .. } = self; diff --git a/core/lib/vm_executor/src/oneshot/contracts.rs b/core/lib/vm_executor/src/oneshot/contracts.rs index 57635ff3ef57..f38c90c92442 100644 --- a/core/lib/vm_executor/src/oneshot/contracts.rs +++ b/core/lib/vm_executor/src/oneshot/contracts.rs @@ -3,25 +3,25 @@ use zksync_types::ProtocolVersionId; /// System contracts (bootloader and default account abstraction) for all VM versions. #[derive(Debug, Clone)] -pub struct MultiVMBaseSystemContracts { +pub(super) struct MultiVMBaseSystemContracts { /// Contracts to be used for pre-virtual-blocks protocol versions. - pub(crate) pre_virtual_blocks: BaseSystemContracts, + pre_virtual_blocks: BaseSystemContracts, /// Contracts to be used for post-virtual-blocks protocol versions. - pub(crate) post_virtual_blocks: BaseSystemContracts, + post_virtual_blocks: BaseSystemContracts, /// Contracts to be used for protocol versions after virtual block upgrade fix. - pub(crate) post_virtual_blocks_finish_upgrade_fix: BaseSystemContracts, + post_virtual_blocks_finish_upgrade_fix: BaseSystemContracts, /// Contracts to be used for post-boojum protocol versions. - pub(crate) post_boojum: BaseSystemContracts, + post_boojum: BaseSystemContracts, /// Contracts to be used after the allow-list removal upgrade - pub(crate) post_allowlist_removal: BaseSystemContracts, + post_allowlist_removal: BaseSystemContracts, /// Contracts to be used after the 1.4.1 upgrade - pub(crate) post_1_4_1: BaseSystemContracts, + post_1_4_1: BaseSystemContracts, /// Contracts to be used after the 1.4.2 upgrade - pub(crate) post_1_4_2: BaseSystemContracts, + post_1_4_2: BaseSystemContracts, /// Contracts to be used during the `v23` upgrade. This upgrade was done on an internal staging environment only. - pub(crate) vm_1_5_0_small_memory: BaseSystemContracts, + vm_1_5_0_small_memory: BaseSystemContracts, /// Contracts to be used after the 1.5.0 upgrade - pub(crate) vm_1_5_0_increased_memory: BaseSystemContracts, + vm_1_5_0_increased_memory: BaseSystemContracts, } impl MultiVMBaseSystemContracts { @@ -57,7 +57,7 @@ impl MultiVMBaseSystemContracts { } } - pub fn load_estimate_gas_blocking() -> Self { + pub(super) fn load_estimate_gas_blocking() -> Self { Self { pre_virtual_blocks: BaseSystemContracts::estimate_gas_pre_virtual_blocks(), post_virtual_blocks: BaseSystemContracts::estimate_gas_post_virtual_blocks(), @@ -73,7 +73,7 @@ impl MultiVMBaseSystemContracts { } } - pub fn load_eth_call_blocking() -> Self { + pub(super) fn load_eth_call_blocking() -> Self { Self { pre_virtual_blocks: BaseSystemContracts::playground_pre_virtual_blocks(), post_virtual_blocks: BaseSystemContracts::playground_post_virtual_blocks(), diff --git a/core/lib/vm_executor/src/oneshot/mod.rs b/core/lib/vm_executor/src/oneshot/mod.rs index 6b8c83276fd6..ab26d1ba3375 100644 --- a/core/lib/vm_executor/src/oneshot/mod.rs +++ b/core/lib/vm_executor/src/oneshot/mod.rs @@ -33,15 +33,16 @@ use zksync_types::{ use zksync_utils::{h256_to_u256, u256_to_h256}; pub use self::{ - block::{BlockInfo, ResolvedBlockInfo, TxSetupArgs}, - contracts::MultiVMBaseSystemContracts, + block::{BlockInfo, ResolvedBlockInfo}, mock::MockOneshotExecutor, + options::{CallOrExecute, EstimateGas, OneshotExecutorOptions}, }; mod block; mod contracts; mod metrics; mod mock; +mod options; /// Main [`OneshotExecutor`] implementation used by the API server. #[derive(Debug, Default)] diff --git a/core/lib/vm_executor/src/oneshot/options.rs b/core/lib/vm_executor/src/oneshot/options.rs new file mode 100644 index 000000000000..1b6f7af8d9a5 --- /dev/null +++ b/core/lib/vm_executor/src/oneshot/options.rs @@ -0,0 +1,121 @@ +use std::marker::PhantomData; + +use anyhow::Context; +use zksync_dal::{Connection, Core}; +use zksync_multivm::interface::{OneshotEnv, TxExecutionMode}; +use zksync_types::{fee_model::BatchFeeInput, l2::L2Tx, AccountTreeId, L2ChainId}; + +use crate::oneshot::{contracts::MultiVMBaseSystemContracts, ResolvedBlockInfo}; + +/// Marker for [`OneshotExecutorOptions`] used for gas estimation. +#[derive(Debug)] +pub struct EstimateGas(()); + +#[derive(Debug)] +pub struct CallOrExecute(()); + +/// Oneshot executor options that are expected to be constant or rarely change during the program lifetime. +#[derive(Debug)] +pub struct OneshotExecutorOptions { + pub(super) chain_id: L2ChainId, + pub(super) base_system_contracts: MultiVMBaseSystemContracts, + pub(super) operator_account: AccountTreeId, + pub(super) validation_computational_gas_limit: u32, + _ty: PhantomData, +} + +impl OneshotExecutorOptions { + pub fn validation_computational_gas_limit(&self) -> u32 { + self.validation_computational_gas_limit + } +} + +impl OneshotExecutorOptions { + pub async fn for_gas_estimation( + chain_id: L2ChainId, + operator_account: AccountTreeId, + ) -> anyhow::Result { + Ok(Self { + chain_id, + base_system_contracts: tokio::task::spawn_blocking( + MultiVMBaseSystemContracts::load_estimate_gas_blocking, + ) + .await + .context("failed loading system contracts for gas estimation")?, + operator_account, + validation_computational_gas_limit: u32::MAX, + _ty: PhantomData, + }) + } + + pub async fn to_env( + &self, + connection: &mut Connection<'_, Core>, + resolved_block_info: &ResolvedBlockInfo, + fee_input: BatchFeeInput, + base_fee: u64, + ) -> anyhow::Result { + self.to_env_inner( + connection, + TxExecutionMode::EstimateFee, + resolved_block_info, + fee_input, + Some(base_fee), + ) + .await + } +} + +impl OneshotExecutorOptions { + pub async fn for_execution( + chain_id: L2ChainId, + operator_account: AccountTreeId, + validation_computational_gas_limit: u32, + ) -> anyhow::Result { + Ok(Self { + chain_id, + base_system_contracts: tokio::task::spawn_blocking( + MultiVMBaseSystemContracts::load_eth_call_blocking, + ) + .await + .context("failed loading system contracts for calls")?, + operator_account, + validation_computational_gas_limit, + _ty: PhantomData, + }) + } + + pub async fn to_call_env( + &self, + connection: &mut Connection<'_, Core>, + resolved_block_info: &ResolvedBlockInfo, + fee_input: BatchFeeInput, + enforced_base_fee: Option, + ) -> anyhow::Result { + self.to_env_inner( + connection, + TxExecutionMode::EthCall, + resolved_block_info, + fee_input, + enforced_base_fee, + ) + .await + } + + pub async fn to_execute_env( + &self, + connection: &mut Connection<'_, Core>, + resolved_block_info: &ResolvedBlockInfo, + fee_input: BatchFeeInput, + tx: &L2Tx, + ) -> anyhow::Result { + self.to_env_inner( + connection, + TxExecutionMode::VerifyExecute, + resolved_block_info, + fee_input, + Some(tx.common_data.fee.max_fee_per_gas.as_u64()), + ) + .await + } +} diff --git a/core/node/api_server/src/execution_sandbox/apply.rs b/core/node/api_server/src/execution_sandbox/apply.rs deleted file mode 100644 index 47f7b0887866..000000000000 --- a/core/node/api_server/src/execution_sandbox/apply.rs +++ /dev/null @@ -1,55 +0,0 @@ -use std::time::{Duration, Instant}; - -use anyhow::Context as _; -use tokio::runtime::Handle; -use zksync_dal::{Connection, Core}; -use zksync_multivm::interface::OneshotEnv; -use zksync_state::{PostgresStorage, PostgresStorageCaches}; -use zksync_vm_executor::oneshot::TxSetupArgs; - -use super::{ - vm_metrics::{SandboxStage, SANDBOX_METRICS}, - BlockArgs, -}; - -// FIXME: pass args by ref -pub(super) async fn prepare_env_and_storage( - mut connection: Connection<'static, Core>, - setup_args: TxSetupArgs, - block_args: &BlockArgs, - storage_caches: Option, -) -> anyhow::Result<(OneshotEnv, PostgresStorage<'static>)> { - let initialization_stage = SANDBOX_METRICS.sandbox[&SandboxStage::Initialization].start(); - let resolve_started_at = Instant::now(); - let resolve_time = resolve_started_at.elapsed(); - let resolved_block_info = block_args.inner.resolve(&mut connection).await?; - // We don't want to emit too many logs. - if resolve_time > Duration::from_millis(10) { - tracing::debug!("Resolved block numbers (took {resolve_time:?})"); - } - - let env = setup_args - .to_env(&mut connection, &resolved_block_info) - .await?; - - if block_args.resolves_to_latest_sealed_l2_block() { - if let Some(caches) = &storage_caches { - caches.schedule_values_update(resolved_block_info.state_l2_block_number()); - } - } - - let mut storage = PostgresStorage::new_async( - Handle::current(), - connection, - resolved_block_info.state_l2_block_number(), - false, - ) - .await - .context("cannot create `PostgresStorage`")?; - - if let Some(caches) = storage_caches { - storage = storage.with_caches(caches); - } - initialization_stage.observe(); - Ok((env, storage)) -} diff --git a/core/node/api_server/src/execution_sandbox/execute.rs b/core/node/api_server/src/execution_sandbox/execute.rs index a19cdd48b1f0..58d080551473 100644 --- a/core/node/api_server/src/execution_sandbox/execute.rs +++ b/core/node/api_server/src/execution_sandbox/execute.rs @@ -1,6 +1,10 @@ //! Implementation of "executing" methods, e.g. `eth_call`. +use std::time::{Duration, Instant}; + +use anyhow::Context as _; use async_trait::async_trait; +use tokio::runtime::Handle; use zksync_dal::{Connection, Core}; use zksync_multivm::interface::{ executor::{OneshotExecutor, TransactionValidator}, @@ -9,17 +13,69 @@ use zksync_multivm::interface::{ Call, OneshotEnv, OneshotTracingParams, OneshotTransactionExecutionResult, TransactionExecutionMetrics, TxExecutionArgs, VmExecutionResultAndLogs, }; -use zksync_state::PostgresStorageCaches; -use zksync_types::{api::state_override::StateOverride, l2::L2Tx}; -use zksync_vm_executor::oneshot::{MainOneshotExecutor, MockOneshotExecutor, TxSetupArgs}; +use zksync_state::{PostgresStorage, PostgresStorageCaches}; +use zksync_types::{ + api::state_override::StateOverride, fee_model::BatchFeeInput, l2::L2Tx, Transaction, +}; +use zksync_vm_executor::oneshot::{MainOneshotExecutor, MockOneshotExecutor}; use super::{ - apply, storage::StorageWithOverrides, vm_metrics, BlockArgs, VmPermit, SANDBOX_METRICS, + storage::StorageWithOverrides, + vm_metrics::{self, SandboxStage}, + BlockArgs, VmPermit, SANDBOX_METRICS, }; -use crate::execution_sandbox::vm_metrics::SandboxStage; +use crate::tx_sender::SandboxExecutorOptions; + +#[derive(Debug)] +pub(crate) enum SandboxAction { + Execution { + tx: L2Tx, + fee_input: BatchFeeInput, + }, + Call { + call: L2Tx, + fee_input: BatchFeeInput, + enforced_base_fee: Option, + tracing_params: OneshotTracingParams, + }, + GasEstimation { + tx: Transaction, + fee_input: BatchFeeInput, + base_fee: u64, + }, +} + +impl SandboxAction { + fn factory_deps_count(&self) -> usize { + match self { + Self::Execution { tx, .. } | Self::Call { call: tx, .. } => { + tx.execute.factory_deps.len() + } + Self::GasEstimation { tx, .. } => tx.execute.factory_deps.len(), + } + } + + fn into_parts(self) -> (TxExecutionArgs, OneshotTracingParams) { + match self { + Self::Execution { tx, .. } => ( + TxExecutionArgs::for_validation(tx), + OneshotTracingParams::default(), + ), + Self::GasEstimation { tx, .. } => ( + TxExecutionArgs::for_gas_estimate(tx), + OneshotTracingParams::default(), + ), + Self::Call { + call, + tracing_params, + .. + } => (TxExecutionArgs::for_eth_call(call), tracing_params), + } + } +} #[derive(Debug, Clone)] -pub struct TransactionExecutionOutput { +pub(crate) struct SandboxExecutionOutput { /// Output of the VM. pub vm: VmExecutionResultAndLogs, /// Traced calls if requested. @@ -30,57 +86,68 @@ pub struct TransactionExecutionOutput { pub are_published_bytecodes_ok: bool, } -/// Executor of transactions. #[derive(Debug)] -pub enum TransactionExecutor { - Real { - executor: MainOneshotExecutor, - caches: PostgresStorageCaches, - }, - #[doc(hidden)] // Intended for tests only +enum SandboxExecutorEngine { + Real(MainOneshotExecutor), Mock(MockOneshotExecutor), } -impl TransactionExecutor { - pub fn real(missed_storage_invocation_limit: usize, caches: PostgresStorageCaches) -> Self { +/// Executor of transactions / calls used in the API server. +#[derive(Debug)] +pub(crate) struct SandboxExecutor { + engine: SandboxExecutorEngine, + pub(super) options: SandboxExecutorOptions, + storage_caches: Option, +} + +impl SandboxExecutor { + pub fn real( + options: SandboxExecutorOptions, + caches: PostgresStorageCaches, + missed_storage_invocation_limit: usize, + ) -> Self { let mut executor = MainOneshotExecutor::new(missed_storage_invocation_limit); executor .set_execution_latency_histogram(&SANDBOX_METRICS.sandbox[&SandboxStage::Execution]); - Self::Real { executor, caches } + Self { + engine: SandboxExecutorEngine::Real(executor), + options, + storage_caches: Some(caches), + } } - pub(super) fn storage_caches(&self) -> Option { - match self { - Self::Real { caches, .. } => Some(caches.clone()), - Self::Mock(_) => None, + pub(crate) async fn mock(executor: MockOneshotExecutor) -> Self { + Self { + engine: SandboxExecutorEngine::Mock(executor), + options: SandboxExecutorOptions::mock().await, + storage_caches: None, } } + pub(super) fn storage_caches(&self) -> Option<&PostgresStorageCaches> { + self.storage_caches.as_ref() + } + /// This method assumes that (block with number `resolved_block_number` is present in DB) /// or (`block_id` is `pending` and block with number `resolved_block_number - 1` is present in DB) #[allow(clippy::too_many_arguments)] #[tracing::instrument(level = "debug", skip_all)] - pub async fn execute_tx_in_sandbox( + pub async fn execute_in_sandbox( &self, vm_permit: VmPermit, - setup_args: TxSetupArgs, - execution_args: TxExecutionArgs, connection: Connection<'static, Core>, + action: SandboxAction, block_args: &BlockArgs, state_override: Option, - tracing_params: OneshotTracingParams, - ) -> anyhow::Result { - let total_factory_deps = execution_args.transaction.execute.factory_deps.len() as u16; - let (env, storage) = apply::prepare_env_and_storage( - connection, - setup_args, - block_args, - self.storage_caches(), - ) - .await?; + ) -> anyhow::Result { + let total_factory_deps = action.factory_deps_count() as u16; + let (env, storage) = self + .prepare_env_and_storage(connection, block_args, &action) + .await?; + let state_override = state_override.unwrap_or_default(); let storage = StorageWithOverrides::new(storage, &state_override); - + let (execution_args, tracing_params) = action.into_parts(); let result = self .inspect_transaction_with_bytecode_compression( storage, @@ -93,23 +160,89 @@ impl TransactionExecutor { let metrics = vm_metrics::collect_tx_execution_metrics(total_factory_deps, &result.tx_result); - Ok(TransactionExecutionOutput { + Ok(SandboxExecutionOutput { vm: *result.tx_result, call_traces: result.call_traces, metrics, are_published_bytecodes_ok: result.compression_result.is_ok(), }) } -} -impl From for TransactionExecutor { - fn from(executor: MockOneshotExecutor) -> Self { - Self::Mock(executor) + pub(super) async fn prepare_env_and_storage( + &self, + mut connection: Connection<'static, Core>, + block_args: &BlockArgs, + action: &SandboxAction, + ) -> anyhow::Result<(OneshotEnv, PostgresStorage<'static>)> { + let initialization_stage = SANDBOX_METRICS.sandbox[&SandboxStage::Initialization].start(); + let resolve_started_at = Instant::now(); + let resolve_time = resolve_started_at.elapsed(); + let resolved_block_info = block_args.inner.resolve(&mut connection).await?; + // We don't want to emit too many logs. + if resolve_time > Duration::from_millis(10) { + tracing::debug!("Resolved block numbers (took {resolve_time:?})"); + } + + let env = match action { + SandboxAction::Execution { fee_input, tx } => { + self.options + .eth_call + .to_execute_env(&mut connection, &resolved_block_info, *fee_input, tx) + .await? + } + &SandboxAction::Call { + fee_input, + enforced_base_fee, + .. + } => { + self.options + .eth_call + .to_call_env( + &mut connection, + &resolved_block_info, + fee_input, + enforced_base_fee, + ) + .await? + } + &SandboxAction::GasEstimation { + fee_input, + base_fee, + .. + } => { + self.options + .estimate_gas + .to_env(&mut connection, &resolved_block_info, fee_input, base_fee) + .await? + } + }; + + let storage_caches = self.storage_caches(); + if block_args.resolves_to_latest_sealed_l2_block() { + if let Some(caches) = &storage_caches { + caches.schedule_values_update(resolved_block_info.state_l2_block_number()); + } + } + + let mut storage = PostgresStorage::new_async( + Handle::current(), + connection, + resolved_block_info.state_l2_block_number(), + false, + ) + .await + .context("cannot create `PostgresStorage`")?; + + if let Some(caches) = storage_caches { + storage = storage.with_caches(caches.clone()); + } + initialization_stage.observe(); + Ok((env, storage)) } } #[async_trait] -impl OneshotExecutor for TransactionExecutor +impl OneshotExecutor for SandboxExecutor where S: ReadStorage + Send + 'static, { @@ -120,8 +253,8 @@ where args: TxExecutionArgs, tracing_params: OneshotTracingParams, ) -> anyhow::Result { - match self { - Self::Real { executor, .. } => { + match &self.engine { + SandboxExecutorEngine::Real(executor) => { executor .inspect_transaction_with_bytecode_compression( storage, @@ -131,7 +264,7 @@ where ) .await } - Self::Mock(executor) => { + SandboxExecutorEngine::Mock(executor) => { executor .inspect_transaction_with_bytecode_compression( storage, @@ -146,7 +279,7 @@ where } #[async_trait] -impl TransactionValidator for TransactionExecutor +impl TransactionValidator for SandboxExecutor where S: ReadStorage + Send + 'static, { @@ -157,13 +290,13 @@ where tx: L2Tx, validation_params: ValidationParams, ) -> anyhow::Result> { - match self { - Self::Real { executor, .. } => { + match &self.engine { + SandboxExecutorEngine::Real(executor) => { executor .validate_transaction(storage, env, tx, validation_params) .await } - Self::Mock(executor) => { + SandboxExecutorEngine::Mock(executor) => { executor .validate_transaction(storage, env, tx, validation_params) .await diff --git a/core/node/api_server/src/execution_sandbox/mod.rs b/core/node/api_server/src/execution_sandbox/mod.rs index a2c7ac2b389c..36f10b8e9b08 100644 --- a/core/node/api_server/src/execution_sandbox/mod.rs +++ b/core/node/api_server/src/execution_sandbox/mod.rs @@ -7,19 +7,20 @@ use anyhow::Context as _; use rand::{thread_rng, Rng}; use zksync_dal::{pruning_dal::PruningInfo, Connection, Core, CoreDal, DalError}; use zksync_multivm::utils::get_eth_call_gas_limit; -use zksync_types::{api, L1BatchNumber, L2BlockNumber, ProtocolVersionId, U256}; +use zksync_types::{ + api, fee_model::BatchFeeInput, L1BatchNumber, L2BlockNumber, ProtocolVersionId, U256, +}; use zksync_vm_executor::oneshot::BlockInfo; -pub use self::execute::TransactionExecutor; // FIXME (PLA-1018): remove use self::vm_metrics::SandboxStage; pub(super) use self::{ error::SandboxExecutionError, + execute::{SandboxAction, SandboxExecutor}, validate::ValidationError, vm_metrics::{SubmitTxStage, SANDBOX_METRICS}, }; // Note: keep the modules private, and instead re-export functions that make public interface. -mod apply; mod error; mod execute; mod storage; @@ -285,13 +286,13 @@ pub enum BlockArgsError { /// Information about a block provided to VM. #[derive(Debug, Clone, Copy)] -pub struct BlockArgs { +pub(crate) struct BlockArgs { inner: BlockInfo, block_id: api::BlockId, } impl BlockArgs { - pub(crate) async fn pending(connection: &mut Connection<'_, Core>) -> anyhow::Result { + pub async fn pending(connection: &mut Connection<'_, Core>) -> anyhow::Result { let inner = BlockInfo::pending(connection).await?; Ok(Self { inner, @@ -351,7 +352,14 @@ impl BlockArgs { ) } - pub(crate) async fn default_eth_call_gas( + pub async fn historical_fee_input( + &self, + connection: &mut Connection<'_, Core>, + ) -> anyhow::Result { + self.inner.historical_fee_input(connection).await + } + + pub async fn default_eth_call_gas( &self, connection: &mut Connection<'_, Core>, ) -> anyhow::Result { diff --git a/core/node/api_server/src/execution_sandbox/tests.rs b/core/node/api_server/src/execution_sandbox/tests.rs index 5a9dc8a63f7d..294503b776f3 100644 --- a/core/node/api_server/src/execution_sandbox/tests.rs +++ b/core/node/api_server/src/execution_sandbox/tests.rs @@ -1,31 +1,27 @@ //! Tests for the VM execution sandbox. +// FIXME: simplify to use TransactionExecutor directly + use std::collections::HashMap; use assert_matches::assert_matches; use test_casing::test_casing; use zksync_dal::ConnectionPool; -use zksync_multivm::{ - interface::{ - executor::{OneshotExecutor, TransactionValidator}, - tracer::ValidationError, - Halt, OneshotTracingParams, TxExecutionArgs, TxExecutionMode, - }, - utils::derive_base_fee_and_gas_per_pubdata, -}; +use zksync_multivm::{interface::ExecutionResult, utils::derive_base_fee_and_gas_per_pubdata}; use zksync_node_genesis::{insert_genesis_batch, GenesisParams}; use zksync_node_test_utils::{create_l2_block, prepare_recovery_snapshot}; +use zksync_state::PostgresStorageCaches; use zksync_types::{ api::state_override::{OverrideAccount, StateOverride}, fee::Fee, + fee_model::BatchFeeInput, l2::L2Tx, transaction_request::PaymasterParams, Address, K256PrivateKey, L2ChainId, Nonce, ProtocolVersionId, Transaction, U256, }; -use zksync_vm_executor::oneshot::{MainOneshotExecutor, TxSetupArgs}; -use super::{storage::StorageWithOverrides, *}; -use crate::tx_sender::ApiContracts; +use super::*; +use crate::{execution_sandbox::execute::SandboxExecutor, tx_sender::SandboxExecutorOptions}; #[tokio::test] async fn creating_block_args() { @@ -207,28 +203,31 @@ async fn estimating_gas() { } async fn test_instantiating_vm(connection: Connection<'static, Core>, block_args: BlockArgs) { - let estimate_gas_contracts = ApiContracts::load_from_disk().await.unwrap().estimate_gas; - let mut setup_args = TxSetupArgs::mock(TxExecutionMode::EstimateFee, estimate_gas_contracts); - let (base_fee, gas_per_pubdata) = derive_base_fee_and_gas_per_pubdata( - setup_args.fee_input, - ProtocolVersionId::latest().into(), + let executor = SandboxExecutor::real( + SandboxExecutorOptions::mock().await, + PostgresStorageCaches::new(1, 1), + usize::MAX, ); - setup_args.enforced_base_fee = Some(base_fee); - let transaction = Transaction::from(create_transfer(base_fee, gas_per_pubdata)); - let execution_args = TxExecutionArgs::for_gas_estimate(transaction.clone()); - let (env, storage) = apply::prepare_env_and_storage(connection, setup_args, &block_args, None) - .await - .unwrap(); - let storage = StorageWithOverrides::new(storage, &StateOverride::default()); + let fee_input = BatchFeeInput::l1_pegged(55, 555); + let (base_fee, gas_per_pubdata) = + derive_base_fee_and_gas_per_pubdata(fee_input, ProtocolVersionId::latest().into()); + let tx = Transaction::from(create_transfer(base_fee, gas_per_pubdata)); - let tracing_params = OneshotTracingParams::default(); - let output = MainOneshotExecutor::new(usize::MAX) - .inspect_transaction_with_bytecode_compression(storage, env, execution_args, tracing_params) + let (limiter, _) = VmConcurrencyLimiter::new(1); + let vm_permit = limiter.acquire().await.unwrap(); + let action = SandboxAction::GasEstimation { + fee_input, + base_fee, + tx, + }; + let output = executor + .execute_in_sandbox(vm_permit, connection, action, &block_args, None) .await .unwrap(); - output.compression_result.unwrap(); - let tx_result = *output.tx_result; + + assert!(output.are_published_bytecodes_ok); + let tx_result = output.vm; assert!(!tx_result.result.is_failed(), "{tx_result:#?}"); } @@ -264,47 +263,47 @@ async fn validating_transaction(set_balance: bool) { let block_args = BlockArgs::pending(&mut connection).await.unwrap(); - let call_contracts = ApiContracts::load_from_disk().await.unwrap().eth_call; - let mut setup_args = TxSetupArgs::mock(TxExecutionMode::VerifyExecute, call_contracts); - let (base_fee, gas_per_pubdata) = derive_base_fee_and_gas_per_pubdata( - setup_args.fee_input, - ProtocolVersionId::latest().into(), + let executor = SandboxExecutor::real( + SandboxExecutorOptions::mock().await, + PostgresStorageCaches::new(1, 1), + usize::MAX, ); - setup_args.enforced_base_fee = Some(base_fee); - let transaction = create_transfer(base_fee, gas_per_pubdata); - let validation_params = - validate::get_validation_params(&mut connection, &transaction, u32::MAX, &[]) - .await - .unwrap(); - let (env, storage) = apply::prepare_env_and_storage(connection, setup_args, &block_args, None) - .await - .unwrap(); + let fee_input = BatchFeeInput::l1_pegged(55, 555); + let (base_fee, gas_per_pubdata) = + derive_base_fee_and_gas_per_pubdata(fee_input, ProtocolVersionId::latest().into()); + let tx = create_transfer(base_fee, gas_per_pubdata); + + let (limiter, _) = VmConcurrencyLimiter::new(1); + let vm_permit = limiter.acquire().await.unwrap(); let state_override = if set_balance { let account_override = OverrideAccount { balance: Some(U256::from(1) << 128), ..OverrideAccount::default() }; - StateOverride::new(HashMap::from([( - transaction.initiator_account(), - account_override, - )])) + StateOverride::new(HashMap::from([(tx.initiator_account(), account_override)])) } else { StateOverride::default() }; - let storage = StorageWithOverrides::new(storage, &state_override); - let validation_result = MainOneshotExecutor::new(usize::MAX) - .validate_transaction(storage, env, transaction, validation_params) + let result = executor + .execute_in_sandbox( + vm_permit, + connection, + SandboxAction::Execution { tx, fee_input }, + &block_args, + Some(state_override), + ) .await .unwrap(); + + let result = result.vm.result; if set_balance { - validation_result.expect("validation failed"); + assert_matches!(result, ExecutionResult::Success { .. }); } else { assert_matches!( - validation_result.unwrap_err(), - ValidationError::FailedTx(Halt::ValidationFailed(reason)) - if reason.to_string().contains("Not enough balance") + result, + ExecutionResult::Halt { reason } if reason.to_string().contains("Not enough balance") ); } } diff --git a/core/node/api_server/src/execution_sandbox/validate.rs b/core/node/api_server/src/execution_sandbox/validate.rs index 7cfa46797753..9a3c88f8bf0c 100644 --- a/core/node/api_server/src/execution_sandbox/validate.rs +++ b/core/node/api_server/src/execution_sandbox/validate.rs @@ -8,14 +8,12 @@ use zksync_multivm::interface::{ tracer::{ValidationError as RawValidationError, ValidationParams}, }; use zksync_types::{ - api::state_override::StateOverride, l2::L2Tx, Address, TRUSTED_ADDRESS_SLOTS, - TRUSTED_TOKEN_SLOTS, + api::state_override::StateOverride, fee_model::BatchFeeInput, l2::L2Tx, Address, + TRUSTED_ADDRESS_SLOTS, TRUSTED_TOKEN_SLOTS, }; -use zksync_vm_executor::oneshot::TxSetupArgs; use super::{ - apply, - execute::TransactionExecutor, + execute::{SandboxAction, SandboxExecutor}, storage::StorageWithOverrides, vm_metrics::{SandboxStage, EXECUTION_METRICS, SANDBOX_METRICS}, BlockArgs, VmPermit, @@ -31,34 +29,34 @@ pub(crate) enum ValidationError { Internal(#[from] anyhow::Error), } -impl TransactionExecutor { +impl SandboxExecutor { #[tracing::instrument(level = "debug", skip_all)] pub(crate) async fn validate_tx_in_sandbox( &self, - mut connection: Connection<'static, Core>, vm_permit: VmPermit, + mut connection: Connection<'static, Core>, tx: L2Tx, - setup_args: TxSetupArgs, block_args: BlockArgs, + fee_input: BatchFeeInput, whitelisted_tokens_for_aa: &[Address], ) -> Result<(), ValidationError> { let total_latency = SANDBOX_METRICS.sandbox[&SandboxStage::ValidateInSandbox].start(); let validation_params = get_validation_params( &mut connection, &tx, - setup_args.validation_computational_gas_limit, + self.options.eth_call.validation_computational_gas_limit(), whitelisted_tokens_for_aa, ) .await .context("failed getting validation params")?; - let (env, storage) = apply::prepare_env_and_storage( - connection, - setup_args, - &block_args, - self.storage_caches(), - ) - .await?; + let action = SandboxAction::Execution { fee_input, tx }; + let (env, storage) = self + .prepare_env_and_storage(connection, &block_args, &action) + .await?; + let SandboxAction::Execution { tx, .. } = action else { + unreachable!(); // by construction + }; let storage = StorageWithOverrides::new(storage, &StateOverride::default()); let stage_latency = SANDBOX_METRICS.sandbox[&SandboxStage::Validation].start(); diff --git a/core/node/api_server/src/tx_sender/mod.rs b/core/node/api_server/src/tx_sender/mod.rs index 07aeb8aec9c9..75f551e91056 100644 --- a/core/node/api_server/src/tx_sender/mod.rs +++ b/core/node/api_server/src/tx_sender/mod.rs @@ -9,15 +9,11 @@ use zksync_dal::{ transactions_dal::L2TxSubmissionResult, Connection, ConnectionPool, Core, CoreDal, }; use zksync_multivm::{ - interface::{ - OneshotTracingParams, TransactionExecutionMetrics, TxExecutionArgs, TxExecutionMode, - VmExecutionResultAndLogs, - }, + interface::{OneshotTracingParams, TransactionExecutionMetrics, VmExecutionResultAndLogs}, utils::{ adjust_pubdata_price_for_tx, derive_base_fee_and_gas_per_pubdata, derive_overhead, get_max_batch_gas_limit, }, - vm_latest::constants::BATCH_COMPUTATIONAL_GAS_LIMIT, }; use zksync_node_fee_model::{ApiFeeInputProvider, BatchFeeModelInputProvider}; use zksync_state::PostgresStorageCaches; @@ -38,14 +34,14 @@ use zksync_types::{ ProtocolVersionId, Transaction, H160, H256, MAX_L2_TX_GAS_LIMIT, MAX_NEW_FACTORY_DEPS, U256, }; use zksync_utils::h256_to_u256; -use zksync_vm_executor::oneshot::{MultiVMBaseSystemContracts, TxSetupArgs}; +use zksync_vm_executor::oneshot::{CallOrExecute, EstimateGas, OneshotExecutorOptions}; pub(super) use self::result::SubmitTxError; use self::{master_pool_sink::MasterPoolSink, tx_sink::TxSink}; use crate::{ execution_sandbox::{ - BlockArgs, SubmitTxStage, TransactionExecutor, VmConcurrencyBarrier, VmConcurrencyLimiter, - VmPermit, SANDBOX_METRICS, + BlockArgs, SandboxAction, SandboxExecutor, SubmitTxStage, VmConcurrencyBarrier, + VmConcurrencyLimiter, VmPermit, SANDBOX_METRICS, }, tx_sender::result::ApiCallResult, }; @@ -80,46 +76,59 @@ pub async fn build_tx_sender( let batch_fee_input_provider = ApiFeeInputProvider::new(batch_fee_model_input_provider, replica_pool); - + let executor_options = SandboxExecutorOptions::new( + tx_sender_config.chain_id, + AccountTreeId::new(tx_sender_config.fee_account_addr), + tx_sender_config.validation_computational_gas_limit, + ) + .await?; let tx_sender = tx_sender_builder.build( Arc::new(batch_fee_input_provider), Arc::new(vm_concurrency_limiter), - ApiContracts::load_from_disk().await?, + executor_options, storage_caches, ); Ok((tx_sender, vm_barrier)) } -/// Smart contracts to be used in the API sandbox requests, e.g. for estimating gas and -/// performing `eth_call` requests. -#[derive(Debug, Clone)] -pub struct ApiContracts { +/// Oneshot executor options used by the API server sandbox. +#[derive(Debug)] +pub struct SandboxExecutorOptions { /// Contracts to be used when estimating gas. /// These contracts (mainly, bootloader) normally should be tuned to provide accurate /// execution metrics. - pub(crate) estimate_gas: MultiVMBaseSystemContracts, + pub(crate) estimate_gas: OneshotExecutorOptions, /// Contracts to be used when performing `eth_call` requests. /// These contracts (mainly, bootloader) normally should be tuned to provide better UX /// experience (e.g. revert messages). - pub(crate) eth_call: MultiVMBaseSystemContracts, + pub(crate) eth_call: OneshotExecutorOptions, } -impl ApiContracts { +impl SandboxExecutorOptions { /// Loads the contracts from the local file system. /// This method is *currently* preferred to be used in all contexts, /// given that there is no way to fetch "playground" contracts from the main node. - pub async fn load_from_disk() -> anyhow::Result { - tokio::task::spawn_blocking(Self::load_from_disk_blocking) - .await - .context("loading `ApiContracts` panicked") + pub async fn new( + chain_id: L2ChainId, + operator_account: AccountTreeId, + validation_computational_gas_limit: u32, + ) -> anyhow::Result { + Ok(Self { + estimate_gas: OneshotExecutorOptions::for_gas_estimation(chain_id, operator_account) + .await?, + eth_call: OneshotExecutorOptions::for_execution( + chain_id, + operator_account, + validation_computational_gas_limit, + ) + .await?, + }) } - /// Blocking version of [`Self::load_from_disk()`]. - pub fn load_from_disk_blocking() -> Self { - Self { - estimate_gas: MultiVMBaseSystemContracts::load_estimate_gas_blocking(), - eth_call: MultiVMBaseSystemContracts::load_eth_call_blocking(), - } + pub(crate) async fn mock() -> Self { + Self::new(L2ChainId::default(), AccountTreeId::default(), u32::MAX) + .await + .unwrap() } } @@ -167,7 +176,7 @@ impl TxSenderBuilder { self, batch_fee_input_provider: Arc, vm_concurrency_limiter: Arc, - api_contracts: ApiContracts, + executor_options: SandboxExecutorOptions, storage_caches: PostgresStorageCaches, ) -> TxSender { // Use noop sealer if no sealer was explicitly provided. @@ -180,17 +189,21 @@ impl TxSenderBuilder { .config .vm_execution_cache_misses_limit .unwrap_or(usize::MAX); + let executor = SandboxExecutor::real( + executor_options, + storage_caches, + missed_storage_invocation_limit, + ); TxSender(Arc::new(TxSenderInner { sender_config: self.config, tx_sink: self.tx_sink, replica_connection_pool: self.replica_connection_pool, batch_fee_input_provider, - api_contracts, vm_concurrency_limiter, whitelisted_tokens_for_aa_cache, sealer, - executor: TransactionExecutor::real(missed_storage_invocation_limit, storage_caches), + executor, })) } } @@ -239,14 +252,13 @@ pub struct TxSenderInner { pub replica_connection_pool: ConnectionPool, // Used to keep track of gas prices for the fee ticker. pub batch_fee_input_provider: Arc, - pub(super) api_contracts: ApiContracts, /// Used to limit the amount of VMs that can be executed simultaneously. pub(super) vm_concurrency_limiter: Arc, // Cache for white-listed tokens. pub(super) whitelisted_tokens_for_aa_cache: Arc>>, /// Batch sealer used to check whether transaction can be executed by the sequencer. pub(super) sealer: Arc, - pub(super) executor: TransactionExecutor, + pub(super) executor: SandboxExecutor, } #[derive(Clone)] @@ -289,8 +301,20 @@ impl TxSender { stage_latency.observe(); let stage_latency = SANDBOX_METRICS.start_tx_submit_stage(tx_hash, SubmitTxStage::DryRun); - let setup_args = self.call_args(&tx, None).await?; + // **Important.** For the main node, this method acquires a DB connection inside `get_batch_fee_input()`. + // Thus, it must not be called it if you're holding a DB connection already. + let fee_input = self + .0 + .batch_fee_input_provider + .get_batch_fee_input() + .await + .context("cannot get batch fee input")?; + let vm_permit = self.0.vm_concurrency_limiter.acquire().await; + let action = SandboxAction::Execution { + fee_input, + tx: tx.clone(), + }; let vm_permit = vm_permit.ok_or(SubmitTxError::ServerShuttingDown)?; let mut connection = self.acquire_replica_connection().await?; let block_args = BlockArgs::pending(&mut connection).await?; @@ -298,15 +322,7 @@ impl TxSender { let execution_output = self .0 .executor - .execute_tx_in_sandbox( - vm_permit.clone(), - setup_args.clone(), - TxExecutionArgs::for_validation(tx.clone()), - connection, - &block_args, - None, - OneshotTracingParams::default(), - ) + .execute_in_sandbox(vm_permit.clone(), connection, action, &block_args, None) .await?; tracing::info!( "Submit tx {tx_hash:?} with execution metrics {:?}", @@ -321,11 +337,11 @@ impl TxSender { .0 .executor .validate_tx_in_sandbox( - connection, vm_permit, + connection, tx.clone(), - setup_args, block_args, + fee_input, &self.read_whitelisted_tokens_for_aa_cache().await, ) .await; @@ -378,41 +394,6 @@ impl TxSender { } } - /// **Important.** For the main node, this method acquires a DB connection inside `get_batch_fee_input()`. - /// Thus, you shouldn't call it if you're holding a DB connection already. - async fn call_args( - &self, - tx: &L2Tx, - call_overrides: Option<&CallOverrides>, - ) -> anyhow::Result { - let fee_input = self - .0 - .batch_fee_input_provider - .get_batch_fee_input() - .await - .context("cannot get batch fee input")?; - Ok(TxSetupArgs { - execution_mode: if call_overrides.is_some() { - TxExecutionMode::EthCall - } else { - TxExecutionMode::VerifyExecute - }, - operator_account: AccountTreeId::new(self.0.sender_config.fee_account_addr), - fee_input, - base_system_contracts: self.0.api_contracts.eth_call.clone(), - validation_computational_gas_limit: self - .0 - .sender_config - .validation_computational_gas_limit, - chain_id: self.0.sender_config.chain_id, - enforced_base_fee: if let Some(overrides) = call_overrides { - overrides.enforced_base_fee - } else { - Some(tx.common_data.fee.max_fee_per_gas.as_u64()) - }, - }) - } - async fn validate_tx( &self, tx: &L2Tx, @@ -589,7 +570,7 @@ impl TxSender { mut tx: Transaction, tx_gas_limit: u64, gas_price_per_pubdata: u32, - fee_model_params: BatchFeeInput, + fee_input: BatchFeeInput, block_args: BlockArgs, base_fee: u64, vm_version: VmVersion, @@ -618,47 +599,26 @@ impl TxSender { } ExecuteTransactionCommon::ProtocolUpgrade(common_data) => { common_data.gas_limit = forced_gas_limit.into(); - let required_funds = common_data.gas_limit * common_data.max_fee_per_gas + tx.execute.value; - common_data.to_mint = required_funds; } } - let setup_args = self.args_for_gas_estimate(fee_model_params, base_fee).await; - let execution_args = TxExecutionArgs::for_gas_estimate(tx); let connection = self.acquire_replica_connection().await?; + let action = SandboxAction::GasEstimation { + fee_input, + base_fee, + tx, + }; let execution_output = self .0 .executor - .execute_tx_in_sandbox( - vm_permit, - setup_args, - execution_args, - connection, - &block_args, - state_override, - OneshotTracingParams::default(), - ) + .execute_in_sandbox(vm_permit, connection, action, &block_args, state_override) .await?; Ok((execution_output.vm, execution_output.metrics)) } - async fn args_for_gas_estimate(&self, fee_input: BatchFeeInput, base_fee: u64) -> TxSetupArgs { - let config = &self.0.sender_config; - TxSetupArgs { - execution_mode: TxExecutionMode::EstimateFee, - operator_account: AccountTreeId::new(config.fee_account_addr), - fee_input, - // We want to bypass the computation gas limit check for gas estimation - validation_computational_gas_limit: BATCH_COMPUTATIONAL_GAS_LIMIT, - base_system_contracts: self.0.api_contracts.estimate_gas.clone(), - chain_id: config.chain_id, - enforced_base_fee: Some(base_fee), - } - } - #[tracing::instrument(level = "debug", skip_all, fields( initiator = ?tx.initiator_account(), nonce = ?tx.nonce(), @@ -915,30 +875,41 @@ impl TxSender { .await } - pub async fn eth_call( + pub(crate) async fn eth_call( &self, block_args: BlockArgs, call_overrides: CallOverrides, - tx: L2Tx, + call: L2Tx, state_override: Option, ) -> Result, SubmitTxError> { let vm_permit = self.0.vm_concurrency_limiter.acquire().await; let vm_permit = vm_permit.ok_or(SubmitTxError::ServerShuttingDown)?; - let setup_args = self.call_args(&tx, Some(&call_overrides)).await?; - let connection = self.acquire_replica_connection().await?; + let mut connection; + let fee_input = if block_args.resolves_to_latest_sealed_l2_block() { + let fee_input = self + .0 + .batch_fee_input_provider + .get_batch_fee_input() + .await?; + // It is important to acquire a connection after calling the provider; see the comment above. + connection = self.acquire_replica_connection().await?; + fee_input + } else { + connection = self.acquire_replica_connection().await?; + block_args.historical_fee_input(&mut connection).await? + }; + + let action = SandboxAction::Call { + call, + fee_input, + enforced_base_fee: call_overrides.enforced_base_fee, + tracing_params: OneshotTracingParams::default(), + }; let result = self .0 .executor - .execute_tx_in_sandbox( - vm_permit, - setup_args, - TxExecutionArgs::for_eth_call(tx), - connection, - &block_args, - state_override, - OneshotTracingParams::default(), - ) + .execute_in_sandbox(vm_permit, connection, action, &block_args, state_override) .await?; result.vm.into_api_call_result() } diff --git a/core/node/api_server/src/tx_sender/tests.rs b/core/node/api_server/src/tx_sender/tests.rs index 0ac3eb0b4f38..ece35fbdbdac 100644 --- a/core/node/api_server/src/tx_sender/tests.rs +++ b/core/node/api_server/src/tx_sender/tests.rs @@ -32,7 +32,8 @@ async fn getting_nonce_for_account() { .await .unwrap(); - let tx_executor = MockOneshotExecutor::default().into(); + let tx_executor = MockOneshotExecutor::default(); + let tx_executor = SandboxExecutor::mock(tx_executor).await; let (tx_sender, _) = create_test_tx_sender(pool.clone(), l2_chain_id, tx_executor).await; let nonce = tx_sender.get_expected_nonce(test_address).await.unwrap(); @@ -82,7 +83,8 @@ async fn getting_nonce_for_account_after_snapshot_recovery() { .await; let l2_chain_id = L2ChainId::default(); - let tx_executor = MockOneshotExecutor::default().into(); + let tx_executor = MockOneshotExecutor::default(); + let tx_executor = SandboxExecutor::mock(tx_executor).await; let (tx_sender, _) = create_test_tx_sender(pool.clone(), l2_chain_id, tx_executor).await; storage @@ -142,7 +144,7 @@ async fn submitting_tx_requires_one_connection() { assert_eq!(received_tx.hash(), tx_hash); ExecutionResult::Success { output: vec![] } }); - let tx_executor = tx_executor.into(); + let tx_executor = SandboxExecutor::mock(tx_executor).await; let (tx_sender, _) = create_test_tx_sender(pool.clone(), l2_chain_id, tx_executor).await; let submission_result = tx_sender.submit_tx(tx).await.unwrap(); @@ -184,7 +186,7 @@ async fn eth_call_requires_single_connection() { output: b"success!".to_vec(), } }); - let tx_executor = tx_executor.into(); + let tx_executor = SandboxExecutor::mock(tx_executor).await; let (tx_sender, _) = create_test_tx_sender( pool.clone(), genesis_params.config().l2_chain_id, diff --git a/core/node/api_server/src/web3/namespaces/debug.rs b/core/node/api_server/src/web3/namespaces/debug.rs index 6b91e3a96cee..dc1da601655a 100644 --- a/core/node/api_server/src/web3/namespaces/debug.rs +++ b/core/node/api_server/src/web3/namespaces/debug.rs @@ -1,11 +1,6 @@ use anyhow::Context as _; use zksync_dal::{CoreDal, DalError}; -use zksync_multivm::{ - interface::{ - Call, CallType, ExecutionResult, OneshotTracingParams, TxExecutionArgs, TxExecutionMode, - }, - vm_latest::constants::BATCH_COMPUTATIONAL_GAS_LIMIT, -}; +use zksync_multivm::interface::{Call, CallType, ExecutionResult, OneshotTracingParams}; use zksync_system_constants::MAX_ENCODED_TX_SIZE; use zksync_types::{ api::{BlockId, BlockNumber, DebugCall, DebugCallType, ResultDebugCall, TracerConfig}, @@ -13,13 +8,12 @@ use zksync_types::{ fee_model::BatchFeeInput, l2::L2Tx, transaction_request::CallRequest, - web3, AccountTreeId, H256, U256, + web3, H256, U256, }; -use zksync_vm_executor::oneshot::TxSetupArgs; use zksync_web3_decl::error::Web3Error; use crate::{ - tx_sender::{ApiContracts, TxSenderConfig}, + execution_sandbox::SandboxAction, web3::{backend_jsonrpsee::MethodTracer, state::RpcState}, }; @@ -27,13 +21,12 @@ use crate::{ pub(crate) struct DebugNamespace { batch_fee_input: BatchFeeInput, state: RpcState, - api_contracts: ApiContracts, } impl DebugNamespace { pub async fn new(state: RpcState) -> anyhow::Result { - let api_contracts = ApiContracts::load_from_disk().await?; let fee_input_provider = &state.tx_sender.0.batch_fee_input_provider; + // FIXME: is using constant input correct? let batch_fee_input = fee_input_provider .get_batch_fee_input_scaled( state.api_config.estimate_gas_scale_factor, @@ -46,7 +39,6 @@ impl DebugNamespace { // For now, the same scaling is used for both the L1 gas price and the pubdata price batch_fee_input, state, - api_contracts, }) } @@ -79,10 +71,6 @@ impl DebugNamespace { } } - fn sender_config(&self) -> &TxSenderConfig { - &self.state.tx_sender.0.sender_config - } - pub(crate) fn current_method(&self) -> &MethodTracer { &self.state.current_method } @@ -177,9 +165,8 @@ impl DebugNamespace { drop(connection); let call_overrides = request.get_call_overrides()?; - let tx = L2Tx::from_request(request.into(), MAX_ENCODED_TX_SIZE)?; + let call = L2Tx::from_request(request.into(), MAX_ENCODED_TX_SIZE)?; - let setup_args = self.call_args(call_overrides.enforced_base_fee).await; let vm_permit = self .state .tx_sender @@ -196,14 +183,17 @@ impl DebugNamespace { let connection = self.state.acquire_connection().await?; let executor = &self.state.tx_sender.0.executor; let result = executor - .execute_tx_in_sandbox( + .execute_in_sandbox( vm_permit, - setup_args, - TxExecutionArgs::for_eth_call(tx.clone()), connection, + SandboxAction::Call { + call: call.clone(), + fee_input: self.batch_fee_input, + enforced_base_fee: call_overrides.enforced_base_fee, + tracing_params, + }, &block_args, None, - tracing_params, ) .await?; @@ -219,27 +209,14 @@ impl DebugNamespace { }; let call = Call::new_high_level( - tx.common_data.fee.gas_limit.as_u64(), + call.common_data.fee.gas_limit.as_u64(), result.vm.statistics.gas_used, - tx.execute.value, - tx.execute.calldata, + call.execute.value, + call.execute.calldata, output, revert_reason, result.call_traces, ); Ok(Self::map_call(call, false)) } - - async fn call_args(&self, enforced_base_fee: Option) -> TxSetupArgs { - let sender_config = self.sender_config(); - TxSetupArgs { - execution_mode: TxExecutionMode::EthCall, - operator_account: AccountTreeId::default(), - fee_input: self.batch_fee_input, - base_system_contracts: self.api_contracts.eth_call.clone(), - validation_computational_gas_limit: BATCH_COMPUTATIONAL_GAS_LIMIT, - chain_id: sender_config.chain_id, - enforced_base_fee, - } - } } diff --git a/core/node/api_server/src/web3/testonly.rs b/core/node/api_server/src/web3/testonly.rs index a77498d4341d..0e791fc4a321 100644 --- a/core/node/api_server/src/web3/testonly.rs +++ b/core/node/api_server/src/web3/testonly.rs @@ -17,7 +17,7 @@ use zksync_types::{ use zksync_vm_executor::oneshot::MockOneshotExecutor; use super::{metrics::ApiTransportLabel, *}; -use crate::{execution_sandbox::TransactionExecutor, tx_sender::TxSenderConfig}; +use crate::{execution_sandbox::SandboxExecutor, tx_sender::TxSenderConfig}; const TEST_TIMEOUT: Duration = Duration::from_secs(90); const POLL_INTERVAL: Duration = Duration::from_millis(50); @@ -51,7 +51,7 @@ impl BatchFeeModelInputProvider for MockApiBatchFeeParamsProvider { pub(crate) async fn create_test_tx_sender( pool: ConnectionPool, l2_chain_id: L2ChainId, - tx_executor: TransactionExecutor, + tx_executor: SandboxExecutor, ) -> (TxSender, VmConcurrencyBarrier) { let web3_config = Web3JsonRpcConfig::for_tests(); let state_keeper_config = StateKeeperConfig::for_tests(); @@ -177,8 +177,9 @@ async fn spawn_server( method_tracer: Arc, stop_receiver: watch::Receiver, ) -> (ApiServerHandles, mpsc::UnboundedReceiver) { + let tx_executor = SandboxExecutor::mock(tx_executor).await; let (tx_sender, vm_barrier) = - create_test_tx_sender(pool.clone(), api_config.l2_chain_id, tx_executor.into()).await; + create_test_tx_sender(pool.clone(), api_config.l2_chain_id, tx_executor).await; let (pub_sub_events_sender, pub_sub_events_receiver) = mpsc::unbounded_channel(); let mut namespaces = Namespace::DEFAULT.to_vec(); diff --git a/core/node/consensus/src/vm.rs b/core/node/consensus/src/vm.rs index 632381608e95..e59bc75917bb 100644 --- a/core/node/consensus/src/vm.rs +++ b/core/node/consensus/src/vm.rs @@ -1,16 +1,15 @@ use anyhow::Context as _; use tokio::runtime::Handle; -use zksync_concurrency::{ctx, error::Wrap as _, scope}; +use zksync_concurrency::{ctx, error::Wrap as _}; use zksync_consensus_roles::attester; use zksync_state::PostgresStorage; use zksync_system_constants::DEFAULT_L2_TX_GAS_PER_PUBDATA_BYTE; use zksync_types::{ ethabi, fee::Fee, fee_model::BatchFeeInput, l2::L2Tx, AccountTreeId, L2ChainId, Nonce, U256, }; -use zksync_vm_executor::oneshot::{MainOneshotExecutor, MultiVMBaseSystemContracts, TxSetupArgs}; +use zksync_vm_executor::oneshot::{CallOrExecute, MainOneshotExecutor, OneshotExecutorOptions}; use zksync_vm_interface::{ executor::OneshotExecutor, ExecutionResult, OneshotTracingParams, TxExecutionArgs, - TxExecutionMode, }; use crate::{abi, storage::ConnectionPool}; @@ -19,7 +18,7 @@ use crate::{abi, storage::ConnectionPool}; #[derive(Debug)] pub(crate) struct VM { pool: ConnectionPool, - setup_args: TxSetupArgs, + options: OneshotExecutorOptions, executor: MainOneshotExecutor, } @@ -28,18 +27,13 @@ impl VM { pub async fn new(pool: ConnectionPool) -> Self { Self { pool, - setup_args: TxSetupArgs { - execution_mode: TxExecutionMode::EthCall, - operator_account: AccountTreeId::default(), - fee_input: BatchFeeInput::sensible_l1_pegged_default(), - base_system_contracts: scope::wait_blocking( - MultiVMBaseSystemContracts::load_eth_call_blocking, - ) - .await, - validation_computational_gas_limit: u32::MAX, - chain_id: L2ChainId::default(), - enforced_base_fee: None, - }, + options: OneshotExecutorOptions::for_execution( + L2ChainId::default(), + AccountTreeId::default(), + u32::MAX, + ) + .await + .expect("OneshotExecutorOptions"), executor: MainOneshotExecutor::new(usize::MAX), } } @@ -72,8 +66,12 @@ impl VM { .vm_block_info(ctx, batch) .await .wrap("vm_block_info()")?; + let fee_input = BatchFeeInput::sensible_l1_pegged_default(); // FIXME: double-check let env = ctx - .wait(self.setup_args.to_env(&mut conn.0, &block_info)) + .wait( + self.options + .to_call_env(&mut conn.0, &block_info, fee_input, None), + ) .await? .context("to_env()")?; let storage = ctx diff --git a/core/node/node_framework/src/implementations/layers/web3_api/tx_sender.rs b/core/node/node_framework/src/implementations/layers/web3_api/tx_sender.rs index 3574b8e8c24c..a09938055fae 100644 --- a/core/node/node_framework/src/implementations/layers/web3_api/tx_sender.rs +++ b/core/node/node_framework/src/implementations/layers/web3_api/tx_sender.rs @@ -3,10 +3,10 @@ use std::{sync::Arc, time::Duration}; use tokio::sync::RwLock; use zksync_node_api_server::{ execution_sandbox::{VmConcurrencyBarrier, VmConcurrencyLimiter}, - tx_sender::{ApiContracts, TxSenderBuilder, TxSenderConfig}, + tx_sender::{SandboxExecutorOptions, TxSenderBuilder, TxSenderConfig}, }; use zksync_state::{PostgresStorageCaches, PostgresStorageCachesTask}; -use zksync_types::Address; +use zksync_types::{AccountTreeId, Address}; use zksync_web3_decl::{ client::{DynClient, L2}, jsonrpsee, @@ -58,7 +58,6 @@ pub struct TxSenderLayer { tx_sender_config: TxSenderConfig, postgres_storage_caches_config: PostgresStorageCachesConfig, max_vm_concurrency: usize, - api_contracts: ApiContracts, whitelisted_tokens_for_aa_cache: bool, } @@ -89,13 +88,11 @@ impl TxSenderLayer { tx_sender_config: TxSenderConfig, postgres_storage_caches_config: PostgresStorageCachesConfig, max_vm_concurrency: usize, - api_contracts: ApiContracts, ) -> Self { Self { tx_sender_config, postgres_storage_caches_config, max_vm_concurrency, - api_contracts, whitelisted_tokens_for_aa_cache: false, } } @@ -148,8 +145,17 @@ impl WiringLayer for TxSenderLayer { let (vm_concurrency_limiter, vm_concurrency_barrier) = VmConcurrencyLimiter::new(self.max_vm_concurrency); + // TODO (BFT-138): Allow to dynamically reload API contracts + let config = self.tx_sender_config; + let executor_options = SandboxExecutorOptions::new( + config.chain_id, + AccountTreeId::new(config.fee_account_addr), + config.validation_computational_gas_limit, + ) + .await?; + // Build `TxSender`. - let mut tx_sender = TxSenderBuilder::new(self.tx_sender_config, replica_pool, tx_sink); + let mut tx_sender = TxSenderBuilder::new(config, replica_pool, tx_sink); if let Some(sealer) = sealer { tx_sender = tx_sender.with_sealer(sealer); } @@ -176,7 +182,7 @@ impl WiringLayer for TxSenderLayer { let tx_sender = tx_sender.build( fee_input, Arc::new(vm_concurrency_limiter), - self.api_contracts, + executor_options, storage_caches, ); From 661d9166cfe4e26c3084351c5fb019e99205d372 Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Thu, 12 Sep 2024 16:58:32 +0300 Subject: [PATCH 05/12] Brush up oneshot executor --- core/lib/vm_executor/src/oneshot/block.rs | 4 ++-- core/lib/vm_executor/src/oneshot/mod.rs | 6 ++++++ core/lib/vm_executor/src/oneshot/options.rs | 8 ++++++++ .../src/execution_sandbox/execute.rs | 19 ++++++++----------- 4 files changed, 24 insertions(+), 13 deletions(-) diff --git a/core/lib/vm_executor/src/oneshot/block.rs b/core/lib/vm_executor/src/oneshot/block.rs index f28aa4b6786b..06fcb4c46b3b 100644 --- a/core/lib/vm_executor/src/oneshot/block.rs +++ b/core/lib/vm_executor/src/oneshot/block.rs @@ -16,8 +16,8 @@ use zksync_utils::{h256_to_u256, time::seconds_since_epoch}; use super::options::OneshotExecutorOptions; -/// Block information necessary to execute a transaction / call. -// FIXME: consider combining with `ResolvedBlockInfo`? +/// Block information necessary to execute a transaction / call. Unlike [`ResolvedBlockInfo`], this information is *partially* resolved, +/// which is beneficial for some workflows. #[derive(Debug, Clone, Copy)] pub struct BlockInfo { resolved_block_number: L2BlockNumber, diff --git a/core/lib/vm_executor/src/oneshot/mod.rs b/core/lib/vm_executor/src/oneshot/mod.rs index ab26d1ba3375..16b272a1e859 100644 --- a/core/lib/vm_executor/src/oneshot/mod.rs +++ b/core/lib/vm_executor/src/oneshot/mod.rs @@ -1,4 +1,10 @@ //! Oneshot VM executor. +//! +//! # Overview +//! +//! The root type of this module is [`MainOneshotExecutor`], a "default" [`OneshotExecutor`] implementation. +//! In addition to it, the module provides [`OneshotExecutorOptions`] and [`BlockInfo`] / [`ResolvedBlockInfo`], +//! which can be used to prepare environment for `MainOneshotExecutor` (i.e., a [`OneshotEnv`] instance). use std::{ sync::Arc, diff --git a/core/lib/vm_executor/src/oneshot/options.rs b/core/lib/vm_executor/src/oneshot/options.rs index 1b6f7af8d9a5..30dc67a3a360 100644 --- a/core/lib/vm_executor/src/oneshot/options.rs +++ b/core/lib/vm_executor/src/oneshot/options.rs @@ -11,10 +11,12 @@ use crate::oneshot::{contracts::MultiVMBaseSystemContracts, ResolvedBlockInfo}; #[derive(Debug)] pub struct EstimateGas(()); +/// Marker for [`OneshotExecutorOptions`] used for calls and/or transaction execution. #[derive(Debug)] pub struct CallOrExecute(()); /// Oneshot executor options that are expected to be constant or rarely change during the program lifetime. +/// These options can be used to create [a full environment](OneshotEnv) for transaction / call execution. #[derive(Debug)] pub struct OneshotExecutorOptions { pub(super) chain_id: L2ChainId, @@ -25,12 +27,14 @@ pub struct OneshotExecutorOptions { } impl OneshotExecutorOptions { + /// Returns gas limit for account validation of transactions. pub fn validation_computational_gas_limit(&self) -> u32 { self.validation_computational_gas_limit } } impl OneshotExecutorOptions { + /// Creates executor options for gas estimation. pub async fn for_gas_estimation( chain_id: L2ChainId, operator_account: AccountTreeId, @@ -48,6 +52,7 @@ impl OneshotExecutorOptions { }) } + /// Prepares environment for gas estimation. pub async fn to_env( &self, connection: &mut Connection<'_, Core>, @@ -67,6 +72,7 @@ impl OneshotExecutorOptions { } impl OneshotExecutorOptions { + /// Creates executor options for transaction / call execution. pub async fn for_execution( chain_id: L2ChainId, operator_account: AccountTreeId, @@ -85,6 +91,7 @@ impl OneshotExecutorOptions { }) } + /// Prepares environment for a call. pub async fn to_call_env( &self, connection: &mut Connection<'_, Core>, @@ -102,6 +109,7 @@ impl OneshotExecutorOptions { .await } + /// Prepares environment for executing a provided transaction. pub async fn to_execute_env( &self, connection: &mut Connection<'_, Core>, diff --git a/core/node/api_server/src/execution_sandbox/execute.rs b/core/node/api_server/src/execution_sandbox/execute.rs index 58d080551473..d974f2e9aa1d 100644 --- a/core/node/api_server/src/execution_sandbox/execute.rs +++ b/core/node/api_server/src/execution_sandbox/execute.rs @@ -26,18 +26,19 @@ use super::{ }; use crate::tx_sender::SandboxExecutorOptions; +/// Action that can be executed by [`SandboxExecutor`]. #[derive(Debug)] pub(crate) enum SandboxAction { - Execution { - tx: L2Tx, - fee_input: BatchFeeInput, - }, + /// Execute a transaction. + Execution { tx: L2Tx, fee_input: BatchFeeInput }, + /// Execute a call, possibly with tracing. Call { call: L2Tx, fee_input: BatchFeeInput, enforced_base_fee: Option, tracing_params: OneshotTracingParams, }, + /// Estimate gas for a transaction. GasEstimation { tx: Transaction, fee_input: BatchFeeInput, @@ -74,6 +75,7 @@ impl SandboxAction { } } +/// Output of [`SandboxExecutor::execute_in_sandbox()`]. #[derive(Debug, Clone)] pub(crate) struct SandboxExecutionOutput { /// Output of the VM. @@ -124,10 +126,6 @@ impl SandboxExecutor { } } - pub(super) fn storage_caches(&self) -> Option<&PostgresStorageCaches> { - self.storage_caches.as_ref() - } - /// This method assumes that (block with number `resolved_block_number` is present in DB) /// or (`block_id` is `pending` and block with number `resolved_block_number - 1` is present in DB) #[allow(clippy::too_many_arguments)] @@ -217,9 +215,8 @@ impl SandboxExecutor { } }; - let storage_caches = self.storage_caches(); if block_args.resolves_to_latest_sealed_l2_block() { - if let Some(caches) = &storage_caches { + if let Some(caches) = &self.storage_caches { caches.schedule_values_update(resolved_block_info.state_l2_block_number()); } } @@ -233,7 +230,7 @@ impl SandboxExecutor { .await .context("cannot create `PostgresStorage`")?; - if let Some(caches) = storage_caches { + if let Some(caches) = &self.storage_caches { storage = storage.with_caches(caches.clone()); } initialization_stage.observe(); From 0cfb22f0a9791ad067b691db773333d4a75219cd Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Mon, 16 Sep 2024 11:19:14 +0300 Subject: [PATCH 06/12] Brush up env parameters --- core/lib/vm_executor/src/oneshot/block.rs | 8 ++--- core/lib/vm_executor/src/oneshot/contracts.rs | 2 +- .../src/oneshot/{options.rs => env.rs} | 29 ++++++++++++------- core/lib/vm_executor/src/oneshot/mod.rs | 6 ++-- .../api_server/src/execution_sandbox/tests.rs | 2 -- core/node/api_server/src/tx_sender/mod.rs | 18 +++++------- core/node/consensus/src/vm.rs | 6 ++-- 7 files changed, 37 insertions(+), 34 deletions(-) rename core/lib/vm_executor/src/oneshot/{options.rs => env.rs} (76%) diff --git a/core/lib/vm_executor/src/oneshot/block.rs b/core/lib/vm_executor/src/oneshot/block.rs index 06fcb4c46b3b..8ba77305ad7d 100644 --- a/core/lib/vm_executor/src/oneshot/block.rs +++ b/core/lib/vm_executor/src/oneshot/block.rs @@ -14,10 +14,10 @@ use zksync_types::{ }; use zksync_utils::{h256_to_u256, time::seconds_since_epoch}; -use super::options::OneshotExecutorOptions; +use super::env::OneshotEnvParameters; /// Block information necessary to execute a transaction / call. Unlike [`ResolvedBlockInfo`], this information is *partially* resolved, -/// which is beneficial for some workflows. +/// which is beneficial for some data workflows. #[derive(Debug, Clone, Copy)] pub struct BlockInfo { resolved_block_number: L2BlockNumber, @@ -39,7 +39,7 @@ impl BlockInfo { }) } - /// Fetches information for an existing block. + /// Fetches information for an existing block. Will error if the block is not present in Postgres. pub async fn for_existing_block( connection: &mut Connection<'_, Core>, number: L2BlockNumber, @@ -163,7 +163,7 @@ impl ResolvedBlockInfo { } } -impl OneshotExecutorOptions { +impl OneshotEnvParameters { pub(super) async fn to_env_inner( &self, connection: &mut Connection<'_, Core>, diff --git a/core/lib/vm_executor/src/oneshot/contracts.rs b/core/lib/vm_executor/src/oneshot/contracts.rs index f38c90c92442..3b3a65fe30ba 100644 --- a/core/lib/vm_executor/src/oneshot/contracts.rs +++ b/core/lib/vm_executor/src/oneshot/contracts.rs @@ -1,7 +1,7 @@ use zksync_contracts::BaseSystemContracts; use zksync_types::ProtocolVersionId; -/// System contracts (bootloader and default account abstraction) for all VM versions. +/// System contracts (bootloader and default account abstraction) for all supported VM versions. #[derive(Debug, Clone)] pub(super) struct MultiVMBaseSystemContracts { /// Contracts to be used for pre-virtual-blocks protocol versions. diff --git a/core/lib/vm_executor/src/oneshot/options.rs b/core/lib/vm_executor/src/oneshot/env.rs similarity index 76% rename from core/lib/vm_executor/src/oneshot/options.rs rename to core/lib/vm_executor/src/oneshot/env.rs index 30dc67a3a360..51154d561ec6 100644 --- a/core/lib/vm_executor/src/oneshot/options.rs +++ b/core/lib/vm_executor/src/oneshot/env.rs @@ -7,18 +7,21 @@ use zksync_types::{fee_model::BatchFeeInput, l2::L2Tx, AccountTreeId, L2ChainId} use crate::oneshot::{contracts::MultiVMBaseSystemContracts, ResolvedBlockInfo}; -/// Marker for [`OneshotExecutorOptions`] used for gas estimation. +/// Marker for [`OneshotEnvParameters`] used for gas estimation. #[derive(Debug)] pub struct EstimateGas(()); -/// Marker for [`OneshotExecutorOptions`] used for calls and/or transaction execution. +/// Marker for [`OneshotEnvParameters`] used for calls and/or transaction execution. #[derive(Debug)] pub struct CallOrExecute(()); -/// Oneshot executor options that are expected to be constant or rarely change during the program lifetime. -/// These options can be used to create [a full environment](OneshotEnv) for transaction / call execution. +/// Oneshot environment parameters that are expected to be constant or rarely change during the program lifetime. +/// These parameters can be used to create [a full environment](OneshotEnv) for transaction / call execution. +/// +/// Notably, these parameters include base system contracts (bootloader and default account abstraction) for all supported +/// VM versions. #[derive(Debug)] -pub struct OneshotExecutorOptions { +pub struct OneshotEnvParameters { pub(super) chain_id: L2ChainId, pub(super) base_system_contracts: MultiVMBaseSystemContracts, pub(super) operator_account: AccountTreeId, @@ -26,15 +29,18 @@ pub struct OneshotExecutorOptions { _ty: PhantomData, } -impl OneshotExecutorOptions { +impl OneshotEnvParameters { /// Returns gas limit for account validation of transactions. pub fn validation_computational_gas_limit(&self) -> u32 { self.validation_computational_gas_limit } } -impl OneshotExecutorOptions { - /// Creates executor options for gas estimation. +impl OneshotEnvParameters { + /// Creates env parameters for gas estimation. + /// + /// System contracts (mainly, bootloader) for these params are tuned to provide accurate + /// execution metrics. pub async fn for_gas_estimation( chain_id: L2ChainId, operator_account: AccountTreeId, @@ -71,8 +77,11 @@ impl OneshotExecutorOptions { } } -impl OneshotExecutorOptions { - /// Creates executor options for transaction / call execution. +impl OneshotEnvParameters { + /// Creates env parameters for transaction / call execution. + /// + /// System contracts (mainly, bootloader) for these params tuned to provide better UX + /// experience (e.g. revert messages). pub async fn for_execution( chain_id: L2ChainId, operator_account: AccountTreeId, diff --git a/core/lib/vm_executor/src/oneshot/mod.rs b/core/lib/vm_executor/src/oneshot/mod.rs index 16b272a1e859..a85b0211bf83 100644 --- a/core/lib/vm_executor/src/oneshot/mod.rs +++ b/core/lib/vm_executor/src/oneshot/mod.rs @@ -3,7 +3,7 @@ //! # Overview //! //! The root type of this module is [`MainOneshotExecutor`], a "default" [`OneshotExecutor`] implementation. -//! In addition to it, the module provides [`OneshotExecutorOptions`] and [`BlockInfo`] / [`ResolvedBlockInfo`], +//! In addition to it, the module provides [`OneshotEnvParameters`] and [`BlockInfo`] / [`ResolvedBlockInfo`], //! which can be used to prepare environment for `MainOneshotExecutor` (i.e., a [`OneshotEnv`] instance). use std::{ @@ -40,15 +40,15 @@ use zksync_utils::{h256_to_u256, u256_to_h256}; pub use self::{ block::{BlockInfo, ResolvedBlockInfo}, + env::{CallOrExecute, EstimateGas, OneshotEnvParameters}, mock::MockOneshotExecutor, - options::{CallOrExecute, EstimateGas, OneshotExecutorOptions}, }; mod block; mod contracts; +mod env; mod metrics; mod mock; -mod options; /// Main [`OneshotExecutor`] implementation used by the API server. #[derive(Debug, Default)] diff --git a/core/node/api_server/src/execution_sandbox/tests.rs b/core/node/api_server/src/execution_sandbox/tests.rs index 294503b776f3..9a5b483f768a 100644 --- a/core/node/api_server/src/execution_sandbox/tests.rs +++ b/core/node/api_server/src/execution_sandbox/tests.rs @@ -1,7 +1,5 @@ //! Tests for the VM execution sandbox. -// FIXME: simplify to use TransactionExecutor directly - use std::collections::HashMap; use assert_matches::assert_matches; diff --git a/core/node/api_server/src/tx_sender/mod.rs b/core/node/api_server/src/tx_sender/mod.rs index 75f551e91056..e2285c167110 100644 --- a/core/node/api_server/src/tx_sender/mod.rs +++ b/core/node/api_server/src/tx_sender/mod.rs @@ -34,7 +34,7 @@ use zksync_types::{ ProtocolVersionId, Transaction, H160, H256, MAX_L2_TX_GAS_LIMIT, MAX_NEW_FACTORY_DEPS, U256, }; use zksync_utils::h256_to_u256; -use zksync_vm_executor::oneshot::{CallOrExecute, EstimateGas, OneshotExecutorOptions}; +use zksync_vm_executor::oneshot::{CallOrExecute, EstimateGas, OneshotEnvParameters}; pub(super) use self::result::SubmitTxError; use self::{master_pool_sink::MasterPoolSink, tx_sink::TxSink}; @@ -94,14 +94,10 @@ pub async fn build_tx_sender( /// Oneshot executor options used by the API server sandbox. #[derive(Debug)] pub struct SandboxExecutorOptions { - /// Contracts to be used when estimating gas. - /// These contracts (mainly, bootloader) normally should be tuned to provide accurate - /// execution metrics. - pub(crate) estimate_gas: OneshotExecutorOptions, - /// Contracts to be used when performing `eth_call` requests. - /// These contracts (mainly, bootloader) normally should be tuned to provide better UX - /// experience (e.g. revert messages). - pub(crate) eth_call: OneshotExecutorOptions, + /// Env parameters to be used when estimating gas. + pub(crate) estimate_gas: OneshotEnvParameters, + /// Env parameters to be used when performing `eth_call` requests. + pub(crate) eth_call: OneshotEnvParameters, } impl SandboxExecutorOptions { @@ -114,9 +110,9 @@ impl SandboxExecutorOptions { validation_computational_gas_limit: u32, ) -> anyhow::Result { Ok(Self { - estimate_gas: OneshotExecutorOptions::for_gas_estimation(chain_id, operator_account) + estimate_gas: OneshotEnvParameters::for_gas_estimation(chain_id, operator_account) .await?, - eth_call: OneshotExecutorOptions::for_execution( + eth_call: OneshotEnvParameters::for_execution( chain_id, operator_account, validation_computational_gas_limit, diff --git a/core/node/consensus/src/vm.rs b/core/node/consensus/src/vm.rs index e59bc75917bb..c8d33742e1ab 100644 --- a/core/node/consensus/src/vm.rs +++ b/core/node/consensus/src/vm.rs @@ -7,7 +7,7 @@ use zksync_system_constants::DEFAULT_L2_TX_GAS_PER_PUBDATA_BYTE; use zksync_types::{ ethabi, fee::Fee, fee_model::BatchFeeInput, l2::L2Tx, AccountTreeId, L2ChainId, Nonce, U256, }; -use zksync_vm_executor::oneshot::{CallOrExecute, MainOneshotExecutor, OneshotExecutorOptions}; +use zksync_vm_executor::oneshot::{CallOrExecute, MainOneshotExecutor, OneshotEnvParameters}; use zksync_vm_interface::{ executor::OneshotExecutor, ExecutionResult, OneshotTracingParams, TxExecutionArgs, }; @@ -18,7 +18,7 @@ use crate::{abi, storage::ConnectionPool}; #[derive(Debug)] pub(crate) struct VM { pool: ConnectionPool, - options: OneshotExecutorOptions, + options: OneshotEnvParameters, executor: MainOneshotExecutor, } @@ -27,7 +27,7 @@ impl VM { pub async fn new(pool: ConnectionPool) -> Self { Self { pool, - options: OneshotExecutorOptions::for_execution( + options: OneshotEnvParameters::for_execution( L2ChainId::default(), AccountTreeId::default(), u32::MAX, From 58858c911934a19af4c4f1a8e0b1da6e0ecf23d4 Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Mon, 16 Sep 2024 11:32:57 +0300 Subject: [PATCH 07/12] Fix formatting --- infrastructure/zk/src/docker.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/infrastructure/zk/src/docker.ts b/infrastructure/zk/src/docker.ts index a100d1231da6..035061a8ed0d 100644 --- a/infrastructure/zk/src/docker.ts +++ b/infrastructure/zk/src/docker.ts @@ -1,4 +1,4 @@ -import {Command} from 'commander'; +import { Command } from 'commander'; import * as utils from 'utils'; const IMAGES = [ @@ -31,7 +31,7 @@ async function dockerCommand( dockerOrg: string = 'matterlabs' ) { // Generating all tags for containers. We need 2 tags here: SHA and SHA+TS - const {stdout: COMMIT_SHORT_SHA}: { stdout: string } = await utils.exec('git rev-parse --short HEAD'); + const { stdout: COMMIT_SHORT_SHA }: { stdout: string } = await utils.exec('git rev-parse --short HEAD'); // COMMIT_SHORT_SHA returns with newline, so we need to trim it const imageTagShaTS: string = process.env.IMAGE_TAG_SUFFIX ? process.env.IMAGE_TAG_SUFFIX @@ -126,7 +126,7 @@ async function _build(image: string, tagList: string[], dockerOrg: string, platf } buildArgs += extraArgs; - console.log("Build args: ", buildArgs); + console.log('Build args: ', buildArgs); const buildCommand = `DOCKER_BUILDKIT=1 docker buildx build ${tagsToBuild}` + From bc7ccac132db21e307a68513c024c3fb92a9ca25 Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Mon, 16 Sep 2024 15:03:28 +0300 Subject: [PATCH 08/12] Fix batch fee input for trace calls --- core/node/api_server/src/web3/namespaces/debug.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/core/node/api_server/src/web3/namespaces/debug.rs b/core/node/api_server/src/web3/namespaces/debug.rs index dc1da601655a..8c3c23ba759a 100644 --- a/core/node/api_server/src/web3/namespaces/debug.rs +++ b/core/node/api_server/src/web3/namespaces/debug.rs @@ -162,6 +162,11 @@ impl DebugNamespace { if request.gas.is_none() { request.gas = Some(block_args.default_eth_call_gas(&mut connection).await?); } + let fee_input = if block_args.resolves_to_latest_sealed_l2_block() { + self.batch_fee_input + } else { + block_args.historical_fee_input(&mut connection).await? + }; drop(connection); let call_overrides = request.get_call_overrides()?; @@ -188,7 +193,7 @@ impl DebugNamespace { connection, SandboxAction::Call { call: call.clone(), - fee_input: self.batch_fee_input, + fee_input, enforced_base_fee: call_overrides.enforced_base_fee, tracing_params, }, From db013aa94f17b0a6ba54992484a32d80db5c4543 Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Mon, 16 Sep 2024 15:04:07 +0300 Subject: [PATCH 09/12] Test batch fee input --- core/node/api_server/src/web3/testonly.rs | 1 + core/node/api_server/src/web3/tests/vm.rs | 109 +++++++++++++++++----- core/node/fee_model/src/lib.rs | 28 ++++-- 3 files changed, 110 insertions(+), 28 deletions(-) diff --git a/core/node/api_server/src/web3/testonly.rs b/core/node/api_server/src/web3/testonly.rs index 0e791fc4a321..bb17c7530e28 100644 --- a/core/node/api_server/src/web3/testonly.rs +++ b/core/node/api_server/src/web3/testonly.rs @@ -24,6 +24,7 @@ const POLL_INTERVAL: Duration = Duration::from_millis(50); /// Same as [`MockBatchFeeParamsProvider`], but also artificially acquires a Postgres connection on each call /// (same as the real node implementation). +// FIXME: obsolete? (wrapped in `ApiFeeInputProvider` anyway) #[derive(Debug)] struct MockApiBatchFeeParamsProvider { inner: MockBatchFeeParamsProvider, diff --git a/core/node/api_server/src/web3/tests/vm.rs b/core/node/api_server/src/web3/tests/vm.rs index d8d1a2c7768e..9bdcf1159303 100644 --- a/core/node/api_server/src/web3/tests/vm.rs +++ b/core/node/api_server/src/web3/tests/vm.rs @@ -1,14 +1,22 @@ //! Tests for the VM-instantiating methods (e.g., `eth_call`). -use std::sync::atomic::{AtomicU32, Ordering}; +use std::sync::{ + atomic::{AtomicU32, Ordering}, + Mutex, +}; use api::state_override::{OverrideAccount, StateOverride}; use zksync_multivm::interface::{ ExecutionResult, VmExecutionLogs, VmExecutionResultAndLogs, VmRevertReason, }; +use zksync_node_fee_model::BatchFeeModelInputProvider; use zksync_types::{ - api::ApiStorageLog, get_intrinsic_constants, transaction_request::CallRequest, K256PrivateKey, - L2ChainId, PackedEthSignature, StorageLogKind, StorageLogWithPreviousValue, U256, + api::ApiStorageLog, + fee_model::{BatchFeeInput, FeeParams}, + get_intrinsic_constants, + transaction_request::CallRequest, + K256PrivateKey, L2ChainId, PackedEthSignature, StorageLogKind, StorageLogWithPreviousValue, + U256, }; use zksync_utils::u256_to_h256; use zksync_vm_executor::oneshot::MockOneshotExecutor; @@ -16,8 +24,47 @@ use zksync_web3_decl::namespaces::DebugNamespaceClient; use super::*; -#[derive(Debug)] -struct CallTest; +#[derive(Debug, Clone)] +struct ExpectedFeeInput(Arc>); + +impl Default for ExpectedFeeInput { + fn default() -> Self { + let this = Self(Arc::default()); + this.expect_default(1.0); // works for transaction execution and calls + this + } +} + +impl ExpectedFeeInput { + fn expect_for_block(&self, number: api::BlockNumber, scale: f64) { + *self.0.lock().unwrap() = match number { + api::BlockNumber::Number(number) => create_l2_block(number.as_u32()).batch_fee_input, + _ => ::default_batch_fee_input_scaled( + FeeParams::sensible_v1_default(), + scale, + scale, + ), + }; + } + + fn expect_default(&self, scale: f64) { + self.expect_for_block(api::BlockNumber::Pending, scale); + } + + fn assert_eq(&self, actual: BatchFeeInput) { + let expected = *self.0.lock().unwrap(); + // We do relaxed comparisons to deal with the fact that the fee input provider may convert inputs to pubdata independent form. + assert_eq!( + actual.into_pubdata_independent(), + expected.into_pubdata_independent() + ); + } +} + +#[derive(Debug, Default)] +struct CallTest { + fee_input: ExpectedFeeInput, +} impl CallTest { fn call_request(data: &[u8]) -> CallRequest { @@ -31,9 +78,14 @@ impl CallTest { } } - fn create_executor(latest_block: L2BlockNumber) -> MockOneshotExecutor { + fn create_executor( + latest_block: L2BlockNumber, + expected_fee_input: ExpectedFeeInput, + ) -> MockOneshotExecutor { let mut tx_executor = MockOneshotExecutor::default(); tx_executor.set_call_responses(move |tx, env| { + expected_fee_input.assert_eq(env.l1_batch.fee_input); + let expected_block_number = match tx.execute.calldata() { b"pending" => latest_block + 1, b"latest" => latest_block, @@ -52,7 +104,7 @@ impl CallTest { #[async_trait] impl HttpTest for CallTest { fn transaction_executor(&self) -> MockOneshotExecutor { - Self::create_executor(L2BlockNumber(1)) + Self::create_executor(L2BlockNumber(1), self.fee_input.clone()) } async fn test( @@ -73,9 +125,10 @@ impl HttpTest for CallTest { let valid_block_numbers_and_calldata = [ (api::BlockNumber::Pending, b"pending" as &[_]), (api::BlockNumber::Latest, b"latest"), - (0.into(), b"latest"), + (1.into(), b"latest"), ]; for (number, calldata) in valid_block_numbers_and_calldata { + self.fee_input.expect_for_block(number, 1.0); let number = api::BlockIdVariant::BlockNumber(number); let call_result = client .call(Self::call_request(calldata), Some(number), None) @@ -101,11 +154,13 @@ impl HttpTest for CallTest { #[tokio::test] async fn call_method_basics() { - test_http_server(CallTest).await; + test_http_server(CallTest::default()).await; } -#[derive(Debug)] -struct CallTestAfterSnapshotRecovery; +#[derive(Debug, Default)] +struct CallTestAfterSnapshotRecovery { + fee_input: ExpectedFeeInput, +} #[async_trait] impl HttpTest for CallTestAfterSnapshotRecovery { @@ -115,7 +170,7 @@ impl HttpTest for CallTestAfterSnapshotRecovery { fn transaction_executor(&self) -> MockOneshotExecutor { let first_local_l2_block = StorageInitialization::SNAPSHOT_RECOVERY_BLOCK + 1; - CallTest::create_executor(first_local_l2_block) + CallTest::create_executor(first_local_l2_block, self.fee_input.clone()) } async fn test( @@ -150,6 +205,7 @@ impl HttpTest for CallTestAfterSnapshotRecovery { let first_l2_block_numbers = [api::BlockNumber::Latest, first_local_l2_block.0.into()]; for number in first_l2_block_numbers { + self.fee_input.expect_for_block(number, 1.0); let number = api::BlockIdVariant::BlockNumber(number); let call_result = client .call(CallTest::call_request(b"latest"), Some(number), None) @@ -162,7 +218,7 @@ impl HttpTest for CallTestAfterSnapshotRecovery { #[tokio::test] async fn call_method_after_snapshot_recovery() { - test_http_server(CallTestAfterSnapshotRecovery).await; + test_http_server(CallTestAfterSnapshotRecovery::default()).await; } #[derive(Debug)] @@ -390,10 +446,14 @@ async fn send_raw_transaction_with_detailed_output() { test_http_server(SendTransactionWithDetailedOutputTest).await; } -#[derive(Debug)] -struct TraceCallTest; +#[derive(Debug, Default)] +struct TraceCallTest { + fee_input: ExpectedFeeInput, +} impl TraceCallTest { + const FEE_SCALE: f64 = 1.2; // set in the tx sender config + fn assert_debug_call(call_request: &CallRequest, call_result: &api::DebugCall) { assert_eq!(call_result.from, Address::zero()); assert_eq!(call_result.gas, call_request.gas.unwrap()); @@ -413,7 +473,7 @@ impl TraceCallTest { #[async_trait] impl HttpTest for TraceCallTest { fn transaction_executor(&self) -> MockOneshotExecutor { - CallTest::create_executor(L2BlockNumber(1)) + CallTest::create_executor(L2BlockNumber(1), self.fee_input.clone()) } async fn test( @@ -426,6 +486,7 @@ impl HttpTest for TraceCallTest { store_l2_block(&mut connection, L2BlockNumber(1), &[]).await?; drop(connection); + self.fee_input.expect_default(Self::FEE_SCALE); let call_request = CallTest::call_request(b"pending"); let call_result = client.trace_call(call_request.clone(), None, None).await?; Self::assert_debug_call(&call_request, &call_result); @@ -438,6 +499,7 @@ impl HttpTest for TraceCallTest { let latest_block_numbers = [api::BlockNumber::Latest, 1.into()]; let call_request = CallTest::call_request(b"latest"); for number in latest_block_numbers { + self.fee_input.expect_for_block(number, Self::FEE_SCALE); let call_result = client .trace_call( call_request.clone(), @@ -469,11 +531,13 @@ impl HttpTest for TraceCallTest { #[tokio::test] async fn trace_call_basics() { - test_http_server(TraceCallTest).await; + test_http_server(TraceCallTest::default()).await; } -#[derive(Debug)] -struct TraceCallTestAfterSnapshotRecovery; +#[derive(Debug, Default)] +struct TraceCallTestAfterSnapshotRecovery { + fee_input: ExpectedFeeInput, +} #[async_trait] impl HttpTest for TraceCallTestAfterSnapshotRecovery { @@ -483,7 +547,7 @@ impl HttpTest for TraceCallTestAfterSnapshotRecovery { fn transaction_executor(&self) -> MockOneshotExecutor { let number = StorageInitialization::SNAPSHOT_RECOVERY_BLOCK + 1; - CallTest::create_executor(number) + CallTest::create_executor(number, self.fee_input.clone()) } async fn test( @@ -491,6 +555,7 @@ impl HttpTest for TraceCallTestAfterSnapshotRecovery { client: &DynClient, _pool: &ConnectionPool, ) -> anyhow::Result<()> { + self.fee_input.expect_default(TraceCallTest::FEE_SCALE); let call_request = CallTest::call_request(b"pending"); let call_result = client.trace_call(call_request.clone(), None, None).await?; TraceCallTest::assert_debug_call(&call_request, &call_result); @@ -514,6 +579,8 @@ impl HttpTest for TraceCallTestAfterSnapshotRecovery { let call_request = CallTest::call_request(b"latest"); let first_l2_block_numbers = [api::BlockNumber::Latest, first_local_l2_block.0.into()]; for number in first_l2_block_numbers { + self.fee_input + .expect_for_block(number, TraceCallTest::FEE_SCALE); let number = api::BlockId::Number(number); let call_result = client .trace_call(call_request.clone(), Some(number), None) @@ -526,7 +593,7 @@ impl HttpTest for TraceCallTestAfterSnapshotRecovery { #[tokio::test] async fn trace_call_after_snapshot_recovery() { - test_http_server(TraceCallTestAfterSnapshotRecovery).await; + test_http_server(TraceCallTestAfterSnapshotRecovery::default()).await; } #[derive(Debug)] diff --git a/core/node/fee_model/src/lib.rs b/core/node/fee_model/src/lib.rs index 217ed71e38cb..fe4f6a27ce29 100644 --- a/core/node/fee_model/src/lib.rs +++ b/core/node/fee_model/src/lib.rs @@ -34,8 +34,27 @@ pub trait BatchFeeModelInputProvider: fmt::Debug + 'static + Send + Sync { l1_pubdata_price_scale_factor: f64, ) -> anyhow::Result { let params = self.get_fee_model_params(); + Ok( + ::default_batch_fee_input_scaled( + params, + l1_gas_price_scale_factor, + l1_pubdata_price_scale_factor, + ), + ) + } + + /// Returns the fee model parameters using the denomination of the base token used (WEI for ETH). + fn get_fee_model_params(&self) -> FeeParams; +} - Ok(match params { +impl dyn BatchFeeModelInputProvider { + /// Provides the default implementation of `get_batch_fee_input_scaled()` given [`FeeParams`]. + pub fn default_batch_fee_input_scaled( + params: FeeParams, + l1_gas_price_scale_factor: f64, + l1_pubdata_price_scale_factor: f64, + ) -> BatchFeeInput { + match params { FeeParams::V1(params) => BatchFeeInput::L1Pegged(compute_batch_fee_model_input_v1( params, l1_gas_price_scale_factor, @@ -47,14 +66,9 @@ pub trait BatchFeeModelInputProvider: fmt::Debug + 'static + Send + Sync { l1_pubdata_price_scale_factor, )), ), - }) + } } - /// Returns the fee model parameters using the denomination of the base token used (WEI for ETH). - fn get_fee_model_params(&self) -> FeeParams; -} - -impl dyn BatchFeeModelInputProvider { /// Returns the batch fee input as-is, i.e. without any scaling for the L1 gas and pubdata prices. pub async fn get_batch_fee_input(&self) -> anyhow::Result { self.get_batch_fee_input_scaled(1.0, 1.0).await From f77c695a6cb0cdeac57acc18f95bbdc27d767099 Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Mon, 16 Sep 2024 15:12:05 +0300 Subject: [PATCH 10/12] Remove unnecessary provider wrapper --- core/node/api_server/src/web3/testonly.rs | 40 ++--------------------- 1 file changed, 3 insertions(+), 37 deletions(-) diff --git a/core/node/api_server/src/web3/testonly.rs b/core/node/api_server/src/web3/testonly.rs index bb17c7530e28..10df1970523d 100644 --- a/core/node/api_server/src/web3/testonly.rs +++ b/core/node/api_server/src/web3/testonly.rs @@ -2,18 +2,14 @@ use std::{pin::Pin, time::Instant}; -use async_trait::async_trait; use tokio::sync::watch; use zksync_config::configs::{api::Web3JsonRpcConfig, chain::StateKeeperConfig, wallets::Wallets}; use zksync_dal::ConnectionPool; use zksync_health_check::CheckHealth; -use zksync_node_fee_model::{BatchFeeModelInputProvider, MockBatchFeeParamsProvider}; +use zksync_node_fee_model::MockBatchFeeParamsProvider; use zksync_state::PostgresStorageCaches; use zksync_state_keeper::seal_criteria::NoopSealer; -use zksync_types::{ - fee_model::{BatchFeeInput, FeeParams}, - L2ChainId, -}; +use zksync_types::L2ChainId; use zksync_vm_executor::oneshot::MockOneshotExecutor; use super::{metrics::ApiTransportLabel, *}; @@ -22,33 +18,6 @@ use crate::{execution_sandbox::SandboxExecutor, tx_sender::TxSenderConfig}; const TEST_TIMEOUT: Duration = Duration::from_secs(90); const POLL_INTERVAL: Duration = Duration::from_millis(50); -/// Same as [`MockBatchFeeParamsProvider`], but also artificially acquires a Postgres connection on each call -/// (same as the real node implementation). -// FIXME: obsolete? (wrapped in `ApiFeeInputProvider` anyway) -#[derive(Debug)] -struct MockApiBatchFeeParamsProvider { - inner: MockBatchFeeParamsProvider, - pool: ConnectionPool, -} - -#[async_trait] -impl BatchFeeModelInputProvider for MockApiBatchFeeParamsProvider { - async fn get_batch_fee_input_scaled( - &self, - l1_gas_price_scale_factor: f64, - l1_pubdata_price_scale_factor: f64, - ) -> anyhow::Result { - let _connection = self.pool.connection().await?; - self.inner - .get_batch_fee_input_scaled(l1_gas_price_scale_factor, l1_pubdata_price_scale_factor) - .await - } - - fn get_fee_model_params(&self) -> FeeParams { - self.inner.get_fee_model_params() - } -} - pub(crate) async fn create_test_tx_sender( pool: ConnectionPool, l2_chain_id: L2ChainId, @@ -65,10 +34,7 @@ pub(crate) async fn create_test_tx_sender( ); let storage_caches = PostgresStorageCaches::new(1, 1); - let batch_fee_model_input_provider = Arc::new(MockApiBatchFeeParamsProvider { - inner: MockBatchFeeParamsProvider::default(), - pool: pool.clone(), - }); + let batch_fee_model_input_provider = Arc::::default(); let (mut tx_sender, vm_barrier) = crate::tx_sender::build_tx_sender( &tx_sender_config, &web3_config, From b9b7f9d1dfe010ac0be1ff6df08210238fc5d4ba Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Fri, 20 Sep 2024 11:55:12 +0300 Subject: [PATCH 11/12] Get fee input from Postgres --- core/node/consensus/src/storage/connection.rs | 15 +++++++++++---- core/node/consensus/src/vm.rs | 8 +++----- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/core/node/consensus/src/storage/connection.rs b/core/node/consensus/src/storage/connection.rs index 228a78c5bdf7..8e0a214ffbdc 100644 --- a/core/node/consensus/src/storage/connection.rs +++ b/core/node/consensus/src/storage/connection.rs @@ -7,7 +7,9 @@ use zksync_dal::{consensus_dal, consensus_dal::Payload, Core, CoreDal, DalError} use zksync_l1_contract_interface::i_executor::structures::StoredBatchInfo; use zksync_node_sync::{fetcher::IoCursorExt as _, ActionQueueSender, SyncState}; use zksync_state_keeper::io::common::IoCursor; -use zksync_types::{commitment::L1BatchWithMetadata, L1BatchNumber, L2BlockNumber}; +use zksync_types::{ + commitment::L1BatchWithMetadata, fee_model::BatchFeeInput, L1BatchNumber, L2BlockNumber, +}; use zksync_vm_executor::oneshot::{BlockInfo, ResolvedBlockInfo}; use super::{InsertCertificateError, PayloadQueue}; @@ -473,7 +475,7 @@ impl<'a> Connection<'a> { &mut self, ctx: &ctx::Ctx, batch: attester::BatchNumber, - ) -> ctx::Result { + ) -> ctx::Result<(ResolvedBlockInfo, BatchFeeInput)> { let (_, block) = self .get_l2_block_range_of_l1_batch(ctx, batch) .await @@ -485,9 +487,14 @@ impl<'a> Connection<'a> { .wait(BlockInfo::for_existing_block(&mut self.0, block)) .await? .context("BlockInfo")?; - Ok(ctx + let resolved_block_info = ctx .wait(block_info.resolve(&mut self.0)) .await? - .context("resolve()")?) + .context("resolve()")?; + let fee_input = ctx + .wait(block_info.historical_fee_input(&mut self.0)) + .await? + .context("historical_fee_input()")?; + Ok((resolved_block_info, fee_input)) } } diff --git a/core/node/consensus/src/vm.rs b/core/node/consensus/src/vm.rs index c8d33742e1ab..1c4509265616 100644 --- a/core/node/consensus/src/vm.rs +++ b/core/node/consensus/src/vm.rs @@ -4,9 +4,7 @@ use zksync_concurrency::{ctx, error::Wrap as _}; use zksync_consensus_roles::attester; use zksync_state::PostgresStorage; use zksync_system_constants::DEFAULT_L2_TX_GAS_PER_PUBDATA_BYTE; -use zksync_types::{ - ethabi, fee::Fee, fee_model::BatchFeeInput, l2::L2Tx, AccountTreeId, L2ChainId, Nonce, U256, -}; +use zksync_types::{ethabi, fee::Fee, l2::L2Tx, AccountTreeId, L2ChainId, Nonce, U256}; use zksync_vm_executor::oneshot::{CallOrExecute, MainOneshotExecutor, OneshotEnvParameters}; use zksync_vm_interface::{ executor::OneshotExecutor, ExecutionResult, OneshotTracingParams, TxExecutionArgs, @@ -27,6 +25,7 @@ impl VM { pub async fn new(pool: ConnectionPool) -> Self { Self { pool, + // L2 chain ID and fee account don't seem to matter for calls, hence the use of default values. options: OneshotEnvParameters::for_execution( L2ChainId::default(), AccountTreeId::default(), @@ -62,11 +61,10 @@ impl VM { ); let mut conn = self.pool.connection(ctx).await.wrap("connection()")?; - let block_info = conn + let (block_info, fee_input) = conn .vm_block_info(ctx, batch) .await .wrap("vm_block_info()")?; - let fee_input = BatchFeeInput::sensible_l1_pegged_default(); // FIXME: double-check let env = ctx .wait( self.options From 9be0b17204aa2e14a9c09a1deacd57a658739bd2 Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Fri, 20 Sep 2024 11:55:38 +0300 Subject: [PATCH 12/12] Update FIXME for `DebugNamespace` --- core/node/api_server/src/web3/namespaces/debug.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/node/api_server/src/web3/namespaces/debug.rs b/core/node/api_server/src/web3/namespaces/debug.rs index 8c3c23ba759a..2c6c70f6faa1 100644 --- a/core/node/api_server/src/web3/namespaces/debug.rs +++ b/core/node/api_server/src/web3/namespaces/debug.rs @@ -26,7 +26,7 @@ pub(crate) struct DebugNamespace { impl DebugNamespace { pub async fn new(state: RpcState) -> anyhow::Result { let fee_input_provider = &state.tx_sender.0.batch_fee_input_provider; - // FIXME: is using constant input correct? + // FIXME (PLA-1033): use the fee input provider instead of a constant value let batch_fee_input = fee_input_provider .get_batch_fee_input_scaled( state.api_config.estimate_gas_scale_factor,