From 5922a6cd381935a73c977371780aff647431df3e Mon Sep 17 00:00:00 2001 From: grw-ms Date: Thu, 14 Sep 2023 06:17:58 -0700 Subject: [PATCH] feat: impl hardhat_mine --- src/evm.rs | 99 ++++++++--------------------------------- src/hardhat.rs | 118 ++++++++++++++++++++++++++++++++++++++++++++++++- src/utils.rs | 110 ++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 239 insertions(+), 88 deletions(-) diff --git a/src/evm.rs b/src/evm.rs index e0c2bf3b..1aefe44d 100644 --- a/src/evm.rs +++ b/src/evm.rs @@ -1,19 +1,9 @@ use std::sync::{Arc, RwLock}; -use crate::{fork::ForkSource, node::InMemoryNodeInner}; +use crate::{fork::ForkSource, node::InMemoryNodeInner, utils::mine_empty_blocks}; use jsonrpc_core::{BoxFuture, Result}; use jsonrpc_derive::rpc; -use vm::{ - utils::BLOCK_GAS_LIMIT, - vm_with_bootloader::{init_vm_inner, BlockContextMode, BootloaderJobType, TxExecutionMode}, - HistoryEnabled, OracleTools, -}; -use zksync_basic_types::H256; use zksync_core::api_server::web3::backend_jsonrpc::error::into_jsrpc_error; -use zksync_state::StorageView; -use zksync_state::WriteStorage; -use zksync_types::api::Block; -use zksync_utils::u256_to_h256; use zksync_web3_decl::error::Web3Error; /// Implementation of EvmNamespace @@ -134,72 +124,8 @@ impl EvmNamespaceT Box::pin(async move { match inner.write() { Ok(mut inner) => { - let (keys, block, bytecodes) = { - let mut storage_view = StorageView::new(&inner.fork_storage); - let mut oracle_tools = OracleTools::new(&mut storage_view, HistoryEnabled); - - let bootloader_code = &inner.system_contracts.baseline_contracts; - let block_context = inner.create_block_context(); - let block_properties = - InMemoryNodeInner::::create_block_properties(bootloader_code); - - let block = Block { - hash: H256::random(), - number: inner.current_miniblock.saturating_add(1).into(), - timestamp: block_context.block_timestamp.into(), - l1_batch_number: Some(block_context.block_number.into()), - ..Default::default() - }; - - // init vm - let mut vm = init_vm_inner( - &mut oracle_tools, - BlockContextMode::NewBlock(block_context.into(), Default::default()), - &block_properties, - BLOCK_GAS_LIMIT, - bootloader_code, - TxExecutionMode::VerifyExecute, - ); - - vm.execute_till_block_end(BootloaderJobType::BlockPostprocessing); - - let bytecodes = vm - .state - .decommittment_processor - .known_bytecodes - .inner() - .clone(); - - let modified_keys = storage_view.modified_storage_keys().clone(); - (modified_keys, block, bytecodes) - }; - - for (key, value) in keys.iter() { - inner.fork_storage.set_value(*key, *value); - } - - // Write all the factory deps. - for (hash, code) in bytecodes.iter() { - inner.fork_storage.store_factory_dep( - u256_to_h256(*hash), - code.iter() - .flat_map(|entry| { - let mut bytes = vec![0u8; 32]; - entry.to_big_endian(&mut bytes); - bytes.to_vec() - }) - .collect(), - ) - } - log::info!("👷 Mined block #{}", block.number.as_u64()); - - // update node state - inner.block_hashes.insert(block.number.as_u64(), block.hash); - inner.blocks.insert(block.hash, block); - inner.current_timestamp += 1; - inner.current_batch += 1; - inner.current_miniblock += 1; - + mine_empty_blocks(&mut inner, 1, 1000); + println!("👷 Mined block #{}", inner.current_miniblock); Ok("0x0".to_string()) } Err(_) => Err(into_jsrpc_error(Web3Error::InternalError)), @@ -526,19 +452,32 @@ mod tests { let evm = EvmNamespaceImpl::new(node.get_inner()); let start_block = node - .get_block_by_number(zksync_types::api::BlockNumber::Latest, true) + .get_block_by_number(zksync_types::api::BlockNumber::Latest, false) .await .unwrap() .expect("block exists"); - let result = evm.evm_mine().await.expect("evm_mine"); assert_eq!(&result, "0x0"); let current_block = node - .get_block_by_number(zksync_types::api::BlockNumber::Latest, true) + .get_block_by_number(zksync_types::api::BlockNumber::Latest, false) .await .unwrap() .expect("block exists"); + assert_eq!(start_block.number + 1, current_block.number); + assert_eq!(start_block.timestamp + 1000, current_block.timestamp); + + let result = evm.evm_mine().await.expect("evm_mine"); + assert_eq!(&result, "0x0"); + + let current_block = node + .get_block_by_number(zksync_types::api::BlockNumber::Latest, false) + .await + .unwrap() + .expect("block exists"); + + assert_eq!(start_block.number + 2, current_block.number); + assert_eq!(start_block.timestamp + 2000, current_block.timestamp); } } diff --git a/src/hardhat.rs b/src/hardhat.rs index 83b9aecf..d9387185 100644 --- a/src/hardhat.rs +++ b/src/hardhat.rs @@ -1,9 +1,9 @@ use std::sync::{Arc, RwLock}; -use crate::{fork::ForkSource, node::InMemoryNodeInner}; +use crate::{fork::ForkSource, node::InMemoryNodeInner, utils::mine_empty_blocks}; use jsonrpc_core::{BoxFuture, Result}; use jsonrpc_derive::rpc; -use zksync_basic_types::{Address, U256}; +use zksync_basic_types::{Address, U256, U64}; use zksync_core::api_server::web3::backend_jsonrpc::error::into_jsrpc_error; use zksync_state::ReadStorage; use zksync_types::{ @@ -52,6 +52,25 @@ pub trait HardhatNamespaceT { /// A `BoxFuture` containing a `Result` with a `bool` representing the success of the operation. #[rpc(name = "hardhat_setNonce")] fn set_nonce(&self, address: Address, balance: U256) -> BoxFuture>; + + /// Sometimes you may want to advance the latest block number of the network by a large number of blocks. + /// One way to do this would be to call the evm_mine RPC method multiple times, but this is too slow if you want to mine thousands of blocks. + /// The hardhat_mine method can mine any number of blocks at once, in constant time. (It exhibits the same performance no matter how many blocks are mined.) + /// + /// # Arguments + /// + /// * `num_blocks` - The number of blocks to mine, defaults to 1 + /// * `interval` - The interval between the timestamps of each block, in seconds, and it also defaults to 1 + /// + /// # Returns + /// + /// A `BoxFuture` containing a `Result` with a `bool` representing the success of the operation. + #[rpc(name = "hardhat_mine")] + fn hardhat_mine( + &self, + num_blocks: Option, + interval: Option, + ) -> BoxFuture>; } impl HardhatNamespaceT @@ -126,6 +145,33 @@ impl HardhatNamespaceT } }) } + + fn hardhat_mine( + &self, + num_blocks: Option, + interval: Option, + ) -> BoxFuture> { + let inner = Arc::clone(&self.node); + Box::pin(async move { + match inner.write() { + Ok(mut inner) => { + let num_blocks = num_blocks.unwrap_or(U64::from(1)); + let interval_ms = interval + .unwrap_or(U64::from(1)) + .saturating_mul(1_000.into()); + if num_blocks.is_zero() { + return Err(jsonrpc_core::Error::invalid_params( + "Number of blocks must be greater than 0".to_string(), + )); + } + mine_empty_blocks(&mut inner, num_blocks.as_u64(), interval_ms.as_u64()); + println!("👷 Mined {} blocks", num_blocks); + Ok(true) + } + Err(_) => Err(into_jsrpc_error(Web3Error::InternalError)), + } + }) + } } #[cfg(test)] @@ -173,4 +219,72 @@ mod tests { let result = hardhat.set_nonce(address, U256::from(1336)).await; assert!(result.is_err()); } + + #[tokio::test] + async fn test_hardhat_mine_default() { + let node = InMemoryNode::::default(); + let hardhat = HardhatNamespaceImpl::new(node.get_inner()); + + let start_block = node + .get_block_by_number(zksync_types::api::BlockNumber::Latest, false) + .await + .unwrap() + .expect("block exists"); + println!("start_block: {:?}", start_block); + + // test with defaults + let result = hardhat + .hardhat_mine(None, None) + .await + .expect("hardhat_mine"); + assert_eq!(result, true); + + println!("mined default"); + let current_block = node + .get_block_by_number(zksync_types::api::BlockNumber::Latest, false) + .await + .unwrap() + .expect("block exists"); + println!("current_block: {:?}", current_block); + + assert_eq!(start_block.number + 1, current_block.number); + assert_eq!(start_block.timestamp + 1000, current_block.timestamp); + let result = hardhat + .hardhat_mine(None, None) + .await + .expect("hardhat_mine"); + assert_eq!(result, true); + + println!("mined default"); + let current_block = node + .get_block_by_number(zksync_types::api::BlockNumber::Latest, false) + .await + .unwrap() + .expect("block exists"); + println!("current_block: {:?}", current_block); + + assert_eq!(start_block.number + 2, current_block.number); + assert_eq!(start_block.timestamp + 2000, current_block.timestamp); + } + + #[tokio::test] + async fn test_hardhat_mine_custom() { + let node = InMemoryNode::::default(); + let hardhat = HardhatNamespaceImpl::new(node.get_inner()); + + let start_block = node + .get_block_by_number(zksync_types::api::BlockNumber::Latest, false) + .await + .unwrap() + .expect("block exists"); + println!("start_block: {:?}", start_block); + + // test with custom values + let result = hardhat + .hardhat_mine(Some(U64::from(5)), Some(U64::from(10))) + .await + .expect("hardhat_mine"); + assert_eq!(result, true); + println!("mined twice"); + } } diff --git a/src/utils.rs b/src/utils.rs index fb62c00a..30660ded 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,13 +1,27 @@ use std::pin::Pin; use futures::Future; -use vm::vm_with_bootloader::{ - derive_base_fee_and_gas_per_pubdata, BLOCK_OVERHEAD_GAS, BLOCK_OVERHEAD_PUBDATA, - BOOTLOADER_TX_ENCODING_SPACE, +use vm::{ + utils::BLOCK_GAS_LIMIT, + vm_with_bootloader::{ + derive_base_fee_and_gas_per_pubdata, init_vm_inner, BlockContext, BlockContextMode, + BootloaderJobType, TxExecutionMode, BLOCK_OVERHEAD_GAS, BLOCK_OVERHEAD_PUBDATA, + BOOTLOADER_TX_ENCODING_SPACE, + }, + HistoryEnabled, OracleTools, +}; +use zksync_basic_types::{H256, U256}; +use zksync_state::StorageView; +use zksync_state::WriteStorage; +use zksync_types::{ + api::Block, zk_evm::zkevm_opcode_defs::system_params::MAX_TX_ERGS_LIMIT, MAX_TXS_IN_BLOCK, +}; +use zksync_utils::{ceil_div_u256, u256_to_h256}; + +use crate::{ + fork::{ForkSource, ForkStorage}, + node::{compute_hash, InMemoryNodeInner}, }; -use zksync_basic_types::U256; -use zksync_types::{zk_evm::zkevm_opcode_defs::system_params::MAX_TX_ERGS_LIMIT, MAX_TXS_IN_BLOCK}; -use zksync_utils::ceil_div_u256; pub(crate) trait IntoBoxedFuture: Sized + Send + 'static { fn into_boxed_future(self) -> Pin + Send>> { @@ -143,6 +157,90 @@ pub fn to_human_size(input: U256) -> String { tmp.iter().rev().collect() } +/// Creates and inserts a given number of empty blocks into the node, with a given interval between them. +/// The blocks will be empty (contain no transactions). +/// The test system contracts will be used to force overwriting the block number and timestamp in VM state, +/// otherwise the VM will reject subsequent blocks as invalid. +pub fn mine_empty_blocks( + node: &mut InMemoryNodeInner, + num_blocks: u64, + interval_ms: u64, +) { + // build and insert new blocks + for _ in 0..num_blocks { + node.current_miniblock = node.current_miniblock.saturating_add(1); + + let block = Block { + hash: compute_hash(node.current_miniblock as u32, H256::zero()), + number: node.current_batch.into(), + timestamp: node.current_timestamp.into(), + ..Default::default() + }; + + node.block_hashes.insert(node.current_miniblock, 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); + node.current_batch = node.current_batch.saturating_add(1); + } + + // roll the vm + let (keys, bytecodes) = { + let mut storage_view: StorageView<&ForkStorage> = StorageView::new(&node.fork_storage); + let mut oracle_tools = OracleTools::new(&mut storage_view, HistoryEnabled); + + let bootloader_code = &node.system_contracts.playground_contracts; + + let block_context = BlockContext { + block_number: node.current_batch - 1, + block_timestamp: node.current_timestamp, + ..node.create_block_context() + }; + let block_properties = InMemoryNodeInner::::create_block_properties(bootloader_code); + + // init vm + let mut vm = init_vm_inner( + &mut oracle_tools, + BlockContextMode::OverrideCurrent(block_context.into()), + &block_properties, + BLOCK_GAS_LIMIT, + bootloader_code, + TxExecutionMode::VerifyExecute, + ); + + vm.execute_till_block_end(BootloaderJobType::BlockPostprocessing); + + let bytecodes = vm + .state + .decommittment_processor + .known_bytecodes + .inner() + .clone(); + + let modified_keys = storage_view.modified_storage_keys().clone(); + (modified_keys, bytecodes) + }; + + for (key, value) in keys.iter() { + node.fork_storage.set_value(*key, *value); + } + + // Write all the factory deps. + for (hash, code) in bytecodes.iter() { + node.fork_storage.store_factory_dep( + u256_to_h256(*hash), + code.iter() + .flat_map(|entry| { + let mut bytes = vec![0u8; 32]; + entry.to_big_endian(&mut bytes); + bytes.to_vec() + }) + .collect(), + ) + } +} + #[cfg(test)] mod tests { use zksync_basic_types::U256;