anvil_zksync_core/node/inner/
fork.rs

1use anvil_zksync_common::{
2    cache::{Cache, CacheConfig},
3    sh_err,
4};
5use anvil_zksync_config::constants::{
6    DEFAULT_ESTIMATE_GAS_PRICE_SCALE_FACTOR, DEFAULT_ESTIMATE_GAS_SCALE_FACTOR,
7    DEFAULT_FAIR_PUBDATA_PRICE,
8};
9use anyhow::Context;
10use async_trait::async_trait;
11use futures::TryFutureExt;
12use itertools::Itertools;
13use std::fmt;
14use std::future::Future;
15use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard};
16use tracing::Instrument;
17use url::Url;
18use zksync_types::fee_model::FeeParams;
19use zksync_types::url::SensitiveUrl;
20use zksync_types::web3::Index;
21use zksync_types::{
22    api, Address, L1BatchNumber, L2BlockNumber, L2ChainId, ProtocolVersionId, Transaction, H256,
23    U256,
24};
25use zksync_web3_decl::client::{DynClient, L2};
26use zksync_web3_decl::error::Web3Error;
27use zksync_web3_decl::namespaces::{EthNamespaceClient, ZksNamespaceClient};
28
29/// Trait that provides necessary data when forking a remote chain.
30///
31/// Most methods' signatures are similar to corresponding methods from [`EthNamespaceClient`] and [`ZksNamespaceClient`]
32/// but with domain-specific types where that makes sense. Additionally, return types are wrapped
33/// into [`anyhow::Result`] to avoid leaking RPC-specific implementation details.
34#[async_trait]
35pub trait ForkSource: fmt::Debug + Send + Sync {
36    /// Alternative for [`Clone::clone`] that is object safe.
37    fn dyn_cloned(&self) -> Box<dyn ForkSource>;
38
39    /// Human-readable description on the fork's origin. None if there is no fork.
40    fn url(&self) -> Option<Url>;
41
42    /// Details on the fork's state at the moment when we forked from it. None if there is no fork.
43    fn details(&self) -> Option<ForkDetails>;
44
45    /// Fetches fork's storage value at a given index for given address for the forked blocked.
46    async fn get_storage_at(
47        &self,
48        address: Address,
49        idx: U256,
50        block: Option<api::BlockIdVariant>,
51    ) -> anyhow::Result<H256>;
52
53    /// Fetches fork's storage value at a given index for given address for the forked blocked.
54    async fn get_storage_at_forked(&self, address: Address, idx: U256) -> anyhow::Result<H256>;
55
56    /// Returns the bytecode stored under this hash (if available).
57    async fn get_bytecode_by_hash(&self, hash: H256) -> anyhow::Result<Option<Vec<u8>>>;
58
59    /// Fetches fork's transaction for a given hash.
60    async fn get_transaction_by_hash(&self, hash: H256)
61        -> anyhow::Result<Option<api::Transaction>>;
62
63    /// Fetches fork's transaction details for a given hash.
64    async fn get_transaction_details(
65        &self,
66        hash: H256,
67    ) -> anyhow::Result<Option<api::TransactionDetails>>;
68
69    /// Fetches fork's transactions that belong to a block with the given number.
70    async fn get_raw_block_transactions(
71        &self,
72        block_number: L2BlockNumber,
73    ) -> anyhow::Result<Vec<zksync_types::Transaction>>;
74
75    /// Fetches fork's block for a given hash.
76    async fn get_block_by_hash(
77        &self,
78        hash: H256,
79    ) -> anyhow::Result<Option<api::Block<api::TransactionVariant>>>;
80
81    /// Fetches fork's block for a given number.
82    async fn get_block_by_number(
83        &self,
84        block_number: api::BlockNumber,
85    ) -> anyhow::Result<Option<api::Block<api::TransactionVariant>>>;
86
87    /// Fetches fork's block for a given id.
88    async fn get_block_by_id(
89        &self,
90        block_id: api::BlockId,
91    ) -> anyhow::Result<Option<api::Block<api::TransactionVariant>>> {
92        match block_id {
93            api::BlockId::Hash(hash) => self.get_block_by_hash(hash).await,
94            api::BlockId::Number(number) => self.get_block_by_number(number).await,
95        }
96    }
97
98    /// Fetches fork's block details for a given block number.
99    async fn get_block_details(
100        &self,
101        block_number: L2BlockNumber,
102    ) -> anyhow::Result<Option<api::BlockDetails>>;
103
104    /// Fetches fork's transaction count for a given block hash.
105    async fn get_block_transaction_count_by_hash(
106        &self,
107        block_hash: H256,
108    ) -> anyhow::Result<Option<U256>>;
109
110    /// Fetches fork's transaction count for a given block number.
111    async fn get_block_transaction_count_by_number(
112        &self,
113        block_number: api::BlockNumber,
114    ) -> anyhow::Result<Option<U256>>;
115
116    /// Fetches fork's transaction count for a given block id.
117    async fn get_block_transaction_count_by_id(
118        &self,
119        block_id: api::BlockId,
120    ) -> anyhow::Result<Option<U256>> {
121        match block_id {
122            api::BlockId::Hash(hash) => self.get_block_transaction_count_by_hash(hash).await,
123            api::BlockId::Number(number) => {
124                self.get_block_transaction_count_by_number(number).await
125            }
126        }
127    }
128
129    /// Fetches fork's transaction by block hash and transaction index position.
130    async fn get_transaction_by_block_hash_and_index(
131        &self,
132        block_hash: H256,
133        index: Index,
134    ) -> anyhow::Result<Option<api::Transaction>>;
135
136    /// Fetches fork's transaction by block number and transaction index position.
137    async fn get_transaction_by_block_number_and_index(
138        &self,
139        block_number: api::BlockNumber,
140        index: Index,
141    ) -> anyhow::Result<Option<api::Transaction>>;
142
143    /// Fetches fork's transaction by block id and transaction index position.
144    async fn get_transaction_by_block_id_and_index(
145        &self,
146        block_id: api::BlockId,
147        index: Index,
148    ) -> anyhow::Result<Option<api::Transaction>> {
149        match block_id {
150            api::BlockId::Hash(hash) => {
151                self.get_transaction_by_block_hash_and_index(hash, index)
152                    .await
153            }
154            api::BlockId::Number(number) => {
155                self.get_transaction_by_block_number_and_index(number, index)
156                    .await
157            }
158        }
159    }
160
161    /// Fetches fork's addresses of the default bridge contracts.
162    async fn get_bridge_contracts(&self) -> anyhow::Result<Option<api::BridgeAddresses>>;
163
164    /// Fetches fork's confirmed tokens.
165    async fn get_confirmed_tokens(
166        &self,
167        from: u32,
168        limit: u8,
169    ) -> anyhow::Result<Option<Vec<zksync_web3_decl::types::Token>>>;
170}
171
172impl Clone for Box<dyn ForkSource> {
173    fn clone(&self) -> Self {
174        self.dyn_cloned()
175    }
176}
177
178#[derive(Debug, Clone)]
179pub struct ForkDetails {
180    /// Chain ID of the fork.
181    pub chain_id: L2ChainId,
182    /// Protocol version of the block at which we forked (to be used for all new blocks).
183    pub protocol_version: ProtocolVersionId,
184    /// Batch number at which we forked (the next batch to seal locally is `batch_number + 1`).
185    pub batch_number: L1BatchNumber,
186    /// Block number at which we forked (the next block to seal locally is `block_number + 1`).
187    pub block_number: L2BlockNumber,
188    /// Block hash at which we forked (corresponds to the hash of block #`block_number`).
189    pub block_hash: H256,
190    /// Block timestamp at which we forked (corresponds to the timestamp of block #`block_number`).
191    pub block_timestamp: u64,
192    /// API block at which we forked (corresponds to the hash of block #`block_number`).
193    pub api_block: api::Block<api::TransactionVariant>,
194    pub l1_gas_price: u64,
195    pub l2_fair_gas_price: u64,
196    // Cost of publishing one byte.
197    pub fair_pubdata_price: u64,
198    /// L1 Gas Price Scale Factor for gas estimation.
199    pub estimate_gas_price_scale_factor: f64,
200    /// The factor by which to scale the gasLimit.
201    pub estimate_gas_scale_factor: f32,
202    pub fee_params: FeeParams,
203}
204
205pub struct ForkConfig {
206    pub url: Url,
207    pub estimate_gas_price_scale_factor: f64,
208    pub estimate_gas_scale_factor: f32,
209}
210
211impl ForkConfig {
212    /// Default configuration for an unknown chain.
213    pub fn unknown(url: Url) -> Self {
214        // TODO: Unfortunately there is no endpoint that exposes this information and there is no
215        //       easy way to derive these values either. Recent releases of zksync-era report unscaled
216        //       open batch's fee input and we should mimic something similar.
217        let (estimate_gas_price_scale_factor, estimate_gas_scale_factor) = (
218            DEFAULT_ESTIMATE_GAS_PRICE_SCALE_FACTOR,
219            DEFAULT_ESTIMATE_GAS_SCALE_FACTOR,
220        );
221        Self {
222            url,
223            estimate_gas_price_scale_factor,
224            estimate_gas_scale_factor,
225        }
226    }
227}
228
229/// Simple wrapper over `eth`/`zks`-capable client that propagates all [`ForkSource`] RPC requests to it.
230#[derive(Debug, Clone)]
231pub struct ForkClient {
232    pub url: Url,
233    pub details: ForkDetails,
234    l2_client: Box<DynClient<L2>>,
235}
236
237impl ForkClient {
238    async fn new(
239        config: ForkConfig,
240        l2_client: Box<DynClient<L2>>,
241        block_number: L2BlockNumber,
242    ) -> anyhow::Result<Self> {
243        let ForkConfig {
244            url,
245            estimate_gas_price_scale_factor,
246            estimate_gas_scale_factor,
247        } = config;
248        let chain_id = l2_client
249            .chain_id()
250            .await
251            .with_context(|| format!("failed to get chain id from fork={url}"))?;
252        let chain_id = L2ChainId::try_from(chain_id.as_u64())
253            .map_err(|e| anyhow::anyhow!("fork has malformed chain id: {e}"))?;
254        let block_details = l2_client
255            .get_block_details(block_number)
256            .await?
257            .ok_or_else(
258                || anyhow::anyhow!("could not find block #{block_number} at fork={url}",),
259            )?;
260        let root_hash = block_details
261            .base
262            .root_hash
263            .ok_or_else(|| anyhow::anyhow!("fork block #{block_number} missing root hash"))?;
264        let mut block = l2_client
265            .get_block_by_hash(root_hash, true)
266            .await?
267            .ok_or_else(|| {
268                anyhow::anyhow!(
269                    "could not find API block #{block_number} at fork={url} despite finding its details \
270                    through `zks_getBlockDetails` previously; this is likely a bug, please report this",
271                )
272            })?;
273        let batch_number = block_details.l1_batch_number;
274        // TODO: This is a bit weird, we should just grab last block from the latest sealed L1 batch
275        //       instead to ensure `l1BatchNumber` is always present.
276        block.l1_batch_number = Some(batch_number.0.into());
277
278        let Some(protocol_version) = block_details.protocol_version else {
279            // It is possible that some external nodes do not store protocol versions for versions below 9.
280            // That's why we assume that whenever a protocol version is not present, it is unsupported by anvil-zksync.
281            anyhow::bail!(
282                "Block #{block_number} from fork={url} does not have protocol version set. \
283                Likely you are using an external node with a block for protocol version below 9 which are unsupported in anvil-zksync. \
284                Please report this as a bug if that's not the case."
285            )
286        };
287        if !SupportedProtocolVersions::is_supported(protocol_version) {
288            anyhow::bail!(
289                    "Block #{block_number} from fork={url} is using unsupported protocol version `{protocol_version}`. \
290                    anvil-zksync only supports the following versions: {SupportedProtocolVersions}."
291                )
292        }
293
294        let fee_params = l2_client.get_fee_params().await?;
295        let details = ForkDetails {
296            chain_id,
297            protocol_version,
298            batch_number,
299            block_number,
300            block_hash: root_hash,
301            block_timestamp: block_details.base.timestamp,
302            api_block: block,
303            l1_gas_price: block_details.base.l1_gas_price,
304            l2_fair_gas_price: block_details.base.l2_fair_gas_price,
305            fair_pubdata_price: block_details
306                .base
307                .fair_pubdata_price
308                .unwrap_or(DEFAULT_FAIR_PUBDATA_PRICE),
309            estimate_gas_price_scale_factor,
310            estimate_gas_scale_factor,
311            fee_params,
312        };
313        let fork = ForkClient {
314            url,
315            details,
316            l2_client,
317        };
318        Ok(fork)
319    }
320
321    /// Initializes a fork based on config at a given block number.
322    pub async fn at_block_number(
323        config: ForkConfig,
324        block_number: Option<L2BlockNumber>,
325    ) -> anyhow::Result<Self> {
326        let l2_client =
327            zksync_web3_decl::client::Client::http(SensitiveUrl::from(config.url.clone()))?.build();
328        let block_number = if let Some(block_number) = block_number {
329            block_number
330        } else {
331            let block_number = l2_client
332                .get_block_number()
333                .await
334                .with_context(|| format!("failed to get block number from fork={}", config.url))?;
335            L2BlockNumber(block_number.as_u32())
336        };
337
338        Self::new(config, Box::new(l2_client), block_number).await
339    }
340
341    /// Initializes a fork based on config at a block BEFORE given transaction.
342    /// This will allow us to apply this transaction locally on top of this fork.
343    pub async fn at_before_tx(
344        config: ForkConfig,
345        tx_hash: H256,
346    ) -> anyhow::Result<(Self, Vec<Transaction>)> {
347        let l2_client =
348            zksync_web3_decl::client::Client::http(SensitiveUrl::from(config.url.clone()))?.build();
349        let tx_details = l2_client
350            .get_transaction_by_hash(tx_hash)
351            .await?
352            .ok_or_else(|| {
353                anyhow::anyhow!(
354                    "could not find tx with hash={tx_hash:?} at fork={}",
355                    config.url
356                )
357            })?;
358        let block_number = tx_details.block_number.ok_or_else(|| {
359            anyhow::anyhow!(
360                "could not initialize fork from tx with hash={tx_hash:?} as it is still pending"
361            )
362        })?;
363        let block_number = L2BlockNumber(block_number.as_u32());
364        if block_number == L2BlockNumber(0) {
365            anyhow::bail!(
366                "could not initialize fork from tx with hash={tx_hash:?} as it belongs to genesis"
367            );
368        }
369        let mut earlier_txs = Vec::new();
370        for tx in l2_client.get_raw_block_transactions(block_number).await? {
371            let hash = tx.hash();
372            earlier_txs.push(tx);
373
374            if hash == tx_hash {
375                break;
376            }
377        }
378
379        // We initialize fork from the parent of the block containing transaction.
380        Ok((
381            Self::new(config, Box::new(l2_client), block_number - 1).await?,
382            earlier_txs,
383        ))
384    }
385}
386
387impl ForkClient {
388    pub async fn get_fee_params(&self) -> anyhow::Result<FeeParams> {
389        self.l2_client
390            .get_fee_params()
391            .await
392            .with_context(|| format!("failed to get fee parameters from fork={}", self.url))
393    }
394}
395
396#[cfg(test)]
397impl ForkClient {
398    pub fn mock(details: ForkDetails, storage: crate::deps::InMemoryStorage) -> Self {
399        use zksync_types::{u256_to_h256, AccountTreeId, StorageKey, H160};
400
401        let storage = Arc::new(RwLock::new(storage));
402        let storage_clone = storage.clone();
403        let l2_client = Box::new(
404            zksync_web3_decl::client::MockClient::builder(L2::default())
405                .method(
406                    "eth_getStorageAt",
407                    move |address: Address, idx: U256, _block: Option<api::BlockIdVariant>| {
408                        let key = StorageKey::new(AccountTreeId::new(address), u256_to_h256(idx));
409                        Ok(storage
410                            .read()
411                            .unwrap()
412                            .state
413                            .get(&key)
414                            .cloned()
415                            .unwrap_or_default())
416                    },
417                )
418                .method("zks_getBytecodeByHash", move |hash: H256| {
419                    Ok(storage_clone
420                        .read()
421                        .unwrap()
422                        .factory_deps
423                        .get(&hash)
424                        .cloned())
425                })
426                .method("zks_getBlockDetails", move |block_number: L2BlockNumber| {
427                    Ok(Some(api::BlockDetails {
428                        number: block_number,
429                        l1_batch_number: L1BatchNumber(123),
430                        base: api::BlockDetailsBase {
431                            timestamp: 0,
432                            l1_tx_count: 0,
433                            l2_tx_count: 0,
434                            root_hash: None,
435                            status: api::BlockStatus::Sealed,
436                            commit_tx_hash: None,
437                            committed_at: None,
438                            commit_chain_id: None,
439                            prove_tx_hash: None,
440                            proven_at: None,
441                            prove_chain_id: None,
442                            execute_tx_hash: None,
443                            executed_at: None,
444                            execute_chain_id: None,
445                            l1_gas_price: 123,
446                            l2_fair_gas_price: 234,
447                            fair_pubdata_price: Some(345),
448                            base_system_contracts_hashes: Default::default(),
449                            commit_tx_finality: None,
450                            prove_tx_finality: None,
451                            execute_tx_finality: None,
452                        },
453                        operator_address: H160::zero(),
454                        protocol_version: None,
455                    }))
456                })
457                .build(),
458        );
459        ForkClient {
460            url: Url::parse("http://test-fork-in-memory-storage.local").unwrap(),
461            details,
462            l2_client,
463        }
464    }
465}
466
467#[derive(Debug, Clone)]
468pub(super) struct Fork {
469    state: Arc<RwLock<ForkState>>,
470}
471
472#[derive(Debug)]
473struct ForkState {
474    client: Option<ForkClient>,
475    cache: Cache,
476}
477
478impl Fork {
479    pub(super) fn new(client: Option<ForkClient>, cache_config: CacheConfig) -> Self {
480        let cache = Cache::new(cache_config);
481        Self {
482            state: Arc::new(RwLock::new(ForkState { client, cache })),
483        }
484    }
485
486    pub(super) fn reset_fork_client(&self, client: Option<ForkClient>) {
487        // TODO: We don't clean cache here so it might interfere with the new fork. Consider
488        //       parametrizing cache by fork URL to avoid this.
489        self.write().client = client;
490    }
491
492    pub(super) fn set_fork_url(&self, new_url: Url) -> Option<Url> {
493        // We are assuming that the new url is pointing to the same logical data source so we do not
494        // invalidate cache
495        let mut writer = self.write();
496        if let Some(client) = writer.client.as_mut() {
497            Some(std::mem::replace(&mut client.url, new_url))
498        } else {
499            None
500        }
501    }
502
503    fn read(&self) -> RwLockReadGuard<ForkState> {
504        self.state.read().expect("Fork lock is poisoned")
505    }
506
507    fn write(&self) -> RwLockWriteGuard<ForkState> {
508        self.state.write().expect("Fork lock is poisoned")
509    }
510
511    async fn make_call<T, F: Future<Output = anyhow::Result<T>>>(
512        &self,
513        method: &str,
514        call_body: impl FnOnce(Box<DynClient<L2>>) -> F,
515    ) -> Option<anyhow::Result<T>> {
516        let (client, span) = if let Some(client) = self.read().client.as_ref() {
517            let span = tracing::info_span!("fork_rpc_call", method, url = %client.url);
518            (client.l2_client.clone(), span)
519        } else {
520            return None;
521        };
522        Some(
523            call_body(client)
524                .map_err(|error| {
525                    sh_err!("call failed: {}", error);
526                    error
527                })
528                .instrument(span)
529                .await,
530        )
531    }
532}
533
534#[async_trait]
535impl ForkSource for Fork {
536    fn dyn_cloned(&self) -> Box<dyn ForkSource> {
537        Box::new(self.clone())
538    }
539
540    fn url(&self) -> Option<Url> {
541        self.read().client.as_ref().map(|client| client.url.clone())
542    }
543
544    fn details(&self) -> Option<ForkDetails> {
545        self.read()
546            .client
547            .as_ref()
548            .map(|client| client.details.clone())
549    }
550
551    async fn get_storage_at(
552        &self,
553        address: Address,
554        idx: U256,
555        block: Option<api::BlockIdVariant>,
556    ) -> anyhow::Result<H256> {
557        // TODO: This is currently cached at the `ForkStorage` level but I am unsure if this is a
558        //       good thing. Intuitively it feels like cache should be centralized in a single place.
559        self.make_call("get_storage_at", |client| async move {
560            client
561                .get_storage_at(address, idx, block)
562                .await
563                .with_context(|| format!("(address={address:?}, idx={idx:?})"))
564        })
565        .await
566        .unwrap_or(Ok(H256::zero()))
567    }
568
569    async fn get_storage_at_forked(&self, address: Address, idx: U256) -> anyhow::Result<H256> {
570        let Some(block_number) = self
571            .read()
572            .client
573            .as_ref()
574            .map(|client| client.details.block_number)
575        else {
576            return Ok(H256::zero());
577        };
578        self.get_storage_at(
579            address,
580            idx,
581            Some(api::BlockIdVariant::BlockNumber(api::BlockNumber::Number(
582                block_number.0.into(),
583            ))),
584        )
585        .await
586    }
587
588    async fn get_bytecode_by_hash(&self, hash: H256) -> anyhow::Result<Option<Vec<u8>>> {
589        // TODO: This is currently cached at the `ForkStorage` level but I am unsure if this is a
590        //       good thing. Intuitively it feels like cache should be centralized in a single place.
591        self.make_call("get_bytecode_by_hash", |client| async move {
592            client
593                .get_bytecode_by_hash(hash)
594                .await
595                .with_context(|| format!("(hash={hash:?})"))
596        })
597        .await
598        .unwrap_or(Ok(None))
599    }
600
601    async fn get_transaction_by_hash(
602        &self,
603        hash: H256,
604    ) -> anyhow::Result<Option<api::Transaction>> {
605        if let Some(tx) = self.read().cache.get_transaction(&hash).cloned() {
606            tracing::debug!(?hash, "using cached transaction");
607            return Ok(Some(tx));
608        }
609
610        let tx = self
611            .make_call("get_transaction_by_hash", |client| async move {
612                client
613                    .get_transaction_by_hash(hash)
614                    .await
615                    .with_context(|| format!("(hash={hash:?})"))
616            })
617            .await
618            .unwrap_or(Ok(None))?;
619
620        if let Some(tx) = tx {
621            self.write().cache.insert_transaction(hash, tx.clone());
622            Ok(Some(tx))
623        } else {
624            Ok(None)
625        }
626    }
627
628    async fn get_transaction_details(
629        &self,
630        hash: H256,
631    ) -> anyhow::Result<Option<api::TransactionDetails>> {
632        // N.B. We don't cache these responses as they will change through the lifecycle of the transaction
633        // and caching could be error-prone. In theory, we could cache responses once the txn status
634        // is `final` or `failed` but currently this does not warrant the additional complexity.
635        self.make_call("get_transaction_details", |client| async move {
636            client
637                .get_transaction_details(hash)
638                .await
639                .with_context(|| format!("(hash={hash:?})"))
640        })
641        .await
642        .unwrap_or(Ok(None))
643    }
644
645    async fn get_raw_block_transactions(
646        &self,
647        block_number: L2BlockNumber,
648    ) -> anyhow::Result<Vec<zksync_types::Transaction>> {
649        if let Some(txs) = self
650            .read()
651            .cache
652            .get_block_raw_transactions(&(block_number.0 as u64))
653            .cloned()
654        {
655            tracing::debug!(%block_number, "using cached block raw transactions");
656            return Ok(txs);
657        }
658
659        let txs = self
660            .make_call("get_raw_block_transactions", |client| async move {
661                client
662                    .get_raw_block_transactions(block_number)
663                    .await
664                    .with_context(|| format!("(block_number={block_number})"))
665            })
666            .await
667            .unwrap_or(Err(Web3Error::NoBlock.into()))?;
668
669        self.write()
670            .cache
671            .insert_block_raw_transactions(block_number.0 as u64, txs.clone());
672        Ok(txs)
673    }
674
675    async fn get_block_by_hash(
676        &self,
677        hash: H256,
678    ) -> anyhow::Result<Option<api::Block<api::TransactionVariant>>> {
679        if let Some(block) = self.read().cache.get_block(&hash, true).cloned() {
680            tracing::debug!(?hash, "using cached block");
681            return Ok(Some(block));
682        }
683
684        let block = self
685            .make_call("get_block_by_hash", |client| async move {
686                client
687                    .get_block_by_hash(hash, true)
688                    .await
689                    .with_context(|| format!("(hash={hash:?}, full_transactions=true)"))
690            })
691            .await
692            .unwrap_or(Ok(None))?;
693
694        if let Some(block) = block {
695            self.write().cache.insert_block(hash, true, block.clone());
696            Ok(Some(block))
697        } else {
698            Ok(None)
699        }
700    }
701
702    async fn get_block_by_number(
703        &self,
704        block_number: api::BlockNumber,
705    ) -> anyhow::Result<Option<api::Block<api::TransactionVariant>>> {
706        match block_number {
707            api::BlockNumber::Number(block_number) => {
708                {
709                    let guard = self.read();
710                    let cache = &guard.cache;
711                    if let Some(block) = cache
712                        .get_block_hash(&block_number.as_u64())
713                        .and_then(|hash| cache.get_block(hash, true))
714                        .cloned()
715                    {
716                        tracing::debug!(%block_number, "using cached block");
717                        return Ok(Some(block));
718                    }
719                }
720
721                let block = self
722                    .make_call("get_block_by_number", |client| async move {
723                        client
724                            .get_block_by_number(api::BlockNumber::Number(block_number), true)
725                            .await
726                            .with_context(|| {
727                                format!("(block_number={block_number}, full_transactions=true)")
728                            })
729                    })
730                    .await
731                    .unwrap_or(Ok(None))?;
732
733                if let Some(block) = block {
734                    self.write()
735                        .cache
736                        .insert_block(block.hash, true, block.clone());
737                    Ok(Some(block))
738                } else {
739                    Ok(None)
740                }
741            }
742            _ => self
743                .make_call("get_block_by_number", |client| async move {
744                    client
745                        .get_block_by_number(block_number, true)
746                        .await
747                        .with_context(|| {
748                            format!("(block_number={block_number}, full_transactions=true)")
749                        })
750                })
751                .await
752                .unwrap_or(Ok(None)),
753        }
754    }
755
756    async fn get_block_details(
757        &self,
758        block_number: L2BlockNumber,
759    ) -> anyhow::Result<Option<api::BlockDetails>> {
760        // N.B. We don't cache these responses as they will change through the lifecycle of the block
761        // and caching could be error-prone. In theory, we could cache responses once the block
762        // is finalized but currently this does not warrant the additional complexity.
763        self.make_call("get_block_details", |client| async move {
764            client
765                .get_block_details(block_number)
766                .await
767                .with_context(|| format!("(block_number={block_number})"))
768        })
769        .await
770        .unwrap_or(Ok(None))
771    }
772
773    async fn get_block_transaction_count_by_hash(
774        &self,
775        block_hash: H256,
776    ) -> anyhow::Result<Option<U256>> {
777        // TODO: Cache?
778        self.make_call("get_block_transaction_count_by_hash", |client| async move {
779            client
780                .get_block_transaction_count_by_hash(block_hash)
781                .await
782                .with_context(|| format!("(block_hash={block_hash:?})"))
783        })
784        .await
785        .unwrap_or(Ok(None))
786    }
787
788    async fn get_block_transaction_count_by_number(
789        &self,
790        block_number: api::BlockNumber,
791    ) -> anyhow::Result<Option<U256>> {
792        // TODO: Cache?
793        self.make_call(
794            "get_block_transaction_count_by_number",
795            |client| async move {
796                client
797                    .get_block_transaction_count_by_number(block_number)
798                    .await
799                    .with_context(|| format!("(block_number={block_number})"))
800            },
801        )
802        .await
803        .unwrap_or(Ok(None))
804    }
805
806    async fn get_transaction_by_block_hash_and_index(
807        &self,
808        block_hash: H256,
809        index: Index,
810    ) -> anyhow::Result<Option<api::Transaction>> {
811        // TODO: Cache?
812        self.make_call(
813            "get_transaction_by_block_hash_and_index",
814            |client| async move {
815                client
816                    .get_transaction_by_block_hash_and_index(block_hash, index)
817                    .await
818                    .with_context(|| format!("(block_hash={block_hash:?}, index={index})"))
819            },
820        )
821        .await
822        .unwrap_or(Ok(None))
823    }
824
825    async fn get_transaction_by_block_number_and_index(
826        &self,
827        block_number: api::BlockNumber,
828        index: Index,
829    ) -> anyhow::Result<Option<api::Transaction>> {
830        // TODO: Cache?
831        self.make_call(
832            "get_transaction_by_block_number_and_index",
833            |client| async move {
834                client
835                    .get_transaction_by_block_number_and_index(block_number, index)
836                    .await
837                    .with_context(|| format!("(block_number={block_number}, index={index})"))
838            },
839        )
840        .await
841        .unwrap_or(Ok(None))
842    }
843
844    async fn get_bridge_contracts(&self) -> anyhow::Result<Option<api::BridgeAddresses>> {
845        if let Some(bridge_contracts) = self.read().cache.get_bridge_addresses().cloned() {
846            tracing::debug!("using cached bridge contracts");
847            return Ok(Some(bridge_contracts));
848        }
849
850        let bridge_contracts = self
851            .make_call("get_bridge_contracts", |client| async move {
852                Ok(Some(client.get_bridge_contracts().await?))
853            })
854            .await
855            .unwrap_or(Ok(None))?;
856
857        if let Some(bridge_contracts) = bridge_contracts {
858            self.write()
859                .cache
860                .set_bridge_addresses(bridge_contracts.clone());
861            Ok(Some(bridge_contracts))
862        } else {
863            Ok(None)
864        }
865    }
866
867    async fn get_confirmed_tokens(
868        &self,
869        from: u32,
870        limit: u8,
871    ) -> anyhow::Result<Option<Vec<zksync_web3_decl::types::Token>>> {
872        if let Some(confirmed_tokens) = self.read().cache.get_confirmed_tokens(from, limit).cloned()
873        {
874            tracing::debug!(from, limit, "using cached confirmed tokens");
875            return Ok(Some(confirmed_tokens));
876        }
877
878        let confirmed_tokens = self
879            .make_call("get_block_details", |client| async move {
880                Ok(Some(
881                    client
882                        .get_confirmed_tokens(from, limit)
883                        .await
884                        .with_context(|| format!("(from={from}, limit={limit})"))?,
885                ))
886            })
887            .await
888            .unwrap_or(Ok(None))?;
889
890        if let Some(confirmed_tokens) = confirmed_tokens {
891            self.write()
892                .cache
893                .set_confirmed_tokens(from, limit, confirmed_tokens.clone());
894            Ok(Some(confirmed_tokens))
895        } else {
896            Ok(None)
897        }
898    }
899}
900
901struct SupportedProtocolVersions;
902
903impl SupportedProtocolVersions {
904    const SUPPORTED_VERSIONS: [ProtocolVersionId; 3] = [
905        ProtocolVersionId::Version26,
906        ProtocolVersionId::Version27,
907        ProtocolVersionId::Version28,
908    ];
909
910    fn is_supported(version: ProtocolVersionId) -> bool {
911        Self::SUPPORTED_VERSIONS.contains(&version)
912    }
913}
914
915impl fmt::Display for SupportedProtocolVersions {
916    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
917        f.write_str(
918            &Self::SUPPORTED_VERSIONS
919                .iter()
920                .map(|v| v.to_string())
921                .join(", "),
922        )
923    }
924}
925
926#[cfg(test)]
927mod test {
928    use super::*;
929    use crate::deps::InMemoryStorage;
930    use maplit::hashmap;
931    use zksync_types::block::{pack_block_info, unpack_block_info};
932    use zksync_types::fee_model::{BaseTokenConversionRatio, FeeModelConfigV2, FeeParamsV2};
933    use zksync_types::{h256_to_u256, u256_to_h256, AccountTreeId, StorageKey};
934
935    impl Default for ForkDetails {
936        fn default() -> Self {
937            let config = FeeModelConfigV2 {
938                minimal_l2_gas_price: 10_000_000_000,
939                compute_overhead_part: 0.0,
940                pubdata_overhead_part: 1.0,
941                batch_overhead_l1_gas: 800_000,
942                max_gas_per_batch: 200_000_000,
943                max_pubdata_per_batch: 500_000,
944            };
945            Self {
946                chain_id: Default::default(),
947                protocol_version: ProtocolVersionId::latest(),
948                batch_number: Default::default(),
949                block_number: Default::default(),
950                block_hash: Default::default(),
951                block_timestamp: 0,
952                api_block: Default::default(),
953                l1_gas_price: 0,
954                l2_fair_gas_price: 0,
955                fair_pubdata_price: 0,
956                estimate_gas_price_scale_factor: 0.0,
957                estimate_gas_scale_factor: 0.0,
958                fee_params: FeeParams::V2(FeeParamsV2::new(
959                    config,
960                    10_000_000_000,
961                    5_000_000_000,
962                    BaseTokenConversionRatio::default(),
963                )),
964            }
965        }
966    }
967
968    #[tokio::test]
969    async fn test_mock_client() {
970        let input_batch = 1;
971        let input_l2_block = 2;
972        let input_timestamp = 3;
973        let input_bytecode = vec![0x4];
974        let batch_key = StorageKey::new(
975            AccountTreeId::new(zksync_types::SYSTEM_CONTEXT_ADDRESS),
976            zksync_types::SYSTEM_CONTEXT_BLOCK_INFO_POSITION,
977        );
978        let l2_block_key = StorageKey::new(
979            AccountTreeId::new(zksync_types::SYSTEM_CONTEXT_ADDRESS),
980            zksync_types::SYSTEM_CONTEXT_CURRENT_L2_BLOCK_INFO_POSITION,
981        );
982
983        let client = ForkClient::mock(
984            ForkDetails::default(),
985            InMemoryStorage {
986                state: hashmap! {
987                    batch_key => u256_to_h256(U256::from(input_batch)),
988                    l2_block_key => u256_to_h256(pack_block_info(
989                        input_l2_block,
990                        input_timestamp,
991                    ))
992                },
993                factory_deps: hashmap! {
994                    H256::repeat_byte(0x1) => input_bytecode.clone(),
995                },
996            },
997        );
998        let fork = Fork::new(Some(client), CacheConfig::None);
999
1000        let actual_batch = fork
1001            .get_storage_at(
1002                zksync_types::SYSTEM_CONTEXT_ADDRESS,
1003                h256_to_u256(zksync_types::SYSTEM_CONTEXT_BLOCK_INFO_POSITION),
1004                None,
1005            )
1006            .await
1007            .map(|value| h256_to_u256(value).as_u64())
1008            .expect("failed getting batch number");
1009        assert_eq!(input_batch, actual_batch);
1010
1011        let (actual_l2_block, actual_timestamp) = fork
1012            .get_storage_at(
1013                zksync_types::SYSTEM_CONTEXT_ADDRESS,
1014                h256_to_u256(zksync_types::SYSTEM_CONTEXT_CURRENT_L2_BLOCK_INFO_POSITION),
1015                None,
1016            )
1017            .await
1018            .map(|value| unpack_block_info(h256_to_u256(value)))
1019            .expect("failed getting l2 block info");
1020        assert_eq!(input_l2_block, actual_l2_block);
1021        assert_eq!(input_timestamp, actual_timestamp);
1022
1023        let zero_missing_value = fork
1024            .get_storage_at(
1025                zksync_types::SYSTEM_CONTEXT_ADDRESS,
1026                h256_to_u256(H256::repeat_byte(0x1e)),
1027                None,
1028            )
1029            .await
1030            .map(|value| h256_to_u256(value).as_u64())
1031            .expect("failed missing value");
1032        assert_eq!(0, zero_missing_value);
1033
1034        let actual_bytecode = fork
1035            .get_bytecode_by_hash(H256::repeat_byte(0x1))
1036            .await
1037            .expect("failed getting bytecode")
1038            .expect("missing bytecode");
1039        assert_eq!(input_bytecode, actual_bytecode);
1040    }
1041}