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 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 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}