anvil_zksync_l1_sidecar/
lib.rs

1use crate::anvil::AnvilHandle;
2use crate::commitment_generator::CommitmentGenerator;
3use crate::l1_executor::L1Executor;
4use crate::l1_sender::{L1Sender, L1SenderHandle};
5use crate::l1_watcher::L1Watcher;
6use crate::upgrade_tx::UpgradeTx;
7use crate::zkstack_config::contracts::ContractsConfig;
8use crate::zkstack_config::genesis::GenesisConfig;
9use crate::zkstack_config::ZkstackConfig;
10use alloy::providers::Provider;
11use anvil_zksync_core::node::blockchain::ReadBlockchain;
12use anvil_zksync_core::node::node_executor::NodeExecutorHandle;
13use anvil_zksync_core::node::{TxBatch, TxPool};
14use std::sync::Arc;
15use tokio::sync::watch;
16use tokio::task::JoinHandle;
17use zksync_types::protocol_upgrade::ProtocolUpgradeTxCommonData;
18use zksync_types::{
19    ExecuteTransactionCommon, L1BatchNumber, ProtocolVersionId, Transaction, H256, U256,
20};
21
22mod anvil;
23mod commitment_generator;
24mod contracts;
25mod l1_executor;
26mod l1_sender;
27mod l1_watcher;
28mod upgrade_tx;
29mod zkstack_config;
30
31#[derive(Debug, Clone)]
32pub struct L1Sidecar {
33    inner: Option<L1SidecarInner>,
34}
35
36#[derive(Debug, Clone)]
37struct L1SidecarInner {
38    commitment_generator: CommitmentGenerator,
39    l1_sender_handle: L1SenderHandle,
40    zkstack_config: ZkstackConfig,
41}
42
43impl L1Sidecar {
44    pub fn none() -> Self {
45        Self { inner: None }
46    }
47
48    async fn new(
49        blockchain: Box<dyn ReadBlockchain>,
50        node_handle: NodeExecutorHandle,
51        pool: TxPool,
52        zkstack_config: ZkstackConfig,
53        anvil_handle: AnvilHandle,
54        anvil_provider: Arc<dyn Provider + 'static>,
55        auto_execute_l1: bool,
56    ) -> anyhow::Result<(Self, L1SidecarRunner)> {
57        let commitment_generator = CommitmentGenerator::new(&zkstack_config, blockchain);
58        let genesis_with_metadata = commitment_generator
59            .get_or_generate_metadata(L1BatchNumber(0))
60            .await
61            .ok_or(anyhow::anyhow!(
62                "genesis is missing from local storage, can't start L1 sidecar"
63            ))?;
64        let (l1_sender, l1_sender_handle) = L1Sender::new(
65            &zkstack_config,
66            genesis_with_metadata,
67            anvil_provider.clone(),
68        );
69        let l1_watcher = L1Watcher::new(&zkstack_config, anvil_provider, pool);
70        let protocol_version = zkstack_config.genesis.genesis_protocol_version;
71        let l1_executor = if auto_execute_l1 {
72            L1Executor::auto(commitment_generator.clone(), l1_sender_handle.clone())
73        } else {
74            L1Executor::manual()
75        };
76        let this = Self {
77            inner: Some(L1SidecarInner {
78                commitment_generator,
79                l1_sender_handle,
80                zkstack_config,
81            }),
82        };
83        let upgrade_handle = tokio::spawn(Self::upgrade(protocol_version, node_handle));
84        let runner = L1SidecarRunner {
85            anvil_handle,
86            l1_sender,
87            l1_watcher,
88            l1_executor,
89            upgrade_handle,
90        };
91        Ok((this, runner))
92    }
93
94    pub async fn process(
95        protocol_version: ProtocolVersionId,
96        port: u16,
97        blockchain: Box<dyn ReadBlockchain>,
98        node_handle: NodeExecutorHandle,
99        pool: TxPool,
100        auto_execute_l1: bool,
101    ) -> anyhow::Result<(Self, L1SidecarRunner)> {
102        let zkstack_config = ZkstackConfig::builtin(protocol_version);
103        let (anvil_handle, anvil_provider) = anvil::spawn_process(port, &zkstack_config).await?;
104        Self::new(
105            blockchain,
106            node_handle,
107            pool,
108            zkstack_config,
109            anvil_handle,
110            anvil_provider,
111            auto_execute_l1,
112        )
113        .await
114    }
115
116    pub async fn external(
117        protocol_version: ProtocolVersionId,
118        address: &str,
119        blockchain: Box<dyn ReadBlockchain>,
120        node_handle: NodeExecutorHandle,
121        pool: TxPool,
122        auto_execute_l1: bool,
123    ) -> anyhow::Result<(Self, L1SidecarRunner)> {
124        let zkstack_config = ZkstackConfig::builtin(protocol_version);
125        let (anvil_handle, anvil_provider) = anvil::external(address, &zkstack_config).await?;
126        Self::new(
127            blockchain,
128            node_handle,
129            pool,
130            zkstack_config,
131            anvil_handle,
132            anvil_provider,
133            auto_execute_l1,
134        )
135        .await
136    }
137
138    /// Clean L1 always expects the very first transaction to upgrade system contracts. Thus, L1
139    /// sidecar has to be initialized before any other component that can submit transactions.
140    async fn upgrade(
141        protocol_version: ProtocolVersionId,
142        node_handle: NodeExecutorHandle,
143    ) -> anyhow::Result<()> {
144        let upgrade_tx = UpgradeTx::builtin(protocol_version);
145        tracing::info!(
146            tx_hash = ?upgrade_tx.hash,
147            initiator_address = ?upgrade_tx.initiator_address,
148            contract_address = ?upgrade_tx.data.contract_address,
149            "executing upgrade transaction"
150        );
151        let upgrade_tx = Transaction {
152            common_data: ExecuteTransactionCommon::ProtocolUpgrade(ProtocolUpgradeTxCommonData {
153                sender: upgrade_tx.initiator_address,
154                upgrade_id: protocol_version,
155                max_fee_per_gas: U256::from(upgrade_tx.max_fee_per_gas),
156                gas_limit: U256::from(upgrade_tx.gas_limit),
157                gas_per_pubdata_limit: U256::from(upgrade_tx.gas_per_pubdata_limit),
158                eth_block: upgrade_tx.l1_block_number,
159                canonical_tx_hash: upgrade_tx.hash,
160                to_mint: U256::from(upgrade_tx.l1_tx_mint),
161                refund_recipient: upgrade_tx.l1_tx_refund_recipient,
162            }),
163            execute: upgrade_tx.data,
164            received_timestamp_ms: 0,
165            raw_bytes: None,
166        };
167        let upgrade_block = node_handle
168            .seal_block_sync(TxBatch {
169                impersonating: false,
170                txs: vec![upgrade_tx],
171            })
172            .await?;
173        tracing::info!(%upgrade_block, "upgrade finished successfully");
174        Ok(())
175    }
176}
177
178pub struct L1SidecarRunner {
179    anvil_handle: AnvilHandle,
180    l1_sender: L1Sender,
181    l1_watcher: L1Watcher,
182    l1_executor: L1Executor,
183    upgrade_handle: JoinHandle<anyhow::Result<()>>,
184}
185
186impl L1SidecarRunner {
187    pub async fn run(self) -> anyhow::Result<()> {
188        // We ensure L2 upgrade finishes before the rest of L1 logic can be run.
189        self.upgrade_handle.await??;
190        let (_stop_sender, mut stop_receiver) = watch::channel(false);
191        tokio::select! {
192            result = self.l1_sender.run() => {
193                tracing::trace!("L1 sender was stopped");
194                result
195            },
196            result = self.l1_watcher.run() => {
197                tracing::trace!("L1 watcher was stopped");
198                result
199            },
200            result = self.anvil_handle.run() => {
201                tracing::trace!("L1 anvil exited unexpectedly");
202                result
203            },
204            result = self.l1_executor.run(&mut stop_receiver) => result,
205        }
206    }
207}
208
209impl L1Sidecar {
210    pub async fn commit_batch(&self, batch_number: L1BatchNumber) -> anyhow::Result<H256> {
211        let Some(inner) = self.inner.as_ref() else {
212            return Err(anyhow::anyhow!(
213                "cannot commit a batch as there is no L1 configured"
214            ));
215        };
216        let batch_with_metadata = inner
217            .commitment_generator
218            .get_or_generate_metadata(batch_number)
219            .await
220            .ok_or_else(|| anyhow::anyhow!("batch #{batch_number} does not exist"))?;
221        inner
222            .l1_sender_handle
223            .commit_sync(batch_with_metadata)
224            .await
225    }
226
227    pub async fn prove_batch(&self, batch_number: L1BatchNumber) -> anyhow::Result<H256> {
228        let Some(inner) = self.inner.as_ref() else {
229            return Err(anyhow::anyhow!(
230                "cannot prove a batch as there is no L1 configured"
231            ));
232        };
233        let batch_with_metadata = inner
234            .commitment_generator
235            .get_or_generate_metadata(batch_number)
236            .await
237            .ok_or_else(|| anyhow::anyhow!("batch #{batch_number} does not exist"))?;
238        inner.l1_sender_handle.prove_sync(batch_with_metadata).await
239    }
240
241    pub async fn execute_batch(&self, batch_number: L1BatchNumber) -> anyhow::Result<H256> {
242        let Some(inner) = self.inner.as_ref() else {
243            return Err(anyhow::anyhow!(
244                "cannot execute a batch as there is no L1 configured"
245            ));
246        };
247        let batch_with_metadata = inner
248            .commitment_generator
249            .get_or_generate_metadata(batch_number)
250            .await
251            .ok_or_else(|| anyhow::anyhow!("batch #{batch_number} does not exist"))?;
252        inner
253            .l1_sender_handle
254            .execute_sync(batch_with_metadata)
255            .await
256    }
257
258    pub fn contracts_config(&self) -> anyhow::Result<&ContractsConfig> {
259        let Some(inner) = self.inner.as_ref() else {
260            return Err(anyhow::anyhow!(
261                "cannot get contracts config as there is no L1 configured"
262            ));
263        };
264        Ok(&inner.zkstack_config.contracts)
265    }
266
267    pub fn genesis_config(&self) -> anyhow::Result<&GenesisConfig> {
268        let Some(inner) = self.inner.as_ref() else {
269            return Err(anyhow::anyhow!(
270                "cannot get genesis config as there is no L1 configured"
271            ));
272        };
273        Ok(&inner.zkstack_config.genesis)
274    }
275}