diff --git a/Cargo.lock b/Cargo.lock index ba6704378cba..83f73bce6225 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8597,6 +8597,7 @@ dependencies = [ "chrono", "once_cell", "test-casing", + "test-log", "thiserror", "tokio", "tracing", diff --git a/core/lib/dal/.sqlx/query-05ba766701795f258d9d8480b69357e734d864cf9f2506794b5a59348fc06ccf.json b/core/lib/dal/.sqlx/query-05ba766701795f258d9d8480b69357e734d864cf9f2506794b5a59348fc06ccf.json new file mode 100644 index 000000000000..e1c954e9ec34 --- /dev/null +++ b/core/lib/dal/.sqlx/query-05ba766701795f258d9d8480b69357e734d864cf9f2506794b5a59348fc06ccf.json @@ -0,0 +1,34 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT\n eth_commit_tx_id,\n eth_prove_tx_id,\n eth_execute_tx_id\n FROM\n l1_batches\n WHERE\n number = $1\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "eth_commit_tx_id", + "type_info": "Int4" + }, + { + "ordinal": 1, + "name": "eth_prove_tx_id", + "type_info": "Int4" + }, + { + "ordinal": 2, + "name": "eth_execute_tx_id", + "type_info": "Int4" + } + ], + "parameters": { + "Left": [ + "Int8" + ] + }, + "nullable": [ + true, + true, + true + ] + }, + "hash": "05ba766701795f258d9d8480b69357e734d864cf9f2506794b5a59348fc06ccf" +} diff --git a/core/lib/dal/.sqlx/query-c6c7bd5dbbd8bcad2b3d76109f31a8884504fee27358b099340043d9809e6037.json b/core/lib/dal/.sqlx/query-c6c7bd5dbbd8bcad2b3d76109f31a8884504fee27358b099340043d9809e6037.json new file mode 100644 index 000000000000..ac4ca7dfd7d0 --- /dev/null +++ b/core/lib/dal/.sqlx/query-c6c7bd5dbbd8bcad2b3d76109f31a8884504fee27358b099340043d9809e6037.json @@ -0,0 +1,20 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT\n MAX(id)\n FROM\n eth_txs_history\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "max", + "type_info": "Int4" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + null + ] + }, + "hash": "c6c7bd5dbbd8bcad2b3d76109f31a8884504fee27358b099340043d9809e6037" +} diff --git a/core/lib/dal/src/eth_sender_dal.rs b/core/lib/dal/src/eth_sender_dal.rs index d45d8470b379..52f491893e72 100644 --- a/core/lib/dal/src/eth_sender_dal.rs +++ b/core/lib/dal/src/eth_sender_dal.rs @@ -685,3 +685,55 @@ impl EthSenderDal<'_, '_> { Ok(()) } } + +/// These methods should only be used for tests. +impl EthSenderDal<'_, '_> { + pub async fn get_eth_txs_history_entries_max_id(&mut self) -> usize { + sqlx::query!( + r#" + SELECT + MAX(id) + FROM + eth_txs_history + "# + ) + .fetch_one(self.storage.conn()) + .await + .unwrap() + .max + .unwrap() + .try_into() + .unwrap() + } + + pub async fn get_last_sent_eth_tx_hash( + &mut self, + l1_batch_number: L1BatchNumber, + op_type: AggregatedActionType, + ) -> Option { + let row = sqlx::query!( + r#" + SELECT + eth_commit_tx_id, + eth_prove_tx_id, + eth_execute_tx_id + FROM + l1_batches + WHERE + number = $1 + "#, + i64::from(l1_batch_number.0) + ) + .fetch_optional(self.storage.conn()) + .await + .unwrap() + .unwrap(); + let eth_tx_id = match op_type { + AggregatedActionType::Commit => row.eth_commit_tx_id, + AggregatedActionType::PublishProofOnchain => row.eth_prove_tx_id, + AggregatedActionType::Execute => row.eth_execute_tx_id, + } + .unwrap() as u32; + self.get_last_sent_eth_tx(eth_tx_id).await.unwrap() + } +} diff --git a/core/lib/eth_client/src/clients/mock.rs b/core/lib/eth_client/src/clients/mock.rs index 9fbc5ceb4b2e..7c9dbcc0d3b4 100644 --- a/core/lib/eth_client/src/clients/mock.rs +++ b/core/lib/eth_client/src/clients/mock.rs @@ -8,7 +8,7 @@ use jsonrpsee::{core::ClientError, types::ErrorObject}; use zksync_types::{ ethabi, web3::{self, contract::Tokenize, BlockId}, - Address, L1ChainId, H160, H256, U256, U64, + Address, L1ChainId, EIP_4844_TX_TYPE, H160, H256, U256, U64, }; use zksync_web3_decl::client::{DynClient, MockClient, L1}; @@ -28,7 +28,15 @@ struct MockTx { } impl From> for MockTx { - fn from(tx: Vec) -> Self { + fn from(raw_tx: Vec) -> Self { + let is_eip4844 = raw_tx[0] == EIP_4844_TX_TYPE; + let tx: Vec = if is_eip4844 { + // decoding rlp-encoded length + let len = raw_tx[2] as usize * 256 + raw_tx[3] as usize - 2; + raw_tx[3..3 + len].to_vec() + } else { + raw_tx + }; let len = tx.len(); let recipient = Address::from_slice(&tx[len - 116..len - 96]); let max_fee_per_gas = U256::from(&tx[len - 96..len - 64]); @@ -37,6 +45,9 @@ impl From> for MockTx { let hash = { let mut buffer = [0_u8; 32]; buffer.copy_from_slice(&tx[..32]); + if is_eip4844 { + buffer[0] = 0x00; + } buffer.into() }; @@ -94,11 +105,12 @@ impl MockEthereumInner { self.block_number += confirmations; let nonce = self.current_nonce; self.current_nonce += 1; + tracing::info!("Executing tx with hash {tx_hash:?}, success: {success}, current nonce: {}, confirmations: {confirmations}", self.current_nonce); let tx_nonce = self.sent_txs[&tx_hash].nonce; if non_ordering_confirmations { if tx_nonce >= nonce { - self.current_nonce = tx_nonce; + self.current_nonce = tx_nonce + 1; } } else { assert_eq!(tx_nonce, nonce, "nonce mismatch"); @@ -140,6 +152,7 @@ impl MockEthereumInner { fn send_raw_transaction(&mut self, tx: web3::Bytes) -> Result { let mock_tx = MockTx::from(tx.0); let mock_tx_hash = mock_tx.hash; + tracing::info!("Sending tx with hash {mock_tx_hash:?}"); if mock_tx.nonce < self.current_nonce { let err = ErrorObject::owned( diff --git a/core/node/consistency_checker/src/tests/mod.rs b/core/node/consistency_checker/src/tests/mod.rs index 13c1caec381a..914e21069bdd 100644 --- a/core/node/consistency_checker/src/tests/mod.rs +++ b/core/node/consistency_checker/src/tests/mod.rs @@ -45,6 +45,7 @@ pub(crate) fn create_pre_boojum_l1_batch_with_metadata(number: u32) -> L1BatchWi raw_published_factory_deps: vec![], }; l1_batch.header.protocol_version = Some(PRE_BOOJUM_PROTOCOL_VERSION); + l1_batch.header.l2_to_l1_logs = vec![]; l1_batch.metadata.bootloader_initial_content_commitment = None; l1_batch.metadata.events_queue_commitment = None; l1_batch diff --git a/core/node/eth_sender/Cargo.toml b/core/node/eth_sender/Cargo.toml index 4f2b27ff1d9f..a7aa88c3550e 100644 --- a/core/node/eth_sender/Cargo.toml +++ b/core/node/eth_sender/Cargo.toml @@ -36,3 +36,4 @@ test-casing.workspace = true zksync_node_test_utils.workspace = true once_cell.workspace = true assert_matches.workspace = true +test-log.workspace = true diff --git a/core/node/eth_sender/src/abstract_l1_interface.rs b/core/node/eth_sender/src/abstract_l1_interface.rs index acc7c265186d..9c9af82553e9 100644 --- a/core/node/eth_sender/src/abstract_l1_interface.rs +++ b/core/node/eth_sender/src/abstract_l1_interface.rs @@ -50,9 +50,14 @@ pub(super) trait AbstractL1Interface: 'static + Sync + Send + fmt::Debug { async fn get_tx_status( &self, tx_hash: H256, + operator_type: OperatorType, ) -> Result, EthSenderError>; - async fn send_raw_tx(&self, tx_bytes: RawTransactionBytes) -> EnrichedClientResult; + async fn send_raw_tx( + &self, + tx_bytes: RawTransactionBytes, + operator_type: OperatorType, + ) -> EnrichedClientResult; fn get_blobs_operator_account(&self) -> Option
; @@ -89,6 +94,14 @@ impl RealL1Interface { pub(crate) fn query_client(&self) -> &DynClient { self.ethereum_gateway().as_ref() } + + pub(crate) fn query_client_for_operator(&self, operator_type: OperatorType) -> &DynClient { + if operator_type == OperatorType::Blob { + self.ethereum_gateway_blobs().unwrap().as_ref() + } else { + self.ethereum_gateway().as_ref() + } + } } #[async_trait] impl AbstractL1Interface for RealL1Interface { @@ -106,15 +119,22 @@ impl AbstractL1Interface for RealL1Interface { async fn get_tx_status( &self, tx_hash: H256, + operator_type: OperatorType, ) -> Result, EthSenderError> { - self.query_client() + self.query_client_for_operator(operator_type) .get_tx_status(tx_hash) .await .map_err(Into::into) } - async fn send_raw_tx(&self, tx_bytes: RawTransactionBytes) -> EnrichedClientResult { - self.query_client().send_raw_tx(tx_bytes).await + async fn send_raw_tx( + &self, + tx_bytes: RawTransactionBytes, + operator_type: OperatorType, + ) -> EnrichedClientResult { + self.query_client_for_operator(operator_type) + .send_raw_tx(tx_bytes) + .await } fn get_blobs_operator_account(&self) -> Option
{ diff --git a/core/node/eth_sender/src/eth_fees_oracle.rs b/core/node/eth_sender/src/eth_fees_oracle.rs index c985a987eeb5..89d10bc2b1e5 100644 --- a/core/node/eth_sender/src/eth_fees_oracle.rs +++ b/core/node/eth_sender/src/eth_fees_oracle.rs @@ -115,7 +115,7 @@ impl GasAdjusterFeesOracle { base_fee_to_use: u64, ) -> Result<(), EthSenderError> { let next_block_minimal_base_fee = self.gas_adjuster.get_next_block_minimal_base_fee(); - if base_fee_to_use <= min(next_block_minimal_base_fee, previous_base_fee) { + if base_fee_to_use < min(next_block_minimal_base_fee, previous_base_fee) { // If the base fee is lower than the previous used one // or is lower than the minimal possible value for the next block, sending is skipped. tracing::info!( diff --git a/core/node/eth_sender/src/eth_tx_manager.rs b/core/node/eth_sender/src/eth_tx_manager.rs index d9fa504e498f..8014336be262 100644 --- a/core/node/eth_sender/src/eth_tx_manager.rs +++ b/core/node/eth_sender/src/eth_tx_manager.rs @@ -1,6 +1,5 @@ use std::{sync::Arc, time::Duration}; -use anyhow::Context as _; use tokio::sync::watch; use zksync_config::configs::eth_sender::SenderConfig; use zksync_dal::{Connection, ConnectionPool, Core, CoreDal}; @@ -78,10 +77,20 @@ impl EthTxManager { .await .unwrap() { + let operator_type = if op.blob_sidecar.is_some() { + OperatorType::Blob + } else { + OperatorType::NonBlob + }; + // `status` is a Result here and we don't unwrap it with `?` // because if we do and get an `Err`, we won't finish the for loop, // which means we might miss the transaction that actually succeeded. - match self.l1_interface.get_tx_status(history_item.tx_hash).await { + match self + .l1_interface + .get_tx_status(history_item.tx_hash, operator_type) + .await + { Ok(Some(s)) => return Ok(Some(s)), Ok(_) => continue, Err(err) => { @@ -121,10 +130,17 @@ impl EthTxManager { time_in_mempool, )?; + let operator_type = if tx.blob_sidecar.is_some() { + OperatorType::Blob + } else { + OperatorType::NonBlob + }; + if let Some(previous_sent_tx) = previous_sent_tx { METRICS.transaction_resent.inc(); tracing::info!( - "Resending tx {} at block {current_block} with \ + "Resending {operator_type:?} tx {} (nonce {}) \ + at block {current_block} with \ base_fee_per_gas {base_fee_per_gas:?}, \ priority_fee_per_gas {priority_fee_per_gas:?}, \ blob_fee_per_gas {blob_base_fee_per_gas:?}, \ @@ -134,17 +150,20 @@ impl EthTxManager { blob_fee_per_gas {:?}, \ ", tx.id, + tx.nonce, previous_sent_tx.base_fee_per_gas, previous_sent_tx.priority_fee_per_gas, previous_sent_tx.blob_base_fee_per_gas ); } else { tracing::info!( - "Sending tx {} at block {current_block} with \ + "Sending {operator_type:?} tx {} (nonce {}) \ + at block {current_block} with \ base_fee_per_gas {base_fee_per_gas:?}, \ priority_fee_per_gas {priority_fee_per_gas:?}, \ blob_fee_per_gas {blob_base_fee_per_gas:?}", - tx.id + tx.id, + tx.nonce ); } @@ -201,16 +220,17 @@ impl EthTxManager { .unwrap() { if let Err(error) = self - .send_raw_transaction(storage, tx_history_id, signed_tx.raw_tx) + .send_raw_transaction(storage, tx_history_id, signed_tx.raw_tx, operator_type) .await { tracing::warn!( - "Error Sending tx {} at block {current_block} with \ + "Error Sending {operator_type:?} tx {} (nonce {}) at block {current_block} with \ base_fee_per_gas {base_fee_per_gas:?}, \ priority_fee_per_gas {priority_fee_per_gas:?}, \ blob_fee_per_gas {blob_base_fee_per_gas:?},\ error {error}", - tx.id + tx.id, + tx.nonce, ); } } @@ -222,8 +242,9 @@ impl EthTxManager { storage: &mut Connection<'_, Core>, tx_history_id: u32, raw_tx: RawTransactionBytes, + operator_type: OperatorType, ) -> Result<(), EthSenderError> { - match self.l1_interface.send_raw_tx(raw_tx).await { + match self.l1_interface.send_raw_tx(raw_tx, operator_type).await { Ok(_) => Ok(()), Err(error) => { // In transient errors, server may have received the transaction @@ -302,7 +323,7 @@ impl EthTxManager { // Not confirmed transactions, ordered by nonce for tx in inflight_txs { - tracing::trace!( + tracing::info!( "Checking tx id: {}, operator_nonce: {:?}, tx nonce: {}", tx.id, operator_nonce, @@ -377,54 +398,6 @@ impl EthTxManager { Ok(None) } - async fn send_unsent_txs( - &mut self, - storage: &mut Connection<'_, Core>, - l1_block_numbers: L1BlockNumbers, - ) { - for tx in storage.eth_sender_dal().get_unsent_txs().await.unwrap() { - // Check already sent txs not marked as sent and mark them as sent. - // The common reason for this behavior is that we sent tx and stop the server - // before updating the database - let tx_status = self.l1_interface.get_tx_status(tx.tx_hash).await; - - if let Ok(Some(tx_status)) = tx_status { - tracing::info!("The tx {:?} has been already sent", tx.tx_hash); - storage - .eth_sender_dal() - .set_sent_at_block(tx.id, tx_status.receipt.block_number.unwrap().as_u32()) - .await - .unwrap(); - - let eth_tx = storage - .eth_sender_dal() - .get_eth_tx(tx.eth_tx_id) - .await - .unwrap() - .expect("Eth tx should exist"); - - self.apply_tx_status(storage, ð_tx, tx_status, l1_block_numbers.finalized) - .await; - } else { - storage - .eth_sender_dal() - .set_sent_at_block(tx.id, l1_block_numbers.latest.0) - .await - .unwrap(); - if let Err(error) = self - .send_raw_transaction( - storage, - tx.id, - RawTransactionBytes::new_unchecked(tx.signed_raw_tx.clone()), - ) - .await - { - tracing::warn!("Error sending transaction {tx:?}: {error}"); - } - } - } - } - async fn apply_tx_status( &self, storage: &mut Connection<'_, Core>, @@ -440,7 +413,7 @@ impl EthTxManager { self.fail_tx(storage, tx, tx_status).await; } } else { - tracing::debug!( + tracing::trace!( "Transaction {} with id {} is not yet finalized: block in receipt {receipt_block_number}, finalized block {finalized_block}", tx_status.tx_hash, tx.id, @@ -526,15 +499,6 @@ impl EthTxManager { pub async fn run(mut self, stop_receiver: watch::Receiver) -> anyhow::Result<()> { let pool = self.pool.clone(); - { - let l1_block_numbers = self - .l1_interface - .get_l1_block_numbers() - .await - .context("get_l1_block_numbers()")?; - let mut storage = pool.connection_tagged("eth_sender").await.unwrap(); - self.send_unsent_txs(&mut storage, l1_block_numbers).await; - } // It's mandatory to set `last_known_l1_block` to zero, otherwise the first iteration // will never check in-flight txs status @@ -592,7 +556,7 @@ impl EthTxManager { new_eth_tx.len() ); } else { - tracing::trace!("No new transactions to send"); + tracing::debug!("No new {operator_type:?} transactions to send"); } for tx in new_eth_tx { let result = self.send_eth_tx(storage, &tx, 0, current_block).await; @@ -631,12 +595,12 @@ impl EthTxManager { } #[tracing::instrument(skip_all, name = "EthTxManager::loop_iteration")] - async fn loop_iteration( + pub async fn loop_iteration( &mut self, storage: &mut Connection<'_, Core>, l1_block_numbers: L1BlockNumbers, ) { - tracing::trace!("Loop iteration at block {}", l1_block_numbers.latest); + tracing::debug!("Loop iteration at block {}", l1_block_numbers.latest); // We can treat those two operators independently as they have different nonces and // aggregator makes sure that corresponding Commit transaction is confirmed before creating // a PublishProof transaction diff --git a/core/node/eth_sender/src/lib.rs b/core/node/eth_sender/src/lib.rs index 504c9b68a63c..747ece93b811 100644 --- a/core/node/eth_sender/src/lib.rs +++ b/core/node/eth_sender/src/lib.rs @@ -14,6 +14,9 @@ mod eth_fees_oracle; #[cfg(test)] mod tests; +#[cfg(test)] +mod tester; + pub use self::{ aggregator::Aggregator, error::EthSenderError, eth_tx_aggregator::EthTxAggregator, eth_tx_manager::EthTxManager, diff --git a/core/node/eth_sender/src/tester.rs b/core/node/eth_sender/src/tester.rs new file mode 100644 index 000000000000..cf090e28ea70 --- /dev/null +++ b/core/node/eth_sender/src/tester.rs @@ -0,0 +1,560 @@ +use std::sync::Arc; + +use zksync_config::{ + configs::eth_sender::{ProofSendingMode, PubdataSendingMode, SenderConfig}, + ContractsConfig, EthConfig, GasAdjusterConfig, +}; +use zksync_dal::{Connection, ConnectionPool, Core, CoreDal}; +use zksync_eth_client::{clients::MockEthereum, BaseFees, BoundEthInterface}; +use zksync_l1_contract_interface::i_executor::methods::{ExecuteBatches, ProveBatches}; +use zksync_node_fee_model::l1_gas_price::GasAdjuster; +use zksync_node_test_utils::{create_l1_batch, l1_batch_metadata_to_commitment_artifacts}; +use zksync_object_store::MockObjectStore; +use zksync_types::{ + aggregated_operations::AggregatedActionType, block::L1BatchHeader, + commitment::L1BatchCommitmentMode, eth_sender::EthTx, pubdata_da::PubdataDA, Address, + L1BatchNumber, ProtocolVersion, H256, +}; + +use crate::{ + abstract_l1_interface::{L1BlockNumbers, OperatorType}, + aggregated_operations::AggregatedOperation, + tests::{default_l1_batch_metadata, l1_batch_with_metadata}, + Aggregator, EthTxAggregator, EthTxManager, +}; + +// Alias to conveniently call static methods of `ETHSender`. +type MockEthTxManager = EthTxManager; + +pub(crate) struct TestL1Batch { + pub number: L1BatchNumber, +} + +impl TestL1Batch { + pub async fn commit(&self, tester: &mut EthSenderTester, confirm: bool) -> H256 { + assert_ne!(self.number, L1BatchNumber(0), "Cannot commit genesis batch"); + tester.commit_l1_batch(self.number, confirm).await + } + + pub async fn save_commit_tx(&self, tester: &mut EthSenderTester) { + assert_ne!(self.number, L1BatchNumber(0), "Cannot commit genesis batch"); + tester.save_commit_tx(self.number).await; + } + + pub async fn prove(&self, tester: &mut EthSenderTester, confirm: bool) -> H256 { + assert_ne!(self.number, L1BatchNumber(0), "Cannot prove genesis batch"); + tester.prove_l1_batch(self.number, confirm).await + } + + pub async fn save_prove_tx(&self, tester: &mut EthSenderTester) { + assert_ne!(self.number, L1BatchNumber(0), "Cannot commit genesis batch"); + tester.save_prove_tx(self.number).await; + } + pub async fn execute(&self, tester: &mut EthSenderTester, confirm: bool) -> H256 { + assert_ne!( + self.number, + L1BatchNumber(0), + "Cannot execute genesis batch" + ); + tester.execute_l1_batch(self.number, confirm).await + } + + pub async fn execute_commit_tx(&self, tester: &mut EthSenderTester) { + tester + .execute_tx( + self.number, + AggregatedActionType::Commit, + true, + EthSenderTester::WAIT_CONFIRMATIONS, + ) + .await; + } + + pub async fn execute_prove_tx(&self, tester: &mut EthSenderTester) { + tester + .execute_tx( + self.number, + AggregatedActionType::PublishProofOnchain, + true, + EthSenderTester::WAIT_CONFIRMATIONS, + ) + .await; + } + + pub async fn fail_commit_tx(&self, tester: &mut EthSenderTester) { + tester + .execute_tx( + self.number, + AggregatedActionType::Commit, + false, + EthSenderTester::WAIT_CONFIRMATIONS, + ) + .await; + } + + pub async fn assert_commit_tx_just_sent(&self, tester: &mut EthSenderTester) { + tester + .assert_tx_was_sent_in_last_iteration(self.number, AggregatedActionType::Commit) + .await; + } + + pub async fn sealed(tester: &mut EthSenderTester) -> Self { + tester.seal_l1_batch().await; + Self { + number: tester.next_l1_batch_number_to_seal - 1, + } + } +} + +#[derive(Debug)] +pub(crate) struct EthSenderTester { + pub conn: ConnectionPool, + pub gateway: Box, + pub gateway_blobs: Box, + pub manager: MockEthTxManager, + pub aggregator: EthTxAggregator, + pub gas_adjuster: Arc, + pub pubdata_sending_mode: PubdataSendingMode, + next_l1_batch_number_to_seal: L1BatchNumber, + next_l1_batch_number_to_commit: L1BatchNumber, + next_l1_batch_number_to_prove: L1BatchNumber, + next_l1_batch_number_to_execute: L1BatchNumber, + tx_sent_in_last_iteration_count: usize, +} + +impl EthSenderTester { + pub const WAIT_CONFIRMATIONS: u64 = 10; + pub const MAX_BASE_FEE_SAMPLES: usize = 3; + + pub async fn new( + connection_pool: ConnectionPool, + history: Vec, + non_ordering_confirmations: bool, + aggregator_operate_4844_mode: bool, + commitment_mode: L1BatchCommitmentMode, + ) -> Self { + let eth_sender_config = EthConfig::for_tests(); + let contracts_config = ContractsConfig::for_tests(); + let pubdata_sending_mode = + if aggregator_operate_4844_mode && commitment_mode == L1BatchCommitmentMode::Rollup { + PubdataSendingMode::Blobs + } else { + PubdataSendingMode::Calldata + }; + let aggregator_config = SenderConfig { + aggregated_proof_sizes: vec![1], + pubdata_sending_mode, + ..eth_sender_config.clone().sender.unwrap() + }; + + let history: Vec<_> = history + .into_iter() + .map(|base_fee_per_gas| BaseFees { + base_fee_per_gas, + base_fee_per_blob_gas: 0.into(), + }) + .collect(); + + let gateway = MockEthereum::builder() + .with_fee_history( + std::iter::repeat_with(|| BaseFees { + base_fee_per_gas: 0, + base_fee_per_blob_gas: 0.into(), + }) + .take(Self::WAIT_CONFIRMATIONS as usize) + .chain(history.clone()) + .collect(), + ) + .with_non_ordering_confirmation(non_ordering_confirmations) + .with_call_handler(move |call, _| { + assert_eq!(call.to, Some(contracts_config.l1_multicall3_addr)); + crate::tests::mock_multicall_response() + }) + .build(); + gateway.advance_block_number(Self::WAIT_CONFIRMATIONS); + let gateway = Box::new(gateway); + + let gateway_blobs = MockEthereum::builder() + .with_fee_history( + std::iter::repeat_with(|| BaseFees { + base_fee_per_gas: 0, + base_fee_per_blob_gas: 0.into(), + }) + .take(Self::WAIT_CONFIRMATIONS as usize) + .chain(history) + .collect(), + ) + .with_non_ordering_confirmation(non_ordering_confirmations) + .with_call_handler(move |call, _| { + assert_eq!(call.to, Some(contracts_config.l1_multicall3_addr)); + crate::tests::mock_multicall_response() + }) + .build(); + gateway_blobs.advance_block_number(Self::WAIT_CONFIRMATIONS); + let gateway_blobs = Box::new(gateway_blobs); + + let gas_adjuster = Arc::new( + GasAdjuster::new( + Box::new(gateway.clone().into_client()), + GasAdjusterConfig { + max_base_fee_samples: Self::MAX_BASE_FEE_SAMPLES, + pricing_formula_parameter_a: 3.0, + pricing_formula_parameter_b: 2.0, + ..eth_sender_config.gas_adjuster.unwrap() + }, + pubdata_sending_mode, + commitment_mode, + ) + .await + .unwrap(), + ); + + let eth_sender = eth_sender_config.sender.clone().unwrap(); + + let custom_commit_sender_addr = + if aggregator_operate_4844_mode && commitment_mode == L1BatchCommitmentMode::Rollup { + Some(gateway_blobs.sender_account()) + } else { + None + }; + + let aggregator = EthTxAggregator::new( + connection_pool.clone(), + SenderConfig { + proof_sending_mode: ProofSendingMode::SkipEveryProof, + pubdata_sending_mode, + ..eth_sender.clone() + }, + // Aggregator - unused + Aggregator::new( + aggregator_config.clone(), + MockObjectStore::arc(), + aggregator_operate_4844_mode, + commitment_mode, + ), + gateway.clone(), + // ZKsync contract address + Address::random(), + contracts_config.l1_multicall3_addr, + Address::random(), + Default::default(), + custom_commit_sender_addr, + ) + .await; + + let manager = EthTxManager::new( + connection_pool.clone(), + eth_sender.clone(), + gas_adjuster.clone(), + gateway.clone(), + Some(gateway_blobs.clone()), + ); + + let connection_pool_clone = connection_pool.clone(); + let mut storage = connection_pool_clone.connection().await.unwrap(); + storage + .protocol_versions_dal() + .save_protocol_version_with_tx(&ProtocolVersion::default()) + .await + .unwrap(); + + Self { + gateway, + gateway_blobs, + manager, + aggregator, + gas_adjuster, + conn: connection_pool, + pubdata_sending_mode, + next_l1_batch_number_to_seal: L1BatchNumber(0), + next_l1_batch_number_to_commit: L1BatchNumber(1), + next_l1_batch_number_to_execute: L1BatchNumber(1), + next_l1_batch_number_to_prove: L1BatchNumber(1), + tx_sent_in_last_iteration_count: 0, + } + } + + pub async fn storage(&self) -> Connection<'_, Core> { + self.conn.connection().await.unwrap() + } + + pub async fn get_block_numbers(&self) -> L1BlockNumbers { + let latest = self + .manager + .l1_interface() + .get_l1_block_numbers() + .await + .unwrap() + .latest; + let finalized = latest - Self::WAIT_CONFIRMATIONS as u32; + L1BlockNumbers { + finalized, + latest, + safe: finalized, + } + } + async fn insert_l1_batch(&self, number: L1BatchNumber) -> L1BatchHeader { + let header = create_l1_batch(number.0); + + // Save L1 batch to the database + self.storage() + .await + .blocks_dal() + .insert_mock_l1_batch(&header) + .await + .unwrap(); + let metadata = default_l1_batch_metadata(); + self.storage() + .await + .blocks_dal() + .save_l1_batch_tree_data(header.number, &metadata.tree_data()) + .await + .unwrap(); + self.storage() + .await + .blocks_dal() + .save_l1_batch_commitment_artifacts( + header.number, + &l1_batch_metadata_to_commitment_artifacts(&metadata), + ) + .await + .unwrap(); + header + } + + pub async fn execute_tx( + &mut self, + l1_batch_number: L1BatchNumber, + operation_type: AggregatedActionType, + success: bool, + confirmations: u64, + ) { + let tx = self + .conn + .connection() + .await + .unwrap() + .eth_sender_dal() + .get_last_sent_eth_tx_hash(l1_batch_number, operation_type) + .await + .unwrap(); + let (gateway, other) = if tx.blob_base_fee_per_gas.is_some() { + (self.gateway_blobs.as_ref(), self.gateway.as_ref()) + } else { + (self.gateway.as_ref(), self.gateway_blobs.as_ref()) + }; + gateway.execute_tx(tx.tx_hash, success, confirmations); + other.advance_block_number(confirmations); + } + + pub async fn seal_l1_batch(&mut self) -> L1BatchHeader { + let header = self + .insert_l1_batch(self.next_l1_batch_number_to_seal) + .await; + self.next_l1_batch_number_to_seal += 1; + header + } + + pub async fn save_execute_tx(&mut self, l1_batch_number: L1BatchNumber) -> EthTx { + assert_eq!(l1_batch_number, self.next_l1_batch_number_to_execute); + let operation = AggregatedOperation::Execute(ExecuteBatches { + l1_batches: vec![ + self.get_l1_batch_header_from_db(self.next_l1_batch_number_to_execute) + .await, + ] + .into_iter() + .map(l1_batch_with_metadata) + .collect(), + }); + self.next_l1_batch_number_to_execute += 1; + self.save_operation(operation).await + } + pub async fn execute_l1_batch( + &mut self, + l1_batch_number: L1BatchNumber, + confirm: bool, + ) -> H256 { + let tx = self.save_execute_tx(l1_batch_number).await; + self.send_tx(tx, confirm).await + } + + pub async fn save_prove_tx(&mut self, l1_batch_number: L1BatchNumber) -> EthTx { + assert_eq!(l1_batch_number, self.next_l1_batch_number_to_prove); + let operation = AggregatedOperation::PublishProofOnchain(ProveBatches { + prev_l1_batch: l1_batch_with_metadata( + self.get_l1_batch_header_from_db(self.next_l1_batch_number_to_prove - 1) + .await, + ), + l1_batches: vec![l1_batch_with_metadata( + self.get_l1_batch_header_from_db(self.next_l1_batch_number_to_prove) + .await, + )], + proofs: vec![], + should_verify: false, + }); + self.next_l1_batch_number_to_prove += 1; + self.save_operation(operation).await + } + + pub async fn prove_l1_batch(&mut self, l1_batch_number: L1BatchNumber, confirm: bool) -> H256 { + let tx = self.save_prove_tx(l1_batch_number).await; + self.send_tx(tx, confirm).await + } + + pub async fn run_eth_sender_tx_manager_iteration(&mut self) { + self.gateway.advance_block_number(1); + self.gateway_blobs.advance_block_number(1); + let tx_sent_before = self.gateway.sent_tx_count() + self.gateway_blobs.sent_tx_count(); + self.manager + .loop_iteration( + &mut self.conn.connection().await.unwrap(), + self.get_block_numbers().await, + ) + .await; + self.tx_sent_in_last_iteration_count = + (self.gateway.sent_tx_count() + self.gateway_blobs.sent_tx_count()) - tx_sent_before; + } + + async fn get_l1_batch_header_from_db(&mut self, number: L1BatchNumber) -> L1BatchHeader { + self.conn + .connection() + .await + .unwrap() + .blocks_dal() + .get_l1_batch_header(number) + .await + .unwrap() + .unwrap_or_else(|| panic!("expected to find header for {}", number)) + } + pub async fn save_commit_tx(&mut self, l1_batch_number: L1BatchNumber) -> EthTx { + assert_eq!(l1_batch_number, self.next_l1_batch_number_to_commit); + let pubdata_mode = if self.pubdata_sending_mode == PubdataSendingMode::Blobs { + PubdataDA::Blobs + } else { + PubdataDA::Calldata + }; + let operation = AggregatedOperation::Commit( + l1_batch_with_metadata( + self.get_l1_batch_header_from_db(self.next_l1_batch_number_to_commit - 1) + .await, + ), + vec![l1_batch_with_metadata( + self.get_l1_batch_header_from_db(self.next_l1_batch_number_to_commit) + .await, + )], + pubdata_mode, + ); + self.next_l1_batch_number_to_commit += 1; + self.save_operation(operation).await + } + + pub async fn commit_l1_batch(&mut self, l1_batch_number: L1BatchNumber, confirm: bool) -> H256 { + let tx = self.save_commit_tx(l1_batch_number).await; + self.send_tx(tx, confirm).await + } + + pub async fn save_operation(&mut self, aggregated_operation: AggregatedOperation) -> EthTx { + self.aggregator + .save_eth_tx( + &mut self.conn.connection().await.unwrap(), + &aggregated_operation, + false, + ) + .await + .unwrap() + } + + pub async fn send_tx(&mut self, tx: EthTx, confirm: bool) -> H256 { + let hash = self + .manager + .send_eth_tx( + &mut self.conn.connection().await.unwrap(), + &tx, + 0, + self.get_block_numbers().await.latest, + ) + .await + .unwrap(); + + if confirm { + self.confirm_tx(hash, tx.blob_sidecar.is_some()).await; + } + hash + } + + pub async fn confirm_tx(&mut self, hash: H256, is_blob: bool) { + let (gateway, other) = if is_blob { + (self.gateway_blobs.as_ref(), self.gateway.as_ref()) + } else { + (self.gateway.as_ref(), self.gateway_blobs.as_ref()) + }; + gateway.execute_tx(hash, true, EthSenderTester::WAIT_CONFIRMATIONS); + other.advance_block_number(EthSenderTester::WAIT_CONFIRMATIONS); + + self.run_eth_sender_tx_manager_iteration().await; + } + + pub async fn assert_just_sent_tx_count_equals(&self, value: usize) { + assert_eq!( + value, self.tx_sent_in_last_iteration_count, + "unexpected number of transactions sent in last tx manager iteration" + ) + } + + pub async fn assert_tx_was_sent_in_last_iteration( + &self, + l1_batch_number: L1BatchNumber, + operation_type: AggregatedActionType, + ) { + let last_entry = self + .conn + .connection() + .await + .unwrap() + .eth_sender_dal() + .get_last_sent_eth_tx_hash(l1_batch_number, operation_type) + .await + .unwrap(); + let max_id = self + .conn + .connection() + .await + .unwrap() + .eth_sender_dal() + .get_eth_txs_history_entries_max_id() + .await; + assert!( + max_id - self.tx_sent_in_last_iteration_count < last_entry.id as usize, + "expected tx to be sent in last iteration, \ + max_id: {max_id}, \ + last_entry.id: {}, \ + txs sent in last iteration: {}", + last_entry.id, + self.tx_sent_in_last_iteration_count + ); + } + + pub async fn assert_inflight_txs_count_equals(&mut self, value: usize) { + //sanity check + assert!(self.manager.operator_address(OperatorType::Blob).is_some()); + assert_eq!( + self.storage() + .await + .eth_sender_dal() + .get_inflight_txs(self.manager.operator_address(OperatorType::NonBlob)) + .await + .unwrap() + .len() + + self + .storage() + .await + .eth_sender_dal() + .get_inflight_txs(self.manager.operator_address(OperatorType::Blob)) + .await + .unwrap() + .len(), + value, + "Unexpected number of in-flight transactions" + ); + } +} diff --git a/core/node/eth_sender/src/tests.rs b/core/node/eth_sender/src/tests.rs index 45835a50c33b..e19ba225b7e1 100644 --- a/core/node/eth_sender/src/tests.rs +++ b/core/node/eth_sender/src/tests.rs @@ -1,51 +1,27 @@ -use std::sync::Arc; - use assert_matches::assert_matches; -use once_cell::sync::Lazy; use test_casing::{test_casing, Product}; -use zksync_config::{ - configs::eth_sender::{ProofSendingMode, PubdataSendingMode, SenderConfig}, - ContractsConfig, EthConfig, GasAdjusterConfig, -}; -use zksync_contracts::BaseSystemContractsHashes; -use zksync_dal::{Connection, ConnectionPool, Core, CoreDal}; -use zksync_eth_client::{clients::MockEthereum, BaseFees}; -use zksync_l1_contract_interface::i_executor::methods::{ExecuteBatches, ProveBatches}; -use zksync_node_fee_model::l1_gas_price::GasAdjuster; -use zksync_node_test_utils::{create_l1_batch, l1_batch_metadata_to_commitment_artifacts}; -use zksync_object_store::MockObjectStore; +use zksync_dal::{ConnectionPool, Core, CoreDal}; +use zksync_l1_contract_interface::i_executor::methods::ExecuteBatches; +use zksync_node_test_utils::create_l1_batch; use zksync_types::{ + aggregated_operations::AggregatedActionType, block::L1BatchHeader, commitment::{ L1BatchCommitmentMode, L1BatchMetaParameters, L1BatchMetadata, L1BatchWithMetadata, }, ethabi::Token, helpers::unix_timestamp_ms, - l2_to_l1_log::{L2ToL1Log, UserL2ToL1Log}, - pubdata_da::PubdataDA, web3::contract::Error, - Address, L1BatchNumber, ProtocolVersion, ProtocolVersionId, H256, + ProtocolVersionId, H256, }; use crate::{ - abstract_l1_interface::{L1BlockNumbers, OperatorType}, + abstract_l1_interface::OperatorType, aggregated_operations::AggregatedOperation, - Aggregator, EthSenderError, EthTxAggregator, EthTxManager, + tester::{EthSenderTester, TestL1Batch}, + EthSenderError, }; -// Alias to conveniently call static methods of `ETHSender`. -type MockEthTxManager = EthTxManager; - -static DUMMY_OPERATION: Lazy = Lazy::new(|| { - AggregatedOperation::Execute(ExecuteBatches { - l1_batches: vec![L1BatchWithMetadata { - header: create_l1_batch(1), - metadata: default_l1_batch_metadata(), - raw_published_factory_deps: Vec::new(), - }], - }) -}); - fn get_dummy_operation(number: u32) -> AggregatedOperation { AggregatedOperation::Execute(ExecuteBatches { l1_batches: vec![L1BatchWithMetadata { @@ -61,33 +37,7 @@ const COMMITMENT_MODES: [L1BatchCommitmentMode; 2] = [ L1BatchCommitmentMode::Validium, ]; -fn mock_l1_batch_header(number: u32) -> L1BatchHeader { - let mut header = L1BatchHeader::new( - L1BatchNumber(number), - 100, - BaseSystemContractsHashes { - bootloader: H256::repeat_byte(1), - default_aa: H256::repeat_byte(42), - }, - ProtocolVersionId::latest(), - ); - header.l1_tx_count = 3; - header.l2_tx_count = 5; - header.l2_to_l1_logs.push(UserL2ToL1Log(L2ToL1Log { - shard_id: 0, - is_service: false, - tx_number_in_block: 2, - sender: Address::repeat_byte(2), - key: H256::repeat_byte(3), - value: H256::zero(), - })); - header.l2_to_l1_messages.push(vec![22; 22]); - header.l2_to_l1_messages.push(vec![33; 33]); - - header -} - -fn mock_multicall_response() -> Token { +pub(crate) fn mock_multicall_response() -> Token { Token::Array(vec![ Token::Tuple(vec![Token::Bool(true), Token::Bytes(vec![1u8; 32])]), Token::Tuple(vec![Token::Bool(true), Token::Bytes(vec![2u8; 32])]), @@ -104,139 +54,7 @@ fn mock_multicall_response() -> Token { ]) } -#[derive(Debug)] -struct EthSenderTester { - conn: ConnectionPool, - gateway: Box, - manager: MockEthTxManager, - aggregator: EthTxAggregator, - gas_adjuster: Arc, -} - -impl EthSenderTester { - const WAIT_CONFIRMATIONS: u64 = 10; - const MAX_BASE_FEE_SAMPLES: usize = 3; - - async fn new( - connection_pool: ConnectionPool, - history: Vec, - non_ordering_confirmations: bool, - aggregator_operate_4844_mode: bool, - commitment_mode: L1BatchCommitmentMode, - ) -> Self { - let eth_sender_config = EthConfig::for_tests(); - let contracts_config = ContractsConfig::for_tests(); - let aggregator_config = SenderConfig { - aggregated_proof_sizes: vec![1], - ..eth_sender_config.clone().sender.unwrap() - }; - - let history: Vec<_> = history - .into_iter() - .map(|base_fee_per_gas| BaseFees { - base_fee_per_gas, - base_fee_per_blob_gas: 0.into(), - }) - .collect(); - - let gateway = MockEthereum::builder() - .with_fee_history( - std::iter::repeat_with(|| BaseFees { - base_fee_per_gas: 0, - base_fee_per_blob_gas: 0.into(), - }) - .take(Self::WAIT_CONFIRMATIONS as usize) - .chain(history) - .collect(), - ) - .with_non_ordering_confirmation(non_ordering_confirmations) - .with_call_handler(move |call, _| { - assert_eq!(call.to, Some(contracts_config.l1_multicall3_addr)); - mock_multicall_response() - }) - .build(); - gateway.advance_block_number(Self::WAIT_CONFIRMATIONS); - let gateway = Box::new(gateway); - - let gas_adjuster = Arc::new( - GasAdjuster::new( - Box::new(gateway.clone().into_client()), - GasAdjusterConfig { - max_base_fee_samples: Self::MAX_BASE_FEE_SAMPLES, - pricing_formula_parameter_a: 3.0, - pricing_formula_parameter_b: 2.0, - ..eth_sender_config.gas_adjuster.unwrap() - }, - PubdataSendingMode::Calldata, - commitment_mode, - ) - .await - .unwrap(), - ); - - let eth_sender = eth_sender_config.sender.clone().unwrap(); - let aggregator = EthTxAggregator::new( - connection_pool.clone(), - SenderConfig { - proof_sending_mode: ProofSendingMode::SkipEveryProof, - pubdata_sending_mode: PubdataSendingMode::Calldata, - ..eth_sender.clone() - }, - // Aggregator - unused - Aggregator::new( - aggregator_config.clone(), - MockObjectStore::arc(), - aggregator_operate_4844_mode, - commitment_mode, - ), - gateway.clone(), - // ZKsync contract address - Address::random(), - contracts_config.l1_multicall3_addr, - Address::random(), - Default::default(), - None, - ) - .await; - - let manager = EthTxManager::new( - connection_pool.clone(), - eth_sender.clone(), - gas_adjuster.clone(), - gateway.clone(), - None, - ); - Self { - gateway, - manager, - aggregator, - gas_adjuster, - conn: connection_pool, - } - } - - async fn storage(&self) -> Connection<'_, Core> { - self.conn.connection().await.unwrap() - } - - async fn get_block_numbers(&self) -> L1BlockNumbers { - let latest = self - .manager - .l1_interface() - .get_l1_block_numbers() - .await - .unwrap() - .latest; - let finalized = latest - Self::WAIT_CONFIRMATIONS as u32; - L1BlockNumbers { - finalized, - latest, - safe: finalized, - } - } -} - -fn l1_batch_with_metadata(header: L1BatchHeader) -> L1BatchWithMetadata { +pub(crate) fn l1_batch_with_metadata(header: L1BatchHeader) -> L1BatchWithMetadata { L1BatchWithMetadata { header, metadata: default_l1_batch_metadata(), @@ -244,7 +62,7 @@ fn l1_batch_with_metadata(header: L1BatchHeader) -> L1BatchWithMetadata { } } -fn default_l1_batch_metadata() -> L1BatchMetadata { +pub(crate) fn default_l1_batch_metadata() -> L1BatchMetadata { L1BatchMetadata { root_hash: H256::default(), rollup_last_leaf_index: 0, @@ -269,7 +87,7 @@ fn default_l1_batch_metadata() -> L1BatchMetadata { // Tests that we send multiple transactions and confirm them all in one iteration. #[test_casing(4, Product(([false, true], COMMITMENT_MODES)))] -#[tokio::test] +#[test_log::test(tokio::test)] async fn confirm_many( aggregator_operate_4844_mode: bool, commitment_mode: L1BatchCommitmentMode, @@ -284,106 +102,46 @@ async fn confirm_many( ) .await; - let mut hashes = vec![]; + let mut l1_batches = vec![]; - connection_pool - .clone() - .connection() - .await - .unwrap() - .protocol_versions_dal() - .save_protocol_version_with_tx(&ProtocolVersion::default()) - .await - .unwrap(); - - for number in 0..5 { - connection_pool - .clone() - .connection() - .await - .unwrap() - .blocks_dal() - .insert_mock_l1_batch(&mock_l1_batch_header(number + 1)) - .await - .unwrap(); - let tx = tester - .aggregator - .save_eth_tx( - &mut tester.conn.connection().await.unwrap(), - &get_dummy_operation(number + 1), - false, - ) - .await?; - let hash = tester - .manager - .send_eth_tx( - &mut tester.conn.connection().await.unwrap(), - &tx, - 0, - tester.get_block_numbers().await.latest, - ) - .await?; - hashes.push(hash); + let _genesis_batch = TestL1Batch::sealed(&mut tester).await; + for _ in 1..6 { + let l1_batch = TestL1Batch::sealed(&mut tester).await; + l1_batch.save_commit_tx(&mut tester).await; + l1_batches.push(l1_batch); } + tester.run_eth_sender_tx_manager_iteration().await; // check that we sent something - assert_eq!(tester.gateway.sent_tx_count(), 5); - assert_eq!( - tester - .storage() - .await - .eth_sender_dal() - .get_inflight_txs(tester.manager.operator_address(OperatorType::NonBlob)) - .await - .unwrap() - .len(), - 5 - ); + tester.assert_just_sent_tx_count_equals(5).await; - for hash in hashes { - tester - .gateway - .execute_tx(hash, true, EthSenderTester::WAIT_CONFIRMATIONS); + tester.run_eth_sender_tx_manager_iteration().await; + + for l1_batch in l1_batches { + l1_batch.execute_commit_tx(&mut tester).await; } - let to_resend = tester - .manager - .monitor_inflight_transactions_single_operator( - &mut tester.conn.connection().await.unwrap(), - tester.get_block_numbers().await, - OperatorType::NonBlob, - ) - .await?; + tester.run_eth_sender_tx_manager_iteration().await; - // check that transaction is marked as accepted - assert_eq!( - tester - .storage() - .await - .eth_sender_dal() - .get_inflight_txs(tester.manager.operator_address(OperatorType::NonBlob)) - .await - .unwrap() - .len(), - 0 - ); + // check that all transactions are marked as accepted + tester.assert_inflight_txs_count_equals(0).await; // also check that we didn't try to resend it - assert!(to_resend.is_none()); + tester.assert_just_sent_tx_count_equals(0).await; Ok(()) } // Tests that we resend first un-mined transaction every block with an increased gas price. #[test_casing(2, COMMITMENT_MODES)] -#[tokio::test] +#[test_log::test(tokio::test)] async fn resend_each_block(commitment_mode: L1BatchCommitmentMode) -> anyhow::Result<()> { let connection_pool = ConnectionPool::::test_pool().await; let mut tester = EthSenderTester::new( connection_pool.clone(), vec![7, 6, 5, 5, 5, 2, 1], false, - false, + true, commitment_mode, ) .await; @@ -392,25 +150,7 @@ async fn resend_each_block(commitment_mode: L1BatchCommitmentMode) -> anyhow::Re tester.gateway.advance_block_number(3); tester.gas_adjuster.keep_updated().await?; - connection_pool - .clone() - .connection() - .await - .unwrap() - .protocol_versions_dal() - .save_protocol_version_with_tx(&ProtocolVersion::default()) - .await - .unwrap(); - - connection_pool - .clone() - .connection() - .await - .unwrap() - .blocks_dal() - .insert_mock_l1_batch(&mock_l1_batch_header(1)) - .await - .unwrap(); + TestL1Batch::sealed(&mut tester).await; let block = tester.get_block_numbers().await.latest; @@ -418,7 +158,7 @@ async fn resend_each_block(commitment_mode: L1BatchCommitmentMode) -> anyhow::Re .aggregator .save_eth_tx( &mut tester.conn.connection().await.unwrap(), - &get_dummy_operation(1), + &get_dummy_operation(0), false, ) .await?; @@ -514,278 +254,151 @@ async fn resend_each_block(commitment_mode: L1BatchCommitmentMode) -> anyhow::Re // Tests that if transaction was mined, but not enough blocks has been mined since, // we won't mark it as confirmed but also won't resend it. #[test_casing(2, COMMITMENT_MODES)] -#[tokio::test] +#[test_log::test(tokio::test)] async fn dont_resend_already_mined(commitment_mode: L1BatchCommitmentMode) -> anyhow::Result<()> { let connection_pool = ConnectionPool::::test_pool().await; let mut tester = EthSenderTester::new( connection_pool.clone(), vec![100; 100], false, - false, + true, commitment_mode, ) .await; - connection_pool - .clone() - .connection() - .await - .unwrap() - .protocol_versions_dal() - .save_protocol_version_with_tx(&ProtocolVersion::default()) - .await - .unwrap(); - - connection_pool - .clone() - .connection() - .await - .unwrap() - .blocks_dal() - .insert_mock_l1_batch(&mock_l1_batch_header(1)) - .await - .unwrap(); - - let tx = tester - .aggregator - .save_eth_tx( - &mut tester.conn.connection().await.unwrap(), - &DUMMY_OPERATION, - false, - ) - .await - .unwrap(); + let _genesis_batch = TestL1Batch::sealed(&mut tester).await; + let l1_batch = TestL1Batch::sealed(&mut tester).await; + l1_batch.save_commit_tx(&mut tester).await; - let hash = tester - .manager - .send_eth_tx( - &mut tester.conn.connection().await.unwrap(), - &tx, - 0, - tester.get_block_numbers().await.latest, - ) - .await - .unwrap(); + tester.run_eth_sender_tx_manager_iteration().await; // check that we sent something and stored it in the db - assert_eq!(tester.gateway.sent_tx_count(), 1); - assert_eq!( - tester - .storage() - .await - .eth_sender_dal() - .get_inflight_txs(tester.manager.operator_address(OperatorType::NonBlob)) - .await - .unwrap() - .len(), - 1 - ); + tester.assert_just_sent_tx_count_equals(1).await; + tester.assert_inflight_txs_count_equals(1).await; // mine the transaction but don't have enough confirmations yet tester - .gateway - .execute_tx(hash, true, EthSenderTester::WAIT_CONFIRMATIONS - 1); - - let to_resend = tester - .manager - .monitor_inflight_transactions_single_operator( - &mut tester.conn.connection().await.unwrap(), - tester.get_block_numbers().await, - OperatorType::NonBlob, + .execute_tx( + l1_batch.number, + AggregatedActionType::Commit, + true, + // we use -2 as running eth_sender iteration implicitly advances block number by 1 + EthSenderTester::WAIT_CONFIRMATIONS - 2, ) - .await?; + .await; + tester.run_eth_sender_tx_manager_iteration().await; // check that transaction is still considered in-flight - assert_eq!( - tester - .storage() - .await - .eth_sender_dal() - .get_inflight_txs(tester.manager.operator_address(OperatorType::NonBlob)) - .await - .unwrap() - .len(), - 1 - ); + tester.assert_inflight_txs_count_equals(1).await; // also check that we didn't try to resend it - assert!(to_resend.is_none()); + tester.assert_just_sent_tx_count_equals(0).await; Ok(()) } #[test_casing(2, COMMITMENT_MODES)] -#[tokio::test] +#[test_log::test(tokio::test)] async fn three_scenarios(commitment_mode: L1BatchCommitmentMode) -> anyhow::Result<()> { let connection_pool = ConnectionPool::::test_pool().await; let mut tester = EthSenderTester::new( connection_pool.clone(), vec![100; 100], false, - false, + true, commitment_mode, ) .await; - let mut hashes = vec![]; + let _genesis_batch = TestL1Batch::sealed(&mut tester).await; - connection_pool - .clone() - .connection() - .await - .unwrap() - .protocol_versions_dal() - .save_protocol_version_with_tx(&ProtocolVersion::default()) - .await - .unwrap(); + let first_batch = TestL1Batch::sealed(&mut tester).await; + let second_batch = TestL1Batch::sealed(&mut tester).await; + let third_batch = TestL1Batch::sealed(&mut tester).await; + let fourth_batch = TestL1Batch::sealed(&mut tester).await; - for number in 0..3 { - connection_pool - .clone() - .connection() - .await - .unwrap() - .blocks_dal() - .insert_mock_l1_batch(&mock_l1_batch_header(number + 1)) - .await - .unwrap(); - let tx = tester - .aggregator - .save_eth_tx( - &mut tester.conn.connection().await.unwrap(), - &get_dummy_operation(number + 1), - false, - ) - .await - .unwrap(); - - let hash = tester - .manager - .send_eth_tx( - &mut tester.conn.connection().await.unwrap(), - &tx, - 0, - tester.get_block_numbers().await.latest, - ) - .await - .unwrap(); + first_batch.save_commit_tx(&mut tester).await; + second_batch.save_commit_tx(&mut tester).await; + third_batch.save_commit_tx(&mut tester).await; + fourth_batch.save_commit_tx(&mut tester).await; - hashes.push(hash); - } - - // check that we sent something - assert_eq!(tester.gateway.sent_tx_count(), 3); - - // mined & confirmed - tester - .gateway - .execute_tx(hashes[0], true, EthSenderTester::WAIT_CONFIRMATIONS); - // mined but not confirmed - tester - .gateway - .execute_tx(hashes[1], true, EthSenderTester::WAIT_CONFIRMATIONS - 1); + tester.run_eth_sender_tx_manager_iteration().await; + // we should have sent transactions for all batches for the first time + tester.assert_just_sent_tx_count_equals(4).await; - let (to_resend, _) = tester - .manager - .monitor_inflight_transactions_single_operator( - &mut tester.conn.connection().await.unwrap(), - tester.get_block_numbers().await, - OperatorType::NonBlob, - ) - .await? - .expect("we should be trying to resend the last tx"); + first_batch.execute_commit_tx(&mut tester).await; + second_batch.execute_commit_tx(&mut tester).await; + tester.run_eth_sender_tx_manager_iteration().await; // check that last 2 transactions are still considered in-flight - assert_eq!( - tester - .storage() - .await - .eth_sender_dal() - .get_inflight_txs(tester.manager.operator_address(OperatorType::NonBlob)) - .await - .unwrap() - .len(), - 2 - ); + tester.assert_inflight_txs_count_equals(2).await; - // last sent transaction has nonce == 2, because they start from 0 - assert_eq!(to_resend.nonce.0, 2); + //We should have resent only first not-mined transaction + third_batch.assert_commit_tx_just_sent(&mut tester).await; + tester.assert_just_sent_tx_count_equals(1).await; Ok(()) } #[should_panic(expected = "We can't operate after tx fail")] #[test_casing(2, COMMITMENT_MODES)] -#[tokio::test] +#[test_log::test(tokio::test)] async fn failed_eth_tx(commitment_mode: L1BatchCommitmentMode) { let connection_pool = ConnectionPool::::test_pool().await; let mut tester = EthSenderTester::new( connection_pool.clone(), vec![100; 100], false, - false, + true, commitment_mode, ) .await; - connection_pool - .clone() - .connection() - .await - .unwrap() - .protocol_versions_dal() - .save_protocol_version_with_tx(&ProtocolVersion::default()) - .await - .unwrap(); + let _genesis_batch = TestL1Batch::sealed(&mut tester).await; + let first_batch = TestL1Batch::sealed(&mut tester).await; - connection_pool - .clone() - .connection() - .await - .unwrap() - .blocks_dal() - .insert_mock_l1_batch(&mock_l1_batch_header(1)) - .await - .unwrap(); + first_batch.save_commit_tx(&mut tester).await; + tester.run_eth_sender_tx_manager_iteration().await; - let tx = tester - .aggregator - .save_eth_tx( - &mut tester.conn.connection().await.unwrap(), - &DUMMY_OPERATION, - false, - ) - .await - .unwrap(); + first_batch.fail_commit_tx(&mut tester).await; - let hash = tester - .manager - .send_eth_tx( - &mut tester.conn.connection().await.unwrap(), - &tx, - 0, - tester.get_block_numbers().await.latest, - ) - .await - .unwrap(); + tester.run_eth_sender_tx_manager_iteration().await; +} - // fail this tx - tester - .gateway - .execute_tx(hash, false, EthSenderTester::WAIT_CONFIRMATIONS); - tester - .manager - .monitor_inflight_transactions_single_operator( - &mut tester.conn.connection().await.unwrap(), - tester.get_block_numbers().await, - OperatorType::NonBlob, - ) - .await - .unwrap(); +#[test_log::test(tokio::test)] +async fn blob_transactions_are_resent_independently_of_non_blob_txs() { + let mut tester = EthSenderTester::new( + ConnectionPool::::test_pool().await, + vec![100; 100], + true, + true, + L1BatchCommitmentMode::Rollup, + ) + .await; + + let _genesis_l1_batch = TestL1Batch::sealed(&mut tester).await; + let first_l1_batch = TestL1Batch::sealed(&mut tester).await; + let second_l1_batch = TestL1Batch::sealed(&mut tester).await; + + first_l1_batch.save_commit_tx(&mut tester).await; + second_l1_batch.save_commit_tx(&mut tester).await; + tester.run_eth_sender_tx_manager_iteration().await; + // first iteration sends two commit txs for the first time + tester.assert_just_sent_tx_count_equals(2).await; + + first_l1_batch.save_prove_tx(&mut tester).await; + first_l1_batch.execute_commit_tx(&mut tester).await; + tester.run_eth_sender_tx_manager_iteration().await; + // second iteration sends first_batch prove tx and resends second_batch commit tx + tester.assert_just_sent_tx_count_equals(2).await; + + tester.run_eth_sender_tx_manager_iteration().await; + // we should resend both of those transactions here as they use different operators + tester.assert_just_sent_tx_count_equals(2).await; } #[test_casing(2, COMMITMENT_MODES)] -#[tokio::test] +#[test_log::test(tokio::test)] async fn correct_order_for_confirmations( commitment_mode: L1BatchCommitmentMode, ) -> anyhow::Result<()> { @@ -793,45 +406,21 @@ async fn correct_order_for_confirmations( ConnectionPool::::test_pool().await, vec![100; 100], true, - false, + true, commitment_mode, ) .await; - insert_genesis_protocol_version(&tester).await; - let genesis_l1_batch = insert_l1_batch(&tester, L1BatchNumber(0)).await; - let first_l1_batch = insert_l1_batch(&tester, L1BatchNumber(1)).await; - let second_l1_batch = insert_l1_batch(&tester, L1BatchNumber(2)).await; + let _genesis_l1_batch = TestL1Batch::sealed(&mut tester).await; + let first_l1_batch = TestL1Batch::sealed(&mut tester).await; + let second_l1_batch = TestL1Batch::sealed(&mut tester).await; - commit_l1_batch( - &mut tester, - genesis_l1_batch.clone(), - first_l1_batch.clone(), - true, - ) - .await; - prove_l1_batch( - &mut tester, - genesis_l1_batch.clone(), - first_l1_batch.clone(), - true, - ) - .await; - execute_l1_batches(&mut tester, vec![first_l1_batch.clone()], true).await; - commit_l1_batch( - &mut tester, - first_l1_batch.clone(), - second_l1_batch.clone(), - true, - ) - .await; - prove_l1_batch( - &mut tester, - first_l1_batch.clone(), - second_l1_batch.clone(), - true, - ) - .await; + first_l1_batch.commit(&mut tester, true).await; + first_l1_batch.prove(&mut tester, true).await; + first_l1_batch.execute(&mut tester, true).await; + + second_l1_batch.commit(&mut tester, true).await; + second_l1_batch.prove(&mut tester, true).await; let l1_batches = tester .storage() @@ -843,7 +432,7 @@ async fn correct_order_for_confirmations( assert_eq!(l1_batches.len(), 1); assert_eq!(l1_batches[0].header.number.0, 2); - execute_l1_batches(&mut tester, vec![second_l1_batch.clone()], true).await; + second_l1_batch.execute(&mut tester, true).await; let l1_batches = tester .storage() .await @@ -856,7 +445,7 @@ async fn correct_order_for_confirmations( } #[test_casing(2, COMMITMENT_MODES)] -#[tokio::test] +#[test_log::test(tokio::test)] async fn skipped_l1_batch_at_the_start( commitment_mode: L1BatchCommitmentMode, ) -> anyhow::Result<()> { @@ -864,79 +453,35 @@ async fn skipped_l1_batch_at_the_start( ConnectionPool::::test_pool().await, vec![100; 100], true, - false, + true, commitment_mode, ) .await; - insert_genesis_protocol_version(&tester).await; - let genesis_l1_batch = insert_l1_batch(&tester, L1BatchNumber(0)).await; - let first_l1_batch = insert_l1_batch(&tester, L1BatchNumber(1)).await; - let second_l1_batch = insert_l1_batch(&tester, L1BatchNumber(2)).await; + let _genesis_l1_batch = TestL1Batch::sealed(&mut tester).await; + let first_l1_batch = TestL1Batch::sealed(&mut tester).await; + let second_l1_batch = TestL1Batch::sealed(&mut tester).await; - commit_l1_batch( - &mut tester, - genesis_l1_batch.clone(), - first_l1_batch.clone(), - true, - ) - .await; - prove_l1_batch( - &mut tester, - genesis_l1_batch.clone(), - first_l1_batch.clone(), - true, - ) - .await; - execute_l1_batches(&mut tester, vec![first_l1_batch.clone()], true).await; - commit_l1_batch( - &mut tester, - first_l1_batch.clone(), - second_l1_batch.clone(), - true, - ) - .await; - prove_l1_batch( - &mut tester, - first_l1_batch.clone(), - second_l1_batch.clone(), - true, - ) - .await; - execute_l1_batches(&mut tester, vec![second_l1_batch.clone()], true).await; + first_l1_batch.commit(&mut tester, true).await; + first_l1_batch.prove(&mut tester, true).await; + first_l1_batch.execute(&mut tester, true).await; - let third_l1_batch = insert_l1_batch(&tester, L1BatchNumber(3)).await; - let fourth_l1_batch = insert_l1_batch(&tester, L1BatchNumber(4)).await; - // DO NOT CONFIRM THIRD BLOCK - let third_l1_batch_commit_tx_hash = commit_l1_batch( - &mut tester, - second_l1_batch.clone(), - third_l1_batch.clone(), - false, - ) - .await; + second_l1_batch.commit(&mut tester, true).await; + second_l1_batch.prove(&mut tester, true).await; + second_l1_batch.execute(&mut tester, true).await; + + let third_l1_batch = TestL1Batch::sealed(&mut tester).await; + let fourth_l1_batch = TestL1Batch::sealed(&mut tester).await; + + // DO NOT CONFIRM PROVE TXS + third_l1_batch.commit(&mut tester, true).await; + fourth_l1_batch.commit(&mut tester, true).await; + third_l1_batch.prove(&mut tester, false).await; + fourth_l1_batch.prove(&mut tester, false).await; + + //sanity check, 2 commit txs are still in-flight + tester.assert_inflight_txs_count_equals(2).await; - prove_l1_batch( - &mut tester, - second_l1_batch.clone(), - third_l1_batch.clone(), - true, - ) - .await; - commit_l1_batch( - &mut tester, - third_l1_batch.clone(), - fourth_l1_batch.clone(), - true, - ) - .await; - prove_l1_batch( - &mut tester, - third_l1_batch.clone(), - fourth_l1_batch.clone(), - true, - ) - .await; let l1_batches = tester .storage() .await @@ -946,7 +491,7 @@ async fn skipped_l1_batch_at_the_start( .unwrap(); assert_eq!(l1_batches.len(), 2); - confirm_tx(&mut tester, third_l1_batch_commit_tx_hash).await; + third_l1_batch.execute_prove_tx(&mut tester).await; let l1_batches = tester .storage() .await @@ -959,7 +504,7 @@ async fn skipped_l1_batch_at_the_start( } #[test_casing(2, COMMITMENT_MODES)] -#[tokio::test] +#[test_log::test(tokio::test)] async fn skipped_l1_batch_in_the_middle( commitment_mode: L1BatchCommitmentMode, ) -> anyhow::Result<()> { @@ -967,71 +512,31 @@ async fn skipped_l1_batch_in_the_middle( ConnectionPool::::test_pool().await, vec![100; 100], true, - false, + true, commitment_mode, ) .await; - insert_genesis_protocol_version(&tester).await; - let genesis_l1_batch = insert_l1_batch(&tester, L1BatchNumber(0)).await; - let first_l1_batch = insert_l1_batch(&tester, L1BatchNumber(1)).await; - let second_l1_batch = insert_l1_batch(&tester, L1BatchNumber(2)).await; - commit_l1_batch( - &mut tester, - genesis_l1_batch.clone(), - first_l1_batch.clone(), - true, - ) - .await; - prove_l1_batch(&mut tester, genesis_l1_batch, first_l1_batch.clone(), true).await; - execute_l1_batches(&mut tester, vec![first_l1_batch.clone()], true).await; - commit_l1_batch( - &mut tester, - first_l1_batch.clone(), - second_l1_batch.clone(), - true, - ) - .await; - prove_l1_batch( - &mut tester, - first_l1_batch.clone(), - second_l1_batch.clone(), - true, - ) - .await; + let _genesis_l1_batch = TestL1Batch::sealed(&mut tester).await; + let first_l1_batch = TestL1Batch::sealed(&mut tester).await; + let second_l1_batch = TestL1Batch::sealed(&mut tester).await; + + first_l1_batch.commit(&mut tester, true).await; + first_l1_batch.prove(&mut tester, true).await; + first_l1_batch.execute(&mut tester, true).await; + + second_l1_batch.commit(&mut tester, true).await; + second_l1_batch.prove(&mut tester, true).await; + + let third_l1_batch = TestL1Batch::sealed(&mut tester).await; + let fourth_l1_batch = TestL1Batch::sealed(&mut tester).await; - let third_l1_batch = insert_l1_batch(&tester, L1BatchNumber(3)).await; - let fourth_l1_batch = insert_l1_batch(&tester, L1BatchNumber(4)).await; // DO NOT CONFIRM THIRD BLOCK - let third_l1_batch_commit_tx_hash = commit_l1_batch( - &mut tester, - second_l1_batch.clone(), - third_l1_batch.clone(), - false, - ) - .await; + third_l1_batch.commit(&mut tester, true).await; + third_l1_batch.prove(&mut tester, false).await; + fourth_l1_batch.commit(&mut tester, true).await; + fourth_l1_batch.prove(&mut tester, true).await; - prove_l1_batch( - &mut tester, - second_l1_batch.clone(), - third_l1_batch.clone(), - true, - ) - .await; - commit_l1_batch( - &mut tester, - third_l1_batch.clone(), - fourth_l1_batch.clone(), - true, - ) - .await; - prove_l1_batch( - &mut tester, - third_l1_batch.clone(), - fourth_l1_batch.clone(), - true, - ) - .await; let l1_batches = tester .storage() .await @@ -1043,7 +548,7 @@ async fn skipped_l1_batch_in_the_middle( assert_eq!(l1_batches.len(), 3); assert_eq!(l1_batches[0].header.number.0, 2); - confirm_tx(&mut tester, third_l1_batch_commit_tx_hash).await; + third_l1_batch.execute_commit_tx(&mut tester).await; let l1_batches = tester .storage() .await @@ -1056,13 +561,13 @@ async fn skipped_l1_batch_in_the_middle( } #[test_casing(2, COMMITMENT_MODES)] -#[tokio::test] +#[test_log::test(tokio::test)] async fn test_parse_multicall_data(commitment_mode: L1BatchCommitmentMode) { let tester = EthSenderTester::new( ConnectionPool::::test_pool().await, vec![100; 100], false, - false, + true, commitment_mode, ) .await; @@ -1128,155 +633,16 @@ async fn test_parse_multicall_data(commitment_mode: L1BatchCommitmentMode) { } #[test_casing(2, COMMITMENT_MODES)] -#[tokio::test] +#[test_log::test(tokio::test)] async fn get_multicall_data(commitment_mode: L1BatchCommitmentMode) { let mut tester = EthSenderTester::new( ConnectionPool::::test_pool().await, vec![100; 100], false, - false, + true, commitment_mode, ) .await; let multicall_data = tester.aggregator.get_multicall_data().await; assert!(multicall_data.is_ok()); } - -async fn insert_genesis_protocol_version(tester: &EthSenderTester) { - tester - .storage() - .await - .protocol_versions_dal() - .save_protocol_version_with_tx(&ProtocolVersion::default()) - .await - .unwrap(); -} - -async fn insert_l1_batch(tester: &EthSenderTester, number: L1BatchNumber) -> L1BatchHeader { - let header = create_l1_batch(number.0); - - // Save L1 batch to the database - tester - .storage() - .await - .blocks_dal() - .insert_mock_l1_batch(&header) - .await - .unwrap(); - let metadata = default_l1_batch_metadata(); - tester - .storage() - .await - .blocks_dal() - .save_l1_batch_tree_data(header.number, &metadata.tree_data()) - .await - .unwrap(); - tester - .storage() - .await - .blocks_dal() - .save_l1_batch_commitment_artifacts( - header.number, - &l1_batch_metadata_to_commitment_artifacts(&metadata), - ) - .await - .unwrap(); - header -} - -async fn execute_l1_batches( - tester: &mut EthSenderTester, - l1_batches: Vec, - confirm: bool, -) -> H256 { - let operation = AggregatedOperation::Execute(ExecuteBatches { - l1_batches: l1_batches.into_iter().map(l1_batch_with_metadata).collect(), - }); - send_operation(tester, operation, confirm).await -} - -async fn prove_l1_batch( - tester: &mut EthSenderTester, - last_committed_l1_batch: L1BatchHeader, - l1_batch: L1BatchHeader, - confirm: bool, -) -> H256 { - let operation = AggregatedOperation::PublishProofOnchain(ProveBatches { - prev_l1_batch: l1_batch_with_metadata(last_committed_l1_batch), - l1_batches: vec![l1_batch_with_metadata(l1_batch)], - proofs: vec![], - should_verify: false, - }); - send_operation(tester, operation, confirm).await -} - -async fn commit_l1_batch( - tester: &mut EthSenderTester, - last_committed_l1_batch: L1BatchHeader, - l1_batch: L1BatchHeader, - confirm: bool, -) -> H256 { - let operation = AggregatedOperation::Commit( - l1_batch_with_metadata(last_committed_l1_batch), - vec![l1_batch_with_metadata(l1_batch)], - PubdataDA::Calldata, - ); - send_operation(tester, operation, confirm).await -} - -async fn send_operation( - tester: &mut EthSenderTester, - aggregated_operation: AggregatedOperation, - confirm: bool, -) -> H256 { - let tx = tester - .aggregator - .save_eth_tx( - &mut tester.conn.connection().await.unwrap(), - &aggregated_operation, - false, - ) - .await - .unwrap(); - - let hash = tester - .manager - .send_eth_tx( - &mut tester.conn.connection().await.unwrap(), - &tx, - 0, - tester.get_block_numbers().await.latest, - ) - .await - .unwrap(); - - if confirm { - confirm_tx(tester, hash).await; - } - hash -} - -async fn confirm_tx(tester: &mut EthSenderTester, hash: H256) { - tester - .gateway - .execute_tx(hash, true, EthSenderTester::WAIT_CONFIRMATIONS); - tester - .manager - .monitor_inflight_transactions_single_operator( - &mut tester.conn.connection().await.unwrap(), - tester.get_block_numbers().await, - OperatorType::NonBlob, - ) - .await - .unwrap(); - - tester - .manager - .monitor_inflight_transactions_single_operator( - &mut tester.conn.connection().await.unwrap(), - tester.get_block_numbers().await, - OperatorType::Blob, - ) - .await - .unwrap(); -} diff --git a/core/node/test_utils/src/lib.rs b/core/node/test_utils/src/lib.rs index ee3503322aea..614d64805b9c 100644 --- a/core/node/test_utils/src/lib.rs +++ b/core/node/test_utils/src/lib.rs @@ -17,6 +17,7 @@ use zksync_types::{ fee::Fee, fee_model::BatchFeeInput, l2::L2Tx, + l2_to_l1_log::{L2ToL1Log, UserL2ToL1Log}, protocol_version::ProtocolSemanticVersion, snapshots::{SnapshotRecoveryStatus, SnapshotStorageLog}, transaction_request::PaymasterParams, @@ -46,12 +47,29 @@ pub fn create_l2_block(number: u32) -> L2BlockHeader { /// Creates an L1 batch header with the specified number and deterministic contents. pub fn create_l1_batch(number: u32) -> L1BatchHeader { - L1BatchHeader::new( + let mut header = L1BatchHeader::new( L1BatchNumber(number), number.into(), - BaseSystemContractsHashes::default(), + BaseSystemContractsHashes { + bootloader: H256::repeat_byte(1), + default_aa: H256::repeat_byte(42), + }, ProtocolVersionId::latest(), - ) + ); + header.l1_tx_count = 3; + header.l2_tx_count = 5; + header.l2_to_l1_logs.push(UserL2ToL1Log(L2ToL1Log { + shard_id: 0, + is_service: false, + tx_number_in_block: 2, + sender: Address::repeat_byte(2), + key: H256::repeat_byte(3), + value: H256::zero(), + })); + header.l2_to_l1_messages.push(vec![22; 22]); + header.l2_to_l1_messages.push(vec![33; 33]); + + header } /// Creates metadata for an L1 batch with the specified number.