Skip to content

Commit

Permalink
feat: impl evm_mine and hardhat_mine (#116)
Browse files Browse the repository at this point in the history
  • Loading branch information
grw-ms authored Sep 18, 2023
1 parent 35ac2c7 commit 0b73bd3
Show file tree
Hide file tree
Showing 6 changed files with 373 additions and 13 deletions.
54 changes: 52 additions & 2 deletions SUPPORTED_APIS.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ The `status` options are:
| `ETH` | `eth_unsubscribe` | `NOT IMPLEMENTED` | Cancel a subscription to a particular event |
| `EVM` | `evm_addAccount` | `NOT IMPLEMENTED` | Adds any arbitrary account |
| [`EVM`](#evm-namespace) | [`evm_increaseTime`](#evm_increasetime) | `SUPPORTED` | Jump forward in time by the given amount of time, in seconds |
| `EVM` | `evm_mine` | `NOT IMPLEMENTED`<br />[GitHub Issue #67](https://github.com/matter-labs/era-test-node/issues/67) | Force a single block to be mined |
| [`EVM`](#evm-namespace) | [`evm_mine`](#evm_mine) | `SUPPORTED` | Force a single block to be mined |
| `EVM` | `evm_removeAccount` | `NOT IMPLEMENTED` | Removes an account |
| `EVM` | `evm_revert` | `NOT IMPLEMENTED`<br />[GitHub Issue #70](https://github.com/matter-labs/era-test-node/issues/70) | Revert the state of the blockchain to a previous snapshot |
| `EVM` | `evm_setAccountBalance` | `NOT IMPLEMENTED` | Sets the given account's balance to the specified WEI value |
Expand All @@ -93,7 +93,7 @@ The `status` options are:
| `HARDHAT` | `hardhat_impersonateAccount` | `NOT IMPLEMENTED`<br />[GitHub Issue #73](https://github.com/matter-labs/era-test-node/issues/73) | Impersonate an account |
| `HARDHAT` | `hardhat_getAutomine` | `NOT IMPLEMENTED` | Returns `true` if automatic mining is enabled, and `false` otherwise |
| `HARDHAT` | `hardhat_metadata` | `NOT IMPLEMENTED` | Returns the metadata of the current network |
| `HARDHAT` | `hardhat_mine` | `NOT IMPLEMENTED`<br />[GitHub Issue #75](https://github.com/matter-labs/era-test-node/issues/75) | Mine any number of blocks at once, in constant time |
| [`HARDHAT`](#hardhat-namespace) | [`hardhat_mine`](#hardhat_mine) | Mine any number of blocks at once, in constant time |
| `HARDHAT` | `hardhat_reset` | `NOT IMPLEMENTED` | Resets the state of the network |
| [`HARDHAT`](#hardhat-namespace) | [`hardhat_setBalance`](#hardhat_setbalance) | `SUPPORTED` | Modifies the balance of an account |
| `HARDHAT` | `hardhat_setCode` | `NOT IMPLEMENTED` | Sets the bytecode of a given account |
Expand Down Expand Up @@ -856,8 +856,58 @@ curl --request POST \
}'
```

### `hardhat_mine`

[source](src/hardhat.rs)

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: U64` - The number of blocks to mine. (Optional: defaults to 1)
+ `interval: U646` - The interval between the timestamps of each block, in seconds. (Optional: defaults to 1)

#### Example

```bash
curl --request POST \
--url http://localhost:8011/ \
--header 'content-type: application/json' \
--data '{
"jsonrpc": "2.0",
"id": "2",
"method": "hardhat_mine",
"params": [
"0xaa",
"0x100"
]
}'
```

## `EVM NAMESPACE`

### `evm_mine`

[source](src/evm.rs)

Mines an empty block

#### Status

`SUPPORTED`

#### Example

```bash
curl --request POST \
--url http://localhost:8011/ \
--header 'content-type: application/json' \
--data '{"jsonrpc": "2.0","id": "1","method": "evm_mine","params": []
}'
```

### `evm_increaseTime`

[source](src/evm.rs)
Expand Down
61 changes: 60 additions & 1 deletion src/evm.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
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_core::api_server::web3::backend_jsonrpc::error::into_jsrpc_error;
Expand Down Expand Up @@ -30,6 +30,15 @@ pub trait EvmNamespaceT {
#[rpc(name = "evm_increaseTime")]
fn increase_time(&self, time_delta_seconds: u64) -> BoxFuture<Result<u64>>;

/// Force a single block to be mined.
///
/// Will mine an empty block (containing zero transactions)
///
/// # Returns
/// The string "0x0".
#[rpc(name = "evm_mine")]
fn evm_mine(&self) -> BoxFuture<Result<String>>;

/// Set the current timestamp for the node. The timestamp must be in future.
///
/// # Parameters
Expand Down Expand Up @@ -109,11 +118,26 @@ impl<S: Send + Sync + 'static + ForkSource + std::fmt::Debug> EvmNamespaceT
}
})
}

fn evm_mine(&self) -> BoxFuture<Result<String>> {
let inner = Arc::clone(&self.node);
Box::pin(async move {
match inner.write() {
Ok(mut inner) => {
mine_empty_blocks(&mut inner, 1, 1000);
log::info!("👷 Mined block #{}", inner.current_miniblock);
Ok("0x0".to_string())
}
Err(_) => Err(into_jsrpc_error(Web3Error::InternalError)),
}
})
}
}

#[cfg(test)]
mod tests {
use crate::{http_fork_source::HttpForkSource, node::InMemoryNode};
use zksync_core::api_server::web3::backend_jsonrpc::namespaces::eth::EthNamespaceT;

use super::*;

Expand Down Expand Up @@ -421,4 +445,39 @@ mod tests {
);
}
}

#[tokio::test]
async fn test_evm_mine() {
let node = InMemoryNode::<HttpForkSource>::default();
let evm = EvmNamespaceImpl::new(node.get_inner());

let start_block = node
.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, 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);
}
}
128 changes: 126 additions & 2 deletions src/hardhat.rs
Original file line number Diff line number Diff line change
@@ -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::{
Expand Down Expand Up @@ -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<Result<bool>>;

/// 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<U64>,
interval: Option<U64>,
) -> BoxFuture<Result<bool>>;
}

