Skip to content

Commit

Permalink
feat: add anvil_{set,remove}BlockTimestampInterval (#442)
Browse files Browse the repository at this point in the history
* major refactoring of time management

* refactor enforcing next timestamp

* add `anvil_{set,remove}BlockTimestampInterval`

* return early for 0 interval

* clippy

* uncomment tests

* rename time traits and methods
  • Loading branch information
itegulov authored Nov 28, 2024
1 parent e5cd460 commit 2e9c7c8
Show file tree
Hide file tree
Showing 12 changed files with 397 additions and 291 deletions.
2 changes: 2 additions & 0 deletions SUPPORTED_APIS.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ The `status` options are:

| Namespace | API | <div style="width:130px">Status</div> | Description |
| --- | --- | --- | --- |
| `ANVIL` | `anvil_setBlockTimestampInterval` | `SUPPORTED` | Sets the block timestamp interval |
| `ANVIL` | `anvil_removeBlockTimestampInterval` | `SUPPORTED` | Removes the block timestamp interval |
| `ANVIL` | `anvil_setMinGasPrice` | `NOT IMPLEMENTED` | Set the minimum gas price for the node. Unsupported for ZKsync as it is only relevant for pre-EIP1559 chains |
| `ANVIL` | `anvil_setLoggingEnabled` | `SUPPORTED` | Enables or disables logging |
| `ANVIL` | `anvil_snapshot` | `SUPPORTED` | Snapshot the state of the blockchain at the current block |
Expand Down
41 changes: 41 additions & 0 deletions e2e-tests/test/anvil-apis.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,47 @@ import * as fs from "node:fs";

const provider = getTestProvider();

describe("anvil_setBlockTimestampInterval & anvil_removeBlockTimestampInterval", function () {
it("Should control timestamp interval between blocks", async function () {
// Arrange
const interval = 42;
let expectedTimestamp: number = await provider.send("config_getCurrentTimestamp", []);
expectedTimestamp += interval;
const wallet = new Wallet(RichAccounts[0].PrivateKey, provider);
const userWallet = Wallet.createRandom().connect(provider);

// Set interval
await provider.send("anvil_setBlockTimestampInterval", [interval]);

const txResponse = await wallet.sendTransaction({
to: userWallet.address,
value: ethers.utils.parseEther("0.1"),
});
const txReceipt = await txResponse.wait();

// Assert new block is `interval` apart from start
const newBlockTimestamp = (await provider.getBlock(txReceipt.blockNumber)).timestamp;
expect(newBlockTimestamp).to.equal(expectedTimestamp);

// Accomodate for virtual block
expectedTimestamp += interval;

// Remove interval
const result: boolean = await provider.send("anvil_removeBlockTimestampInterval", []);
expect(result);

const txResponse2 = await wallet.sendTransaction({
to: userWallet.address,
value: ethers.utils.parseEther("0.1"),
});
const txReceipt2 = await txResponse2.wait();

// Assert new block is `1` apart from previous block
const newBlockTimestamp2 = (await provider.getBlock(txReceipt2.blockNumber)).timestamp;
expect(newBlockTimestamp2).to.equal(expectedTimestamp + 1);
});
});

describe("anvil_setLoggingEnabled", function () {
it("Should disable and enable logging", async function () {
// Arrange
Expand Down
17 changes: 17 additions & 0 deletions src/namespaces/anvil.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,23 @@ use crate::utils::Numeric;

#[rpc]
pub trait AnvilNamespaceT {
/// Sets the block timestamp interval. All future blocks' timestamps will
/// have the provided amount of seconds in-between of them. Does not affect
/// the block production interval.
///
/// # Arguments
///
/// * `seconds` - The minimum gas price to be set
#[rpc(name = "anvil_setBlockTimestampInterval")]
fn set_block_timestamp_interval(&self, seconds: u64) -> RpcResult<()>;

/// Removes the block timestamp interval if it exists.
///
/// # Returns
/// `true` if an existing interval was removed, `false` otherwise
#[rpc(name = "anvil_removeBlockTimestampInterval")]
fn remove_block_timestamp_interval(&self) -> RpcResult<bool>;

/// Set the minimum gas price for the node. Unsupported for ZKsync as it is only relevant for
/// pre-EIP1559 chains.
///
Expand Down
9 changes: 9 additions & 0 deletions src/node/anvil.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,15 @@ use crate::{
impl<S: ForkSource + std::fmt::Debug + Clone + Send + Sync + 'static> AnvilNamespaceT
for InMemoryNode<S>
{
fn set_block_timestamp_interval(&self, seconds: u64) -> RpcResult<()> {
self.time.set_block_timestamp_interval(seconds);
Ok(()).into_boxed_future()
}

fn remove_block_timestamp_interval(&self) -> RpcResult<bool> {
Ok(self.time.remove_block_timestamp_interval()).into_boxed_future()
}

fn set_min_gas_price(&self, _gas: U256) -> RpcResult<()> {
tracing::info!("anvil_setMinGasPrice is unsupported as ZKsync is a post-EIP1559 chain");
Err(into_jsrpc_error(Web3Error::MethodNotImplemented)).into_boxed_future()
Expand Down
2 changes: 1 addition & 1 deletion src/node/block_producer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ impl<S: ForkSource + Clone + fmt::Debug> Future for BlockProducer<S> {
.contracts(TxExecutionMode::VerifyExecute, impersonating)
.clone();
pin.node
.seal_block(txs, base_system_contracts)
.seal_block(&mut pin.node.time.lock(), txs, base_system_contracts)
.expect("block sealing failed");
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/node/config_api.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use zksync_web3_decl::error::Web3Error;

use crate::node::time::ReadTime;
use crate::{
config::show_details::{ShowCalls, ShowGasDetails, ShowStorageLogs, ShowVMDetails},
fork::ForkSource,
Expand Down Expand Up @@ -37,7 +38,7 @@ impl<S: ForkSource + std::fmt::Debug + Clone + Send + Sync + 'static> Configurat
}

fn config_get_current_timestamp(&self) -> Result<u64> {
Ok(self.time.last_timestamp())
Ok(self.time.current_timestamp())
}

fn config_set_show_calls(&self, value: String) -> Result<String> {
Expand Down
4 changes: 3 additions & 1 deletion src/node/debug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ impl<S: ForkSource + std::fmt::Debug + Clone + Send + Sync + 'static> DebugNames
) -> RpcResult<DebugCall> {
let only_top = options.is_some_and(|o| o.tracer_config.only_top_call);
let inner = self.get_inner().clone();
let time = self.time.clone();
Box::pin(async move {
if block.is_some() && !matches!(block, Some(BlockId::Number(BlockNumber::Latest))) {
return Err(jsonrpc_core::Error::invalid_params(
Expand All @@ -165,7 +166,8 @@ impl<S: ForkSource + std::fmt::Debug + Clone + Send + Sync + 'static> DebugNames
let storage = StorageView::new(&inner.fork_storage).into_rc_ptr();

// init vm
let (mut l1_batch_env, _block_context) = inner.create_l1_batch_env(storage.clone());
let (mut l1_batch_env, _block_context) =
inner.create_l1_batch_env(&time, storage.clone());

// update the enforced_base_fee within l1_batch_env to match the logic in zksync_core
l1_batch_env.enforced_base_fee = Some(l2_tx.common_data.fee.max_fee_per_gas.as_u64());
Expand Down
18 changes: 4 additions & 14 deletions src/node/eth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -662,7 +662,7 @@ impl<S: ForkSource + std::fmt::Debug + Clone + Send + Sync + 'static> EthNamespa
}
};

let result: jsonrpc_core::Result<Fee> = reader.estimate_gas_impl(req);
let result: jsonrpc_core::Result<Fee> = reader.estimate_gas_impl(&self.time, req);
match result {
Ok(fee) => Ok(fee.gas_limit).into_boxed_future(),
Err(err) => return futures::future::err(err).boxed(),
Expand Down Expand Up @@ -2842,7 +2842,7 @@ mod tests {
inner.current_batch = 1;
inner.current_miniblock = 1;
inner.current_miniblock_hash = H256::repeat_byte(0x1);
inner.time.set_last_timestamp_unchecked(1);
node.time.set_current_timestamp_unchecked(1);
inner
.filters
.add_block_filter()
Expand All @@ -2859,7 +2859,6 @@ mod tests {

let storage = inner.fork_storage.inner.read().unwrap();
let expected_snapshot = Snapshot {
current_timestamp: inner.time.last_timestamp(),
current_batch: inner.current_batch,
current_miniblock: inner.current_miniblock,
current_miniblock_hash: inner.current_miniblock_hash,
Expand All @@ -2877,10 +2876,6 @@ mod tests {
};
let actual_snapshot = inner.snapshot().expect("failed taking snapshot");

assert_eq!(
expected_snapshot.current_timestamp,
actual_snapshot.current_timestamp
);
assert_eq!(
expected_snapshot.current_batch,
actual_snapshot.current_batch
Expand Down Expand Up @@ -2948,7 +2943,7 @@ mod tests {
inner.current_batch = 1;
inner.current_miniblock = 1;
inner.current_miniblock_hash = H256::repeat_byte(0x1);
inner.time.set_last_timestamp_unchecked(1);
node.time.set_current_timestamp_unchecked(1);
inner
.filters
.add_block_filter()
Expand All @@ -2966,7 +2961,6 @@ mod tests {
let expected_snapshot = {
let storage = inner.fork_storage.inner.read().unwrap();
Snapshot {
current_timestamp: inner.time.last_timestamp(),
current_batch: inner.current_batch,
current_miniblock: inner.current_miniblock,
current_miniblock_hash: inner.current_miniblock_hash,
Expand Down Expand Up @@ -3001,7 +2995,7 @@ mod tests {
inner.current_batch = 2;
inner.current_miniblock = 2;
inner.current_miniblock_hash = H256::repeat_byte(0x2);
inner.time.set_last_timestamp_unchecked(2);
node.time.set_current_timestamp_unchecked(2);
inner
.filters
.add_pending_transaction_filter()
Expand All @@ -3022,10 +3016,6 @@ mod tests {
.expect("failed restoring snapshot");

let storage = inner.fork_storage.inner.read().unwrap();
assert_eq!(
expected_snapshot.current_timestamp,
inner.time.last_timestamp()
);
assert_eq!(expected_snapshot.current_batch, inner.current_batch);
assert_eq!(expected_snapshot.current_miniblock, inner.current_miniblock);
assert_eq!(
Expand Down
Loading

0 comments on commit 2e9c7c8

Please sign in to comment.