From e0f4abc2a6f37a747bbfaa5f97a3610599696c24 Mon Sep 17 00:00:00 2001 From: Nisheeth Barthwal Date: Tue, 26 Sep 2023 13:29:41 +0200 Subject: [PATCH 1/7] normalize current_miniblock, current_batch, and current_timestamp to have latest values --- src/evm.rs | 4 +- src/hardhat.rs | 6 +- src/node.rs | 164 +++++++++++++++++++++++++++++++++++-------------- src/testing.rs | 2 +- src/utils.rs | 28 +++++---- 5 files changed, 139 insertions(+), 65 deletions(-) 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 b76027cc..09641fce 100644 --- a/src/node.rs +++ b/src/node.rs @@ -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 @@ -277,16 +278,26 @@ type L2TxResult = ( VmExecutionResultAndLogs, Block, HashMap>, + NextBlock, ); impl InMemoryNodeInner { - pub fn create_l1_batch_env(&self, storage: StoragePtr) -> L1BatchEnv { + pub fn create_l1_batch_env( + &self, + storage: StoragePtr, + ) -> (L1BatchEnv, NextBlock) { let last_l2_block = load_last_l2_block(storage); - L1BatchEnv { + let next_block = NextBlock::from_current( + self.current_batch, + self.current_miniblock, + self.current_timestamp, + ); + let next_block = next_block.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(next_block.batch), + timestamp: next_block.timestamp, l1_gas_price: self.l1_gas_price, fair_l2_gas_price: L2_GAS_PRICE, fee_account: H160::zero(), @@ -294,8 +305,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: next_block.miniblock as u32, + timestamp: next_block.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. @@ -306,7 +317,9 @@ impl InMemoryNodeInner { // depend on block number or timestamp. max_virtual_blocks_to_create: 1, }, - } + }; + + (batch_env, next_block) } pub fn create_system_env( @@ -413,7 +426,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, next_block) = 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(), @@ -695,8 +708,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(), @@ -721,13 +734,14 @@ impl InMemoryNode { H256::zero(), Block:: { gas_limit: U256::from(ETH_CALL_GAS_LIMIT), + timestamp: U256::from(NON_FORK_FIRST_BLOCK_TIMESTAMP), ..Default::default() }, ); 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(), @@ -804,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 +1041,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, next_block) = inner.create_l1_batch_env(storage.clone()); // if we are impersonating an account, we need to use non-verifying system contracts let nonverifying_contracts; @@ -1179,7 +1193,7 @@ impl InMemoryNode { let hash = compute_hash(batch_env.number.0, l2_tx.hash()); let block = Block { hash, - number: U64::from(inner.current_miniblock.saturating_add(1)), + number: U64::from(next_block.miniblock), timestamp: U256::from(batch_env.timestamp), l1_batch_number: Some(U64::from(batch_env.number.0)), transactions: vec![TransactionVariant::Full( @@ -1202,7 +1216,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, next_block)) } /// Runs L2 transaction and commits it to a new block. @@ -1219,7 +1233,7 @@ impl InMemoryNode { inner.filters.notify_new_pending_transaction(tx_hash); } - let (keys, result, block, bytecodes) = + let (keys, result, block, bytecodes, next_block) = self.run_l2_tx_inner(l2_tx.clone(), execution_mode)?; if let ExecutionResult::Halt { reason } = result.result { @@ -1251,8 +1265,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 { @@ -1269,7 +1281,7 @@ impl InMemoryNode { log_type: None, removed: None, }, - U64::from(current_miniblock), + block.number, ); } let tx_receipt = TransactionReceipt { @@ -1319,48 +1331,108 @@ 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 next_block = next_block.new_block(); 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, + next_block.miniblock as u32, + next_block.timestamp, + next_block.batch, ); - inner.filters.notify_new_block(empty_block_hash); - { - // 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. + 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() + ); + } + + if block.number.as_u64() != current_miniblock { + panic!( + "expected next block to have miniblock {}, got {}", + current_miniblock, + block.number.as_u64() + ); + } + + 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 next block's batch number, miniblock number and timestamp. +pub struct NextBlock { + pub batch: u32, + pub miniblock: u64, + pub timestamp: u64, +} + +impl NextBlock { + /// 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) -> NextBlock { + 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 aa0555ee..aa6e0e48 100644 --- a/src/testing.rs +++ b/src/testing.rs @@ -360,7 +360,7 @@ pub fn apply_tx(node: &InMemoryNode, tx_hash let current_batch = node .get_inner() .read() - .map(|reader| reader.current_batch) + .map(|reader| reader.current_batch.saturating_add(1)) .expect("failed getting current batch number"); let produced_block_hash = compute_hash(current_batch, tx_hash); diff --git a/src/utils.rs b/src/utils.rs index a3300882..101bad84 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, next_block) = { 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 next_block) = node.create_l1_batch_env(storage.clone()); + // override the next block's timestamp to match up with interval for subsequent blocks + if i != 0 { + next_block.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, next_block) }; for (key, value) in keys.iter() { @@ -142,22 +146,20 @@ 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, + next_block.miniblock as u32, + next_block.timestamp, + next_block.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 = next_block.batch; + node.current_miniblock = next_block.miniblock; + node.current_timestamp = next_block.timestamp; } } From 1bafc9fced851ba3f8bd56815f524412423c95f0 Mon Sep 17 00:00:00 2001 From: Nisheeth Barthwal Date: Tue, 26 Sep 2023 13:33:37 +0200 Subject: [PATCH 2/7] fix compute_hash to use miniblock number --- src/node.rs | 15 ++++++--------- src/testing.rs | 8 ++++---- src/utils.rs | 7 ++----- 3 files changed, 12 insertions(+), 18 deletions(-) diff --git a/src/node.rs b/src/node.rs index 09641fce..d4528a21 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, @@ -426,7 +426,7 @@ impl InMemoryNodeInner { let storage = storage_view.to_rc_ptr(); let execution_mode = TxExecutionMode::EstimateFee; - let (mut batch_env, next_block) = 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(), @@ -1190,7 +1190,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(next_block.miniblock, l2_tx.hash()); let block = Block { hash, number: U64::from(next_block.miniblock), @@ -1342,11 +1342,8 @@ impl InMemoryNode { // 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 next_block = next_block.new_block(); - let empty_block_at_end_of_batch = create_empty_block( - next_block.miniblock as u32, - next_block.timestamp, - next_block.batch, - ); + let empty_block_at_end_of_batch = + create_empty_block(next_block.miniblock, next_block.timestamp, next_block.batch); let new_batch = inner.current_batch.saturating_add(1); let mut current_miniblock = inner.current_miniblock; diff --git a/src/testing.rs b/src/testing.rs index aa6e0e48..7c12c1e9 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.saturating_add(1)) + .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 101bad84..bb9df031 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -147,11 +147,8 @@ pub fn mine_empty_blocks( ) } - let block = create_empty_block( - next_block.miniblock as u32, - next_block.timestamp, - next_block.batch, - ); + let block = + create_empty_block(next_block.miniblock, next_block.timestamp, next_block.batch); node.block_hashes.insert(block.number.as_u64(), block.hash); node.blocks.insert(block.hash, block); From 583ffb1d12616aee236b8e9bbe03cb4677b98f6f Mon Sep 17 00:00:00 2001 From: Nisheeth Barthwal Date: Tue, 26 Sep 2023 14:17:07 +0200 Subject: [PATCH 3/7] add tests for mine_empty_blocks --- src/node.rs | 1 + src/utils.rs | 139 ++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 139 insertions(+), 1 deletion(-) diff --git a/src/node.rs b/src/node.rs index d4528a21..971ff47c 100644 --- a/src/node.rs +++ b/src/node.rs @@ -735,6 +735,7 @@ impl InMemoryNode { Block:: { gas_limit: U256::from(ETH_CALL_GAS_LIMIT), timestamp: U256::from(NON_FORK_FIRST_BLOCK_TIMESTAMP), + l1_batch_number: Some(U64::zero()), ..Default::default() }, ); diff --git a/src/utils.rs b/src/utils.rs index bb9df031..858defde 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -183,7 +183,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::*; @@ -232,4 +234,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); + } + } } From 8339b70fabde052efb11854c1a9ae44a3ce2906f Mon Sep 17 00:00:00 2001 From: Nisheeth Barthwal Date: Tue, 26 Sep 2023 18:08:14 +0200 Subject: [PATCH 4/7] merge main --- src/node.rs | 5 ++++- src/utils.rs | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/node.rs b/src/node.rs index 2073f0b1..655538e7 100644 --- a/src/node.rs +++ b/src/node.rs @@ -730,7 +730,10 @@ 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, diff --git a/src/utils.rs b/src/utils.rs index 858defde..e87724aa 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -107,6 +107,7 @@ pub fn mine_empty_blocks( // we need these to use the unsafeOverrideBlock method in SystemContext.sol let bootloader_code = node.system_contracts.contacts_for_l2_call(); let (batch_env, mut next_block) = node.create_l1_batch_env(storage.clone()); + println!("timestamp {}", next_block.timestamp); // override the next block's timestamp to match up with interval for subsequent blocks if i != 0 { next_block.timestamp = node.current_timestamp.saturating_add(interval_ms); From 4bfaab8fcb7031f28dc6f6ef7fd676d5527b0978 Mon Sep 17 00:00:00 2001 From: Nisheeth Barthwal Date: Wed, 27 Sep 2023 18:04:19 +0200 Subject: [PATCH 5/7] fix test, remove println! --- e2e-tests/test/evm-apis.test.ts | 2 +- src/utils.rs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/e2e-tests/test/evm-apis.test.ts b/e2e-tests/test/evm-apis.test.ts index d94700e2..8c5f29ac 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 block // Assert const newBlockTimestamp = (await provider.getBlock("latest")).timestamp; diff --git a/src/utils.rs b/src/utils.rs index e87724aa..858defde 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -107,7 +107,6 @@ pub fn mine_empty_blocks( // we need these to use the unsafeOverrideBlock method in SystemContext.sol let bootloader_code = node.system_contracts.contacts_for_l2_call(); let (batch_env, mut next_block) = node.create_l1_batch_env(storage.clone()); - println!("timestamp {}", next_block.timestamp); // override the next block's timestamp to match up with interval for subsequent blocks if i != 0 { next_block.timestamp = node.current_timestamp.saturating_add(interval_ms); From 973830465a3dbcf35e2047fbe4ac0db1121b6870 Mon Sep 17 00:00:00 2001 From: Nisheeth Barthwal Date: Wed, 27 Sep 2023 19:07:37 +0200 Subject: [PATCH 6/7] fix tests --- e2e-tests/test/evm-apis.test.ts | 6 +++--- e2e-tests/test/hardhat-apis.test.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/e2e-tests/test/evm-apis.test.ts b/e2e-tests/test/evm-apis.test.ts index 8c5f29ac..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 += 2; // New transaction will add two 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" ); }); From 3d75095d43ac9df0fbc284e487c32a56d28a77e0 Mon Sep 17 00:00:00 2001 From: Nisheeth Barthwal Date: Thu, 28 Sep 2023 17:51:56 +0200 Subject: [PATCH 7/7] rename to BlockContext --- src/node.rs | 41 +++++++++++++++++++++-------------------- src/utils.rs | 17 ++++++++--------- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/node.rs b/src/node.rs index 17a08abf..d732a682 100644 --- a/src/node.rs +++ b/src/node.rs @@ -279,26 +279,26 @@ type L2TxResult = ( VmExecutionResultAndLogs, Block, HashMap>, - NextBlock, + BlockContext, ); impl InMemoryNodeInner { pub fn create_l1_batch_env( &self, storage: StoragePtr, - ) -> (L1BatchEnv, NextBlock) { + ) -> (L1BatchEnv, BlockContext) { let last_l2_block = load_last_l2_block(storage); - let next_block = NextBlock::from_current( + let block_ctx = BlockContext::from_current( self.current_batch, self.current_miniblock, self.current_timestamp, ); - let next_block = next_block.new_batch(); + 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(next_block.batch), - timestamp: next_block.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(), @@ -306,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: next_block.miniblock as u32, - timestamp: next_block.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. @@ -320,7 +320,7 @@ impl InMemoryNodeInner { }, }; - (batch_env, next_block) + (batch_env, block_ctx) } pub fn create_system_env( @@ -1043,7 +1043,7 @@ impl InMemoryNode { let storage = StorageView::new(&inner.fork_storage).to_rc_ptr(); - let (batch_env, next_block) = 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; @@ -1192,7 +1192,7 @@ impl InMemoryNode { } // The computed block hash here will be different than that in production. - let hash = compute_hash(next_block.miniblock, 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 @@ -1207,7 +1207,7 @@ impl InMemoryNode { let block = Block { hash, - number: U64::from(next_block.miniblock), + 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)], @@ -1228,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, next_block)) + Ok((modified_keys, tx_result, block, bytecodes, block_ctx)) } /// Runs L2 transaction and commits it to a new block. @@ -1245,7 +1245,7 @@ impl InMemoryNode { inner.filters.notify_new_pending_transaction(tx_hash); } - let (keys, result, block, bytecodes, next_block) = + let (keys, result, block, bytecodes, block_ctx) = self.run_l2_tx_inner(l2_tx.clone(), execution_mode)?; if let ExecutionResult::Halt { reason } = result.result { @@ -1353,9 +1353,9 @@ impl InMemoryNode { // 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 next_block = next_block.new_block(); + let block_ctx = block_ctx.new_block(); let empty_block_at_end_of_batch = - create_empty_block(next_block.miniblock, next_block.timestamp, next_block.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; @@ -1406,14 +1406,15 @@ impl InMemoryNode { } } -/// Keeps track of next block's batch number, miniblock number and timestamp. -pub struct NextBlock { +/// 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 NextBlock { +impl BlockContext { /// Create the current instance that represents the latest block. pub fn from_current(batch: u32, miniblock: u64, timestamp: u64) -> Self { Self { @@ -1433,7 +1434,7 @@ impl NextBlock { } /// Create the next batch instance that uses the same batch number, and has all other parameters incremented by `1`. - pub fn new_block(&self) -> NextBlock { + pub fn new_block(&self) -> BlockContext { Self { batch: self.batch, miniblock: self.miniblock.saturating_add(1), diff --git a/src/utils.rs b/src/utils.rs index 858defde..9605f152 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -100,16 +100,16 @@ pub fn mine_empty_blocks( // build and insert new blocks for i in 0..num_blocks { // roll the vm - let (keys, bytecodes, next_block) = { + 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, mut next_block) = 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 { - next_block.timestamp = node.current_timestamp.saturating_add(interval_ms); + block_ctx.timestamp = node.current_timestamp.saturating_add(interval_ms); } // init vm @@ -126,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, next_block) + (modified_keys, bytecodes, block_ctx) }; for (key, value) in keys.iter() { @@ -147,16 +147,15 @@ pub fn mine_empty_blocks( ) } - let block = - create_empty_block(next_block.miniblock, next_block.timestamp, next_block.batch); + let block = create_empty_block(block_ctx.miniblock, block_ctx.timestamp, block_ctx.batch); 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_batch = next_block.batch; - node.current_miniblock = next_block.miniblock; - node.current_timestamp = next_block.timestamp; + node.current_batch = block_ctx.batch; + node.current_miniblock = block_ctx.miniblock; + node.current_timestamp = block_ctx.timestamp; } }