From b31fdc515e3bc5668ac2a3386ad7a78e10373009 Mon Sep 17 00:00:00 2001 From: Danil Date: Wed, 24 Jan 2024 13:17:12 +0100 Subject: [PATCH] chore(vm): move vm utils outside bwip (#936) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What ❔ For future projects, we have to have some utils for executing txs outside BWIP and zksync_core in general. In this PR, I've moved the necessary functions, as is to external crate. p.s. I've not touched the code by itself, I just moved it. ## Why ❔ For correct block execution in different places, e.g. debug methods this code is pretty useful and could be reused without additional dependencies ## Checklist - [ ] PR title corresponds to the body of PR (we generate changelog entries from PRs). - [ ] Tests for the changes have been added / updated. - [ ] Documentation comments have been added / updated. - [ ] Code has been formatted via `zk fmt` and `zk lint`. - [ ] Spellcheck has been run via `zk spellcheck`. --------- Signed-off-by: Danil --- Cargo.lock | 16 ++ Cargo.toml | 1 + core/lib/vm_utils/Cargo.toml | 21 +++ .../src/lib.rs} | 10 +- core/lib/vm_utils/src/storage.rs | 163 ++++++++++++++++++ core/lib/zksync_core/Cargo.toml | 1 + .../src/basic_witness_input_producer/mod.rs | 8 +- .../src/state_keeper/extractors.rs | 48 +----- .../zksync_core/src/state_keeper/io/common.rs | 70 +------- .../src/state_keeper/io/mempool.rs | 4 +- .../src/state_keeper/io/seal_logic.rs | 3 +- .../zksync_core/src/sync_layer/external_io.rs | 5 +- 12 files changed, 218 insertions(+), 132 deletions(-) create mode 100644 core/lib/vm_utils/Cargo.toml rename core/lib/{zksync_core/src/basic_witness_input_producer/vm_interactions.rs => vm_utils/src/lib.rs} (94%) create mode 100644 core/lib/vm_utils/src/storage.rs diff --git a/Cargo.lock b/Cargo.lock index bf03abed4fa3..02e7be62d2ee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7462,6 +7462,21 @@ dependencies = [ "zksync_utils", ] +[[package]] +name = "vm_utils" +version = "0.1.0" +dependencies = [ + "anyhow", + "multivm", + "tokio", + "tracing", + "zksync_contracts", + "zksync_dal", + "zksync_state", + "zksync_types", + "zksync_utils", +] + [[package]] name = "walkdir" version = "2.4.0" @@ -8505,6 +8520,7 @@ dependencies = [ "tracing", "vise", "vlog", + "vm_utils", "zksync_circuit_breaker", "zksync_commitment_utils", "zksync_concurrency", diff --git a/Cargo.toml b/Cargo.toml index 1f8f52a7991a..e7b94164a1d9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,7 @@ members = [ "core/lib/utils", "core/lib/vlog", "core/lib/multivm", + "core/lib/vm_utils", "core/lib/web3_decl", # Test infrastructure diff --git a/core/lib/vm_utils/Cargo.toml b/core/lib/vm_utils/Cargo.toml new file mode 100644 index 000000000000..f2efb0c8773a --- /dev/null +++ b/core/lib/vm_utils/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "vm_utils" +version = "0.1.0" +edition = "2021" +authors = ["The Matter Labs Team "] +homepage = "https://zksync.io/" +repository = "https://github.com/matter-labs/zksync-era" +license = "MIT OR Apache-2.0" +keywords = ["blockchain", "zksync"] +categories = ["cryptography"] + +[dependencies] +multivm = { path = "../multivm" } +zksync_types = { path = "../types" } +zksync_dal = { path = "../dal" } +zksync_state = { path = "../state" } +tokio = { version = "1" } +anyhow = "1.0" +tracing = "0.1" +zksync_utils = { path = "../utils" } +zksync_contracts = { path = "../contracts" } diff --git a/core/lib/zksync_core/src/basic_witness_input_producer/vm_interactions.rs b/core/lib/vm_utils/src/lib.rs similarity index 94% rename from core/lib/zksync_core/src/basic_witness_input_producer/vm_interactions.rs rename to core/lib/vm_utils/src/lib.rs index 8ad2a66155de..62bb8c2b62e0 100644 --- a/core/lib/zksync_core/src/basic_witness_input_producer/vm_interactions.rs +++ b/core/lib/vm_utils/src/lib.rs @@ -1,3 +1,5 @@ +pub mod storage; + use anyhow::{anyhow, Context}; use multivm::{ interface::{VmInterface, VmInterfaceHistoryEnabled}, @@ -9,14 +11,14 @@ use zksync_dal::StorageProcessor; use zksync_state::{PostgresStorage, StoragePtr, StorageView, WriteStorage}; use zksync_types::{L1BatchNumber, L2ChainId, Transaction}; -use crate::state_keeper::io::common::load_l1_batch_params; +use crate::storage::load_l1_batch_params; -pub(super) type VmAndStorage<'a> = ( +pub type VmAndStorage<'a> = ( VmInstance>, HistoryEnabled>, StoragePtr>>, ); -pub(super) fn create_vm( +pub fn create_vm( rt_handle: Handle, l1_batch_number: L1BatchNumber, mut connection: StorageProcessor<'_>, @@ -66,7 +68,7 @@ pub(super) fn create_vm( Ok((vm, storage_view)) } -pub(super) fn execute_tx( +pub fn execute_tx( tx: &Transaction, vm: &mut VmInstance, ) -> anyhow::Result<()> { diff --git a/core/lib/vm_utils/src/storage.rs b/core/lib/vm_utils/src/storage.rs new file mode 100644 index 000000000000..53298e44562b --- /dev/null +++ b/core/lib/vm_utils/src/storage.rs @@ -0,0 +1,163 @@ +use std::time::{Duration, Instant}; + +use multivm::{ + interface::{L1BatchEnv, L2BlockEnv, SystemEnv, TxExecutionMode}, + vm_latest::constants::BLOCK_GAS_LIMIT, +}; +use zksync_contracts::BaseSystemContracts; +use zksync_dal::StorageProcessor; +use zksync_types::{ + fee_model::BatchFeeInput, Address, L1BatchNumber, L2ChainId, MiniblockNumber, + ProtocolVersionId, H256, U256, ZKPORTER_IS_AVAILABLE, +}; +use zksync_utils::{h256_to_u256, u256_to_h256}; + +pub async fn load_l1_batch_params( + storage: &mut StorageProcessor<'_>, + current_l1_batch_number: L1BatchNumber, + fee_account: Address, + validation_computational_gas_limit: u32, + chain_id: L2ChainId, +) -> Option<(SystemEnv, L1BatchEnv)> { + // If miniblock doesn't exist (for instance if it's pending), it means that there is no unsynced state (i.e. no transactions + // were executed after the last sealed batch). + let pending_miniblock_number = { + let (_, last_miniblock_number_included_in_l1_batch) = storage + .blocks_dal() + .get_miniblock_range_of_l1_batch(current_l1_batch_number - 1) + .await + .unwrap() + .unwrap(); + last_miniblock_number_included_in_l1_batch + 1 + }; + let pending_miniblock_header = storage + .blocks_dal() + .get_miniblock_header(pending_miniblock_number) + .await + .unwrap()?; + + tracing::info!("Getting previous batch hash"); + let (previous_l1_batch_hash, _) = + wait_for_prev_l1_batch_params(storage, current_l1_batch_number).await; + + tracing::info!("Getting previous miniblock hash"); + let prev_miniblock_hash = storage + .blocks_dal() + .get_miniblock_header(pending_miniblock_number - 1) + .await + .unwrap() + .unwrap() + .hash; + + let base_system_contracts = storage + .storage_dal() + .get_base_system_contracts( + pending_miniblock_header + .base_system_contracts_hashes + .bootloader, + pending_miniblock_header + .base_system_contracts_hashes + .default_aa, + ) + .await; + + tracing::info!("Previous l1_batch_hash: {}", previous_l1_batch_hash); + Some(l1_batch_params( + current_l1_batch_number, + fee_account, + pending_miniblock_header.timestamp, + previous_l1_batch_hash, + pending_miniblock_header.batch_fee_input, + pending_miniblock_number, + prev_miniblock_hash, + base_system_contracts, + validation_computational_gas_limit, + pending_miniblock_header + .protocol_version + .expect("`protocol_version` must be set for pending miniblock"), + pending_miniblock_header.virtual_blocks, + chain_id, + )) +} + +pub async fn wait_for_prev_l1_batch_params( + storage: &mut StorageProcessor<'_>, + number: L1BatchNumber, +) -> (U256, u64) { + if number == L1BatchNumber(0) { + return (U256::default(), 0); + } + wait_for_l1_batch_params_unchecked(storage, number - 1).await +} + +/// # Warning +/// +/// If invoked for a `L1BatchNumber` of a non-existent l1 batch, will block current thread indefinitely. +async fn wait_for_l1_batch_params_unchecked( + storage: &mut StorageProcessor<'_>, + number: L1BatchNumber, +) -> (U256, u64) { + // If the state root is not known yet, this duration will be used to back off in the while loops + const SAFE_STATE_ROOT_INTERVAL: Duration = Duration::from_millis(100); + + let stage_started_at: Instant = Instant::now(); + loop { + let data = storage + .blocks_dal() + .get_l1_batch_state_root_and_timestamp(number) + .await + .unwrap(); + if let Some((root_hash, timestamp)) = data { + tracing::trace!( + "Waiting for hash of L1 batch #{number} took {:?}", + stage_started_at.elapsed() + ); + return (h256_to_u256(root_hash), timestamp); + } + + tokio::time::sleep(SAFE_STATE_ROOT_INTERVAL).await; + } +} + +/// Returns the parameters required to initialize the VM for the next L1 batch. +#[allow(clippy::too_many_arguments)] +pub fn l1_batch_params( + current_l1_batch_number: L1BatchNumber, + fee_account: Address, + l1_batch_timestamp: u64, + previous_batch_hash: U256, + fee_input: BatchFeeInput, + first_miniblock_number: MiniblockNumber, + prev_miniblock_hash: H256, + base_system_contracts: BaseSystemContracts, + validation_computational_gas_limit: u32, + protocol_version: ProtocolVersionId, + virtual_blocks: u32, + chain_id: L2ChainId, +) -> (SystemEnv, L1BatchEnv) { + ( + SystemEnv { + zk_porter_available: ZKPORTER_IS_AVAILABLE, + version: protocol_version, + base_system_smart_contracts: base_system_contracts, + gas_limit: BLOCK_GAS_LIMIT, + execution_mode: TxExecutionMode::VerifyExecute, + default_validation_computational_gas_limit: validation_computational_gas_limit, + chain_id, + }, + L1BatchEnv { + previous_batch_hash: Some(u256_to_h256(previous_batch_hash)), + number: current_l1_batch_number, + timestamp: l1_batch_timestamp, + fee_input, + fee_account, + enforced_base_fee: None, + first_l2_block: L2BlockEnv { + number: first_miniblock_number.0, + timestamp: l1_batch_timestamp, + prev_block_hash: prev_miniblock_hash, + max_virtual_blocks_to_create: virtual_blocks, + }, + }, + ) +} diff --git a/core/lib/zksync_core/Cargo.toml b/core/lib/zksync_core/Cargo.toml index 60447b5b55bc..ad4222613347 100644 --- a/core/lib/zksync_core/Cargo.toml +++ b/core/lib/zksync_core/Cargo.toml @@ -12,6 +12,7 @@ categories = ["cryptography"] [dependencies] vise = { git = "https://github.com/matter-labs/vise.git", version = "0.1.0", rev = "1c9cc500e92cf9ea052b230e114a6f9cce4fb2c1" } zksync_state = { path = "../state" } +vm_utils = { path = "../vm_utils" } zksync_types = { path = "../types" } zksync_dal = { path = "../dal" } zksync_config = { path = "../config" } diff --git a/core/lib/zksync_core/src/basic_witness_input_producer/mod.rs b/core/lib/zksync_core/src/basic_witness_input_producer/mod.rs index e91ccc4864eb..ed933c18d5b3 100644 --- a/core/lib/zksync_core/src/basic_witness_input_producer/mod.rs +++ b/core/lib/zksync_core/src/basic_witness_input_producer/mod.rs @@ -4,19 +4,15 @@ use anyhow::Context; use async_trait::async_trait; use multivm::interface::{L2BlockEnv, VmInterface}; use tokio::{runtime::Handle, task::JoinHandle}; +use vm_utils::{create_vm, execute_tx}; use zksync_dal::{basic_witness_input_producer_dal::JOB_MAX_ATTEMPT, ConnectionPool}; use zksync_object_store::{ObjectStore, ObjectStoreFactory}; use zksync_queued_job_processor::JobProcessor; use zksync_types::{witness_block_state::WitnessBlockState, L1BatchNumber, L2ChainId}; -use self::{ - metrics::METRICS, - vm_interactions::{create_vm, execute_tx}, -}; +use self::metrics::METRICS; mod metrics; -mod vm_interactions; - /// Component that extracts all data (from DB) necessary to run a Basic Witness Generator. /// Does this by rerunning an entire L1Batch and extracting information from both the VM run and DB. /// This component will upload Witness Inputs to the object store. diff --git a/core/lib/zksync_core/src/state_keeper/extractors.rs b/core/lib/zksync_core/src/state_keeper/extractors.rs index e31020734f58..8f6f8cac5ba7 100644 --- a/core/lib/zksync_core/src/state_keeper/extractors.rs +++ b/core/lib/zksync_core/src/state_keeper/extractors.rs @@ -1,15 +1,8 @@ //! Pure functions that convert data as required by the state keeper. -use std::{ - convert::TryFrom, - fmt, - time::{Duration, Instant}, -}; +use std::{convert::TryFrom, fmt}; use chrono::{DateTime, TimeZone, Utc}; -use zksync_dal::StorageProcessor; -use zksync_types::{L1BatchNumber, U256}; -use zksync_utils::h256_to_u256; /// Displays a Unix timestamp (seconds since epoch) in human-readable form. Useful for logging. pub(super) fn display_timestamp(timestamp: u64) -> impl fmt::Display { @@ -34,42 +27,3 @@ pub(super) fn display_timestamp(timestamp: u64) -> impl fmt::Display { DisplayedTimestamp::Parsed, ) } - -pub(crate) async fn wait_for_prev_l1_batch_params( - storage: &mut StorageProcessor<'_>, - number: L1BatchNumber, -) -> (U256, u64) { - if number == L1BatchNumber(0) { - return (U256::default(), 0); - } - wait_for_l1_batch_params_unchecked(storage, number - 1).await -} - -/// # Warning -/// -/// If invoked for a `L1BatchNumber` of a non-existent l1 batch, will block current thread indefinitely. -async fn wait_for_l1_batch_params_unchecked( - storage: &mut StorageProcessor<'_>, - number: L1BatchNumber, -) -> (U256, u64) { - // If the state root is not known yet, this duration will be used to back off in the while loops - const SAFE_STATE_ROOT_INTERVAL: Duration = Duration::from_millis(100); - - let stage_started_at: Instant = Instant::now(); - loop { - let data = storage - .blocks_dal() - .get_l1_batch_state_root_and_timestamp(number) - .await - .unwrap(); - if let Some((root_hash, timestamp)) = data { - tracing::trace!( - "Waiting for hash of L1 batch #{number} took {:?}", - stage_started_at.elapsed() - ); - return (h256_to_u256(root_hash), timestamp); - } - - tokio::time::sleep(SAFE_STATE_ROOT_INTERVAL).await; - } -} diff --git a/core/lib/zksync_core/src/state_keeper/io/common.rs b/core/lib/zksync_core/src/state_keeper/io/common.rs index dad43b7ee9d9..54632c468ab4 100644 --- a/core/lib/zksync_core/src/state_keeper/io/common.rs +++ b/core/lib/zksync_core/src/state_keeper/io/common.rs @@ -4,6 +4,7 @@ use multivm::{ interface::{L1BatchEnv, L2BlockEnv, SystemEnv, TxExecutionMode}, vm_latest::constants::BLOCK_GAS_LIMIT, }; +use vm_utils::storage::load_l1_batch_params; use zksync_contracts::BaseSystemContracts; use zksync_dal::StorageProcessor; use zksync_types::{ @@ -13,7 +14,6 @@ use zksync_types::{ use zksync_utils::u256_to_h256; use super::PendingBatchData; -use crate::state_keeper::extractors; /// Returns the parameters required to initialize the VM for the next L1 batch. #[allow(clippy::too_many_arguments)] @@ -67,74 +67,6 @@ pub(crate) fn poll_iters(delay_interval: Duration, max_wait: Duration) -> usize ((max_wait_millis + delay_interval_millis - 1) / delay_interval_millis).max(1) as usize } -pub(crate) async fn load_l1_batch_params( - storage: &mut StorageProcessor<'_>, - current_l1_batch_number: L1BatchNumber, - fee_account: Address, - validation_computational_gas_limit: u32, - chain_id: L2ChainId, -) -> Option<(SystemEnv, L1BatchEnv)> { - // If miniblock doesn't exist (for instance if it's pending), it means that there is no unsynced state (i.e. no transactions - // were executed after the last sealed batch). - let pending_miniblock_number = { - let (_, last_miniblock_number_included_in_l1_batch) = storage - .blocks_dal() - .get_miniblock_range_of_l1_batch(current_l1_batch_number - 1) - .await - .unwrap() - .unwrap(); - last_miniblock_number_included_in_l1_batch + 1 - }; - let pending_miniblock_header = storage - .blocks_dal() - .get_miniblock_header(pending_miniblock_number) - .await - .unwrap()?; - - tracing::info!("Getting previous batch hash"); - let (previous_l1_batch_hash, _) = - extractors::wait_for_prev_l1_batch_params(storage, current_l1_batch_number).await; - - tracing::info!("Getting previous miniblock hash"); - let prev_miniblock_hash = storage - .blocks_dal() - .get_miniblock_header(pending_miniblock_number - 1) - .await - .unwrap() - .unwrap() - .hash; - - let base_system_contracts = storage - .storage_dal() - .get_base_system_contracts( - pending_miniblock_header - .base_system_contracts_hashes - .bootloader, - pending_miniblock_header - .base_system_contracts_hashes - .default_aa, - ) - .await; - - tracing::info!("Previous l1_batch_hash: {}", previous_l1_batch_hash); - Some(l1_batch_params( - current_l1_batch_number, - fee_account, - pending_miniblock_header.timestamp, - previous_l1_batch_hash, - pending_miniblock_header.batch_fee_input, - pending_miniblock_number, - prev_miniblock_hash, - base_system_contracts, - validation_computational_gas_limit, - pending_miniblock_header - .protocol_version - .expect("`protocol_version` must be set for pending miniblock"), - pending_miniblock_header.virtual_blocks, - chain_id, - )) -} - /// Loads the pending L1 block data from the database. pub(crate) async fn load_pending_batch( storage: &mut StorageProcessor<'_>, diff --git a/core/lib/zksync_core/src/state_keeper/io/mempool.rs b/core/lib/zksync_core/src/state_keeper/io/mempool.rs index 4fa9a79638b0..df124335b4b0 100644 --- a/core/lib/zksync_core/src/state_keeper/io/mempool.rs +++ b/core/lib/zksync_core/src/state_keeper/io/mempool.rs @@ -10,6 +10,7 @@ use multivm::{ interface::{FinishedL1Batch, L1BatchEnv, SystemEnv}, utils::derive_base_fee_and_gas_per_pubdata, }; +use vm_utils::storage::wait_for_prev_l1_batch_params; use zksync_config::configs::chain::StateKeeperConfig; use zksync_dal::ConnectionPool; use zksync_mempool::L2TxFilter; @@ -466,8 +467,7 @@ impl MempoolIO { .await .unwrap(); let (batch_hash, _) = - extractors::wait_for_prev_l1_batch_params(&mut storage, self.current_l1_batch_number) - .await; + wait_for_prev_l1_batch_params(&mut storage, self.current_l1_batch_number).await; wait_latency.observe(); tracing::info!( diff --git a/core/lib/zksync_core/src/state_keeper/io/seal_logic.rs b/core/lib/zksync_core/src/state_keeper/io/seal_logic.rs index cea82b59edda..5197c6f8e30d 100644 --- a/core/lib/zksync_core/src/state_keeper/io/seal_logic.rs +++ b/core/lib/zksync_core/src/state_keeper/io/seal_logic.rs @@ -11,6 +11,7 @@ use multivm::{ interface::{FinishedL1Batch, L1BatchEnv}, utils::{get_batch_base_fee, get_max_gas_per_pubdata_byte}, }; +use vm_utils::storage::wait_for_prev_l1_batch_params; use zksync_dal::StorageProcessor; use zksync_system_constants::ACCOUNT_CODE_STORAGE_ADDRESS; use zksync_types::{ @@ -113,7 +114,7 @@ impl UpdatesManager { let progress = L1_BATCH_METRICS.start(L1BatchSealStage::InsertL1BatchHeader); let (_prev_hash, prev_timestamp) = - extractors::wait_for_prev_l1_batch_params(&mut transaction, l1_batch_env.number).await; + wait_for_prev_l1_batch_params(&mut transaction, l1_batch_env.number).await; assert!( prev_timestamp < l1_batch_env.timestamp, "Cannot seal L1 batch #{}: Timestamp of previous L1 batch ({}) >= provisional L1 batch timestamp ({}), \ diff --git a/core/lib/zksync_core/src/sync_layer/external_io.rs b/core/lib/zksync_core/src/sync_layer/external_io.rs index c6098c65a437..c6ca76026ff7 100644 --- a/core/lib/zksync_core/src/sync_layer/external_io.rs +++ b/core/lib/zksync_core/src/sync_layer/external_io.rs @@ -3,6 +3,7 @@ use std::{collections::HashMap, convert::TryInto, iter::FromIterator, time::Dura use async_trait::async_trait; use futures::future; use multivm::interface::{FinishedL1Batch, L1BatchEnv, SystemEnv}; +use vm_utils::storage::wait_for_prev_l1_batch_params; use zksync_contracts::{BaseSystemContracts, SystemContractCode}; use zksync_dal::ConnectionPool; use zksync_types::{ @@ -20,7 +21,6 @@ use super::{ use crate::{ metrics::{BlockStage, APP_METRICS}, state_keeper::{ - extractors, io::{ common::{l1_batch_params, load_pending_batch, poll_iters}, MiniblockParams, MiniblockSealerHandle, PendingBatchData, StateKeeperIO, @@ -112,8 +112,7 @@ impl ExternalIO { let mut storage = self.pool.access_storage_tagged("sync_layer").await.unwrap(); let wait_latency = KEEPER_METRICS.wait_for_prev_hash_time.start(); let (hash, _) = - extractors::wait_for_prev_l1_batch_params(&mut storage, self.current_l1_batch_number) - .await; + wait_for_prev_l1_batch_params(&mut storage, self.current_l1_batch_number).await; wait_latency.observe(); hash }