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

fix: make manual mining produce non-empty blocks #459

Merged
merged 5 commits into from
Dec 3, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions e2e-tests-rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,16 @@ where
.request("anvil_removePoolTransactions", (address,))
.into()
}

fn mine(
&self,
num_blocks: Option<u64>,
interval: Option<u64>,
) -> ProviderCall<T, (Option<u64>, Option<u64>), ()> {
self.client()
.request("anvil_mine", (num_blocks, interval))
.into()
}
}

impl<P, T> EraTestNodeApiProvider<T> for P
Expand Down
42 changes: 42 additions & 0 deletions e2e-tests-rust/tests/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
}

Expand Down Expand Up @@ -275,3 +281,39 @@ 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(())
}
6 changes: 2 additions & 4 deletions src/deps/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
12 changes: 12 additions & 0 deletions src/deps/system_contracts.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -190,3 +192,13 @@ pub static COMPILED_IN_SYSTEM_CONTRACTS: Lazy<Vec<DeployedContract>> = Lazy::new
deployed_system_contracts.extend(empty_system_contracts);
deployed_system_contracts
});

pub fn get_deployed_contracts(
options: &Options,
use_evm_emulator: bool,
) -> Vec<zksync_types::block::DeployedContract> {
match options {
Options::BuiltIn | Options::BuiltInWithoutSecurity => COMPILED_IN_SYSTEM_CONTRACTS.clone(),
Options::Local => get_system_smart_contracts(use_evm_emulator),
}
}
2 changes: 1 addition & 1 deletion src/node/debug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ impl<S: ForkSource + std::fmt::Debug + Clone + Send + Sync + 'static> 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(
Expand All @@ -158,7 +159,6 @@ impl<S: ForkSource + std::fmt::Debug + Clone + Send + Sync + 'static> 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)))?;
Expand Down
16 changes: 12 additions & 4 deletions src/node/eth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -41,7 +41,7 @@ impl<S: ForkSource + std::fmt::Debug + Clone + Send + Sync + 'static> InMemoryNo
&self,
req: zksync_types::transaction_request::CallRequest,
) -> Result<Bytes, Web3Error> {
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)?;
Expand Down Expand Up @@ -89,7 +89,11 @@ impl<S: ForkSource + std::fmt::Debug + Clone + Send + Sync + 'static> 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)?;

Expand Down Expand Up @@ -154,7 +158,11 @@ impl<S: ForkSource + std::fmt::Debug + Clone + Send + Sync + 'static> 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)?;

Expand Down
75 changes: 43 additions & 32 deletions src/node/in_memory.rs
Original file line number Diff line number Diff line change
@@ -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::{
Expand Down Expand Up @@ -275,6 +275,7 @@ impl<S: std::fmt::Debug + ForkSource> InMemoryNodeInner<S> {
config: &TestNodeConfig,
time: &TimestampManager,
impersonation: ImpersonationManager,
system_contracts: SystemContracts,
) -> Self {
let updated_config = config.clone();
if config.enable_auto_impersonate {
Expand Down Expand Up @@ -319,10 +320,7 @@ impl<S: std::fmt::Debug + ForkSource> InMemoryNodeInner<S> {
),
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(),
Expand Down Expand Up @@ -360,10 +358,7 @@ impl<S: std::fmt::Debug + ForkSource> InMemoryNodeInner<S> {
),
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(),
Expand Down Expand Up @@ -972,6 +967,7 @@ pub struct InMemoryNode<S: Clone> {
pub(crate) observability: Option<Observability>,
pub(crate) pool: TxPool,
pub(crate) sealer: BlockSealer,
pub(crate) system_contracts: SystemContracts,
}

fn contract_address_from_tx_result(execution_result: &VmExecutionResultAndLogs) -> Option<H160> {
Expand Down Expand Up @@ -1009,7 +1005,17 @@ impl<S: ForkSource + std::fmt::Debug + Clone> InMemoryNode<S> {
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(),
Expand All @@ -1019,6 +1025,7 @@ impl<S: ForkSource + std::fmt::Debug + Clone> InMemoryNode<S> {
observability,
pool,
sealer,
system_contracts,
}
}

Expand All @@ -1041,6 +1048,18 @@ impl<S: ForkSource + std::fmt::Debug + Clone> InMemoryNode<S> {
self.inner.clone()
}

pub fn read_inner(&self) -> anyhow::Result<RwLockReadGuard<'_, InMemoryNodeInner<S>>> {
self.inner
.read()
.map_err(|e| anyhow!("InMemoryNode lock is poisoned: {}", e))
}

pub fn write_inner(&self) -> anyhow::Result<RwLockWriteGuard<'_, InMemoryNodeInner<S>>> {
self.inner
.write()
.map_err(|e| anyhow!("InMemoryNode lock is poisoned: {}", e))
}

pub fn get_cache_config(&self) -> Result<CacheConfig, String> {
let inner = self
.inner
Expand Down Expand Up @@ -1068,7 +1087,13 @@ impl<S: ForkSource + std::fmt::Debug + Clone> InMemoryNode<S> {

pub fn reset(&self, fork: Option<ForkDetails>) -> 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
Expand Down Expand Up @@ -1142,31 +1167,17 @@ impl<S: ForkSource + std::fmt::Debug + Clone> InMemoryNode<S> {
inner.rich_accounts.insert(address);
}

pub fn system_contracts_for_l2_call(&self) -> anyhow::Result<BaseSystemContracts> {
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<BaseSystemContracts> {
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()
})
Expand Down
Loading