From 3f8c893f4cd67bc04e3ee2d55467d40bcc15012d Mon Sep 17 00:00:00 2001 From: Nicolas Villanueva Date: Thu, 28 Sep 2023 04:49:58 -0700 Subject: [PATCH 1/2] chore: update eth_call to serialize output data in console (#140) --- src/node.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/node.rs b/src/node.rs index 2503cad8..00e3993f 100644 --- a/src/node.rs +++ b/src/node.rs @@ -833,10 +833,12 @@ impl InMemoryNode { match &tx_result.result { ExecutionResult::Success { output } => { - log::info!("Call: {} {:?}", "SUCCESS".green(), output) + log::info!("Call: {}", "SUCCESS".green()); + let output_bytes = zksync_basic_types::Bytes::from(output.clone()); + log::info!("Output: {}", serde_json::to_string(&output_bytes).unwrap()); } ExecutionResult::Revert { output } => { - log::info!("Call: {}: {}", "FAILED".red(), output) + log::info!("Call: {}: {}", "FAILED".red(), output); } ExecutionResult::Halt { reason } => log::info!("Call: {} {}", "HALTED".red(), reason), }; From f19fe6cb3be1f4eb015ade67b53366ce291dc7cd Mon Sep 17 00:00:00 2001 From: Nisheeth Barthwal Date: Thu, 28 Sep 2023 21:47:26 +0200 Subject: [PATCH 2/2] chore: align current_miniblock, current_batch, current_timestamp to point to the latest entities (#135) * normalize current_miniblock, current_batch, and current_timestamp to have latest values * fix compute_hash to use miniblock number * add tests for mine_empty_blocks * merge main * fix test, remove println! * fix tests * rename to BlockContext --- e2e-tests/test/evm-apis.test.ts | 6 +- e2e-tests/test/hardhat-apis.test.ts | 2 +- src/evm.rs | 4 +- src/hardhat.rs | 6 +- src/node.rs | 174 ++++++++++++++++++++-------- src/testing.rs | 8 +- src/utils.rs | 167 +++++++++++++++++++++++--- 7 files changed, 287 insertions(+), 80 deletions(-) diff --git a/e2e-tests/test/evm-apis.test.ts b/e2e-tests/test/evm-apis.test.ts index d94700e2..112b8a2f 100644 --- a/e2e-tests/test/evm-apis.test.ts +++ b/e2e-tests/test/evm-apis.test.ts @@ -36,7 +36,7 @@ describe("evm_increaseTime", function () { to: userWallet.address, value: ethers.utils.parseEther("0.1"), }); - expectedTimestamp += 1; // New transaction will add a second block + expectedTimestamp += 2; // New transaction will add two blocks // Assert const newBlockTimestamp = (await provider.getBlock("latest")).timestamp; @@ -60,7 +60,7 @@ describe("evm_setNextBlockTimestamp", function () { to: userWallet.address, value: ethers.utils.parseEther("0.1"), }); - expectedTimestamp += 1; // New transaction will add a second block + expectedTimestamp += 2; // New transaction will add two blocks // Assert const newBlockTimestamp = (await provider.getBlock("latest")).timestamp; @@ -84,7 +84,7 @@ describe("evm_setTime", function () { to: userWallet.address, value: ethers.utils.parseEther("0.1"), }); - expectedTimestamp += 1; // New transaction will add a second block + expectedTimestamp += 2; // New transaction will add two blocks // Assert const newBlockTimestamp = (await provider.getBlock("latest")).timestamp; diff --git a/e2e-tests/test/hardhat-apis.test.ts b/e2e-tests/test/hardhat-apis.test.ts index 43f1df2a..9c01e882 100644 --- a/e2e-tests/test/hardhat-apis.test.ts +++ b/e2e-tests/test/hardhat-apis.test.ts @@ -54,7 +54,7 @@ describe("hardhat_mine", function () { const latestBlock = await provider.getBlock("latest"); expect(latestBlock.number).to.equal(startingBlock.number + numberOfBlocks, "Block number mismatch"); expect(latestBlock.timestamp).to.equal( - startingTimestamp + (numberOfBlocks - 1) * intervalInSeconds * 1000, + startingTimestamp + (numberOfBlocks - 1) * intervalInSeconds * 1000 + 1, "Timestamp mismatch" ); }); diff --git a/src/evm.rs b/src/evm.rs index 242720f8..6938071f 100644 --- a/src/evm.rs +++ b/src/evm.rs @@ -466,7 +466,7 @@ mod tests { .expect("block exists"); assert_eq!(start_block.number + 1, current_block.number); - assert_eq!(start_block.timestamp + 1000, current_block.timestamp); + assert_eq!(start_block.timestamp + 1, current_block.timestamp); let result = evm.evm_mine().await.expect("evm_mine"); assert_eq!(&result, "0x0"); @@ -478,6 +478,6 @@ mod tests { .expect("block exists"); assert_eq!(start_block.number + 2, current_block.number); - assert_eq!(start_block.timestamp + 2000, current_block.timestamp); + assert_eq!(start_block.timestamp + 2, current_block.timestamp); } } diff --git a/src/hardhat.rs b/src/hardhat.rs index 4d645225..95425aa8 100644 --- a/src/hardhat.rs +++ b/src/hardhat.rs @@ -316,7 +316,7 @@ mod tests { .expect("block exists"); assert_eq!(start_block.number + 1, current_block.number); - assert_eq!(start_block.timestamp + 1000, current_block.timestamp); + assert_eq!(start_block.timestamp + 1, current_block.timestamp); let result = hardhat .hardhat_mine(None, None) .await @@ -330,7 +330,7 @@ mod tests { .expect("block exists"); assert_eq!(start_block.number + 2, current_block.number); - assert_eq!(start_block.timestamp + 2000, current_block.timestamp); + assert_eq!(start_block.timestamp + 2, current_block.timestamp); } #[tokio::test] @@ -347,7 +347,7 @@ mod tests { let num_blocks = 5; let interval = 3; - let start_timestamp = start_block.timestamp + 1_000; + let start_timestamp = start_block.timestamp + 1; let result = hardhat .hardhat_mine(Some(U64::from(num_blocks)), Some(U64::from(interval))) diff --git a/src/node.rs b/src/node.rs index 00e3993f..d732a682 100644 --- a/src/node.rs +++ b/src/node.rs @@ -86,12 +86,12 @@ pub const ESTIMATE_GAS_ACCEPTABLE_OVERESTIMATION: u32 = 1_000; /// The factor by which to scale the gasLimit. pub const ESTIMATE_GAS_SCALE_FACTOR: f32 = 1.3; -pub fn compute_hash(block_number: u32, tx_hash: H256) -> H256 { +pub fn compute_hash(block_number: u64, tx_hash: H256) -> H256 { let digest = [&block_number.to_be_bytes()[..], tx_hash.as_bytes()].concat(); H256(keccak256(&digest)) } -pub fn create_empty_block(block_number: u32, timestamp: u64, batch: u32) -> Block { +pub fn create_empty_block(block_number: u64, timestamp: u64, batch: u32) -> Block { let hash = compute_hash(block_number, H256::zero()); Block { hash, @@ -238,13 +238,14 @@ pub struct TransactionResult { /// Helper struct for InMemoryNode. /// S - is the Source of the Fork. pub struct InMemoryNodeInner { - /// Timestamp, batch number that will be used by the next block. + /// The latest timestamp that was already generated. + /// Next block will be current_timestamp + 1 pub current_timestamp: u64, - /// Batch number that will be used by the next block. + /// The latest batch number that was already generated. + /// Next block will be current_batch + 1 pub current_batch: u32, /// The latest miniblock number that was already generated. /// Next transaction will go to the block current_miniblock + 1 - /// (for now, this is a different behavior than the current_batch) pub current_miniblock: u64, pub l1_gas_price: u64, // Map from transaction to details about the exeuction @@ -278,16 +279,26 @@ type L2TxResult = ( VmExecutionResultAndLogs, Block, HashMap>, + BlockContext, ); impl InMemoryNodeInner { - pub fn create_l1_batch_env(&self, storage: StoragePtr) -> L1BatchEnv { + pub fn create_l1_batch_env( + &self, + storage: StoragePtr, + ) -> (L1BatchEnv, BlockContext) { let last_l2_block = load_last_l2_block(storage); - L1BatchEnv { + let block_ctx = BlockContext::from_current( + self.current_batch, + self.current_miniblock, + self.current_timestamp, + ); + let block_ctx = block_ctx.new_batch(); + let batch_env = L1BatchEnv { // TODO: set the previous batch hash properly (take from fork, when forking, and from local storage, when this is not the first block). previous_batch_hash: None, - number: L1BatchNumber::from(self.current_batch), - timestamp: self.current_timestamp, + number: L1BatchNumber::from(block_ctx.batch), + timestamp: block_ctx.timestamp, l1_gas_price: self.l1_gas_price, fair_l2_gas_price: L2_GAS_PRICE, fee_account: H160::zero(), @@ -295,8 +306,8 @@ impl InMemoryNodeInner { first_l2_block: vm::L2BlockEnv { // the 'current_miniblock' contains the block that was already produced. // So the next one should be one higher. - number: self.current_miniblock.saturating_add(1) as u32, - timestamp: self.current_timestamp, + number: block_ctx.miniblock as u32, + timestamp: block_ctx.timestamp, prev_block_hash: last_l2_block.hash, // This is only used during zksyncEra block timestamp/number transition. // In case of starting a new network, it doesn't matter. @@ -307,7 +318,9 @@ impl InMemoryNodeInner { // depend on block number or timestamp. max_virtual_blocks_to_create: 1, }, - } + }; + + (batch_env, block_ctx) } pub fn create_system_env( @@ -414,7 +427,7 @@ impl InMemoryNodeInner { let storage = storage_view.to_rc_ptr(); let execution_mode = TxExecutionMode::EstimateFee; - let mut batch_env = self.create_l1_batch_env(storage.clone()); + let (mut batch_env, _) = self.create_l1_batch_env(storage.clone()); batch_env.l1_gas_price = l1_gas_price; let system_env = self.create_system_env( self.system_contracts.contracts_for_fee_estimate().clone(), @@ -696,8 +709,8 @@ impl InMemoryNode { blocks.insert(f.l2_block.hash, f.l2_block.clone()); InMemoryNodeInner { - current_timestamp: f.block_timestamp + 1, - current_batch: f.l1_block.0 + 1, + current_timestamp: f.block_timestamp, + current_batch: f.l1_block.0, current_miniblock: f.l2_miniblock, l1_gas_price: f.l1_gas_price, tx_results: Default::default(), @@ -719,11 +732,14 @@ impl InMemoryNode { let mut block_hashes = HashMap::::new(); block_hashes.insert(0, H256::zero()); let mut blocks = HashMap::>::new(); - blocks.insert(H256::zero(), create_empty_block(0, 0, 1)); + blocks.insert( + H256::zero(), + create_empty_block(0, NON_FORK_FIRST_BLOCK_TIMESTAMP, 0), + ); InMemoryNodeInner { current_timestamp: NON_FORK_FIRST_BLOCK_TIMESTAMP, - current_batch: 1, + current_batch: 0, current_miniblock: 0, l1_gas_price: L1_GAS_PRICE, tx_results: Default::default(), @@ -802,7 +818,7 @@ impl InMemoryNode { // init vm - let batch_env = inner.create_l1_batch_env(storage.clone()); + let (batch_env, _) = inner.create_l1_batch_env(storage.clone()); let system_env = inner.create_system_env(bootloader_code.clone(), execution_mode); let mut vm = Vm::new(batch_env, system_env, storage, HistoryDisabled); @@ -1027,7 +1043,7 @@ impl InMemoryNode { let storage = StorageView::new(&inner.fork_storage).to_rc_ptr(); - let batch_env = inner.create_l1_batch_env(storage.clone()); + let (batch_env, block_ctx) = inner.create_l1_batch_env(storage.clone()); // if we are impersonating an account, we need to use non-verifying system contracts let nonverifying_contracts; @@ -1176,7 +1192,7 @@ impl InMemoryNode { } // The computed block hash here will be different than that in production. - let hash = compute_hash(batch_env.number.0, l2_tx.hash()); + let hash = compute_hash(block_ctx.miniblock, l2_tx.hash()); let mut transaction = zksync_types::api::Transaction::from(l2_tx); let block_hash = inner @@ -1191,7 +1207,7 @@ impl InMemoryNode { let block = Block { hash, - number: U64::from(inner.current_miniblock.saturating_add(1)), + number: U64::from(block_ctx.miniblock), timestamp: U256::from(batch_env.timestamp), l1_batch_number: Some(U64::from(batch_env.number.0)), transactions: vec![TransactionVariant::Full(transaction)], @@ -1212,7 +1228,7 @@ impl InMemoryNode { vm.execute(vm::VmExecutionMode::Bootloader); let modified_keys = storage.borrow().modified_storage_keys().clone(); - Ok((modified_keys, tx_result, block, bytecodes)) + Ok((modified_keys, tx_result, block, bytecodes, block_ctx)) } /// Runs L2 transaction and commits it to a new block. @@ -1229,7 +1245,7 @@ impl InMemoryNode { inner.filters.notify_new_pending_transaction(tx_hash); } - let (keys, result, block, bytecodes) = + let (keys, result, block, bytecodes, block_ctx) = self.run_l2_tx_inner(l2_tx.clone(), execution_mode)?; if let ExecutionResult::Halt { reason } = result.result { @@ -1261,8 +1277,6 @@ impl InMemoryNode { ) } - let current_miniblock = inner.current_miniblock.saturating_add(1); - for (log_idx, event) in result.logs.events.iter().enumerate() { inner.filters.notify_new_log( &Log { @@ -1279,7 +1293,7 @@ impl InMemoryNode { log_type: None, removed: None, }, - U64::from(current_miniblock), + block.number, ); } let tx_receipt = TransactionReceipt { @@ -1329,48 +1343,106 @@ impl InMemoryNode { info: TxExecutionInfo { tx: l2_tx, batch_number: block.l1_batch_number.unwrap_or_default().as_u32(), - miniblock_number: current_miniblock, + miniblock_number: block.number.as_u64(), result, }, receipt: tx_receipt, }, ); - let block_hash = block.hash; - inner.block_hashes.insert(current_miniblock, block.hash); - inner.blocks.insert(block.hash, block); - inner.filters.notify_new_block(block_hash); // With the introduction of 'l2 blocks' (and virtual blocks), // we are adding one l2 block at the end of each batch (to handle things like remaining events etc). + // You can look at insert_fictive_l2_block function in VM to see how this fake block is inserted. + let block_ctx = block_ctx.new_block(); + let empty_block_at_end_of_batch = + create_empty_block(block_ctx.miniblock, block_ctx.timestamp, block_ctx.batch); + + let new_batch = inner.current_batch.saturating_add(1); + let mut current_miniblock = inner.current_miniblock; + let mut current_timestamp = inner.current_timestamp; + + for block in vec![block, empty_block_at_end_of_batch] { + current_miniblock = current_miniblock.saturating_add(1); + current_timestamp = current_timestamp.saturating_add(1); + + let actual_l1_batch_number = block + .l1_batch_number + .expect("block must have a l1_batch_number"); + if actual_l1_batch_number.as_u32() != new_batch { + panic!( + "expected next block to have batch_number {}, got {}", + new_batch, + actual_l1_batch_number.as_u32() + ); + } - let empty_block_at_end_of_batch = create_empty_block( - (current_miniblock + 1) as u32, - inner.current_timestamp + 1, - inner.current_batch, - ); - let empty_block_hash = empty_block_at_end_of_batch.hash; - inner - .block_hashes - .insert(current_miniblock + 1, empty_block_hash); - inner.blocks.insert( - empty_block_at_end_of_batch.hash, - empty_block_at_end_of_batch, - ); - inner.filters.notify_new_block(empty_block_hash); + if block.number.as_u64() != current_miniblock { + panic!( + "expected next block to have miniblock {}, got {}", + current_miniblock, + block.number.as_u64() + ); + } - { - // That's why here, we increase the batch by 1, but miniblock (and timestamp) by 2. - // You can look at insert_fictive_l2_block function in VM to see how this fake block is inserted. + if block.timestamp.as_u64() != current_timestamp { + panic!( + "expected next block to have timestamp {}, got {}", + current_timestamp, + block.timestamp.as_u64() + ); + } - inner.current_batch += 1; - inner.current_miniblock += 2; - inner.current_timestamp += 2; + let block_hash = block.hash; + inner.block_hashes.insert(block.number.as_u64(), block.hash); + inner.blocks.insert(block.hash, block); + inner.filters.notify_new_block(block_hash); } + inner.current_batch = new_batch; + inner.current_miniblock = current_miniblock; + inner.current_timestamp = current_timestamp; + Ok(()) } } +/// Keeps track of a block's batch number, miniblock number and timestamp. +/// Useful for keeping track of the current context when creating multiple blocks. +pub struct BlockContext { + pub batch: u32, + pub miniblock: u64, + pub timestamp: u64, +} + +impl BlockContext { + /// Create the current instance that represents the latest block. + pub fn from_current(batch: u32, miniblock: u64, timestamp: u64) -> Self { + Self { + batch, + miniblock, + timestamp, + } + } + + /// Create the next batch instance that has all parameters incremented by `1`. + pub fn new_batch(&self) -> Self { + Self { + batch: self.batch.saturating_add(1), + miniblock: self.miniblock.saturating_add(1), + timestamp: self.timestamp.saturating_add(1), + } + } + + /// Create the next batch instance that uses the same batch number, and has all other parameters incremented by `1`. + pub fn new_block(&self) -> BlockContext { + Self { + batch: self.batch, + miniblock: self.miniblock.saturating_add(1), + timestamp: self.timestamp.saturating_add(1), + } + } +} + impl EthNamespaceT for InMemoryNode { /// Returns the chain ID of the node. fn chain_id(&self) -> jsonrpc_core::BoxFuture> { diff --git a/src/testing.rs b/src/testing.rs index 87c30de2..a2f9399d 100644 --- a/src/testing.rs +++ b/src/testing.rs @@ -357,12 +357,12 @@ impl RawTransactionsResponseBuilder { /// Applies a transaction with a given hash to the node and returns the block hash. pub fn apply_tx(node: &InMemoryNode, tx_hash: H256) -> H256 { - let current_batch = node + let next_miniblock = node .get_inner() .read() - .map(|reader| reader.current_batch) + .map(|reader| reader.current_miniblock.saturating_add(1)) .expect("failed getting current batch number"); - let produced_block_hash = compute_hash(current_batch, tx_hash); + let produced_block_hash = compute_hash(next_miniblock, tx_hash); let private_key = H256::random(); let from_account = PackedEthSignature::address_from_private_key(&private_key) @@ -561,7 +561,7 @@ mod test { let actual_block_hash = apply_tx(&node, H256::repeat_byte(0x01)); assert_eq!( - H256::from_str("0x89c0aa770eba1f187235bdad80de9c01fe81bca415d442ca892f087da56fa109") + H256::from_str("0xd97ba6a5ab0f2d7fbfc697251321cce20bff3da2b0ddaf12c80f80f0ab270b15") .unwrap(), actual_block_hash, ); diff --git a/src/utils.rs b/src/utils.rs index a3300882..9605f152 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -98,15 +98,19 @@ pub fn mine_empty_blocks( interval_ms: u64, ) { // build and insert new blocks - for _ in 0..num_blocks { + for i in 0..num_blocks { // roll the vm - let (keys, bytecodes) = { + let (keys, bytecodes, block_ctx) = { let storage = StorageView::new(&node.fork_storage).to_rc_ptr(); // system_contract.contacts_for_l2_call() will give playground contracts // we need these to use the unsafeOverrideBlock method in SystemContext.sol let bootloader_code = node.system_contracts.contacts_for_l2_call(); - let batch_env = node.create_l1_batch_env(storage.clone()); + let (batch_env, mut block_ctx) = node.create_l1_batch_env(storage.clone()); + // override the next block's timestamp to match up with interval for subsequent blocks + if i != 0 { + block_ctx.timestamp = node.current_timestamp.saturating_add(interval_ms); + } // init vm let system_env = @@ -122,7 +126,7 @@ pub fn mine_empty_blocks( .map(|b| bytecode_to_factory_dep(b.original.clone())) .collect(); let modified_keys = storage.borrow().modified_storage_keys().clone(); - (modified_keys, bytecodes) + (modified_keys, bytecodes, block_ctx) }; for (key, value) in keys.iter() { @@ -142,22 +146,16 @@ pub fn mine_empty_blocks( .collect(), ) } - node.current_miniblock = node.current_miniblock.saturating_add(1); - let block = create_empty_block( - node.current_miniblock as u32, - node.current_timestamp, - node.current_batch, - ); + let block = create_empty_block(block_ctx.miniblock, block_ctx.timestamp, block_ctx.batch); - node.block_hashes.insert(node.current_miniblock, block.hash); + node.block_hashes.insert(block.number.as_u64(), block.hash); node.blocks.insert(block.hash, block); // leave node state ready for next interaction - node.current_timestamp = node.current_timestamp.saturating_add(interval_ms); - - // increment batch - node.current_batch = node.current_batch.saturating_add(1); + node.current_batch = block_ctx.batch; + node.current_miniblock = block_ctx.miniblock; + node.current_timestamp = block_ctx.timestamp; } } @@ -184,7 +182,9 @@ pub fn to_real_block_number(block_number: BlockNumber, latest_block_number: U64) #[cfg(test)] mod tests { - use zksync_basic_types::U256; + use zksync_basic_types::{H256, U256}; + + use crate::{http_fork_source::HttpForkSource, node::InMemoryNode, testing}; use super::*; @@ -233,4 +233,139 @@ mod tests { let actual = to_real_block_number(BlockNumber::Number(U64::from(5)), U64::from(10)); assert_eq!(U64::from(5), actual); } + + #[test] + fn test_mine_empty_blocks_mines_the_first_block_immediately() { + let node = InMemoryNode::::default(); + let inner = node.get_inner(); + + let starting_block = { + let reader = inner.read().expect("failed acquiring reader"); + reader + .block_hashes + .get(&reader.current_miniblock) + .and_then(|hash| reader.blocks.get(hash)) + .expect("failed finding block") + .clone() + }; + assert_eq!(U64::from(0), starting_block.number); + assert_eq!(Some(U64::from(0)), starting_block.l1_batch_number); + assert_eq!(U256::from(1000), starting_block.timestamp); + + { + let mut writer = inner.write().expect("failed acquiring write lock"); + mine_empty_blocks(&mut writer, 1, 1000); + } + + let reader = inner.read().expect("failed acquiring reader"); + let mined_block = reader + .block_hashes + .get(&1) + .and_then(|hash| reader.blocks.get(hash)) + .expect("failed finding block"); + assert_eq!(U64::from(1), mined_block.number); + assert_eq!(Some(U64::from(1)), mined_block.l1_batch_number); + assert_eq!(U256::from(1001), mined_block.timestamp); + } + + #[test] + fn test_mine_empty_blocks_mines_2_blocks_with_interval() { + let node = InMemoryNode::::default(); + let inner = node.get_inner(); + + let starting_block = { + let reader = inner.read().expect("failed acquiring reader"); + reader + .block_hashes + .get(&reader.current_miniblock) + .and_then(|hash| reader.blocks.get(hash)) + .expect("failed finding block") + .clone() + }; + assert_eq!(U64::from(0), starting_block.number); + assert_eq!(Some(U64::from(0)), starting_block.l1_batch_number); + assert_eq!(U256::from(1000), starting_block.timestamp); + + { + let mut writer = inner.write().expect("failed acquiring write lock"); + mine_empty_blocks(&mut writer, 2, 1000); + } + + let reader = inner.read().expect("failed acquiring reader"); + let mined_block_1 = reader + .block_hashes + .get(&1) + .and_then(|hash| reader.blocks.get(hash)) + .expect("failed finding block 1"); + assert_eq!(U64::from(1), mined_block_1.number); + assert_eq!(Some(U64::from(1)), mined_block_1.l1_batch_number); + assert_eq!(U256::from(1001), mined_block_1.timestamp); + + let mined_block_2 = reader + .block_hashes + .get(&2) + .and_then(|hash| reader.blocks.get(hash)) + .expect("failed finding block 2"); + assert_eq!(U64::from(2), mined_block_2.number); + assert_eq!(Some(U64::from(2)), mined_block_2.l1_batch_number); + assert_eq!(U256::from(2001), mined_block_2.timestamp); + } + + #[test] + fn test_mine_empty_blocks_mines_2_blocks_with_interval_and_next_block_immediately() { + let node = InMemoryNode::::default(); + let inner = node.get_inner(); + + let starting_block = { + let reader = inner.read().expect("failed acquiring reader"); + reader + .block_hashes + .get(&reader.current_miniblock) + .and_then(|hash| reader.blocks.get(hash)) + .expect("failed finding block") + .clone() + }; + assert_eq!(U64::from(0), starting_block.number); + assert_eq!(Some(U64::from(0)), starting_block.l1_batch_number); + assert_eq!(U256::from(1000), starting_block.timestamp); + + { + let mut writer = inner.write().expect("failed acquiring write lock"); + mine_empty_blocks(&mut writer, 2, 1000); + } + + { + let reader = inner.read().expect("failed acquiring reader"); + let mined_block_1 = reader + .block_hashes + .get(&1) + .and_then(|hash| reader.blocks.get(hash)) + .expect("failed finding block 1"); + assert_eq!(U64::from(1), mined_block_1.number); + assert_eq!(Some(U64::from(1)), mined_block_1.l1_batch_number); + assert_eq!(U256::from(1001), mined_block_1.timestamp); + + let mined_block_2 = reader + .block_hashes + .get(&2) + .and_then(|hash| reader.blocks.get(hash)) + .expect("failed finding block 2"); + assert_eq!(U64::from(2), mined_block_2.number); + assert_eq!(Some(U64::from(2)), mined_block_2.l1_batch_number); + assert_eq!(U256::from(2001), mined_block_2.timestamp); + } + + { + testing::apply_tx(&node, H256::repeat_byte(0x1)); + let reader = inner.read().expect("failed acquiring reader"); + let tx_block_3 = reader + .block_hashes + .get(&3) + .and_then(|hash| reader.blocks.get(hash)) + .expect("failed finding block 2"); + assert_eq!(U64::from(3), tx_block_3.number); + assert_eq!(Some(U64::from(3)), tx_block_3.l1_batch_number); + assert_eq!(U256::from(2002), tx_block_3.timestamp); + } + } }