1use crate::zkstack_config::ZkstackConfig;
2use anvil_zksync_core::node::blockchain::ReadBlockchain;
3use std::collections::HashMap;
4use std::sync::{Arc, RwLock};
5use zksync_contracts::BaseSystemContractsHashes;
6use zksync_types::blob::num_blobs_required;
7use zksync_types::block::{L1BatchHeader, L1BatchTreeData};
8use zksync_types::commitment::{
9 AuxCommitments, CommitmentCommonInput, CommitmentInput, L1BatchCommitment, L1BatchMetadata,
10 L1BatchWithMetadata,
11};
12use zksync_types::writes::StateDiffRecord;
13use zksync_types::L1BatchNumber;
14use zksync_types::{Address, H256};
15
16#[derive(Debug, Clone)]
18pub struct CommitmentGenerator {
19 base_system_contracts_hashes: BaseSystemContractsHashes,
21 fee_address: Address,
23 blockchain: Box<dyn ReadBlockchain>,
24 batches: Arc<RwLock<HashMap<L1BatchNumber, L1BatchWithMetadata>>>,
26}
27
28impl CommitmentGenerator {
29 pub fn new(zkstack_config: &ZkstackConfig, blockchain: Box<dyn ReadBlockchain>) -> Self {
33 let base_system_contracts_hashes = BaseSystemContractsHashes {
37 bootloader: zkstack_config.genesis.bootloader_hash,
38 default_aa: zkstack_config.genesis.default_aa_hash,
39 evm_emulator: zkstack_config.genesis.evm_emulator_hash,
40 };
41
42 let mut genesis_batch_header = L1BatchHeader::new(
44 L1BatchNumber(0),
45 0,
46 base_system_contracts_hashes,
47 zkstack_config.genesis.genesis_protocol_version,
48 );
49 genesis_batch_header.fee_address = zkstack_config.genesis.fee_account;
50 let commitment_input = CommitmentInput::for_genesis_batch(
51 zkstack_config.genesis.genesis_root,
52 zkstack_config.genesis.genesis_rollup_leaf_index,
53 base_system_contracts_hashes,
54 zkstack_config.genesis.genesis_protocol_version,
55 );
56 let genesis_metadata =
57 Self::generate_metadata_inner(genesis_batch_header, commitment_input);
58 assert_eq!(
59 zkstack_config.genesis.genesis_batch_commitment, genesis_metadata.metadata.commitment,
60 "Computed genesis batch commitment does not match zkstack config"
61 );
62
63 Self {
64 base_system_contracts_hashes,
65 fee_address: zkstack_config.genesis.fee_account,
66 blockchain,
67 batches: Arc::new(RwLock::new(HashMap::from_iter([(
68 L1BatchNumber(0),
69 genesis_metadata,
70 )]))),
71 }
72 }
73
74 pub async fn get_or_generate_metadata(
77 &self,
78 batch_number: L1BatchNumber,
79 ) -> Option<L1BatchWithMetadata> {
80 if let Some(metadata) = self.batches.read().unwrap().get(&batch_number) {
81 return Some(metadata.clone());
82 }
83
84 let mut header = self.blockchain.get_batch_header(batch_number).await?;
88 header.fee_address = self.fee_address;
89 header.base_system_contracts_hashes = self.base_system_contracts_hashes;
90
91 let state_diffs = self.blockchain.get_batch_state_diffs(batch_number).await?;
92 let aggregation_root = self
93 .blockchain
94 .get_batch_aggregation_root(batch_number)
95 .await?;
96
97 let tree_data = L1BatchTreeData {
100 hash: H256::random(),
101 rollup_last_leaf_index: 42,
102 };
103 let metadata = self.generate_metadata(header, state_diffs, aggregation_root, tree_data);
104 self.batches
105 .write()
106 .unwrap()
107 .insert(batch_number, metadata.clone());
108 Some(metadata)
109 }
110
111 fn generate_metadata(
112 &self,
113 header: L1BatchHeader,
114 state_diffs: Vec<StateDiffRecord>,
115 aggregation_root: H256,
116 tree_data: L1BatchTreeData,
117 ) -> L1BatchWithMetadata {
118 let protocol_version = header
119 .protocol_version
120 .expect("batch header is missing protocol version");
121 let common = CommitmentCommonInput {
122 l2_to_l1_logs: header.l2_to_l1_logs.clone(),
123 rollup_last_leaf_index: tree_data.rollup_last_leaf_index,
124 rollup_root_hash: tree_data.hash,
125 bootloader_code_hash: self.base_system_contracts_hashes.bootloader,
126 default_aa_code_hash: self.base_system_contracts_hashes.default_aa,
127 evm_emulator_code_hash: self.base_system_contracts_hashes.evm_emulator,
128 protocol_version,
129 };
130 let commitment_input = CommitmentInput::PostBoojum {
131 common,
132 system_logs: header.system_logs.clone(),
133 state_diffs,
134 aux_commitments: AuxCommitments {
135 events_queue_commitment: H256::zero(),
136 bootloader_initial_content_commitment: H256::zero(),
137 },
138 blob_hashes: {
139 let num_blobs = num_blobs_required(&protocol_version);
140 vec![Default::default(); num_blobs]
141 },
142 aggregation_root,
143 };
144
145 Self::generate_metadata_inner(header, commitment_input)
146 }
147
148 fn generate_metadata_inner(
149 header: L1BatchHeader,
150 commitment_input: CommitmentInput,
151 ) -> L1BatchWithMetadata {
152 let root_hash = commitment_input.common().rollup_root_hash;
153 let rollup_last_leaf_index = commitment_input.common().rollup_last_leaf_index;
154
155 let commitment = L1BatchCommitment::new(commitment_input);
156 let mut commitment_artifacts = commitment.artifacts();
157 if header.number == L1BatchNumber(0) {
158 commitment_artifacts.l2_l1_merkle_root = H256::zero();
160 }
161 tracing::debug!(
162 batch = header.number.0,
163 commitment_hash = ?commitment_artifacts.commitment_hash.commitment,
164 "generated a new batch commitment",
165 );
166 let batch_metadata = L1BatchMetadata {
168 root_hash,
169 rollup_last_leaf_index,
170 initial_writes_compressed: commitment_artifacts.compressed_initial_writes,
171 repeated_writes_compressed: commitment_artifacts.compressed_repeated_writes,
172 commitment: commitment_artifacts.commitment_hash.commitment,
173 l2_l1_merkle_root: commitment_artifacts.l2_l1_merkle_root,
174 block_meta_params: commitment.meta_parameters(),
175 aux_data_hash: commitment_artifacts.commitment_hash.aux_output,
176 meta_parameters_hash: commitment_artifacts.commitment_hash.meta_parameters,
177 pass_through_data_hash: commitment_artifacts.commitment_hash.pass_through_data,
178 events_queue_commitment: commitment_artifacts
179 .aux_commitments
180 .map(|a| a.events_queue_commitment),
181 bootloader_initial_content_commitment: commitment_artifacts
182 .aux_commitments
183 .map(|a| a.bootloader_initial_content_commitment),
184 state_diffs_compressed: commitment_artifacts
185 .compressed_state_diffs
186 .unwrap_or_default(),
187 state_diff_hash: Some(commitment_artifacts.state_diff_hash),
188 local_root: Some(commitment_artifacts.local_root),
189 aggregation_root: Some(commitment_artifacts.aggregation_root),
190 da_inclusion_data: None,
192 };
193
194 L1BatchWithMetadata::new(header, batch_metadata, HashMap::new(), &[])
196 }
197}
198
199#[cfg(test)]
200mod tests {
201 use super::*;
202 use anvil_zksync_core::filters::LogFilter;
203 use async_trait::async_trait;
204 use zksync_types::api::{
205 Block, BlockDetails, BlockId, DebugCall, Log, Transaction, TransactionDetails,
206 TransactionReceipt, TransactionVariant,
207 };
208 use zksync_types::web3::Bytes;
209 use zksync_types::{L2BlockNumber, ProtocolVersionId};
210
211 #[derive(Clone, Debug)]
213 struct MockBlockchain(HashMap<L1BatchNumber, L1BatchHeader>);
214
215 impl MockBlockchain {
216 pub fn new(batches: impl IntoIterator<Item = L1BatchHeader>) -> Self {
217 let genesis = L1BatchHeader::new(
218 L1BatchNumber(0),
219 0,
220 BaseSystemContractsHashes::default(),
221 ProtocolVersionId::latest(),
222 );
223 Self(HashMap::from_iter(
224 batches
225 .into_iter()
226 .map(|h| (h.number, h))
227 .chain([(L1BatchNumber(0), genesis)]),
228 ))
229 }
230 }
231
232 #[async_trait]
233 impl ReadBlockchain for MockBlockchain {
234 fn dyn_cloned(&self) -> Box<dyn ReadBlockchain> {
235 unimplemented!()
236 }
237
238 fn protocol_version(&self) -> ProtocolVersionId {
239 unimplemented!()
240 }
241
242 async fn current_batch(&self) -> L1BatchNumber {
243 unimplemented!()
244 }
245
246 async fn current_block_number(&self) -> L2BlockNumber {
247 unimplemented!()
248 }
249
250 async fn current_block_hash(&self) -> H256 {
251 unimplemented!()
252 }
253
254 async fn get_block_by_hash(&self, _hash: &H256) -> Option<Block<TransactionVariant>> {
255 unimplemented!()
256 }
257
258 async fn get_block_by_number(
259 &self,
260 _number: L2BlockNumber,
261 ) -> Option<Block<TransactionVariant>> {
262 unimplemented!()
263 }
264
265 async fn get_block_by_id(&self, _block_id: BlockId) -> Option<Block<TransactionVariant>> {
266 unimplemented!()
267 }
268
269 async fn get_block_hash_by_number(&self, _number: L2BlockNumber) -> Option<H256> {
270 unimplemented!()
271 }
272
273 async fn get_block_hash_by_id(&self, _block_id: BlockId) -> Option<H256> {
274 unimplemented!()
275 }
276
277 async fn get_block_number_by_hash(&self, _hash: &H256) -> Option<L2BlockNumber> {
278 unimplemented!()
279 }
280
281 async fn get_block_number_by_id(&self, _block_id: BlockId) -> Option<L2BlockNumber> {
282 unimplemented!()
283 }
284
285 async fn get_block_tx_hashes_by_number(&self, _number: L2BlockNumber) -> Option<Vec<H256>> {
286 unimplemented!()
287 }
288
289 async fn get_block_tx_hashes_by_id(&self, _block_id: BlockId) -> Option<Vec<H256>> {
290 unimplemented!()
291 }
292
293 async fn get_block_tx_by_id(
294 &self,
295 _block_id: BlockId,
296 _index: usize,
297 ) -> Option<Transaction> {
298 unimplemented!()
299 }
300
301 async fn get_block_tx_count_by_id(&self, _block_id: BlockId) -> Option<usize> {
302 unimplemented!()
303 }
304
305 async fn get_block_details_by_number(
306 &self,
307 _number: L2BlockNumber,
308 _l2_fair_gas_price: u64,
309 _fair_pubdata_price: Option<u64>,
310 _base_system_contracts_hashes: BaseSystemContractsHashes,
311 ) -> Option<BlockDetails> {
312 unimplemented!()
313 }
314
315 async fn get_tx_receipt(&self, _tx_hash: &H256) -> Option<TransactionReceipt> {
316 unimplemented!()
317 }
318
319 async fn get_tx_debug_info(&self, _tx_hash: &H256, _only_top: bool) -> Option<DebugCall> {
320 unimplemented!()
321 }
322
323 async fn get_tx_api(&self, _tx_hash: &H256) -> anyhow::Result<Option<Transaction>> {
324 unimplemented!()
325 }
326
327 async fn get_detailed_tx(
328 &self,
329 _tx: Transaction,
330 ) -> Option<anvil_zksync_types::api::DetailedTransaction> {
331 unimplemented!()
332 }
333
334 async fn get_tx_details(&self, _tx_hash: &H256) -> Option<TransactionDetails> {
335 unimplemented!()
336 }
337
338 async fn get_zksync_tx(&self, _tx_hash: &H256) -> Option<zksync_types::Transaction> {
339 unimplemented!()
340 }
341
342 async fn get_filter_logs(&self, _log_filter: &LogFilter) -> Vec<Log> {
343 unimplemented!()
344 }
345
346 async fn get_batch_header(&self, batch_number: L1BatchNumber) -> Option<L1BatchHeader> {
347 self.0.get(&batch_number).cloned()
348 }
349
350 async fn get_batch_state_diffs(
351 &self,
352 batch_number: L1BatchNumber,
353 ) -> Option<Vec<StateDiffRecord>> {
354 if self.0.contains_key(&batch_number) {
355 Some(vec![])
356 } else {
357 None
358 }
359 }
360
361 async fn get_batch_aggregation_root(&self, batch_number: L1BatchNumber) -> Option<H256> {
362 if self.0.contains_key(&batch_number) {
363 Some(H256::zero())
364 } else {
365 None
366 }
367 }
368
369 async fn get_raw_transaction(&self, _tx_hash: H256) -> Option<Bytes> {
370 unimplemented!()
371 }
372 async fn get_raw_transactions(&self, _block_number: BlockId) -> Vec<Bytes> {
373 unimplemented!()
374 }
375 }
376
377 #[tokio::test]
378 async fn generates_proper_genesis() {
379 let config = ZkstackConfig::builtin(ProtocolVersionId::latest());
380 let blockchain = MockBlockchain::new([]);
381 let commitment_generator = CommitmentGenerator::new(&config, Box::new(blockchain));
382 let genesis_metadata = commitment_generator
383 .get_or_generate_metadata(L1BatchNumber(0))
384 .await
385 .unwrap();
386 assert_eq!(genesis_metadata.header.number, L1BatchNumber(0));
388 assert_eq!(genesis_metadata.header.timestamp, 0);
389 assert_eq!(genesis_metadata.header.l1_tx_count, 0);
390 assert_eq!(genesis_metadata.header.l2_tx_count, 0);
391
392 assert_eq!(
394 genesis_metadata.metadata.root_hash,
395 config.genesis.genesis_root
396 );
397 assert_eq!(
398 genesis_metadata.metadata.rollup_last_leaf_index,
399 config.genesis.genesis_rollup_leaf_index
400 );
401 assert_eq!(
402 genesis_metadata.metadata.commitment,
403 config.genesis.genesis_batch_commitment
404 );
405 }
406
407 #[tokio::test]
408 async fn returns_none_for_unknown_batch() {
409 let config = ZkstackConfig::builtin(ProtocolVersionId::latest());
410 let blockchain = MockBlockchain::new([]);
411 let commitment_generator = CommitmentGenerator::new(&config, Box::new(blockchain));
412 let metadata = commitment_generator
413 .get_or_generate_metadata(L1BatchNumber(42))
414 .await;
415
416 assert_eq!(metadata, None);
417 }
418
419 #[tokio::test]
420 async fn generates_valid_commitment_for_random_batch() {
421 let config = ZkstackConfig::builtin(ProtocolVersionId::latest());
422 let batch_42_header = L1BatchHeader::new(
423 L1BatchNumber(42),
424 1042,
425 BaseSystemContractsHashes::default(),
426 ProtocolVersionId::latest(),
427 );
428 let blockchain = MockBlockchain::new([batch_42_header.clone()]);
429 let commitment_generator = CommitmentGenerator::new(&config, Box::new(blockchain));
430 let metadata = commitment_generator
431 .get_or_generate_metadata(L1BatchNumber(42))
432 .await
433 .unwrap();
434
435 assert_eq!(metadata.header.number, batch_42_header.number);
437 assert_eq!(metadata.header.timestamp, batch_42_header.timestamp);
438 }
439}