::HeaderResponse,
+ >,
+ > {
+ self.client().request_noparams("anvil_mine_detailed").into()
+ }
}
impl EraTestNodeApiProvider for P
diff --git a/e2e-tests-rust/tests/lib.rs b/e2e-tests-rust/tests/lib.rs
index dea3e988..294593e6 100644
--- a/e2e-tests-rust/tests/lib.rs
+++ b/e2e-tests-rust/tests/lib.rs
@@ -128,9 +128,15 @@ async fn no_sealing_timeout() -> anyhow::Result<()> {
.with_to(address!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045"))
.with_value(U256::from(100));
let pending_tx = provider.send_transaction(tx).await?.register().await?;
+ let tx_hash = pending_tx.tx_hash().clone();
let finalization_result = tokio::time::timeout(Duration::from_secs(3), pending_tx).await;
assert!(finalization_result.is_err());
+ // Mine a block manually and assert that the transaction is finalized now
+ provider.mine(None, None).await?;
+ let receipt = provider.get_transaction_receipt(tx_hash).await?.unwrap();
+ assert!(receipt.status());
+
Ok(())
}
@@ -275,3 +281,69 @@ async fn remove_pool_transactions() -> anyhow::Result<()> {
Ok(())
}
+
+#[tokio::test]
+async fn manual_mining_two_txs_in_one_block() -> anyhow::Result<()> {
+ // Test that we can submit two transaction and then manually mine one block that contains both
+ // transactions in it.
+ let provider = init(|node| node.no_mine()).await?;
+
+ let tx0 = TransactionRequest::default()
+ .with_from(RICH_WALLET0)
+ .with_to(address!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045"))
+ .with_value(U256::from(100));
+ let pending_tx0 = provider.send_transaction(tx0).await?.register().await?;
+ let tx1 = TransactionRequest::default()
+ .with_from(RICH_WALLET1)
+ .with_to(address!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045"))
+ .with_value(U256::from(100));
+ let pending_tx1 = provider.send_transaction(tx1).await?.register().await?;
+
+ // Mine a block manually and assert that both transactions are finalized now
+ provider.mine(None, None).await?;
+ let receipt0 = provider
+ .get_transaction_receipt(pending_tx0.await?)
+ .await?
+ .unwrap();
+ assert!(receipt0.status());
+ let receipt1 = provider
+ .get_transaction_receipt(pending_tx1.await?)
+ .await?
+ .unwrap();
+ assert!(receipt1.status());
+
+ assert_eq!(receipt0.block_hash(), receipt1.block_hash());
+ assert_eq!(receipt0.block_number(), receipt1.block_number());
+
+ Ok(())
+}
+
+#[tokio::test]
+async fn detailed_mining_success() -> anyhow::Result<()> {
+ // Test that we can detailed mining on a successful transaction and get output from it.
+ let provider = init(|node| node.no_mine()).await?;
+
+ let tx = TransactionRequest::default()
+ .with_from(RICH_WALLET0)
+ .with_to(address!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045"))
+ .with_value(U256::from(100));
+ provider.send_transaction(tx).await?.register().await?;
+
+ // Mine a block manually and assert that it has our transaction with extra fields
+ let block = provider.mine_detailed().await?;
+ assert_eq!(block.transactions.len(), 1);
+ let actual_tx = block
+ .transactions
+ .clone()
+ .into_transactions()
+ .next()
+ .unwrap();
+
+ assert_eq!(
+ actual_tx.other.get("output").and_then(|x| x.as_str()),
+ Some("0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000")
+ );
+ assert!(actual_tx.other.get("revertReason").is_none());
+
+ Ok(())
+}
diff --git a/src/deps/mod.rs b/src/deps/mod.rs
index 3e886afd..acdcb450 100644
--- a/src/deps/mod.rs
+++ b/src/deps/mod.rs
@@ -22,10 +22,8 @@ impl InMemoryStorage {
system_contracts_options: &crate::system_contracts::Options,
use_evm_emulator: bool,
) -> Self {
- let contracts = crate::system_contracts::get_deployed_contracts(
- system_contracts_options,
- use_evm_emulator,
- );
+ let contracts =
+ system_contracts::get_deployed_contracts(system_contracts_options, use_evm_emulator);
let system_context_init_log = get_system_context_init_logs(chain_id);
diff --git a/src/deps/system_contracts.rs b/src/deps/system_contracts.rs
index 9d2723b9..65d883f7 100644
--- a/src/deps/system_contracts.rs
+++ b/src/deps/system_contracts.rs
@@ -1,5 +1,7 @@
+use crate::system_contracts::Options;
use once_cell::sync::Lazy;
use serde_json::Value;
+use zksync_types::system_contracts::get_system_smart_contracts;
use zksync_types::{
block::DeployedContract, ACCOUNT_CODE_STORAGE_ADDRESS, BOOTLOADER_ADDRESS,
BOOTLOADER_UTILITIES_ADDRESS, CODE_ORACLE_ADDRESS, COMPRESSOR_ADDRESS,
@@ -190,3 +192,13 @@ pub static COMPILED_IN_SYSTEM_CONTRACTS: Lazy> = Lazy::new
deployed_system_contracts.extend(empty_system_contracts);
deployed_system_contracts
});
+
+pub fn get_deployed_contracts(
+ options: &Options,
+ use_evm_emulator: bool,
+) -> Vec {
+ match options {
+ Options::BuiltIn | Options::BuiltInWithoutSecurity => COMPILED_IN_SYSTEM_CONTRACTS.clone(),
+ Options::Local => get_system_smart_contracts(use_evm_emulator),
+ }
+}
diff --git a/src/namespaces/anvil.rs b/src/namespaces/anvil.rs
index deb24f07..b0e9ed25 100644
--- a/src/namespaces/anvil.rs
+++ b/src/namespaces/anvil.rs
@@ -1,11 +1,21 @@
-use jsonrpc_derive::rpc;
-use zksync_types::{Address, H256, U256, U64};
-
use super::{ResetRequest, RpcResult};
use crate::utils::Numeric;
+use jsonrpc_derive::rpc;
+use serde::{Deserialize, Serialize};
+use zksync_types::api::{Block, Transaction};
+use zksync_types::web3::Bytes;
+use zksync_types::{Address, H256, U256, U64};
#[rpc]
pub trait AnvilNamespaceT {
+ /// Mines a single block in the same way as `evm_mine` but returns extra fields.
+ ///
+ ///
+ /// # Returns
+ /// Freshly mined block's representation along with extra fields.
+ #[rpc(name = "anvil_mine_detailed")]
+ fn mine_detailed(&self) -> RpcResult>;
+
/// Sets the fork RPC url. Assumes the underlying chain is the same as before.
///
/// # Arguments
@@ -278,3 +288,16 @@ pub trait AnvilNamespaceT {
#[rpc(name = "anvil_setStorageAt")]
fn set_storage_at(&self, address: Address, slot: U256, value: U256) -> RpcResult;
}
+
+#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
+pub struct DetailedTransaction {
+ #[serde(flatten)]
+ pub inner: Transaction,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ #[serde(default)]
+ pub output: Option,
+ #[serde(rename = "revertReason")]
+ #[serde(skip_serializing_if = "Option::is_none")]
+ #[serde(default)]
+ pub revert_reason: Option,
+}
diff --git a/src/namespaces/mod.rs b/src/namespaces/mod.rs
index f1102fb4..ab18100a 100644
--- a/src/namespaces/mod.rs
+++ b/src/namespaces/mod.rs
@@ -9,7 +9,7 @@ mod net;
mod web3;
mod zks;
-pub use anvil::AnvilNamespaceT;
+pub use anvil::{AnvilNamespaceT, DetailedTransaction};
pub use config::ConfigurationApiNamespaceT;
pub use debug::DebugNamespaceT;
pub use eth::EthNamespaceT;
diff --git a/src/node/anvil.rs b/src/node/anvil.rs
index f5fdc821..fb7842ab 100644
--- a/src/node/anvil.rs
+++ b/src/node/anvil.rs
@@ -1,6 +1,8 @@
+use zksync_types::api::Block;
use zksync_types::{Address, H256, U256, U64};
use zksync_web3_decl::error::Web3Error;
+use crate::namespaces::DetailedTransaction;
use crate::utils::Numeric;
use crate::{
fork::ForkSource,
@@ -12,6 +14,15 @@ use crate::{
impl AnvilNamespaceT
for InMemoryNode
{
+ fn mine_detailed(&self) -> RpcResult> {
+ self.mine_detailed()
+ .map_err(|err| {
+ tracing::error!("failed mining with detailed view: {:?}", err);
+ into_jsrpc_error(Web3Error::InternalError(err))
+ })
+ .into_boxed_future()
+ }
+
fn set_rpc_url(&self, url: String) -> RpcResult<()> {
self.set_rpc_url(url)
.map_err(|err| {
diff --git a/src/node/debug.rs b/src/node/debug.rs
index 9dbe6be5..3cd7ca9b 100644
--- a/src/node/debug.rs
+++ b/src/node/debug.rs
@@ -145,6 +145,7 @@ impl DebugNames
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();
+ let system_contracts = self.system_contracts.contracts_for_l2_call().clone();
Box::pin(async move {
if block.is_some() && !matches!(block, Some(BlockId::Number(BlockNumber::Latest))) {
return Err(jsonrpc_core::Error::invalid_params(
@@ -158,7 +159,6 @@ impl DebugNames
)))
})?;
- let system_contracts = inner.system_contracts.contracts_for_l2_call();
let allow_no_target = system_contracts.evm_emulator.is_some();
let mut l2_tx = L2Tx::from_request(request.into(), MAX_TX_SIZE, allow_no_target)
.map_err(|err| into_jsrpc_error(Web3Error::SerializationError(err)))?;
diff --git a/src/node/eth.rs b/src/node/eth.rs
index 82f62e16..58fd7a84 100644
--- a/src/node/eth.rs
+++ b/src/node/eth.rs
@@ -4,7 +4,7 @@ use anyhow::Context as _;
use colored::Colorize;
use futures::FutureExt;
use itertools::Itertools;
-use zksync_multivm::interface::ExecutionResult;
+use zksync_multivm::interface::{ExecutionResult, TxExecutionMode};
use zksync_multivm::vm_latest::constants::ETH_CALL_GAS_LIMIT;
use zksync_types::{
api::{Block, BlockIdVariant, BlockNumber, TransactionVariant},
@@ -41,7 +41,7 @@ impl InMemoryNo
&self,
req: zksync_types::transaction_request::CallRequest,
) -> Result {
- let system_contracts = self.system_contracts_for_l2_call()?;
+ let system_contracts = self.system_contracts.contracts_for_l2_call().clone();
let allow_no_target = system_contracts.evm_emulator.is_some();
let mut tx = L2Tx::from_request(req.into(), MAX_TX_SIZE, allow_no_target)?;
@@ -89,7 +89,11 @@ impl InMemoryNo
.chain_id;
let (tx_req, hash) = TransactionRequest::from_bytes(&tx_bytes.0, chain_id)?;
- let system_contracts = self.system_contracts_for_tx(tx_req.from.unwrap_or_default())?;
+ // Impersonation does not matter in this context so we assume the tx is not impersonated:
+ // system contracts here are fetched solely to check for EVM emulator.
+ let system_contracts = self
+ .system_contracts
+ .contracts(TxExecutionMode::VerifyExecute, false);
let allow_no_target = system_contracts.evm_emulator.is_some();
let mut l2_tx = L2Tx::from_request(tx_req, MAX_TX_SIZE, allow_no_target)?;
@@ -154,7 +158,11 @@ impl InMemoryNo
27,
))?;
- let system_contracts = self.system_contracts_for_tx(tx_req.from.unwrap_or_default())?;
+ // Impersonation does not matter in this context so we assume the tx is not impersonated:
+ // system contracts here are fetched solely to check for EVM emulator.
+ let system_contracts = self
+ .system_contracts
+ .contracts(TxExecutionMode::VerifyExecute, false);
let allow_no_target = system_contracts.evm_emulator.is_some();
let mut l2_tx: L2Tx = L2Tx::from_request(tx_req, MAX_TX_SIZE, allow_no_target)?;
diff --git a/src/node/evm.rs b/src/node/evm.rs
index 2979714f..e34ba3d5 100644
--- a/src/node/evm.rs
+++ b/src/node/evm.rs
@@ -36,6 +36,7 @@ impl EvmNamespa
tracing::error!("failed mining block: {:?}", err);
into_jsrpc_error(Web3Error::InternalError(err))
})
+ .map(|_| "0x0".to_string())
.into_boxed_future()
}
diff --git a/src/node/in_memory.rs b/src/node/in_memory.rs
index fee137a7..7492734c 100644
--- a/src/node/in_memory.rs
+++ b/src/node/in_memory.rs
@@ -1,15 +1,15 @@
//! In-memory node, that supports forking other networks.
+use anyhow::{anyhow, Context as _};
+use colored::Colorize;
+use indexmap::IndexMap;
+use once_cell::sync::OnceCell;
+use std::sync::{RwLockReadGuard, RwLockWriteGuard};
use std::{
collections::{HashMap, HashSet},
convert::TryInto,
str::FromStr,
sync::{Arc, RwLock},
};
-
-use anyhow::Context as _;
-use colored::Colorize;
-use indexmap::IndexMap;
-use once_cell::sync::OnceCell;
use zksync_contracts::BaseSystemContracts;
use zksync_multivm::{
interface::{
@@ -275,6 +275,7 @@ impl InMemoryNodeInner {
config: &TestNodeConfig,
time: &TimestampManager,
impersonation: ImpersonationManager,
+ system_contracts: SystemContracts,
) -> Self {
let updated_config = config.clone();
if config.enable_auto_impersonate {
@@ -319,10 +320,7 @@ impl InMemoryNodeInner {
),
config: updated_config.clone(),
console_log_handler: ConsoleLogHandler::default(),
- system_contracts: SystemContracts::from_options(
- &updated_config.system_contracts_options,
- updated_config.use_evm_emulator,
- ),
+ system_contracts,
impersonation,
rich_accounts: HashSet::new(),
previous_states: Default::default(),
@@ -360,10 +358,7 @@ impl InMemoryNodeInner {
),
config: config.clone(),
console_log_handler: ConsoleLogHandler::default(),
- system_contracts: SystemContracts::from_options(
- &config.system_contracts_options,
- config.use_evm_emulator,
- ),
+ system_contracts,
impersonation,
rich_accounts: HashSet::new(),
previous_states: Default::default(),
@@ -972,6 +967,7 @@ pub struct InMemoryNode {
pub(crate) observability: Option,
pub(crate) pool: TxPool,
pub(crate) sealer: BlockSealer,
+ pub(crate) system_contracts: SystemContracts,
}
fn contract_address_from_tx_result(execution_result: &VmExecutionResultAndLogs) -> Option {
@@ -1009,7 +1005,17 @@ impl InMemoryNode {
sealer: BlockSealer,
) -> Self {
let system_contracts_options = config.system_contracts_options;
- let inner = InMemoryNodeInner::new(fork, config, &time, impersonation.clone());
+ let system_contracts = SystemContracts::from_options(
+ &config.system_contracts_options,
+ config.use_evm_emulator,
+ );
+ let inner = InMemoryNodeInner::new(
+ fork,
+ config,
+ &time,
+ impersonation.clone(),
+ system_contracts.clone(),
+ );
InMemoryNode {
inner: Arc::new(RwLock::new(inner)),
snapshots: Default::default(),
@@ -1019,6 +1025,7 @@ impl InMemoryNode {
observability,
pool,
sealer,
+ system_contracts,
}
}
@@ -1041,6 +1048,18 @@ impl InMemoryNode {
self.inner.clone()
}
+ pub fn read_inner(&self) -> anyhow::Result>> {
+ self.inner
+ .read()
+ .map_err(|e| anyhow!("InMemoryNode lock is poisoned: {}", e))
+ }
+
+ pub fn write_inner(&self) -> anyhow::Result>> {
+ self.inner
+ .write()
+ .map_err(|e| anyhow!("InMemoryNode lock is poisoned: {}", e))
+ }
+
pub fn get_cache_config(&self) -> Result {
let inner = self
.inner
@@ -1068,7 +1087,13 @@ impl InMemoryNode {
pub fn reset(&self, fork: Option) -> Result<(), String> {
let config = self.get_config()?;
- let inner = InMemoryNodeInner::new(fork, &config, &self.time, self.impersonation.clone());
+ let inner = InMemoryNodeInner::new(
+ fork,
+ &config,
+ &self.time,
+ self.impersonation.clone(),
+ self.system_contracts.clone(),
+ );
let mut writer = self
.snapshots
@@ -1142,31 +1167,17 @@ impl InMemoryNode {
inner.rich_accounts.insert(address);
}
- pub fn system_contracts_for_l2_call(&self) -> anyhow::Result {
- let inner = self
- .inner
- .read()
- .map_err(|_| anyhow::anyhow!("Failed to acquire read lock"))?;
- Ok(inner.system_contracts.contracts_for_l2_call().clone())
- }
-
pub fn system_contracts_for_tx(
&self,
tx_initiator: Address,
) -> anyhow::Result {
- let inner = self
- .inner
- .read()
- .map_err(|_| anyhow::anyhow!("Failed to acquire read lock"))?;
- Ok(if inner.impersonation.is_impersonating(&tx_initiator) {
+ Ok(if self.impersonation.is_impersonating(&tx_initiator) {
tracing::info!("🕵️ Executing tx from impersonated account {tx_initiator:?}");
- inner
- .system_contracts
+ self.system_contracts
.contracts(TxExecutionMode::VerifyExecute, true)
.clone()
} else {
- inner
- .system_contracts
+ self.system_contracts
.contracts(TxExecutionMode::VerifyExecute, false)
.clone()
})
@@ -1808,6 +1819,13 @@ impl InMemoryNode {
transaction.transaction_index = Some(Index::zero());
transaction.l1_batch_number = Some(U64::from(batch_env.number.0));
transaction.l1_batch_tx_index = Some(Index::zero());
+ if transaction.transaction_type == Some(U64::zero())
+ || transaction.transaction_type.is_none()
+ {
+ transaction.v = transaction
+ .v
+ .map(|v| v + 35 + inner.fork_storage.chain_id.as_u64() * 2);
+ }
transactions.push(TransactionVariant::Full(transaction));
}
diff --git a/src/node/in_memory_ext.rs b/src/node/in_memory_ext.rs
index b1b81c61..57f367a2 100644
--- a/src/node/in_memory_ext.rs
+++ b/src/node/in_memory_ext.rs
@@ -1,3 +1,5 @@
+use crate::namespaces::DetailedTransaction;
+use crate::node::pool::TxBatch;
use crate::node::sealer::BlockSealerMode;
use crate::utils::Numeric;
use crate::{
@@ -9,10 +11,12 @@ use crate::{
use anyhow::{anyhow, Context};
use std::convert::TryInto;
use std::time::Duration;
+use zksync_multivm::interface::{ExecutionResult, TxExecutionMode};
+use zksync_types::api::{Block, TransactionVariant};
use zksync_types::{
get_code_key, get_nonce_key,
utils::{nonces_to_full_nonce, storage_key_for_eth_balance},
- StorageKey,
+ L2BlockNumber, StorageKey,
};
use zksync_types::{AccountTreeId, Address, H256, U256, U64};
use zksync_utils::u256_to_h256;
@@ -72,16 +76,62 @@ impl InMemoryNo
///
/// # Returns
/// The string "0x0".
- pub fn mine_block(&self) -> Result {
- let bootloader_code = self
- .get_inner()
- .read()
- .map_err(|err| anyhow!("failed acquiring lock: {:?}", err))
- .map(|inner| inner.system_contracts.contracts_for_l2_call().clone())?;
- let block_number =
- self.seal_block(&mut self.time.lock(), vec![], bootloader_code.clone())?;
+ pub fn mine_block(&self) -> Result {
+ // TODO: Remove locking once `TestNodeConfig` is refactored into mutable/immutable components
+ let max_transactions = self.read_inner()?.config.max_transactions;
+ let TxBatch { impersonating, txs } =
+ self.pool.take_uniform(max_transactions).unwrap_or(TxBatch {
+ impersonating: false,
+ txs: Vec::new(),
+ });
+ let base_system_contracts = self
+ .system_contracts
+ .contracts(TxExecutionMode::VerifyExecute, impersonating)
+ .clone();
+
+ let block_number = self.seal_block(&mut self.time.lock(), txs, base_system_contracts)?;
tracing::info!("👷 Mined block #{}", block_number);
- Ok("0x0".to_string())
+ Ok(block_number)
+ }
+
+ pub fn mine_detailed(&self) -> Result> {
+ let block_number = self.mine_block()?;
+ let inner = self.read_inner()?;
+ let mut block = inner
+ .block_hashes
+ .get(&(block_number.0 as u64))
+ .and_then(|hash| inner.blocks.get(hash))
+ .expect("freshly mined block is missing from storage")
+ .clone();
+ let detailed_txs = std::mem::take(&mut block.transactions)
+ .into_iter()
+ .map(|tx| match tx {
+ TransactionVariant::Full(tx) => {
+ let tx_result = inner
+ .tx_results
+ .get(&tx.hash)
+ .expect("freshly executed tx is missing from storage");
+ let (output, revert_reason) = match &tx_result.info.result.result {
+ ExecutionResult::Success { output } => (Some(output.clone().into()), None),
+ ExecutionResult::Revert { output } => (
+ Some(output.encoded_data().into()),
+ Some(output.to_user_friendly_string()),
+ ),
+ // Halted transaction should never be a part of a block
+ ExecutionResult::Halt { .. } => unreachable!(),
+ };
+ DetailedTransaction {
+ inner: tx,
+ output,
+ revert_reason,
+ }
+ }
+ TransactionVariant::Hash(_) => {
+ unreachable!()
+ }
+ })
+ .collect();
+ Ok(block.with_transactions(detailed_txs))
}
/// Snapshot the state of the blockchain at the current block. Takes no parameters. Returns the id of the snapshot
@@ -93,37 +143,34 @@ impl InMemoryNo
/// The `U64` identifier for this snapshot.
pub fn snapshot(&self) -> Result {
let snapshots = self.snapshots.clone();
- self.get_inner()
- .write()
- .map_err(|err| anyhow!("failed acquiring lock: {:?}", err))
- .and_then(|writer| {
- // validate max snapshots
- snapshots
- .read()
- .map_err(|err| anyhow!("failed acquiring read lock for snapshot: {:?}", err))
- .and_then(|snapshots| {
- if snapshots.len() >= MAX_SNAPSHOTS as usize {
- return Err(anyhow!(
- "maximum number of '{}' snapshots exceeded",
- MAX_SNAPSHOTS
- ));
- }
-
- Ok(())
- })?;
-
- // snapshot the node
- let snapshot = writer.snapshot().map_err(|err| anyhow!("{}", err))?;
- snapshots
- .write()
- .map(|mut snapshots| {
- snapshots.push(snapshot);
- tracing::info!("Created snapshot '{}'", snapshots.len());
- snapshots.len()
- })
- .map_err(|err| anyhow!("failed storing snapshot: {:?}", err))
- .map(U64::from)
- })
+ self.read_inner().and_then(|writer| {
+ // validate max snapshots
+ snapshots
+ .read()
+ .map_err(|err| anyhow!("failed acquiring read lock for snapshot: {:?}", err))
+ .and_then(|snapshots| {
+ if snapshots.len() >= MAX_SNAPSHOTS as usize {
+ return Err(anyhow!(
+ "maximum number of '{}' snapshots exceeded",
+ MAX_SNAPSHOTS
+ ));
+ }
+
+ Ok(())
+ })?;
+
+ // snapshot the node
+ let snapshot = writer.snapshot().map_err(|err| anyhow!("{}", err))?;
+ snapshots
+ .write()
+ .map(|mut snapshots| {
+ snapshots.push(snapshot);
+ tracing::info!("Created snapshot '{}'", snapshots.len());
+ snapshots.len()
+ })
+ .map_err(|err| anyhow!("failed storing snapshot: {:?}", err))
+ .map(U64::from)
+ })
}
/// Revert the state of the blockchain to a previous snapshot. Takes a single parameter,
@@ -137,70 +184,61 @@ impl InMemoryNo
/// `true` if a snapshot was reverted, otherwise `false`.
pub fn revert_snapshot(&self, snapshot_id: U64) -> Result {
let snapshots = self.snapshots.clone();
- self.get_inner()
- .write()
- .map_err(|err| anyhow!("failed acquiring lock: {:?}", err))
- .and_then(|mut writer| {
- let mut snapshots = snapshots.write().map_err(|err| {
- anyhow!("failed acquiring read lock for snapshots: {:?}", err)
- })?;
- let snapshot_id_index = snapshot_id.as_usize().saturating_sub(1);
- if snapshot_id_index >= snapshots.len() {
- return Err(anyhow!("no snapshot exists for the id '{}'", snapshot_id));
- }
+ self.write_inner().and_then(|mut writer| {
+ let mut snapshots = snapshots
+ .write()
+ .map_err(|err| anyhow!("failed acquiring read lock for snapshots: {:?}", err))?;
+ let snapshot_id_index = snapshot_id.as_usize().saturating_sub(1);
+ if snapshot_id_index >= snapshots.len() {
+ return Err(anyhow!("no snapshot exists for the id '{}'", snapshot_id));
+ }
- // remove all snapshots following the index and use the first snapshot for restore
- let selected_snapshot = snapshots
- .drain(snapshot_id_index..)
- .next()
- .expect("unexpected failure, value must exist");
-
- tracing::info!("Reverting node to snapshot '{snapshot_id:?}'");
- writer
- .restore_snapshot(selected_snapshot)
- .map(|_| {
- tracing::info!("Reverting node to snapshot '{snapshot_id:?}'");
- true
- })
- .map_err(|err| anyhow!("{}", err))
- })
+ // remove all snapshots following the index and use the first snapshot for restore
+ let selected_snapshot = snapshots
+ .drain(snapshot_id_index..)
+ .next()
+ .expect("unexpected failure, value must exist");
+
+ tracing::info!("Reverting node to snapshot '{snapshot_id:?}'");
+ writer
+ .restore_snapshot(selected_snapshot)
+ .map(|_| {
+ tracing::info!("Reverting node to snapshot '{snapshot_id:?}'");
+ true
+ })
+ .map_err(|err| anyhow!("{}", err))
+ })
}
pub fn set_balance(&self, address: Address, balance: U256) -> Result {
- self.get_inner()
- .write()
- .map_err(|err| anyhow!("failed acquiring lock: {:?}", err))
- .map(|mut writer| {
- let balance_key = storage_key_for_eth_balance(&address);
- writer
- .fork_storage
- .set_value(balance_key, u256_to_h256(balance));
- tracing::info!(
- "👷 Balance for address {:?} has been manually set to {} Wei",
- address,
- balance
- );
- true
- })
+ self.write_inner().map(|mut writer| {
+ let balance_key = storage_key_for_eth_balance(&address);
+ writer
+ .fork_storage
+ .set_value(balance_key, u256_to_h256(balance));
+ tracing::info!(
+ "👷 Balance for address {:?} has been manually set to {} Wei",
+ address,
+ balance
+ );
+ true
+ })
}
pub fn set_nonce(&self, address: Address, nonce: U256) -> Result {
- self.get_inner()
- .write()
- .map_err(|err| anyhow!("failed acquiring lock: {:?}", err))
- .map(|mut writer| {
- let nonce_key = get_nonce_key(&address);
- let enforced_full_nonce = nonces_to_full_nonce(nonce, nonce);
- tracing::info!(
- "👷 Nonces for address {:?} have been set to {}",
- address,
- nonce
- );
- writer
- .fork_storage
- .set_value(nonce_key, u256_to_h256(enforced_full_nonce));
- true
- })
+ self.write_inner().map(|mut writer| {
+ let nonce_key = get_nonce_key(&address);
+ let enforced_full_nonce = nonces_to_full_nonce(nonce, nonce);
+ tracing::info!(
+ "👷 Nonces for address {:?} have been set to {}",
+ address,
+ nonce
+ );
+ writer
+ .fork_storage
+ .set_value(nonce_key, u256_to_h256(enforced_full_nonce));
+ true
+ })
}
pub fn mine_blocks(&self, num_blocks: Option, interval: Option) -> Result<()> {
@@ -214,16 +252,22 @@ impl InMemoryNo
anyhow::bail!("Provided interval is `0`; unable to produce {num_blocks} blocks with the same timestamp");
}
- let bootloader_code = self
- .get_inner()
- .read()
- .map_err(|err| anyhow!("failed acquiring lock: {:?}", err))
- .map(|inner| inner.system_contracts.contracts_for_l2_call().clone())?;
+ // TODO: Remove locking once `TestNodeConfig` is refactored into mutable/immutable components
+ let max_transactions = self.read_inner()?.config.max_transactions;
let mut time = self
.time
.lock_with_offsets((0..num_blocks).map(|i| i * interval_sec));
for _ in 0..num_blocks {
- self.seal_block(&mut time, vec![], bootloader_code.clone())?;
+ let TxBatch { impersonating, txs } =
+ self.pool.take_uniform(max_transactions).unwrap_or(TxBatch {
+ impersonating: false,
+ txs: Vec::new(),
+ });
+ let base_system_contracts = self
+ .system_contracts
+ .contracts(TxExecutionMode::VerifyExecute, impersonating)
+ .clone();
+ self.seal_block(&mut time, txs, base_system_contracts)?;
}
tracing::info!("👷 Mined {} blocks", num_blocks);
@@ -314,42 +358,36 @@ impl InMemoryNo
}
pub fn set_code(&self, address: Address, code: String) -> Result<()> {
- self.get_inner()
- .write()
- .map_err(|err| anyhow!("failed acquiring lock: {:?}", err))
- .and_then(|mut writer| {
- let code_key = get_code_key(&address);
- tracing::info!("set code for address {address:#x}");
- let code_slice = code
- .strip_prefix("0x")
- .ok_or_else(|| anyhow!("code must be 0x-prefixed"))?;
- let code_bytes = hex::decode(code_slice)?;
- let hashcode = bytecode_to_factory_dep(code_bytes)?;
- let hash = u256_to_h256(hashcode.0);
- let code = hashcode
- .1
- .iter()
- .flat_map(|entry| {
- let mut bytes = vec![0u8; 32];
- entry.to_big_endian(&mut bytes);
- bytes.to_vec()
- })
- .collect();
- writer.fork_storage.store_factory_dep(hash, code);
- writer.fork_storage.set_value(code_key, hash);
- Ok(())
- })
+ self.write_inner().and_then(|mut writer| {
+ let code_key = get_code_key(&address);
+ tracing::info!("set code for address {address:#x}");
+ let code_slice = code
+ .strip_prefix("0x")
+ .ok_or_else(|| anyhow!("code must be 0x-prefixed"))?;
+ let code_bytes = hex::decode(code_slice)?;
+ let hashcode = bytecode_to_factory_dep(code_bytes)?;
+ let hash = u256_to_h256(hashcode.0);
+ let code = hashcode
+ .1
+ .iter()
+ .flat_map(|entry| {
+ let mut bytes = vec![0u8; 32];
+ entry.to_big_endian(&mut bytes);
+ bytes.to_vec()
+ })
+ .collect();
+ writer.fork_storage.store_factory_dep(hash, code);
+ writer.fork_storage.set_value(code_key, hash);
+ Ok(())
+ })
}
pub fn set_storage_at(&self, address: Address, slot: U256, value: U256) -> Result {
- self.get_inner()
- .write()
- .map_err(|err| anyhow!("failed acquiring lock: {:?}", err))
- .map(|mut writer| {
- let key = StorageKey::new(AccountTreeId::new(address), u256_to_h256(slot));
- writer.fork_storage.set_value(key, u256_to_h256(value));
- true
- })
+ self.write_inner().map(|mut writer| {
+ let key = StorageKey::new(AccountTreeId::new(address), u256_to_h256(slot));
+ writer.fork_storage.set_value(key, u256_to_h256(value));
+ true
+ })
}
pub fn set_logging_enabled(&self, enable: bool) -> Result<()> {
@@ -596,6 +634,7 @@ mod tests {
observability: None,
pool,
sealer: BlockSealer::default(),
+ system_contracts: Default::default(),
};
let address = Address::from_str("0x36615Cf349d7F6344891B1e7CA7C72883F5dc049").unwrap();
@@ -720,12 +759,7 @@ mod tests {
let value = U256::from(42);
let key = StorageKey::new(AccountTreeId::new(address), u256_to_h256(slot));
- let value_before = node
- .get_inner()
- .write()
- .unwrap()
- .fork_storage
- .read_value(&key);
+ let value_before = node.write_inner().unwrap().fork_storage.read_value(&key);
assert_eq!(H256::default(), value_before);
let result = node
@@ -733,12 +767,7 @@ mod tests {
.expect("failed setting value");
assert!(result);
- let value_after = node
- .get_inner()
- .write()
- .unwrap()
- .fork_storage
- .read_value(&key);
+ let value_after = node.write_inner().unwrap().fork_storage.read_value(&key);
assert_eq!(value, h256_to_u256(value_after));
}
@@ -958,7 +987,7 @@ mod tests {
.unwrap()
.expect("block exists");
let result = node.mine_block().expect("mine_block");
- assert_eq!(&result, "0x0");
+ assert_eq!(result, L2BlockNumber(1));
let current_block = node
.get_block_by_number(zksync_types::api::BlockNumber::Latest, false)
@@ -970,7 +999,7 @@ mod tests {
assert_eq!(start_block.timestamp + 1, current_block.timestamp);
let result = node.mine_block().expect("mine_block");
- assert_eq!(&result, "0x0");
+ assert_eq!(result, L2BlockNumber(start_block.number.as_u32() + 2));
let current_block = node
.get_block_by_number(zksync_types::api::BlockNumber::Latest, false)
diff --git a/src/node/zks.rs b/src/node/zks.rs
index a2ed789a..101d385b 100644
--- a/src/node/zks.rs
+++ b/src/node/zks.rs
@@ -359,6 +359,7 @@ impl ZksNamespa
block_number: zksync_types::L2BlockNumber,
) -> RpcResult