Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: impl evm_mine and hardhat_mine #116

Merged
merged 10 commits into from
Sep 18, 2023
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