anvil_zksync_core/node/
in_memory.rs

1//! In-memory node, that supports forking other networks.
2use super::inner::node_executor::NodeExecutorHandle;
3use super::inner::InMemoryNodeInner;
4use super::vm::AnvilVM;
5use crate::delegate_vm;
6use crate::deps::InMemoryStorage;
7use crate::filters::EthFilters;
8use crate::node::fee_model::TestNodeFeeInputProvider;
9use crate::node::impersonate::{ImpersonationManager, ImpersonationState};
10use crate::node::inner::blockchain::ReadBlockchain;
11use crate::node::inner::storage::ReadStorageDyn;
12use crate::node::inner::time::ReadTime;
13use crate::node::sealer::BlockSealerState;
14use crate::node::state::VersionedState;
15use crate::node::state_override::apply_state_override;
16use crate::node::traces::call_error::CallErrorTracer;
17use crate::node::traces::decoder::CallTraceDecoderBuilder;
18use crate::node::{BlockSealer, BlockSealerMode, NodeExecutor, TxBatch, TxPool};
19use crate::observability::Observability;
20use crate::system_contracts::SystemContracts;
21use anvil_zksync_common::cache::CacheConfig;
22use anvil_zksync_common::sh_println;
23use anvil_zksync_common::shell::get_shell;
24use anvil_zksync_config::constants::{NON_FORK_FIRST_BLOCK_TIMESTAMP, TEST_NODE_NETWORK_ID};
25use anvil_zksync_config::types::Genesis;
26use anvil_zksync_config::TestNodeConfig;
27use anvil_zksync_traces::{
28    build_call_trace_arena, decode_trace_arena, filter_call_trace_arena,
29    identifier::SignaturesIdentifier, render_trace_arena_inner,
30};
31use anvil_zksync_types::{
32    traces::CallTraceArena, LogLevel, ShowGasDetails, ShowStorageLogs, ShowVMDetails,
33};
34use flate2::read::GzDecoder;
35use flate2::write::GzEncoder;
36use flate2::Compression;
37use indexmap::IndexMap;
38use once_cell::sync::OnceCell;
39use serde::{Deserialize, Serialize};
40use std::collections::{HashMap, HashSet};
41use std::io::{Read, Write};
42use std::sync::Arc;
43use tokio::sync::RwLock;
44use zksync_contracts::{BaseSystemContracts, BaseSystemContractsHashes};
45use zksync_error::anvil_zksync::node::{
46    generic_error, to_generic, AnvilNodeError, AnvilNodeResult,
47};
48use zksync_error::anvil_zksync::state::{StateLoaderError, StateLoaderResult};
49use zksync_multivm::interface::storage::{
50    ReadStorage, StoragePtr, StorageView, StorageWithOverrides,
51};
52use zksync_multivm::interface::VmFactory;
53use zksync_multivm::interface::{
54    ExecutionResult, InspectExecutionMode, L1BatchEnv, L2BlockEnv, TxExecutionMode, VmInterface,
55};
56use zksync_multivm::tracers::CallTracer;
57use zksync_multivm::utils::{get_batch_base_fee, get_max_batch_gas_limit};
58use zksync_multivm::vm_latest::Vm;
59use zksync_types::api::state_override::StateOverride;
60
61use crate::node::fork::{ForkClient, ForkSource};
62use crate::node::keys::StorageKeyLayout;
63use zksync_multivm::vm_latest::{HistoryDisabled, ToTracerPointer};
64use zksync_multivm::VmVersion;
65use zksync_types::api::{Block, DebugCall, TransactionReceipt, TransactionVariant};
66use zksync_types::block::{unpack_block_info, L1BatchHeader, L2BlockHasher};
67use zksync_types::fee_model::BatchFeeInput;
68use zksync_types::l2::L2Tx;
69use zksync_types::storage::{
70    EMPTY_UNCLES_HASH, SYSTEM_CONTEXT_ADDRESS, SYSTEM_CONTEXT_BLOCK_INFO_POSITION,
71};
72use zksync_types::web3::Bytes;
73use zksync_types::{
74    h256_to_u256, AccountTreeId, Address, Bloom, L1BatchNumber, L2BlockNumber, L2ChainId,
75    PackedEthSignature, ProtocolVersionId, StorageKey, StorageValue, Transaction, H160, H256, H64,
76    U256, U64,
77};
78
79/// Max possible size of an ABI encoded tx (in bytes).
80/// NOTE: this deviates slightly from the default value in the main node config,
81/// that being `api.max_tx_size = 1_000_000`.
82pub const MAX_TX_SIZE: usize = 1_200_000;
83/// Acceptable gas overestimation limit.
84pub const ESTIMATE_GAS_ACCEPTABLE_OVERESTIMATION: u64 = 1_000;
85/// The maximum number of previous blocks to store the state for.
86pub const MAX_PREVIOUS_STATES: u16 = 128;
87/// The zks protocol version.
88pub const PROTOCOL_VERSION: &str = "zks/1";
89
90pub fn compute_hash<'a>(
91    protocol_version: ProtocolVersionId,
92    number: L2BlockNumber,
93    timestamp: u64,
94    prev_l2_block_hash: H256,
95    tx_hashes: impl IntoIterator<Item = &'a H256>,
96) -> H256 {
97    let mut block_hasher = L2BlockHasher::new(number, timestamp, prev_l2_block_hash);
98    for tx_hash in tx_hashes.into_iter() {
99        block_hasher.push_tx_hash(*tx_hash);
100    }
101    block_hasher.finalize(protocol_version)
102}
103
104pub fn create_genesis_from_json(
105    protocol_version: ProtocolVersionId,
106    genesis: &Genesis,
107    timestamp: Option<u64>,
108) -> (Block<TransactionVariant>, L1BatchHeader) {
109    let hash = L2BlockHasher::legacy_hash(L2BlockNumber(0));
110    let timestamp = timestamp
111        .or(genesis.timestamp)
112        .unwrap_or(NON_FORK_FIRST_BLOCK_TIMESTAMP);
113
114    let l1_batch_env = genesis.l1_batch_env.clone().unwrap_or_else(|| L1BatchEnv {
115        previous_batch_hash: None,
116        number: L1BatchNumber(0),
117        timestamp,
118        fee_input: BatchFeeInput::pubdata_independent(0, 0, 0),
119        fee_account: Address::zero(),
120        enforced_base_fee: None,
121        first_l2_block: L2BlockEnv {
122            number: 0,
123            timestamp,
124            prev_block_hash: H256::zero(),
125            max_virtual_blocks_to_create: 0,
126        },
127    });
128
129    let genesis_block = create_block(
130        &l1_batch_env,
131        hash,
132        genesis.parent_hash.unwrap_or_else(H256::zero),
133        genesis.block_number.unwrap_or(0),
134        timestamp,
135        genesis.transactions.clone().unwrap_or_default(),
136        genesis.gas_used.unwrap_or_else(U256::zero),
137        genesis.logs_bloom.unwrap_or_else(Bloom::zero),
138    );
139    let genesis_batch_header = L1BatchHeader::new(
140        L1BatchNumber(0),
141        timestamp,
142        BaseSystemContractsHashes::default(),
143        protocol_version,
144    );
145
146    (genesis_block, genesis_batch_header)
147}
148
149pub fn create_genesis<TX>(
150    protocol_version: ProtocolVersionId,
151    timestamp: Option<u64>,
152) -> (Block<TX>, L1BatchHeader) {
153    let hash = L2BlockHasher::legacy_hash(L2BlockNumber(0));
154    let timestamp = timestamp.unwrap_or(NON_FORK_FIRST_BLOCK_TIMESTAMP);
155    let batch_env = L1BatchEnv {
156        previous_batch_hash: None,
157        number: L1BatchNumber(0),
158        timestamp,
159        fee_input: BatchFeeInput::pubdata_independent(0, 0, 0),
160        fee_account: Default::default(),
161        enforced_base_fee: None,
162        first_l2_block: L2BlockEnv {
163            number: 0,
164            timestamp,
165            prev_block_hash: Default::default(),
166            max_virtual_blocks_to_create: 0,
167        },
168    };
169    let genesis_block = create_block(
170        &batch_env,
171        hash,
172        H256::zero(),
173        0,
174        timestamp,
175        vec![],
176        U256::zero(),
177        Bloom::zero(),
178    );
179    let genesis_batch_header = L1BatchHeader::new(
180        L1BatchNumber(0),
181        timestamp,
182        BaseSystemContractsHashes::default(),
183        protocol_version,
184    );
185
186    (genesis_block, genesis_batch_header)
187}
188
189#[allow(clippy::too_many_arguments)]
190pub fn create_block<TX>(
191    batch_env: &L1BatchEnv,
192    hash: H256,
193    parent_hash: H256,
194    number: u64,
195    timestamp: u64,
196    transactions: Vec<TX>,
197    gas_used: U256,
198    logs_bloom: Bloom,
199) -> Block<TX> {
200    Block {
201        hash,
202        parent_hash,
203        uncles_hash: EMPTY_UNCLES_HASH, // Static for non-PoW chains, see EIP-3675
204        number: U64::from(number),
205        l1_batch_number: Some(U64::from(batch_env.number.0)),
206        base_fee_per_gas: U256::from(get_batch_base_fee(batch_env, VmVersion::latest())),
207        timestamp: U256::from(timestamp),
208        l1_batch_timestamp: Some(U256::from(batch_env.timestamp)),
209        transactions,
210        gas_used,
211        gas_limit: U256::from(get_max_batch_gas_limit(VmVersion::latest())),
212        logs_bloom,
213        author: Address::default(), // Matches core's behavior, irrelevant for ZKsync
214        state_root: H256::default(), // Intentionally empty as blocks in ZKsync don't have state - batches do
215        transactions_root: H256::default(), // Intentionally empty as blocks in ZKsync don't have state - batches do
216        receipts_root: H256::default(), // Intentionally empty as blocks in ZKsync don't have state - batches do
217        extra_data: Bytes::default(),   // Matches core's behavior, not used in ZKsync
218        difficulty: U256::default(), // Empty for non-PoW chains, see EIP-3675, TODO: should be 2500000000000000 to match DIFFICULTY opcode
219        total_difficulty: U256::default(), // Empty for non-PoW chains, see EIP-3675
220        seal_fields: vec![],         // Matches core's behavior, TODO: remove
221        uncles: vec![],              // Empty for non-PoW chains, see EIP-3675
222        size: U256::default(),       // Matches core's behavior, TODO: perhaps it should be computed
223        mix_hash: H256::default(),   // Empty for non-PoW chains, see EIP-3675
224        nonce: H64::default(),       // Empty for non-PoW chains, see EIP-3675
225    }
226}
227
228/// Information about the executed transaction.
229#[derive(Debug, Clone, Serialize, Deserialize)]
230pub struct TxExecutionInfo {
231    pub tx: Transaction,
232    // Batch number where transaction was executed.
233    pub batch_number: u32,
234    pub miniblock_number: u64,
235}
236
237#[derive(Debug, Clone, Serialize, Deserialize)]
238pub struct TransactionResult {
239    pub info: TxExecutionInfo,
240    pub new_bytecodes: Vec<(H256, Vec<u8>)>,
241    pub receipt: TransactionReceipt,
242    pub debug: DebugCall,
243}
244
245impl TransactionResult {
246    /// Returns the debug information for the transaction.
247    /// If `only_top` is true - will only return the top level call.
248    pub fn debug_info(&self, only_top: bool) -> DebugCall {
249        let calls = if only_top {
250            vec![]
251        } else {
252            self.debug.calls.clone()
253        };
254        DebugCall {
255            calls,
256            ..self.debug.clone()
257        }
258    }
259}
260
261/// Creates a restorable snapshot for the [InMemoryNodeInner]. The snapshot contains all the necessary
262/// data required to restore the [InMemoryNodeInner] state to a previous point in time.
263#[derive(Debug, Clone, Default)]
264pub struct Snapshot {
265    pub(crate) current_batch: L1BatchNumber,
266    pub(crate) current_block: L2BlockNumber,
267    pub(crate) current_block_hash: H256,
268    // Currently, the fee is static and the fee input provider is immutable during the test node life cycle,
269    // but in the future, it may contain some mutable state.
270    pub(crate) fee_input_provider: TestNodeFeeInputProvider,
271    pub(crate) tx_results: HashMap<H256, TransactionResult>,
272    pub(crate) blocks: HashMap<H256, Block<TransactionVariant>>,
273    pub(crate) hashes: HashMap<L2BlockNumber, H256>,
274    pub(crate) filters: EthFilters,
275    pub(crate) impersonation_state: ImpersonationState,
276    pub(crate) rich_accounts: HashSet<H160>,
277    pub(crate) previous_states: IndexMap<H256, HashMap<StorageKey, StorageValue>>,
278    pub(crate) raw_storage: InMemoryStorage,
279    pub(crate) value_read_cache: HashMap<StorageKey, H256>,
280    pub(crate) factory_dep_cache: HashMap<H256, Option<Vec<u8>>>,
281}
282
283/// In-memory node, that can be used for local & unit testing.
284/// It also supports the option of forking testnet/mainnet.
285/// All contents are removed when object is destroyed.
286#[derive(Clone)]
287pub struct InMemoryNode {
288    /// A thread safe reference to the [InMemoryNodeInner].
289    pub(crate) inner: Arc<RwLock<InMemoryNodeInner>>,
290    pub(crate) blockchain: Box<dyn ReadBlockchain>,
291    pub(crate) storage: Box<dyn ReadStorageDyn>,
292    pub(crate) fork: Box<dyn ForkSource>,
293    pub node_handle: NodeExecutorHandle,
294    /// List of snapshots of the [InMemoryNodeInner]. This is bounded at runtime by [MAX_SNAPSHOTS].
295    pub(crate) snapshots: Arc<RwLock<Vec<Snapshot>>>,
296    pub(crate) time: Box<dyn ReadTime>,
297    pub(crate) impersonation: ImpersonationManager,
298    /// An optional handle to the observability stack
299    pub(crate) observability: Option<Observability>,
300    pub(crate) pool: TxPool,
301    pub(crate) sealer_state: BlockSealerState,
302    pub(crate) system_contracts: SystemContracts,
303    pub(crate) storage_key_layout: StorageKeyLayout,
304}
305
306impl InMemoryNode {
307    #[allow(clippy::too_many_arguments)]
308    pub fn new(
309        inner: Arc<RwLock<InMemoryNodeInner>>,
310        blockchain: Box<dyn ReadBlockchain>,
311        storage: Box<dyn ReadStorageDyn>,
312        fork: Box<dyn ForkSource>,
313        node_handle: NodeExecutorHandle,
314        observability: Option<Observability>,
315        time: Box<dyn ReadTime>,
316        impersonation: ImpersonationManager,
317        pool: TxPool,
318        sealer_state: BlockSealerState,
319        system_contracts: SystemContracts,
320        storage_key_layout: StorageKeyLayout,
321    ) -> Self {
322        InMemoryNode {
323            inner,
324            blockchain,
325            storage,
326            fork,
327            node_handle,
328            snapshots: Default::default(),
329            time,
330            impersonation,
331            observability,
332            pool,
333            sealer_state,
334            system_contracts,
335            storage_key_layout,
336        }
337    }
338
339    /// Replays transactions consequently in a new block. All transactions are expected to be
340    /// executable and will become a part of the resulting block.
341    pub async fn replay_txs(&self, txs: Vec<Transaction>) -> AnvilNodeResult<()> {
342        let tx_batch = TxBatch {
343            impersonating: false,
344            txs,
345        };
346        let expected_tx_hashes = tx_batch
347            .txs
348            .iter()
349            .map(|tx| tx.hash())
350            .collect::<HashSet<_>>();
351        let block_number = self.node_handle.seal_block_sync(tx_batch).await?;
352        // Fetch the block that was just sealed
353        let block = self
354            .blockchain
355            .get_block_by_number(block_number)
356            .await
357            .expect("freshly sealed block could not be found in storage");
358
359        // Calculate tx hash set from that block
360        let actual_tx_hashes = block
361            .transactions
362            .iter()
363            .map(|tx| match tx {
364                TransactionVariant::Full(tx) => tx.hash,
365                TransactionVariant::Hash(tx_hash) => *tx_hash,
366            })
367            .collect::<HashSet<_>>();
368
369        // Calculate the difference between expected transaction hash set and the actual one.
370        // If the difference is not empty it means some transactions were not executed (i.e.
371        // were halted).
372        let diff_tx_hashes = expected_tx_hashes
373            .difference(&actual_tx_hashes)
374            .collect::<Vec<_>>();
375        if !diff_tx_hashes.is_empty() {
376            return Err(generic_error!(
377                "Failed to replay transactions: {diff_tx_hashes:?}. Please report this."
378            ));
379        }
380
381        Ok(())
382    }
383
384    /// Adds a lot of tokens to a given account with a specified balance.
385    pub async fn set_rich_account(&self, address: H160, balance: U256) {
386        self.inner.write().await.set_rich_account(address, balance)
387    }
388
389    /// Runs L2 'eth call' method - that doesn't commit to a block.
390    pub async fn run_l2_call(
391        &self,
392        mut l2_tx: L2Tx,
393        base_contracts: BaseSystemContracts,
394        state_override: Option<StateOverride>,
395    ) -> AnvilNodeResult<ExecutionResult> {
396        let execution_mode = TxExecutionMode::EthCall;
397
398        let inner = self.inner.read().await;
399
400        // init vm
401
402        let (batch_env, _) = inner.create_l1_batch_env().await;
403        let system_env = inner.create_system_env(base_contracts, execution_mode);
404
405        let storage_override = if let Some(state_override) = state_override {
406            apply_state_override(inner.read_storage(), state_override)
407        } else {
408            // Do not spawn a new thread in the most frequent case.
409            StorageWithOverrides::new(inner.read_storage())
410        };
411
412        let storage = StorageView::new(storage_override).to_rc_ptr();
413
414        let mut vm = if self.system_contracts.boojum.use_boojum {
415            AnvilVM::BoojumOs(super::boojumos::BoojumOsVM::<_, HistoryDisabled>::new(
416                batch_env,
417                system_env,
418                storage,
419                // TODO: this might be causing a deadlock.. check..
420                &inner.fork_storage.inner.read().unwrap().raw_storage,
421                &self.system_contracts.boojum,
422            ))
423        } else {
424            AnvilVM::ZKSync(Vm::new(batch_env, system_env, storage))
425        };
426
427        // We must inject *some* signature (otherwise bootloader code fails to generate hash).
428        if l2_tx.common_data.signature.is_empty() {
429            l2_tx.common_data.signature = PackedEthSignature::default().serialize_packed().into();
430        }
431
432        let tx: Transaction = l2_tx.into();
433        delegate_vm!(vm, push_transaction(tx.clone()));
434
435        let call_tracer_result = Arc::new(OnceCell::default());
436        let error_flags_result = Arc::new(OnceCell::new());
437
438        let tracers = vec![
439            CallErrorTracer::new(error_flags_result.clone()).into_tracer_pointer(),
440            CallTracer::new(call_tracer_result.clone()).into_tracer_pointer(),
441        ];
442        let tx_result = delegate_vm!(
443            vm,
444            inspect(&mut tracers.into(), InspectExecutionMode::OneTx)
445        );
446
447        let call_traces = Arc::try_unwrap(call_tracer_result)
448            .unwrap()
449            .take()
450            .unwrap_or_default();
451
452        let verbosity = get_shell().verbosity;
453        if !call_traces.is_empty() && verbosity >= 2 {
454            let tx_result_for_arena = tx_result.clone();
455            let mut builder = CallTraceDecoderBuilder::default();
456            builder = builder.with_signature_identifier(SignaturesIdentifier::global());
457
458            let decoder = builder.build();
459            let arena: CallTraceArena = futures::executor::block_on(async {
460                let blocking_result = tokio::task::spawn_blocking(move || {
461                    let mut arena = build_call_trace_arena(&call_traces, &tx_result_for_arena);
462                    let rt = tokio::runtime::Runtime::new().expect("Failed to create runtime");
463                    rt.block_on(async {
464                        decode_trace_arena(&mut arena, &decoder).await;
465                        Ok(arena)
466                    })
467                })
468                .await;
469
470                let inner_result: Result<CallTraceArena, AnvilNodeError> =
471                    blocking_result.expect("spawn_blocking failed");
472                inner_result
473            })?;
474
475            let filtered_arena = filter_call_trace_arena(&arena, verbosity);
476            let trace_output = render_trace_arena_inner(&filtered_arena, false);
477            sh_println!("\nTraces:\n{}", trace_output);
478        }
479
480        Ok(tx_result.result)
481    }
482
483    // Forcefully stores the given bytecode at a given account.
484    pub async fn override_bytecode(
485        &self,
486        address: Address,
487        bytecode: Vec<u8>,
488    ) -> AnvilNodeResult<()> {
489        self.node_handle.set_code_sync(address, bytecode).await
490    }
491
492    pub async fn dump_state(&self, preserve_historical_states: bool) -> AnvilNodeResult<Bytes> {
493        let state = self
494            .inner
495            .read()
496            .await
497            .dump_state(preserve_historical_states)
498            .await?;
499        let mut encoder = GzEncoder::new(Vec::new(), Compression::default());
500        encoder
501            .write_all(&serde_json::to_vec(&state).map_err(to_generic)?)
502            .map_err(to_generic)?;
503        Ok(encoder.finish().map_err(to_generic)?.into())
504    }
505
506    pub async fn load_state(&self, buf: Bytes) -> StateLoaderResult<bool> {
507        let orig_buf = &buf.0[..];
508        let mut decoder = GzDecoder::new(orig_buf);
509        let mut decoded_data = Vec::new();
510
511        // Support both compressed and non-compressed state format
512        let decoded = if decoder.header().is_some() {
513            tracing::trace!(bytes = buf.0.len(), "decompressing state");
514            decoder.read_to_end(decoded_data.as_mut()).map_err(|e| {
515                StateLoaderError::StateDecompression {
516                    details: e.to_string(),
517                }
518            })?;
519            &decoded_data
520        } else {
521            &buf.0
522        };
523        tracing::trace!(bytes = decoded.len(), "deserializing state");
524        let state: VersionedState = serde_json::from_slice(decoded).map_err(|e| {
525            StateLoaderError::StateDeserialization {
526                details: e.to_string(),
527            }
528        })?;
529
530        self.inner.write().await.load_state(state).await
531    }
532
533    pub async fn get_chain_id(&self) -> AnvilNodeResult<u32> {
534        Ok(self
535            .inner
536            .read()
537            .await
538            .config
539            .chain_id
540            .unwrap_or(TEST_NODE_NETWORK_ID))
541    }
542
543    pub fn get_current_timestamp(&self) -> AnvilNodeResult<u64> {
544        Ok(self.time.current_timestamp())
545    }
546
547    pub async fn set_show_storage_logs(
548        &self,
549        show_storage_logs: ShowStorageLogs,
550    ) -> AnvilNodeResult<String> {
551        self.inner.write().await.config.show_storage_logs = show_storage_logs;
552        Ok(show_storage_logs.to_string())
553    }
554
555    pub async fn set_show_vm_details(
556        &self,
557        show_vm_details: ShowVMDetails,
558    ) -> AnvilNodeResult<String> {
559        self.inner.write().await.config.show_vm_details = show_vm_details;
560        Ok(show_vm_details.to_string())
561    }
562
563    pub async fn set_show_gas_details(
564        &self,
565        show_gas_details: ShowGasDetails,
566    ) -> AnvilNodeResult<String> {
567        self.inner.write().await.config.show_gas_details = show_gas_details;
568        Ok(show_gas_details.to_string())
569    }
570
571    pub async fn set_show_node_config(&self, value: bool) -> AnvilNodeResult<bool> {
572        self.inner.write().await.config.show_node_config = value;
573        Ok(value)
574    }
575
576    pub fn set_log_level(&self, level: LogLevel) -> AnvilNodeResult<bool> {
577        let Some(observability) = &self.observability else {
578            return Err(generic_error!("Node's logging is not set up."));
579        };
580        tracing::debug!("setting log level to '{}'", level);
581        observability.set_log_level(level)?;
582        Ok(true)
583    }
584
585    pub fn set_logging(&self, directive: String) -> AnvilNodeResult<bool> {
586        let Some(observability) = &self.observability else {
587            return Err(generic_error!("Node's logging is not set up."));
588        };
589        tracing::debug!("setting logging to '{}'", directive);
590        observability.set_logging(directive)?;
591        Ok(true)
592    }
593
594    pub async fn chain_id(&self) -> L2ChainId {
595        self.inner.read().await.chain_id()
596    }
597}
598
599pub fn load_last_l1_batch<S: ReadStorage>(storage: StoragePtr<S>) -> Option<(u64, u64)> {
600    // Get block number and timestamp
601    let current_l1_batch_info_key = StorageKey::new(
602        AccountTreeId::new(SYSTEM_CONTEXT_ADDRESS),
603        SYSTEM_CONTEXT_BLOCK_INFO_POSITION,
604    );
605    let mut storage_ptr = storage.borrow_mut();
606    let current_l1_batch_info = storage_ptr.read_value(&current_l1_batch_info_key);
607    let (batch_number, batch_timestamp) = unpack_block_info(h256_to_u256(current_l1_batch_info));
608    let block_number = batch_number as u32;
609    if block_number == 0 {
610        // The block does not exist yet
611        return None;
612    }
613    Some((batch_number, batch_timestamp))
614}
615
616// Test utils
617// TODO: Consider builder pattern with sensible defaults
618// #[cfg(test)]
619// TODO: Mark with #[cfg(test)] once it is not used in other modules
620impl InMemoryNode {
621    pub fn test_config(fork_client_opt: Option<ForkClient>, config: TestNodeConfig) -> Self {
622        let fee_provider = TestNodeFeeInputProvider::from_fork(
623            fork_client_opt.as_ref().map(|client| &client.details),
624            &config.base_token_config,
625        );
626        let impersonation = ImpersonationManager::default();
627        let system_contracts = SystemContracts::from_options(
628            config.system_contracts_options,
629            config.system_contracts_path.clone(),
630            ProtocolVersionId::latest(),
631            config.use_evm_interpreter,
632            config.boojum.clone(),
633        );
634        let storage_key_layout = if config.boojum.use_boojum {
635            StorageKeyLayout::BoojumOs
636        } else {
637            StorageKeyLayout::ZkEra
638        };
639        let (inner, storage, blockchain, time, fork, vm_runner) = InMemoryNodeInner::init(
640            fork_client_opt,
641            fee_provider,
642            Arc::new(RwLock::new(Default::default())),
643            config,
644            impersonation.clone(),
645            system_contracts.clone(),
646            storage_key_layout,
647            false,
648        );
649        let (node_executor, node_handle) =
650            NodeExecutor::new(inner.clone(), vm_runner, storage_key_layout);
651        let pool = TxPool::new(
652            impersonation.clone(),
653            anvil_zksync_types::TransactionOrder::Fifo,
654        );
655        let tx_listener = pool.add_tx_listener();
656        let (block_sealer, block_sealer_state) = BlockSealer::new(
657            BlockSealerMode::immediate(1000, tx_listener),
658            pool.clone(),
659            node_handle.clone(),
660        );
661        tokio::spawn(node_executor.run());
662        tokio::spawn(block_sealer.run());
663        Self::new(
664            inner,
665            blockchain,
666            storage,
667            fork,
668            node_handle,
669            None,
670            time,
671            impersonation,
672            pool,
673            block_sealer_state,
674            system_contracts,
675            storage_key_layout,
676        )
677    }
678
679    pub fn test(fork_client_opt: Option<ForkClient>) -> Self {
680        let config = TestNodeConfig {
681            cache_config: CacheConfig::None,
682            ..Default::default()
683        };
684        Self::test_config(fork_client_opt, config)
685    }
686}
687
688#[cfg(test)]
689impl InMemoryNode {
690    pub async fn apply_txs(
691        &self,
692        txs: impl IntoIterator<Item = Transaction>,
693    ) -> AnvilNodeResult<Vec<TransactionReceipt>> {
694        use backon::{ConstantBuilder, Retryable};
695        use std::time::Duration;
696
697        let txs = Vec::from_iter(txs);
698        let expected_tx_hashes = txs.iter().map(|tx| tx.hash()).collect::<Vec<_>>();
699        self.pool.add_txs(txs);
700
701        let mut receipts = Vec::with_capacity(expected_tx_hashes.len());
702        for tx_hash in expected_tx_hashes {
703            let receipt = (|| async {
704                self.blockchain
705                    .get_tx_receipt(&tx_hash)
706                    .await
707                    .ok_or(generic_error!("missing tx receipt"))
708            })
709            .retry(
710                ConstantBuilder::default()
711                    .with_delay(Duration::from_millis(200))
712                    .with_max_times(5),
713            )
714            .await?;
715            receipts.push(receipt);
716        }
717        Ok(receipts)
718    }
719}