impl<S: Send + Sync + 'static + ForkSource + std::fmt::Debug> HardhatNamespaceT
Expand Down Expand Up @@ -128,6 +147,33 @@ impl<S: Send + Sync + 'static + ForkSource + std::fmt::Debug> HardhatNamespaceT
}
})
}

fn hardhat_mine(
&self,
num_blocks: Option<U64>,
interval: Option<U64>,
) -> BoxFuture<Result<bool>> {
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());
log::info!("👷 Mined {} blocks", num_blocks);
Ok(true)
}
Err(_) => Err(into_jsrpc_error(Web3Error::InternalError)),
}
})
}
}

#[cfg(test)]
Expand All @@ -136,6 +182,7 @@ mod tests {
use crate::{http_fork_source::HttpForkSource, node::InMemoryNode};
use std::str::FromStr;
use zksync_core::api_server::web3::backend_jsonrpc::namespaces::eth::EthNamespaceT;
use zksync_types::api::BlockNumber;

#[tokio::test]
async fn test_set_balance() {
Expand Down Expand Up @@ -175,4 +222,81 @@ 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::<HttpForkSource>::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");

// test with defaults
let result = hardhat
.hardhat_mine(None, None)
.await
.expect("hardhat_mine");
assert_eq!(result, true);

let current_block = node
.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 = hardhat
.hardhat_mine(None, None)
.await
.expect("hardhat_mine");
assert_eq!(result, true);

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);
}

#[tokio::test]
async fn test_hardhat_mine_custom() {
let node = InMemoryNode::<HttpForkSource>::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");

let num_blocks = 5;
let interval = 3;
let start_timestamp = start_block.timestamp + 1_000;

let result = hardhat
.hardhat_mine(Some(U64::from(num_blocks)), Some(U64::from(interval)))
.await
.expect("hardhat_mine");
assert_eq!(result, true);

for i in 0..num_blocks {
let current_block = node
.get_block_by_number(BlockNumber::Number(start_block.number + i + 1), false)
.await
.unwrap()
.expect("block exists");
assert_eq!(start_block.number + i + 1, current_block.number);
assert_eq!(
start_timestamp + i * interval * 1_000,
current_block.timestamp
);
}
}
}
4 changes: 2 additions & 2 deletions src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ type L2TxResult = (
);

impl<S: std::fmt::Debug + ForkSource> InMemoryNodeInner<S> {
fn create_block_context(&self) -> BlockContext {
pub fn create_block_context(&self) -> BlockContext {
BlockContext {
block_number: self.current_batch,
block_timestamp: self.current_timestamp,
Expand All @@ -263,7 +263,7 @@ impl<S: std::fmt::Debug + ForkSource> InMemoryNodeInner<S> {
}
}

fn create_block_properties(contracts: &BaseSystemContracts) -> BlockProperties {
pub fn create_block_properties(contracts: &BaseSystemContracts) -> BlockProperties {
BlockProperties {
default_aa_code_hash: h256_to_u256(contracts.default_aa.hash),
zkporter_is_available: false,
Expand Down
Loading

0 comments on commit 0b73bd3

Please sign in to comment.