anvil_zksync_core/node/inner/
blockchain.rs

1use crate::filters::LogFilter;
2use crate::node::inner::fork::ForkDetails;
3use crate::node::time::{ReadTime, Time};
4use crate::node::{create_genesis, create_genesis_from_json, TransactionResult};
5use crate::utils::utc_datetime_from_epoch_ms;
6use anvil_zksync_config::types::Genesis;
7use anvil_zksync_types::api::DetailedTransaction;
8use anyhow::Context;
9use async_trait::async_trait;
10use itertools::Itertools;
11use std::collections::HashMap;
12use std::fmt::Debug;
13use std::sync::Arc;
14use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
15use zksync_contracts::BaseSystemContractsHashes;
16use zksync_multivm::interface::storage::{ReadStorage, StoragePtr};
17use zksync_multivm::interface::{FinishedL1Batch, L2Block, VmEvent};
18use zksync_multivm::vm_latest::utils::l2_blocks::load_last_l2_block;
19use zksync_types::block::{unpack_block_info, L1BatchHeader, L2BlockHasher};
20use zksync_types::l2::L2Tx;
21use zksync_types::writes::StateDiffRecord;
22use zksync_types::{
23    api, api::BlockId, h256_to_u256, web3::Bytes, AccountTreeId, Address, ExecuteTransactionCommon,
24    L1BatchNumber, L2BlockNumber, ProtocolVersionId, StorageKey, H256, SYSTEM_CONTEXT_ADDRESS,
25    SYSTEM_CONTEXT_BLOCK_INFO_POSITION, U256, U64,
26};
27
28/// Read-only view on blockchain state.
29#[async_trait]
30pub trait ReadBlockchain: Send + Sync + Debug {
31    /// Alternative for [`Clone::clone`] that is object safe.
32    fn dyn_cloned(&self) -> Box<dyn ReadBlockchain>;
33
34    /// Current protocol version used by the chain.
35    fn protocol_version(&self) -> ProtocolVersionId;
36
37    /// Returns last sealed batch's number. At least one sealed batch is guaranteed to be present
38    /// in the storage at any given time.
39    async fn current_batch(&self) -> L1BatchNumber;
40
41    /// Returns last sealed block's number. At least one sealed block is guaranteed to be present
42    /// in the storage at any given time.
43    async fn current_block_number(&self) -> L2BlockNumber;
44
45    /// Returns last sealed block's hash. At least one sealed block is guaranteed to be present
46    /// in the storage at any given time.
47    async fn current_block_hash(&self) -> H256;
48
49    /// Retrieve full block by its hash. Returns `None` if no block was found. Note that the block
50    /// might still be a part of the chain but is available in the fork instead.
51    async fn get_block_by_hash(&self, hash: &H256) -> Option<api::Block<api::TransactionVariant>>;
52
53    /// Retrieve full block by its number. Returns `None` if no block was found. Note that the block
54    /// might still be a part of the chain but is available in the fork instead.
55    async fn get_block_by_number(
56        &self,
57        number: L2BlockNumber,
58    ) -> Option<api::Block<api::TransactionVariant>>;
59
60    /// Retrieve full block by id. Returns `None` if no block was found. Note that the block
61    /// might still be a part of the chain but is available in the fork instead.
62    async fn get_block_by_id(
63        &self,
64        block_id: api::BlockId,
65    ) -> Option<api::Block<api::TransactionVariant>>;
66
67    /// Retrieve block hash by its number. Returns `None` if no block was found. Note that the block
68    /// might still be a part of the chain but is available in the fork instead.
69    async fn get_block_hash_by_number(&self, number: L2BlockNumber) -> Option<H256>;
70
71    /// Retrieve block hash by id. Returns `None` if no block was found. Note that the block
72    /// might still be a part of the chain but is available in the fork instead.
73    async fn get_block_hash_by_id(&self, block_id: api::BlockId) -> Option<H256>;
74
75    /// Retrieve block number by its hash. Returns `None` if no block was found. Note that the block
76    /// might still be a part of the chain but is available in the fork instead.
77    async fn get_block_number_by_hash(&self, hash: &H256) -> Option<L2BlockNumber>;
78
79    /// Retrieve block number by id. Returns `None` if no block was found. Note that the block
80    /// might still be a part of the chain but is available in the fork instead.
81    async fn get_block_number_by_id(&self, block_id: api::BlockId) -> Option<L2BlockNumber>;
82
83    /// Retrieve all transactions hashes from a block by its number. Returns `None` if no block was
84    /// found. Note that the block might still be a part of the chain but is available in the fork
85    /// instead.
86    async fn get_block_tx_hashes_by_number(&self, number: L2BlockNumber) -> Option<Vec<H256>>;
87
88    /// Retrieve all transactions hashes from a block by id. Returns `None` if no block was
89    /// found. Note that the block might still be a part of the chain but is available in the fork
90    /// instead.
91    async fn get_block_tx_hashes_by_id(&self, block_id: api::BlockId) -> Option<Vec<H256>>;
92
93    // TODO: Distinguish between block not found and tx not found
94    /// Retrieve a transaction from a block by id and index of the transaction. Returns `None` if
95    /// either no block was found or no transaction exists in the block under that index. Note that
96    /// the block might still be a part of the chain but is available in the fork instead.
97    async fn get_block_tx_by_id(
98        &self,
99        block_id: api::BlockId,
100        index: usize,
101    ) -> Option<api::Transaction>;
102
103    /// Retrieve number of transactions in a block by id. Returns `None` if no block was
104    /// found. Note that the block might still be a part of the chain but is available in the fork
105    /// instead.
106    async fn get_block_tx_count_by_id(&self, block_id: api::BlockId) -> Option<usize>;
107
108    /// Retrieve block details (as defined in `zks_getBlockDetails`) by id. Returns `None` if no
109    /// block was found. Note that the block might still be a part of the chain but is available in
110    /// the fork instead.
111    async fn get_block_details_by_number(
112        &self,
113        number: L2BlockNumber,
114        // TODO: Values below should be fetchable from storage
115        l2_fair_gas_price: u64,
116        fair_pubdata_price: Option<u64>,
117        base_system_contracts_hashes: BaseSystemContractsHashes,
118    ) -> Option<api::BlockDetails>;
119
120    /// Retrieve transaction receipt by transaction's hash. Returns `None` if no transaction was
121    /// found. Note that the transaction might still be a part of the chain but is available in the
122    /// fork instead.
123    async fn get_tx_receipt(&self, tx_hash: &H256) -> Option<api::TransactionReceipt>;
124
125    /// Retrieve transaction debug information by transaction's hash. Returns `None` if no transaction was
126    /// found. Note that the transaction might still be a part of the chain but is available in the
127    /// fork instead.
128    async fn get_tx_debug_info(&self, tx_hash: &H256, only_top: bool) -> Option<api::DebugCall>;
129
130    /// Retrieve transaction in API format by transaction's hash. Returns `None` if no transaction was
131    /// found. Note that the transaction might still be a part of the chain but is available in the
132    /// fork instead.
133    async fn get_tx_api(&self, tx_hash: &H256) -> anyhow::Result<Option<api::Transaction>>;
134
135    /// Retrieve detailed transaction (as defined in `anvil_mine_detailed`) by API transaction.
136    /// Returns `None` if no transaction was found. Note that the transaction might still be a part
137    /// of the chain but is available in the fork instead.
138    async fn get_detailed_tx(&self, tx: api::Transaction) -> Option<DetailedTransaction>;
139
140    /// Retrieve detailed transaction (as defined in `zks_getTransactionDetails`) by transaction's hash.
141    /// Returns `None` if no transaction was found. Note that the transaction might still be a part
142    /// of the chain but is available in the fork instead.
143    async fn get_tx_details(&self, tx_hash: &H256) -> Option<api::TransactionDetails>;
144
145    /// Retrieve ZKsync transaction (as defined in `zks_getRawBlockTransactions`) by transaction's hash.
146    /// Returns `None` if no transaction was found. Note that the transaction might still be a part
147    /// of the chain but is available in the fork instead.
148    async fn get_zksync_tx(&self, tx_hash: &H256) -> Option<zksync_types::Transaction>;
149
150    /// Retrieve all logs matching given filter. Does not return matching logs from pre-fork blocks.
151    async fn get_filter_logs(&self, log_filter: &LogFilter) -> Vec<api::Log>;
152
153    /// Retrieve batch header by its number.
154    async fn get_batch_header(&self, batch_number: L1BatchNumber) -> Option<L1BatchHeader>;
155
156    /// Retrieve batch state diffs by its number.
157    async fn get_batch_state_diffs(
158        &self,
159        batch_number: L1BatchNumber,
160    ) -> Option<Vec<StateDiffRecord>>;
161
162    /// Retrieve batch aggregation root by its number.
163    async fn get_batch_aggregation_root(&self, batch_number: L1BatchNumber) -> Option<H256>;
164
165    /// Retrieves raw transaction by its hash.
166    async fn get_raw_transaction(&self, tx_hash: H256) -> Option<Bytes>;
167
168    /// Retrieves raw transactions from a block by its id or number.
169    async fn get_raw_transactions(&self, block_number: BlockId) -> Vec<Bytes>;
170}
171
172impl Clone for Box<dyn ReadBlockchain> {
173    fn clone(&self) -> Self {
174        self.dyn_cloned()
175    }
176}
177
178#[derive(Debug, Clone)]
179pub(super) struct Blockchain {
180    inner: Arc<RwLock<BlockchainState>>,
181    pub(super) protocol_version: ProtocolVersionId,
182}
183
184impl Blockchain {
185    async fn inspect_block_by_hash<T>(
186        &self,
187        hash: &H256,
188        f: impl FnOnce(&api::Block<api::TransactionVariant>) -> T,
189    ) -> Option<T> {
190        Some(f(self.inner.read().await.blocks.get(hash)?))
191    }
192
193    async fn inspect_block_by_number<T>(
194        &self,
195        number: L2BlockNumber,
196        f: impl FnOnce(&api::Block<api::TransactionVariant>) -> T,
197    ) -> Option<T> {
198        let storage = self.inner.read().await;
199        let hash = storage.get_block_hash_by_number(number)?;
200        Some(f(storage.blocks.get(&hash)?))
201    }
202
203    async fn inspect_block_by_id<T>(
204        &self,
205        block_id: api::BlockId,
206        f: impl FnOnce(&api::Block<api::TransactionVariant>) -> T,
207    ) -> Option<T> {
208        let storage = self.inner.read().await;
209        let hash = storage.get_block_hash_by_id(block_id)?;
210        Some(f(storage.blocks.get(&hash)?))
211    }
212
213    async fn inspect_tx<T>(
214        &self,
215        tx_hash: &H256,
216        f: impl FnOnce(&TransactionResult) -> T,
217    ) -> Option<T> {
218        Some(f(self.inner.read().await.tx_results.get(tx_hash)?))
219    }
220
221    async fn inspect_batch<T>(
222        &self,
223        batch_number: &L1BatchNumber,
224        f: impl FnOnce(&StoredL1BatchInfo) -> T,
225    ) -> Option<T> {
226        Some(f(self.inner.read().await.batches.get(batch_number)?))
227    }
228
229    // FIXME: Do not use for new functionality and delete once its only usage is migrated away.
230    async fn inspect_all_txs<T>(
231        &self,
232        f: impl FnOnce(&HashMap<H256, TransactionResult>) -> T,
233    ) -> T {
234        f(&self.inner.read().await.tx_results)
235    }
236}
237
238#[async_trait]
239impl ReadBlockchain for Blockchain {
240    fn dyn_cloned(&self) -> Box<dyn ReadBlockchain> {
241        Box::new(self.clone())
242    }
243
244    fn protocol_version(&self) -> ProtocolVersionId {
245        self.protocol_version
246    }
247
248    async fn current_batch(&self) -> L1BatchNumber {
249        self.inner.read().await.current_batch
250    }
251
252    async fn current_block_number(&self) -> L2BlockNumber {
253        self.inner.read().await.current_block
254    }
255
256    async fn current_block_hash(&self) -> H256 {
257        self.inner.read().await.current_block_hash
258    }
259
260    async fn get_block_by_hash(&self, hash: &H256) -> Option<api::Block<api::TransactionVariant>> {
261        self.inspect_block_by_hash(hash, |block| block.clone())
262            .await
263    }
264
265    async fn get_block_by_number(
266        &self,
267        number: L2BlockNumber,
268    ) -> Option<api::Block<api::TransactionVariant>> {
269        self.inspect_block_by_number(number, |block| block.clone())
270            .await
271    }
272
273    async fn get_block_by_id(
274        &self,
275        block_id: api::BlockId,
276    ) -> Option<api::Block<api::TransactionVariant>> {
277        self.inspect_block_by_id(block_id, |block| block.clone())
278            .await
279    }
280
281    async fn get_block_hash_by_number(&self, number: L2BlockNumber) -> Option<H256> {
282        self.inspect_block_by_number(number, |block| block.hash)
283            .await
284    }
285
286    async fn get_block_hash_by_id(&self, block_id: api::BlockId) -> Option<H256> {
287        self.inspect_block_by_id(block_id, |block| block.hash).await
288    }
289
290    async fn get_block_number_by_hash(&self, hash: &H256) -> Option<L2BlockNumber> {
291        self.inspect_block_by_hash(hash, |block| L2BlockNumber(block.number.as_u32()))
292            .await
293    }
294
295    async fn get_block_number_by_id(&self, block_id: api::BlockId) -> Option<L2BlockNumber> {
296        self.inspect_block_by_id(block_id, |block| L2BlockNumber(block.number.as_u32()))
297            .await
298    }
299
300    async fn get_block_tx_hashes_by_number(&self, number: L2BlockNumber) -> Option<Vec<H256>> {
301        self.get_block_tx_hashes_by_id(api::BlockId::Number(api::BlockNumber::Number(
302            number.0.into(),
303        )))
304        .await
305    }
306
307    async fn get_block_tx_hashes_by_id(&self, block_id: api::BlockId) -> Option<Vec<H256>> {
308        self.inspect_block_by_id(block_id, |block| {
309            block
310                .transactions
311                .iter()
312                .map(|tx| match tx {
313                    api::TransactionVariant::Full(tx) => tx.hash,
314                    api::TransactionVariant::Hash(hash) => *hash,
315                })
316                .collect_vec()
317        })
318        .await
319    }
320
321    async fn get_block_tx_by_id(
322        &self,
323        block_id: api::BlockId,
324        index: usize,
325    ) -> Option<api::Transaction> {
326        self.inspect_block_by_id(block_id, |block| {
327            block.transactions.get(index).map(|tv| match tv {
328                api::TransactionVariant::Full(tx) => tx.clone(),
329                api::TransactionVariant::Hash(_) => {
330                    unreachable!("we only store full txs in blocks")
331                }
332            })
333        })
334        .await
335        .flatten()
336    }
337
338    async fn get_block_tx_count_by_id(&self, block_id: api::BlockId) -> Option<usize> {
339        self.inspect_block_by_id(block_id, |block| block.transactions.len())
340            .await
341    }
342
343    async fn get_block_details_by_number(
344        &self,
345        number: L2BlockNumber,
346        l2_fair_gas_price: u64,
347        fair_pubdata_price: Option<u64>,
348        base_system_contracts_hashes: BaseSystemContractsHashes,
349    ) -> Option<api::BlockDetails> {
350        self.inspect_block_by_number(number, |block| api::BlockDetails {
351            number: L2BlockNumber(block.number.as_u32()),
352            l1_batch_number: L1BatchNumber(block.l1_batch_number.unwrap_or_default().as_u32()),
353            base: api::BlockDetailsBase {
354                timestamp: block.timestamp.as_u64(),
355                l1_tx_count: 1,
356                l2_tx_count: block.transactions.len(),
357                root_hash: Some(block.hash),
358                status: api::BlockStatus::Verified,
359                commit_tx_hash: None,
360                commit_chain_id: None,
361                committed_at: None,
362                prove_tx_hash: None,
363                prove_chain_id: None,
364                proven_at: None,
365                execute_tx_hash: None,
366                execute_chain_id: None,
367                executed_at: None,
368                l1_gas_price: 0,
369                l2_fair_gas_price,
370                fair_pubdata_price,
371                base_system_contracts_hashes,
372                commit_tx_finality: None,
373                prove_tx_finality: None,
374                execute_tx_finality: None,
375            },
376            operator_address: Address::zero(),
377            protocol_version: Some(self.protocol_version),
378        })
379        .await
380    }
381
382    async fn get_tx_receipt(&self, tx_hash: &H256) -> Option<api::TransactionReceipt> {
383        self.inspect_tx(tx_hash, |tx| tx.receipt.clone()).await
384    }
385
386    async fn get_tx_debug_info(&self, tx_hash: &H256, only_top: bool) -> Option<api::DebugCall> {
387        self.inspect_tx(tx_hash, |tx| tx.debug_info(only_top)).await
388    }
389
390    async fn get_tx_api(&self, tx_hash: &H256) -> anyhow::Result<Option<api::Transaction>> {
391        self.inspect_tx(tx_hash, |TransactionResult { info, receipt, .. }| {
392            let l2_tx: L2Tx =
393                info.tx.clone().try_into().map_err(|_| {
394                    anyhow::anyhow!("inspection of non-L2 transactions is unsupported")
395                })?;
396            let chain_id = l2_tx
397                .common_data
398                .extract_chain_id()
399                .context("tx has malformed chain id")?;
400            let input_data = l2_tx
401                .common_data
402                .input
403                .context("tx is missing input data")?;
404            anyhow::Ok(api::Transaction {
405                hash: *tx_hash,
406                nonce: U256::from(l2_tx.common_data.nonce.0),
407                // FIXME: This is mega-incorrect but this whole method should be reworked in general
408                block_hash: Some(*tx_hash),
409                block_number: Some(U64::from(info.miniblock_number)),
410                transaction_index: Some(receipt.transaction_index),
411                from: Some(info.tx.initiator_account()),
412                to: info.tx.recipient_account(),
413                value: info.tx.execute.value,
414                gas_price: Some(U256::from(0)),
415                gas: Default::default(),
416                input: input_data.data.into(),
417                v: Some(chain_id.into()),
418                r: Some(U256::zero()), // TODO: Shouldn't we set the signature?
419                s: Some(U256::zero()), // TODO: Shouldn't we set the signature?
420                y_parity: Some(U64::zero()), // TODO: Shouldn't we set the signature?
421                raw: None,
422                transaction_type: {
423                    let tx_type = match l2_tx.common_data.transaction_type {
424                        zksync_types::l2::TransactionType::LegacyTransaction => 0,
425                        zksync_types::l2::TransactionType::EIP2930Transaction => 1,
426                        zksync_types::l2::TransactionType::EIP1559Transaction => 2,
427                        zksync_types::l2::TransactionType::EIP712Transaction => 113,
428                        zksync_types::l2::TransactionType::PriorityOpTransaction => 255,
429                        zksync_types::l2::TransactionType::ProtocolUpgradeTransaction => 254,
430                    };
431                    Some(tx_type.into())
432                },
433                access_list: None,
434                max_fee_per_gas: Some(l2_tx.common_data.fee.max_fee_per_gas),
435                max_priority_fee_per_gas: Some(l2_tx.common_data.fee.max_priority_fee_per_gas),
436                chain_id: U256::from(chain_id),
437                l1_batch_number: Some(U64::from(info.batch_number as u64)),
438                l1_batch_tx_index: None,
439            })
440        })
441        .await
442        .transpose()
443    }
444
445    async fn get_detailed_tx(&self, tx: api::Transaction) -> Option<DetailedTransaction> {
446        self.inspect_tx(
447            &tx.hash.clone(),
448            |TransactionResult { ref debug, .. }| {
449                let output = Some(debug.output.clone());
450                let revert_reason = debug.revert_reason.clone();
451                DetailedTransaction {
452                    inner: tx,
453                    output,
454                    revert_reason,
455                }
456            },
457        )
458        .await
459    }
460
461    async fn get_tx_details(&self, tx_hash: &H256) -> Option<api::TransactionDetails> {
462        self.inspect_tx(tx_hash, |TransactionResult { info, receipt, .. }| {
463            api::TransactionDetails {
464                is_l1_originated: false,
465                status: api::TransactionStatus::Included,
466                // if these are not set, fee is effectively 0
467                fee: receipt.effective_gas_price.unwrap_or_default()
468                    * receipt.gas_used.unwrap_or_default(),
469                gas_per_pubdata: info.tx.gas_per_pubdata_byte_limit(),
470                initiator_address: info.tx.initiator_account(),
471                received_at: utc_datetime_from_epoch_ms(info.tx.received_timestamp_ms),
472                eth_commit_tx_hash: None,
473                eth_prove_tx_hash: None,
474                eth_execute_tx_hash: None,
475            }
476        })
477        .await
478    }
479
480    async fn get_zksync_tx(&self, tx_hash: &H256) -> Option<zksync_types::Transaction> {
481        self.inspect_tx(tx_hash, |TransactionResult { info, .. }| info.tx.clone())
482            .await
483    }
484
485    async fn get_filter_logs(&self, log_filter: &LogFilter) -> Vec<api::Log> {
486        let latest_block_number = self.current_block_number().await;
487        // FIXME: This should traverse blocks from `log_filter.from_block` to `log_filter.to_block`
488        //        instead. This way we can drastically reduce search scope and avoid holding the
489        //        lock for prolonged amounts of time.
490        self.inspect_all_txs(|tx_results| {
491            tx_results
492                .values()
493                .flat_map(|tx_result| {
494                    tx_result
495                        .receipt
496                        .logs
497                        .iter()
498                        .filter(|log| log_filter.matches(log, U64::from(latest_block_number.0)))
499                        .cloned()
500                })
501                .collect_vec()
502        })
503        .await
504    }
505
506    async fn get_batch_header(&self, batch_number: L1BatchNumber) -> Option<L1BatchHeader> {
507        self.inspect_batch(&batch_number, |StoredL1BatchInfo { header, .. }| {
508            header.clone()
509        })
510        .await
511    }
512
513    async fn get_batch_state_diffs(
514        &self,
515        batch_number: L1BatchNumber,
516    ) -> Option<Vec<StateDiffRecord>> {
517        self.inspect_batch(
518            &batch_number,
519            |StoredL1BatchInfo { state_diffs, .. }| state_diffs.clone(),
520        )
521        .await
522    }
523
524    async fn get_batch_aggregation_root(&self, batch_number: L1BatchNumber) -> Option<H256> {
525        self.inspect_batch(
526            &batch_number,
527            |StoredL1BatchInfo {
528                 aggregation_root, ..
529             }| *aggregation_root,
530        )
531        .await
532    }
533
534    async fn get_raw_transaction(&self, tx_hash: H256) -> Option<Bytes> {
535        self.inspect_tx(&tx_hash, |TransactionResult { info, .. }| {
536            info.tx.raw_bytes.clone()
537        })
538        .await
539        .flatten()
540    }
541
542    async fn get_raw_transactions(&self, block_id: BlockId) -> Vec<Bytes> {
543        self.inspect_block_by_id(block_id, |block| {
544            block
545                .transactions
546                .iter()
547                .filter_map(|tv| match tv {
548                    api::TransactionVariant::Full(tx) => tx.raw.clone(),
549                    api::TransactionVariant::Hash(_) => None,
550                })
551                .collect::<Vec<Bytes>>()
552        })
553        .await
554        .unwrap_or_default()
555    }
556}
557
558impl Blockchain {
559    pub(super) fn new(
560        protocol_version: ProtocolVersionId,
561        fork_details: Option<&ForkDetails>,
562        genesis: Option<&Genesis>,
563        genesis_timestamp: Option<u64>,
564    ) -> Blockchain {
565        let state = if let Some(fork_details) = fork_details {
566            BlockchainState {
567                protocol_version: fork_details.protocol_version,
568                current_batch: fork_details.batch_number,
569                current_block: fork_details.block_number,
570                current_block_hash: fork_details.block_hash,
571                tx_results: Default::default(),
572                blocks: HashMap::from_iter([(
573                    fork_details.block_hash,
574                    fork_details.api_block.clone(),
575                )]),
576                hashes: HashMap::from_iter([(fork_details.block_number, fork_details.block_hash)]),
577                // As we do not support L1-L2 communication when running in forking mode, batches are
578                // irrelevant.
579                batches: HashMap::from_iter([]),
580            }
581        } else {
582            let (genesis_block, genesis_batch_header) = if let Some(genesis) = genesis {
583                create_genesis_from_json(protocol_version, genesis, genesis_timestamp)
584            } else {
585                create_genesis(protocol_version, genesis_timestamp)
586            };
587            let block_hash = genesis_block.hash;
588            let genesis_batch_info = StoredL1BatchInfo {
589                header: genesis_batch_header,
590                state_diffs: Vec::new(),
591                aggregation_root: H256::zero(),
592            };
593
594            BlockchainState {
595                protocol_version,
596                current_batch: L1BatchNumber(0),
597                current_block: L2BlockNumber(0),
598                current_block_hash: block_hash,
599                tx_results: Default::default(),
600                blocks: HashMap::from_iter([(block_hash, genesis_block)]),
601                hashes: HashMap::from_iter([(L2BlockNumber(0), block_hash)]),
602                batches: HashMap::from_iter([(L1BatchNumber(0), genesis_batch_info)]),
603            }
604        };
605        let protocol_version = state.protocol_version;
606        let inner = Arc::new(RwLock::new(state));
607        Self {
608            inner,
609            protocol_version,
610        }
611    }
612}
613
614impl Blockchain {
615    pub(super) async fn read(&self) -> RwLockReadGuard<BlockchainState> {
616        self.inner.read().await
617    }
618
619    pub(super) async fn write(&self) -> RwLockWriteGuard<BlockchainState> {
620        self.inner.write().await
621    }
622}
623
624/// Stores the blockchain data (blocks, transactions)
625#[derive(Debug, Clone)]
626pub(super) struct BlockchainState {
627    /// Protocol version for all produced blocks.
628    pub(super) protocol_version: ProtocolVersionId,
629    /// The latest batch number that was already generated.
630    /// Next block will go to the batch `current_batch + 1`.
631    pub(super) current_batch: L1BatchNumber,
632    /// The latest block number that was already generated.
633    /// Next transaction will go to the block `current_block + 1`.
634    pub(super) current_block: L2BlockNumber,
635    /// The latest block hash.
636    pub(super) current_block_hash: H256,
637    /// Map from transaction to details about the execution.
638    pub(super) tx_results: HashMap<H256, TransactionResult>,
639    /// Map from block hash to information about the block.
640    pub(super) blocks: HashMap<H256, api::Block<api::TransactionVariant>>,
641    /// Map from block number to a block hash.
642    pub(super) hashes: HashMap<L2BlockNumber, H256>,
643    /// Map from batch number to batch info. Hash is not used as the key because it is not
644    /// necessarily computed by the time this entry is inserted (i.e. it is not an inherent property
645    /// of a batch).
646    batches: HashMap<L1BatchNumber, StoredL1BatchInfo>,
647}
648
649/// Represents stored information about a particular batch.
650#[derive(Debug, Clone)]
651struct StoredL1BatchInfo {
652    header: L1BatchHeader,
653    state_diffs: Vec<StateDiffRecord>,
654    aggregation_root: H256,
655}
656
657impl BlockchainState {
658    pub(super) fn get_block_hash_by_number(&self, number: L2BlockNumber) -> Option<H256> {
659        self.hashes.get(&number).copied()
660    }
661
662    pub(super) fn get_block_hash_by_id(&self, block_id: api::BlockId) -> Option<H256> {
663        match block_id {
664            api::BlockId::Number(number) => {
665                let number = match number {
666                    api::BlockNumber::FastFinalized
667                    | api::BlockNumber::Finalized
668                    | api::BlockNumber::Pending
669                    | api::BlockNumber::Committed
670                    | api::BlockNumber::L1Committed
671                    | api::BlockNumber::Latest => self.current_block,
672                    api::BlockNumber::Earliest => L2BlockNumber(0),
673                    api::BlockNumber::Number(n) => L2BlockNumber(n.as_u32()),
674                };
675                self.hashes.get(&number).copied()
676            }
677            api::BlockId::Hash(hash) => Some(hash),
678        }
679    }
680
681    pub(super) fn last_env<S: ReadStorage>(
682        &self,
683        storage: &StoragePtr<S>,
684        time_writer: &Time,
685    ) -> (L1BatchNumber, L2Block) {
686        // TODO: This whole logic seems off to me, reconsider if we need it at all.
687        //       Specifically it is weird that we might not have our latest block in the storage.
688        //       Likely has to do with genesis but let's make it clear if that is actually the case.
689        let last_l1_batch_number = load_last_l1_batch(storage)
690            .map(|(num, _)| L1BatchNumber(num as u32))
691            .unwrap_or(self.current_batch);
692        let last_l2_block = load_last_l2_block(storage).unwrap_or_else(|| L2Block {
693            number: self.current_block.0,
694            hash: L2BlockHasher::legacy_hash(self.current_block),
695            timestamp: time_writer.current_timestamp(),
696        });
697        (last_l1_batch_number, last_l2_block)
698    }
699
700    pub(super) fn apply_block(&mut self, block: api::Block<api::TransactionVariant>, index: u32) {
701        let latest_block = self.blocks.get(&self.current_block_hash).unwrap();
702        self.current_block += 1;
703
704        let actual_l1_batch_number = block
705            .l1_batch_number
706            .expect("block must have a l1_batch_number");
707        if L1BatchNumber(actual_l1_batch_number.as_u32()) != self.current_batch {
708            panic!(
709                "expected next block to have batch_number {}, got {}",
710                self.current_batch,
711                actual_l1_batch_number.as_u32()
712            );
713        }
714
715        if L2BlockNumber(block.number.as_u32()) != self.current_block {
716            panic!(
717                "expected next block to have miniblock {}, got {} | {index}",
718                self.current_block,
719                block.number.as_u64()
720            );
721        }
722
723        if block.timestamp.as_u64() <= latest_block.timestamp.as_u64() {
724            panic!(
725                "expected next block to have timestamp bigger than {}, got {} | {index}",
726                latest_block.timestamp.as_u64(),
727                block.timestamp.as_u64()
728            );
729        }
730
731        let block_hash = block.hash;
732        self.current_block_hash = block_hash;
733        self.hashes
734            .insert(L2BlockNumber(block.number.as_u32()), block.hash);
735        self.blocks.insert(block.hash, block);
736    }
737
738    pub(super) fn apply_batch(
739        &mut self,
740        batch_timestamp: u64,
741        base_system_contracts_hashes: BaseSystemContractsHashes,
742        tx_results: Vec<TransactionResult>,
743        finished_l1_batch: FinishedL1Batch,
744        aggregation_root: H256,
745    ) {
746        self.current_batch += 1;
747
748        let l2_to_l1_messages = VmEvent::extract_long_l2_to_l1_messages(
749            &finished_l1_batch.final_execution_state.events,
750        );
751        let l1_tx_count = tx_results
752            .iter()
753            .filter(|tx| matches!(tx.info.tx.common_data, ExecuteTransactionCommon::L1(_)))
754            .count() as u16;
755        let priority_ops_onchain_data = tx_results
756            .iter()
757            .filter_map(|tx| match &tx.info.tx.common_data {
758                ExecuteTransactionCommon::L1(l1_tx) => {
759                    Some(l1_tx.onchain_metadata().onchain_data.clone())
760                }
761                ExecuteTransactionCommon::L2(_) => None,
762                ExecuteTransactionCommon::ProtocolUpgrade(_) => None,
763            })
764            .collect();
765        let header = L1BatchHeader {
766            number: self.current_batch,
767            timestamp: batch_timestamp,
768            l1_tx_count,
769            l2_tx_count: tx_results.len() as u16 - l1_tx_count,
770            priority_ops_onchain_data,
771            l2_to_l1_logs: finished_l1_batch.final_execution_state.user_l2_to_l1_logs,
772            l2_to_l1_messages,
773            bloom: Default::default(), // This is unused in core
774            used_contract_hashes: finished_l1_batch.final_execution_state.used_contract_hashes,
775            base_system_contracts_hashes,
776            system_logs: finished_l1_batch.final_execution_state.system_logs,
777            protocol_version: Some(self.protocol_version),
778            pubdata_input: finished_l1_batch.pubdata_input,
779            fee_address: Default::default(), // TODO: Use real fee address
780            batch_fee_input: Default::default(), // TODO: Use real batch fee input
781        };
782        let batch_info = StoredL1BatchInfo {
783            header,
784            state_diffs: finished_l1_batch.state_diffs.unwrap_or_default(),
785            aggregation_root,
786        };
787        self.batches.insert(self.current_batch, batch_info);
788        self.tx_results.extend(
789            tx_results
790                .into_iter()
791                .map(|r| (r.receipt.transaction_hash, r)),
792        );
793    }
794
795    pub(super) fn load_blocks(
796        &mut self,
797        time: &mut Time,
798        blocks: Vec<api::Block<api::TransactionVariant>>,
799    ) {
800        tracing::trace!(
801            blocks = blocks.len(),
802            "loading new blocks from supplied state"
803        );
804        for block in blocks {
805            let number = block.number.as_u64();
806            tracing::trace!(
807                number,
808                hash = %block.hash,
809                "loading new block from supplied state"
810            );
811
812            self.hashes.insert(L2BlockNumber(number as u32), block.hash);
813            self.blocks.insert(block.hash, block);
814        }
815
816        // Safe unwrap as there was at least one block in the loaded state
817        let latest_block = self.blocks.values().max_by_key(|b| b.number).unwrap();
818        let latest_number = latest_block.number.as_u64();
819        let latest_hash = latest_block.hash;
820        let Some(latest_batch_number) = latest_block.l1_batch_number.map(|n| n.as_u32()) else {
821            panic!("encountered a block with no batch; this is not supposed to happen")
822        };
823        let latest_timestamp = latest_block.timestamp.as_u64();
824        tracing::info!(
825            number = latest_number,
826            hash = %latest_hash,
827            batch_number = latest_batch_number,
828            timestamp = latest_timestamp,
829            "latest block after loading state"
830        );
831        self.current_block = L2BlockNumber(latest_number as u32);
832        self.current_block_hash = latest_hash;
833        self.current_batch = L1BatchNumber(latest_batch_number);
834        time.reset_to(latest_timestamp);
835    }
836
837    pub(super) fn load_transactions(&mut self, transactions: Vec<TransactionResult>) {
838        tracing::trace!(
839            transactions = transactions.len(),
840            "loading new transactions from supplied state"
841        );
842        for transaction in transactions {
843            tracing::trace!(
844                hash = %transaction.receipt.transaction_hash,
845                "loading new transaction from supplied state"
846            );
847            self.tx_results
848                .insert(transaction.receipt.transaction_hash, transaction);
849        }
850    }
851}
852
853fn load_last_l1_batch<S: ReadStorage>(storage: &StoragePtr<S>) -> Option<(u64, u64)> {
854    // Get block number and timestamp
855    let current_l1_batch_info_key = StorageKey::new(
856        AccountTreeId::new(SYSTEM_CONTEXT_ADDRESS),
857        SYSTEM_CONTEXT_BLOCK_INFO_POSITION,
858    );
859    let mut storage_ptr = storage.borrow_mut();
860    let current_l1_batch_info = storage_ptr.read_value(&current_l1_batch_info_key);
861    let (batch_number, batch_timestamp) = unpack_block_info(h256_to_u256(current_l1_batch_info));
862    let block_number = batch_number as u32;
863    if block_number == 0 {
864        // The block does not exist yet
865        return None;
866    }
867    Some((batch_number, batch_timestamp))
868}