zksync_node_sync/
client.rs

1//! Client abstractions for syncing between the external node and the main node.
2
3use std::fmt;
4
5use async_trait::async_trait;
6use zksync_config::GenesisConfig;
7use zksync_health_check::{CheckHealth, Health, HealthStatus};
8use zksync_system_constants::ACCOUNT_CODE_STORAGE_ADDRESS;
9use zksync_types::{
10    api::{self, en},
11    bytecode::BytecodeHash,
12    get_code_key, h256_to_u256, Address, L2BlockNumber, ProtocolVersionId, H256, U64,
13};
14use zksync_web3_decl::{
15    client::{DynClient, L2},
16    error::{ClientRpcContext, EnrichedClientError, EnrichedClientResult},
17    namespaces::{EnNamespaceClient, EthNamespaceClient, ZksNamespaceClient},
18};
19
20/// Client abstracting connection to the main node.
21#[async_trait]
22pub trait MainNodeClient: 'static + Send + Sync + fmt::Debug {
23    async fn fetch_system_contract_by_hash(
24        &self,
25        hash: H256,
26    ) -> EnrichedClientResult<Option<Vec<u8>>>;
27
28    async fn fetch_genesis_contract_bytecode(
29        &self,
30        address: Address,
31    ) -> EnrichedClientResult<Option<Vec<u8>>>;
32
33    async fn fetch_protocol_version(
34        &self,
35        protocol_version: ProtocolVersionId,
36    ) -> EnrichedClientResult<Option<api::ProtocolVersion>>;
37
38    async fn fetch_l2_block_number(&self) -> EnrichedClientResult<L2BlockNumber>;
39
40    async fn fetch_l2_block(
41        &self,
42        number: L2BlockNumber,
43        with_transactions: bool,
44    ) -> EnrichedClientResult<Option<en::SyncBlock>>;
45
46    async fn fetch_genesis_config(&self) -> EnrichedClientResult<GenesisConfig>;
47}
48
49#[async_trait]
50impl MainNodeClient for Box<DynClient<L2>> {
51    async fn fetch_system_contract_by_hash(
52        &self,
53        hash: H256,
54    ) -> EnrichedClientResult<Option<Vec<u8>>> {
55        let bytecode = self
56            .get_bytecode_by_hash(hash)
57            .rpc_context("get_bytecode_by_hash")
58            .with_arg("hash", &hash)
59            .await?;
60        if let Some(bytecode) = &bytecode {
61            let actual_bytecode_hash = BytecodeHash::for_bytecode(bytecode).value();
62            if actual_bytecode_hash != hash {
63                return Err(EnrichedClientError::custom(
64                    "Got invalid base system contract bytecode from main node",
65                    "get_bytecode_by_hash",
66                )
67                .with_arg("hash", &hash)
68                .with_arg("actual_bytecode_hash", &actual_bytecode_hash));
69            }
70        }
71        Ok(bytecode)
72    }
73
74    async fn fetch_genesis_contract_bytecode(
75        &self,
76        address: Address,
77    ) -> EnrichedClientResult<Option<Vec<u8>>> {
78        const GENESIS_BLOCK: api::BlockIdVariant =
79            api::BlockIdVariant::BlockNumber(api::BlockNumber::Number(U64([0])));
80
81        let code_key = get_code_key(&address);
82        let code_hash = self
83            .get_storage_at(
84                ACCOUNT_CODE_STORAGE_ADDRESS,
85                h256_to_u256(*code_key.key()),
86                Some(GENESIS_BLOCK),
87            )
88            .rpc_context("get_storage_at")
89            .with_arg("address", &address)
90            .await?;
91        self.get_bytecode_by_hash(code_hash)
92            .rpc_context("get_bytecode_by_hash")
93            .with_arg("code_hash", &code_hash)
94            .await
95    }
96
97    async fn fetch_protocol_version(
98        &self,
99        protocol_version: ProtocolVersionId,
100    ) -> EnrichedClientResult<Option<api::ProtocolVersion>> {
101        self.get_protocol_version(Some(protocol_version as u16))
102            .rpc_context("fetch_protocol_version")
103            .with_arg("protocol_version", &protocol_version)
104            .await
105    }
106
107    async fn fetch_genesis_config(&self) -> EnrichedClientResult<GenesisConfig> {
108        let dto = self.genesis_config().rpc_context("genesis_config").await?;
109        Ok(GenesisConfig {
110            protocol_version: Some(dto.protocol_version),
111            genesis_root_hash: Some(dto.genesis_root_hash),
112            rollup_last_leaf_index: Some(dto.rollup_last_leaf_index),
113            genesis_commitment: Some(dto.genesis_commitment),
114            bootloader_hash: Some(dto.bootloader_hash),
115            default_aa_hash: Some(dto.default_aa_hash),
116            evm_emulator_hash: dto.evm_emulator_hash,
117            l1_chain_id: dto.l1_chain_id,
118            l2_chain_id: dto.l2_chain_id,
119            snark_wrapper_vk_hash: dto.snark_wrapper_vk_hash,
120            fflonk_snark_wrapper_vk_hash: dto.fflonk_snark_wrapper_vk_hash,
121            fee_account: dto.fee_account,
122            dummy_verifier: dto.dummy_verifier,
123            l1_batch_commit_data_generator_mode: dto.l1_batch_commit_data_generator_mode,
124            // External node should initialise itself from a snapshot
125            custom_genesis_state_path: None,
126        })
127    }
128
129    async fn fetch_l2_block_number(&self) -> EnrichedClientResult<L2BlockNumber> {
130        let number = self
131            .get_block_number()
132            .rpc_context("get_block_number")
133            .await?;
134        let number = u32::try_from(number)
135            .map_err(|err| EnrichedClientError::custom(err, "u32::try_from"))?;
136        Ok(L2BlockNumber(number))
137    }
138
139    async fn fetch_l2_block(
140        &self,
141        number: L2BlockNumber,
142        with_transactions: bool,
143    ) -> EnrichedClientResult<Option<en::SyncBlock>> {
144        self.sync_l2_block(number, with_transactions)
145            .rpc_context("fetch_l2_block")
146            .with_arg("number", &number)
147            .with_arg("with_transactions", &with_transactions)
148            .await
149    }
150}
151
152/// Main node health check.
153#[derive(Debug)]
154pub struct MainNodeHealthCheck(Box<DynClient<L2>>);
155
156impl From<Box<DynClient<L2>>> for MainNodeHealthCheck {
157    fn from(client: Box<DynClient<L2>>) -> Self {
158        Self(client.for_component("main_node_health_check"))
159    }
160}
161
162#[async_trait]
163impl CheckHealth for MainNodeHealthCheck {
164    fn name(&self) -> &'static str {
165        "main_node_http_rpc"
166    }
167
168    async fn check_health(&self) -> Health {
169        if let Err(err) = self.0.get_block_number().await {
170            tracing::warn!("Health-check call to main node HTTP RPC failed: {err}");
171            let details = serde_json::json!({
172                "error": err.to_string(),
173            });
174            return Health::from(HealthStatus::NotReady).with_details(details);
175        }
176        HealthStatus::Ready.into()
177    }
178}