From daa0b80e0ae5e8303602e7b1c5bd96853ddc7809 Mon Sep 17 00:00:00 2001 From: Marcin M <128217157+mm-zk@users.noreply.github.com> Date: Fri, 17 Jan 2025 08:31:07 +0100 Subject: [PATCH] chore: merge main (#547) * refactor: reduce usage of `ForkStorage` outside `inner` module (#539) * reduce usage of `ForkStorage` outside `inner` module * adapt for zkos * fix destination path (#541) * fix: anvil-zksync able to start even if the port is busy (#542) * fix: closes #512 - start instance even if port is busy * chore: apply make lint:fix * chore: refactor test to make use of anvil-zksync instance * chore: remove no longer needed deps * chore: remove no longer needed deps * chore: bump actions/upload-artifact: v3 to v4 due to CI fail * chore: bump actions/upload-artifact / download: v3 to v4 due to CI fail * compilation fixes --------- Co-authored-by: Daniyar Itegulov Co-authored-by: Nisheeth Barthwal Co-authored-by: Dustin Brickwood --- .github/workflows/checks.yaml | 6 +- .github/workflows/e2e-docker.yml | 2 +- .github/workflows/e2e-rust.yml | 4 +- .github/workflows/e2e.yml | 2 +- .github/workflows/release.yml | 8 +- .github/workflows/spec.yml | 4 +- Makefile | 4 + crates/api_server/src/impls/anvil.rs | 18 ++- crates/api_server/src/server.rs | 22 +-- crates/cli/src/bytecode_override.rs | 2 +- crates/cli/src/main.rs | 60 ++++++- crates/core/src/node/debug.rs | 2 +- crates/core/src/node/eth.rs | 33 ++-- crates/core/src/node/in_memory.rs | 55 ++++--- crates/core/src/node/in_memory_ext.rs | 69 ++++---- crates/core/src/node/inner/fork.rs | 21 +++ crates/core/src/node/inner/in_memory_inner.rs | 71 ++++++--- crates/core/src/node/inner/mod.rs | 9 +- crates/core/src/node/inner/node_executor.rs | 147 +++++++++++++++++- crates/core/src/node/inner/storage.rs | 23 +++ crates/core/src/node/keys.rs | 27 ++-- crates/core/src/node/mod.rs | 2 +- crates/core/src/node/zks.rs | 26 +--- crates/core/src/utils.rs | 8 - e2e-tests-rust/Cargo.lock | 15 ++ e2e-tests-rust/src/lib.rs | 2 +- e2e-tests-rust/tests/lib.rs | 30 +++- scripts/refresh_contracts.sh | 2 +- 28 files changed, 477 insertions(+), 197 deletions(-) create mode 100644 crates/core/src/node/inner/storage.rs diff --git a/.github/workflows/checks.yaml b/.github/workflows/checks.yaml index 340b2d08..900a51df 100644 --- a/.github/workflows/checks.yaml +++ b/.github/workflows/checks.yaml @@ -15,7 +15,7 @@ jobs: name: lint runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install Rust uses: actions-rust-lang/setup-rust-toolchain@v1 with: @@ -39,7 +39,7 @@ jobs: matrix: os: [ubuntu-latest, macos-latest] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install Rust uses: actions-rust-lang/setup-rust-toolchain@v1 @@ -55,7 +55,7 @@ jobs: tar -czf anvil-zksync-${{ matrix.os }}.tar.gz ./anvil-zksync* - name: Upload artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: anvil-zksync-${{ matrix.os }}.tar.gz path: ./target/release/anvil-zksync-${{ matrix.os }}.tar.gz diff --git a/.github/workflows/e2e-docker.yml b/.github/workflows/e2e-docker.yml index 68b8a4b9..fc791833 100644 --- a/.github/workflows/e2e-docker.yml +++ b/.github/workflows/e2e-docker.yml @@ -14,7 +14,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 diff --git a/.github/workflows/e2e-rust.yml b/.github/workflows/e2e-rust.yml index 4a1576f9..69bfc55f 100644 --- a/.github/workflows/e2e-rust.yml +++ b/.github/workflows/e2e-rust.yml @@ -11,12 +11,12 @@ jobs: os: [ ubuntu-latest, macos-latest ] name: e2e-rust steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Download artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: anvil-zksync-${{ matrix.os }}.tar.gz diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index ef45f8a7..b92d326b 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -22,7 +22,7 @@ jobs: cache-dependency-path: 'e2e-tests/yarn.lock' - name: Download artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: anvil-zksync-${{ matrix.os }}.tar.gz diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3f74d1e6..553eef17 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -53,7 +53,7 @@ jobs: needs: [extract-version] steps: - name: Checkout sources - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install Rust uses: actions-rust-lang/setup-rust-toolchain@v1 @@ -99,7 +99,7 @@ jobs: # This is required to share artifacts between different jobs # ======================================================================= - name: Upload artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: anvil-zksync-${{ needs.extract-version.outputs.VERSION }}-${{ matrix.arch }}.tar.gz path: anvil-zksync-${{ needs.extract-version.outputs.VERSION }}-${{ matrix.arch }}.tar.gz @@ -121,7 +121,7 @@ jobs: steps: # This is necessary for generating the changelog. It has to come before "Download Artifacts" or else it deletes the artifacts. - name: Checkout sources - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 @@ -129,7 +129,7 @@ jobs: # Download artifacts # ============================== - name: Download artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 # ============================== # Create release draft diff --git a/.github/workflows/spec.yml b/.github/workflows/spec.yml index 87a50ac0..3d8118a2 100644 --- a/.github/workflows/spec.yml +++ b/.github/workflows/spec.yml @@ -7,12 +7,12 @@ jobs: runs-on: ubuntu-latest name: spec steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: "recursive" - name: Download artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: anvil-zksync-ubuntu-latest.tar.gz diff --git a/Makefile b/Makefile index 630f32d9..c445e687 100644 --- a/Makefile +++ b/Makefile @@ -55,6 +55,10 @@ lint-fix: cd e2e-tests && yarn && yarn lint:fix && yarn fmt:fix cargo clippy --fix cargo fmt + cd e2e-tests-rust && cargo fmt --all + cd e2e-tests-rust && cargo clippy --fix + cd spec-tests && cargo fmt --all + cd spec-tests && cargo clippy --fix # Run unit tests for Rust code test: diff --git a/crates/api_server/src/impls/anvil.rs b/crates/api_server/src/impls/anvil.rs index 58829fea..6e20f3ad 100644 --- a/crates/api_server/src/impls/anvil.rs +++ b/crates/api_server/src/impls/anvil.rs @@ -155,11 +155,19 @@ impl AnvilNamespaceServer for AnvilNamespace { } async fn set_balance(&self, address: Address, balance: U256) -> RpcResult { - Ok(self.node.set_balance(address, balance).await) + Ok(self + .node + .set_balance(address, balance) + .await + .map_err(RpcError::from)?) } async fn set_nonce(&self, address: Address, nonce: U256) -> RpcResult { - Ok(self.node.set_nonce(address, nonce).await) + Ok(self + .node + .set_nonce(address, nonce) + .await + .map_err(RpcError::from)?) } async fn anvil_mine(&self, num_blocks: Option, interval: Option) -> RpcResult<()> { @@ -203,7 +211,11 @@ impl AnvilNamespaceServer for AnvilNamespace { } async fn set_storage_at(&self, address: Address, slot: U256, value: U256) -> RpcResult { - Ok(self.node.set_storage_at(address, slot, value).await) + Ok(self + .node + .set_storage_at(address, slot, value) + .await + .map_err(RpcError::from)?) } async fn set_chain_id(&self, id: u32) -> RpcResult<()> { diff --git a/crates/api_server/src/server.rs b/crates/api_server/src/server.rs index 5fe1577b..cfea2b6a 100644 --- a/crates/api_server/src/server.rs +++ b/crates/api_server/src/server.rs @@ -64,7 +64,7 @@ impl NodeServerBuilder { rpc } - pub async fn build(self, addr: SocketAddr) -> NodeServer { + pub async fn build(self, addr: SocketAddr) -> Result { let cors_layers = tower::util::option_layer(self.cors_enabled.then(|| { // `CorsLayer` adds CORS-specific headers to responses but does not do filtering by itself. // CORS relies on browsers respecting server's access list response headers. @@ -88,14 +88,18 @@ impl NodeServerBuilder { ) .set_rpc_middleware(RpcServiceBuilder::new().rpc_logger(100)); - let server = server_builder.build(addr).await.unwrap(); - let local_addr = server.local_addr().unwrap(); - let rpc = Self::default_rpc(self.node); - // `jsonrpsee` does `tokio::spawn` within `start` method, so we cannot invoke it here, as this method - // should only build the server. This way we delay the launch until the `NodeServer::run` is invoked. - NodeServer { - local_addr, - run_fn: Box::new(move || server.start(rpc)), + match server_builder.build(addr).await { + Ok(server) => { + let local_addr = server.local_addr().unwrap(); + let rpc = Self::default_rpc(self.node); + // `jsonrpsee` does `tokio::spawn` within `start` method, so we cannot invoke it here, as this method + // should only build the server. This way we delay the launch until the `NodeServer::run` is invoked. + Ok(NodeServer { + local_addr, + run_fn: Box::new(move || server.start(rpc)), + }) + } + Err(e) => Err(format!("Failed to bind to address {}: {}", addr, e)), } } } diff --git a/crates/cli/src/bytecode_override.rs b/crates/cli/src/bytecode_override.rs index f6dbe91e..cc71cd6f 100644 --- a/crates/cli/src/bytecode_override.rs +++ b/crates/cli/src/bytecode_override.rs @@ -41,7 +41,7 @@ pub async fn override_bytecodes(node: &InMemoryNode, bytecodes_dir: String) -> a let bytecode = Vec::from_hex(contract.bytecode.object) .with_context(|| format!("Failed to parse hex from {:?}", path))?; - node.override_bytecode(&address, &bytecode) + node.override_bytecode(address, bytecode) .await .expect("Failed to override bytecode"); tracing::info!("+++++ Replacing bytecode at address {:?} +++++", address); diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index 156d45e7..43d45ebf 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -13,7 +13,7 @@ use anvil_zksync_core::filters::EthFilters; use anvil_zksync_core::node::fork::ForkDetails; use anvil_zksync_core::node::{ BlockSealer, BlockSealerMode, ImpersonationManager, InMemoryNode, InMemoryNodeInner, - NodeExecutor, TestNodeFeeInputProvider, TxPool, + NodeExecutor, StorageKeyLayout, TestNodeFeeInputProvider, TxPool, }; use anvil_zksync_core::observability::Observability; use anvil_zksync_core::system_contracts::SystemContracts; @@ -223,18 +223,27 @@ async fn main() -> anyhow::Result<()> { config.use_evm_emulator, config.zkos_config.clone(), ); + let storage_key_layout = if config.zkos_config.use_zkos { + StorageKeyLayout::ZkOs + } else { + StorageKeyLayout::ZkEra + }; - let (node_inner, _fork_storage, blockchain, time) = InMemoryNodeInner::init( + let (node_inner, storage, blockchain, time) = InMemoryNodeInner::init( fork_details, fee_input_provider.clone(), filters, config.clone(), impersonation.clone(), system_contracts.clone(), + storage_key_layout, ); - let (node_executor, node_handle) = - NodeExecutor::new(node_inner.clone(), system_contracts.clone()); + let (node_executor, node_handle) = NodeExecutor::new( + node_inner.clone(), + system_contracts.clone(), + storage_key_layout, + ); let sealing_mode = if config.no_mining { BlockSealerMode::noop() } else if let Some(block_time) = config.block_time { @@ -248,6 +257,7 @@ async fn main() -> anyhow::Result<()> { let node: InMemoryNode = InMemoryNode::new( node_inner, blockchain, + storage, node_handle, Some(observability), time, @@ -255,6 +265,7 @@ async fn main() -> anyhow::Result<()> { pool, block_sealer_state, system_contracts, + storage_key_layout, ); if let Some(ref bytecodes_dir) = config.override_bytecodes_dir { @@ -306,10 +317,43 @@ async fn main() -> anyhow::Result<()> { } let mut server_handles = Vec::with_capacity(config.host.len()); for host in &config.host { - let addr = SocketAddr::new(*host, config.port); - let server = server_builder.clone().build(addr).await; - config.port = server.local_addr().port(); - server_handles.push(server.run()); + let mut addr = SocketAddr::new(*host, config.port); + + match server_builder.clone().build(addr).await { + Ok(server) => { + config.port = server.local_addr().port(); + server_handles.push(server.run()); + } + Err(err) => { + tracing::info!( + "Failed to bind to address {}:{}: {}. Retrying with a different port...", + host, + config.port, + err + ); + + // Attempt to bind to a dynamic port + addr.set_port(0); + match server_builder.clone().build(addr).await { + Ok(server) => { + config.port = server.local_addr().port(); + tracing::info!( + "Successfully started server on port {} for host {}", + config.port, + host + ); + server_handles.push(server.run()); + } + Err(err) => { + return Err(anyhow!( + "Failed to start server on host {} with port: {}", + host, + err + )); + } + } + } + } } let any_server_stopped = futures::future::select_all(server_handles.into_iter().map(|h| Box::pin(h.stopped()))); diff --git a/crates/core/src/node/debug.rs b/crates/core/src/node/debug.rs index 214d80e3..49aae644 100644 --- a/crates/core/src/node/debug.rs +++ b/crates/core/src/node/debug.rs @@ -71,7 +71,7 @@ impl InMemoryNode { // 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()); let system_env = inner.create_system_env(system_contracts.clone(), execution_mode); - let storage = StorageView::new(&inner.fork_storage).into_rc_ptr(); + let storage = StorageView::new(inner.read_storage()).into_rc_ptr(); let mut vm: Vm<_, HistoryDisabled> = Vm::new(l1_batch_env, system_env, storage); // We must inject *some* signature (otherwise bootloader code fails to generate hash). diff --git a/crates/core/src/node/eth.rs b/crates/core/src/node/eth.rs index cbc9fd82..cdfabbf6 100644 --- a/crates/core/src/node/eth.rs +++ b/crates/core/src/node/eth.rs @@ -28,8 +28,6 @@ use crate::{ utils::{h256_to_u64, TransparentError}, }; -use super::keys::StorageKeyLayout; - impl InMemoryNode { pub async fn call_impl( &self, @@ -78,7 +76,7 @@ impl InMemoryNode { } pub async fn send_raw_transaction_impl(&self, tx_bytes: Bytes) -> Result { - let chain_id = self.inner.read().await.fork_storage.chain_id; + let chain_id = self.chain_id().await; let (tx_req, hash) = TransactionRequest::from_bytes(&tx_bytes.0, chain_id)?; let mut l2_tx = @@ -102,10 +100,7 @@ impl InMemoryNode { ) -> Result { let (chain_id, l2_gas_price) = { let reader = self.inner.read().await; - ( - reader.fork_storage.chain_id, - reader.fee_input_provider.gas_price(), - ) + (self.chain_id().await, reader.fee_input_provider.gas_price()) }; let mut tx_req = TransactionRequest::from(tx.clone()); @@ -176,13 +171,10 @@ impl InMemoryNode { // TODO: Support _block: Option, ) -> anyhow::Result { - let balance_key = StorageKeyLayout::get_storage_key_for_base_token( - self.system_contracts.use_zkos(), - &address, - ); - - let inner_guard = self.inner.read().await; - match inner_guard.fork_storage.read_value_internal(&balance_key) { + let balance_key = self + .storage_key_layout + .get_storage_key_for_base_token(&address); + match self.storage.read_value_alt(&balance_key).await { Ok(balance) => Ok(h256_to_u256(balance)), Err(error) => Err(anyhow::anyhow!("failed to read account balance: {error}")), } @@ -251,12 +243,9 @@ impl InMemoryNode { // TODO: Support _block: Option, ) -> anyhow::Result { - let inner = self.inner.write().await; - let code_key = get_code_key(&address); - - match inner.fork_storage.read_value_internal(&code_key) { - Ok(code_hash) => match inner.fork_storage.load_factory_dep_internal(code_hash) { + match self.storage.read_value_alt(&code_key).await { + Ok(code_hash) => match self.storage.load_factory_dep_alt(code_hash).await { Ok(raw_code) => { let code = raw_code.unwrap_or_default(); Ok(Bytes::from(code)) @@ -273,10 +262,8 @@ impl InMemoryNode { // TODO: Support _block: Option, ) -> anyhow::Result { - let inner = self.inner.read().await; - let nonce_key = StorageKeyLayout::get_nonce_key(self.system_contracts.use_zkos(), &address); - - match inner.fork_storage.read_value_internal(&nonce_key) { + let nonce_key = self.storage_key_layout.get_nonce_key(&address); + match self.storage.read_value_alt(&nonce_key).await { Ok(result) => Ok(h256_to_u64(result).into()), Err(error) => Err(anyhow::anyhow!("failed to read nonce storage: {error}")), } diff --git a/crates/core/src/node/in_memory.rs b/crates/core/src/node/in_memory.rs index f8317246..698f4bb5 100644 --- a/crates/core/src/node/in_memory.rs +++ b/crates/core/src/node/in_memory.rs @@ -3,13 +3,15 @@ use super::inner::fork::ForkDetails; use super::inner::node_executor::NodeExecutorHandle; use super::inner::InMemoryNodeInner; use super::vm::AnvilVM; -use crate::deps::{storage_view::StorageView, InMemoryStorage}; +use crate::deps::storage_view::StorageView; +use crate::deps::InMemoryStorage; use crate::filters::EthFilters; use crate::node::call_error_tracer::CallErrorTracer; use crate::node::error::LoadStateError; use crate::node::fee_model::TestNodeFeeInputProvider; use crate::node::impersonate::{ImpersonationManager, ImpersonationState}; use crate::node::inner::blockchain::ReadBlockchain; +use crate::node::inner::storage::ReadStorageDyn; use crate::node::inner::time::ReadTime; use crate::node::sealer::BlockSealerState; use crate::node::state::VersionedState; @@ -45,21 +47,20 @@ use zksync_multivm::tracers::CallTracer; use zksync_multivm::utils::{get_batch_base_fee, get_max_batch_gas_limit}; use zksync_multivm::vm_latest::Vm; +use crate::node::keys::StorageKeyLayout; use zksync_multivm::vm_latest::{HistoryDisabled, ToTracerPointer}; use zksync_multivm::VmVersion; use zksync_types::api::{Block, DebugCall, TransactionReceipt, TransactionVariant}; use zksync_types::block::unpack_block_info; -use zksync_types::bytecode::BytecodeHash; use zksync_types::fee_model::BatchFeeInput; use zksync_types::l2::L2Tx; use zksync_types::storage::{ EMPTY_UNCLES_HASH, SYSTEM_CONTEXT_ADDRESS, SYSTEM_CONTEXT_BLOCK_INFO_POSITION, }; use zksync_types::web3::{keccak256, Bytes}; -use zksync_types::{get_code_key, h256_to_u256}; use zksync_types::{ - AccountTreeId, Address, Bloom, L1BatchNumber, L2BlockNumber, PackedEthSignature, StorageKey, - StorageValue, Transaction, H160, H256, H64, U256, U64, + h256_to_u256, AccountTreeId, Address, Bloom, L1BatchNumber, L2BlockNumber, L2ChainId, + PackedEthSignature, StorageKey, StorageValue, Transaction, H160, H256, H64, U256, U64, }; /// Max possible size of an ABI encoded tx (in bytes). @@ -247,6 +248,7 @@ pub struct InMemoryNode { /// A thread safe reference to the [InMemoryNodeInner]. pub(crate) inner: Arc>, pub(crate) blockchain: Box, + pub(crate) storage: Box, pub(crate) node_handle: NodeExecutorHandle, /// List of snapshots of the [InMemoryNodeInner]. This is bounded at runtime by [MAX_SNAPSHOTS]. pub(crate) snapshots: Arc>>, @@ -257,6 +259,7 @@ pub struct InMemoryNode { pub(crate) pool: TxPool, pub(crate) sealer_state: BlockSealerState, pub(crate) system_contracts: SystemContracts, + pub(crate) storage_key_layout: StorageKeyLayout, } impl InMemoryNode { @@ -264,6 +267,7 @@ impl InMemoryNode { pub fn new( inner: Arc>, blockchain: Box, + storage: Box, node_handle: NodeExecutorHandle, observability: Option, time: Box, @@ -271,10 +275,12 @@ impl InMemoryNode { pool: TxPool, sealer_state: BlockSealerState, system_contracts: SystemContracts, + storage_key_layout: StorageKeyLayout, ) -> Self { InMemoryNode { inner, blockchain, + storage, node_handle, snapshots: Default::default(), time, @@ -283,6 +289,7 @@ impl InMemoryNode { pool, sealer_state, system_contracts, + storage_key_layout, } } @@ -383,7 +390,7 @@ impl InMemoryNode { let (batch_env, _) = inner.create_l1_batch_env().await; let system_env = inner.create_system_env(base_contracts, execution_mode); - let storage = StorageView::new(&inner.fork_storage).into_rc_ptr(); + let storage = StorageView::new(inner.read_storage()).into_rc_ptr(); let mut vm = if self.system_contracts.use_zkos() { AnvilVM::ZKOs(super::zkos::ZKOsVM::<_, HistoryDisabled>::new( @@ -473,22 +480,10 @@ impl InMemoryNode { // Forcefully stores the given bytecode at a given account. pub async fn override_bytecode( &self, - address: &Address, - bytecode: &[u8], - ) -> Result<(), String> { - let inner = self.inner.write().await; - - let code_key = get_code_key(address); - - let bytecode_hash = BytecodeHash::for_bytecode(bytecode).value(); - - inner - .fork_storage - .store_factory_dep(bytecode_hash, bytecode.to_owned()); - - inner.fork_storage.set_value(code_key, bytecode_hash); - - Ok(()) + address: Address, + bytecode: Vec, + ) -> anyhow::Result<()> { + self.node_handle.set_code_sync(address, bytecode).await } pub async fn dump_state(&self, preserve_historical_states: bool) -> anyhow::Result { @@ -623,6 +618,10 @@ impl InMemoryNode { observability.set_logging(directive)?; Ok(true) } + + pub async fn chain_id(&self) -> L2ChainId { + self.inner.read().await.chain_id() + } } pub fn load_last_l1_batch(storage: StoragePtr) -> Option<(u64, u64)> { @@ -655,16 +654,22 @@ impl InMemoryNode { config.use_evm_emulator, config.zkos_config.clone(), ); - let (inner, _, blockchain, time) = InMemoryNodeInner::init( + let storage_key_layout = if config.zkos_config.use_zkos { + StorageKeyLayout::ZkOs + } else { + StorageKeyLayout::ZkEra + }; + let (inner, storage, blockchain, time) = InMemoryNodeInner::init( fork, fee_provider, Arc::new(RwLock::new(Default::default())), config, impersonation.clone(), system_contracts.clone(), + storage_key_layout, ); let (node_executor, node_handle) = - NodeExecutor::new(inner.clone(), system_contracts.clone()); + NodeExecutor::new(inner.clone(), system_contracts.clone(), storage_key_layout); let pool = TxPool::new( impersonation.clone(), anvil_zksync_types::TransactionOrder::Fifo, @@ -680,6 +685,7 @@ impl InMemoryNode { Self::new( inner, blockchain, + storage, node_handle, None, time, @@ -687,6 +693,7 @@ impl InMemoryNode { pool, block_sealer_state, system_contracts, + storage_key_layout, ) } diff --git a/crates/core/src/node/in_memory_ext.rs b/crates/core/src/node/in_memory_ext.rs index 55bb548b..d2c8889f 100644 --- a/crates/core/src/node/in_memory_ext.rs +++ b/crates/core/src/node/in_memory_ext.rs @@ -2,15 +2,13 @@ use super::inner::fork::ForkDetails; use super::pool::TxBatch; use super::sealer::BlockSealerMode; use super::InMemoryNode; -use crate::node::keys::StorageKeyLayout; -use crate::utils::bytecode_to_factory_dep; use anvil_zksync_types::api::{DetailedTransaction, ResetRequest}; -use anyhow::anyhow; +use anyhow::{anyhow, Context}; use std::time::Duration; use zksync_types::api::{Block, TransactionVariant}; +use zksync_types::bytecode::BytecodeHash; use zksync_types::u256_to_h256; -use zksync_types::{get_code_key, utils::nonces_to_full_nonce, L2BlockNumber, StorageKey}; -use zksync_types::{AccountTreeId, Address, H256, U256, U64}; +use zksync_types::{AccountTreeId, Address, L2BlockNumber, StorageKey, H256, U256, U64}; type Result = anyhow::Result; @@ -165,36 +163,24 @@ impl InMemoryNode { .map_err(|err| anyhow!("{}", err)) } - pub async fn set_balance(&self, address: Address, balance: U256) -> bool { - let writer = self.inner.write().await; - let balance_key = StorageKeyLayout::get_storage_key_for_base_token( - self.system_contracts.use_zkos(), - &address, - ); - writer - .fork_storage - .set_value(balance_key, u256_to_h256(balance)); + pub async fn set_balance(&self, address: Address, balance: U256) -> anyhow::Result { + self.node_handle.set_balance_sync(address, balance).await?; tracing::info!( "👷 Balance for address {:?} has been manually set to {} Wei", address, balance ); - true + Ok(true) } - pub async fn set_nonce(&self, address: Address, nonce: U256) -> bool { - let writer = self.inner.write().await; - let nonce_key = StorageKeyLayout::get_nonce_key(self.system_contracts.use_zkos(), &address); - let enforced_full_nonce = nonces_to_full_nonce(nonce, nonce); + pub async fn set_nonce(&self, address: Address, nonce: U256) -> anyhow::Result { + self.node_handle.set_nonce_sync(address, nonce).await?; 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 + Ok(true) } pub async fn mine_blocks(&self, num_blocks: Option, interval: Option) -> Result<()> { @@ -316,24 +302,24 @@ impl InMemoryNode { } pub async fn set_code(&self, address: Address, code: String) -> Result<()> { - let writer = self.inner.write().await; - 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 (hash, code) = bytecode_to_factory_dep(code_bytes)?; - writer.fork_storage.store_factory_dep(hash, code); - writer.fork_storage.set_value(code_key, hash); + let bytecode = hex::decode(code_slice)?; + zksync_types::bytecode::validate_bytecode(&bytecode).context("Invalid bytecode")?; + tracing::info!( + ?address, + bytecode_hash = ?BytecodeHash::for_bytecode(&bytecode).value(), + "set code" + ); + self.node_handle.set_code_sync(address, bytecode).await?; Ok(()) } - pub async fn set_storage_at(&self, address: Address, slot: U256, value: U256) -> bool { - let writer = self.inner.write().await; + pub async fn set_storage_at(&self, address: Address, slot: U256, value: U256) -> Result { let key = StorageKey::new(AccountTreeId::new(address), u256_to_h256(slot)); - writer.fork_storage.set_value(key, u256_to_h256(value)); - true + self.node_handle.set_storage_sync(key, value).await?; + Ok(true) } pub fn set_logging_enabled(&self, enable: bool) -> Result<()> { @@ -460,7 +446,7 @@ mod tests { let balance_before = node.get_balance_impl(address, None).await.unwrap(); - let result = node.set_balance(address, U256::from(1337)).await; + let result = node.set_balance(address, U256::from(1337)).await.unwrap(); assert!(result); let balance_after = node.get_balance_impl(address, None).await.unwrap(); @@ -478,7 +464,7 @@ mod tests { .await .unwrap(); - let result = node.set_nonce(address, U256::from(1337)).await; + let result = node.set_nonce(address, U256::from(1337)).await.unwrap(); assert!(result); let nonce_after = node @@ -488,7 +474,7 @@ mod tests { assert_eq!(nonce_after, U256::from(1337)); assert_ne!(nonce_before, nonce_after); - let result = node.set_nonce(address, U256::from(1336)).await; + let result = node.set_nonce(address, U256::from(1336)).await.unwrap(); assert!(result); let nonce_after = node @@ -582,7 +568,7 @@ mod tests { .get_transaction_count_impl(address, None) .await .unwrap(); - assert!(node.set_nonce(address, U256::from(1337)).await); + assert!(node.set_nonce(address, U256::from(1337)).await.unwrap()); assert!(node.reset_network(None).await.unwrap()); @@ -609,7 +595,10 @@ mod tests { Address::from_str("0xd8da6bf26964af9d7eed9e03e53415d37aa96045").unwrap(); // give impersonated account some balance - let result = node.set_balance(to_impersonate, U256::exp10(18)).await; + let result = node + .set_balance(to_impersonate, U256::exp10(18)) + .await + .unwrap(); assert!(result); // construct a tx @@ -707,7 +696,7 @@ mod tests { let value_before = node.inner.write().await.fork_storage.read_value(&key); assert_eq!(H256::default(), value_before); - let result = node.set_storage_at(address, slot, value).await; + let result = node.set_storage_at(address, slot, value).await.unwrap(); assert!(result); let value_after = node.inner.write().await.fork_storage.read_value(&key); diff --git a/crates/core/src/node/inner/fork.rs b/crates/core/src/node/inner/fork.rs index 6332253d..78c58549 100644 --- a/crates/core/src/node/inner/fork.rs +++ b/crates/core/src/node/inner/fork.rs @@ -3,6 +3,7 @@ //! There is ForkStorage (that is a wrapper over InMemoryStorage) //! And ForkDetails - that parses network address and fork height from arguments. +use crate::node::inner::storage::ReadStorageDyn; use crate::utils::block_on; use crate::{deps::InMemoryStorage, http_fork_source::HttpForkSource}; use anvil_zksync_config::constants::{ @@ -10,6 +11,7 @@ use anvil_zksync_config::constants::{ DEFAULT_FAIR_PUBDATA_PRICE, TEST_NODE_NETWORK_ID, }; use anvil_zksync_config::types::{CacheConfig, SystemContractsOptions}; +use async_trait::async_trait; use eyre::eyre; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; @@ -323,6 +325,25 @@ impl ReadStorage for &ForkStorage { } } +#[async_trait] +impl ReadStorageDyn for ForkStorage { + fn dyn_cloned(&self) -> Box { + Box::new(self.clone()) + } + + async fn read_value_alt(&self, key: &StorageKey) -> anyhow::Result { + // TODO: Get rid of `block_on` inside to propagate asynchronous execution up to this level + self.read_value_internal(key) + .map_err(|e| anyhow::anyhow!("failed reading value: {:?}", e)) + } + + async fn load_factory_dep_alt(&self, hash: H256) -> anyhow::Result>> { + // TODO: Get rid of `block_on` inside to propagate asynchronous execution up to this level + self.load_factory_dep_internal(hash) + .map_err(|e| anyhow::anyhow!("failed to load factory dep: {:?}", e)) + } +} + impl ForkStorage { pub fn set_value(&self, key: StorageKey, value: zksync_types::StorageValue) { let mut mutator = self.inner.write().unwrap(); diff --git a/crates/core/src/node/inner/in_memory_inner.rs b/crates/core/src/node/inner/in_memory_inner.rs index 3ff3d534..10bfc8f1 100644 --- a/crates/core/src/node/inner/in_memory_inner.rs +++ b/crates/core/src/node/inner/in_memory_inner.rs @@ -19,12 +19,13 @@ use crate::node::{ }; use crate::system_contracts::SystemContracts; -use crate::utils::{bytecode_to_factory_dep, create_debug_output}; +use crate::utils::create_debug_output; use crate::{delegate_vm, formatter, utils}; use anvil_zksync_config::constants::NON_FORK_FIRST_BLOCK_TIMESTAMP; use anvil_zksync_config::types::ZKOSConfig; use anvil_zksync_config::TestNodeConfig; use anvil_zksync_types::{ShowCalls, ShowGasDetails, ShowStorageLogs, ShowVMDetails}; +use anyhow::Context; use colored::Colorize; use indexmap::IndexMap; use once_cell::sync::OnceCell; @@ -55,6 +56,7 @@ use zksync_multivm::vm_latest::{ use zksync_multivm::VmVersion; use zksync_types::api::{BlockIdVariant, TransactionVariant}; use zksync_types::block::build_bloom; +use zksync_types::bytecode::BytecodeHash; use zksync_types::fee::Fee; use zksync_types::fee_model::{BatchFeeInput, PubdataIndependentBatchFeeModelInput}; use zksync_types::l2::{L2Tx, TransactionType}; @@ -63,7 +65,7 @@ use zksync_types::utils::{decompose_full_nonce, nonces_to_full_nonce}; use zksync_types::web3::{Bytes, Index}; use zksync_types::{ api, h256_to_address, h256_to_u256, u256_to_h256, AccountTreeId, Address, Bloom, BloomInput, - L1BatchNumber, L2BlockNumber, StorageKey, StorageValue, Transaction, + L1BatchNumber, L2BlockNumber, L2ChainId, StorageKey, StorageValue, Transaction, ACCOUNT_CODE_STORAGE_ADDRESS, H160, H256, MAX_L2_TX_GAS_LIMIT, U256, U64, }; use zksync_web3_decl::error::Web3Error; @@ -89,6 +91,7 @@ pub struct InMemoryNodeInner { pub rich_accounts: HashSet, /// Keeps track of historical states indexed via block hash. Limited to [MAX_PREVIOUS_STATES]. previous_states: IndexMap>, + storage_key_layout: StorageKeyLayout, } impl InMemoryNodeInner { @@ -103,6 +106,7 @@ impl InMemoryNodeInner { config: TestNodeConfig, impersonation: ImpersonationManager, system_contracts: SystemContracts, + storage_key_layout: StorageKeyLayout, ) -> Self { InMemoryNodeInner { blockchain, @@ -116,6 +120,7 @@ impl InMemoryNodeInner { impersonation, rich_accounts: HashSet::new(), previous_states: Default::default(), + storage_key_layout, } } @@ -428,11 +433,9 @@ impl InMemoryNodeInner { let mut bytecodes = HashMap::new(); for b in &*compressed_bytecodes { - let (hash, bytecode) = bytecode_to_factory_dep(b.original.clone()).map_err(|err| { - tracing::error!("{}", format!("cannot convert bytecode: {err}").on_red()); - err - })?; - bytecodes.insert(hash, bytecode); + zksync_types::bytecode::validate_bytecode(&b.original).context("Invalid bytecode")?; + let hash = BytecodeHash::for_bytecode(&b.original).value(); + bytecodes.insert(hash, b.original.clone()); } // Also add bytecodes that were created by EVM. for entry in &tx_result.dynamic_factory_deps { @@ -486,8 +489,8 @@ impl InMemoryNodeInner { } // Write all the factory deps. - for (hash, code) in bytecodes.iter() { - self.fork_storage.store_factory_dep(*hash, code.clone()) + for (hash, code) in bytecodes { + self.fork_storage.store_factory_dep(hash, code) } let logs = result @@ -822,7 +825,7 @@ impl InMemoryNodeInner { // In theory, if the transaction has failed with such large gas limit, we could have returned an API error here right away, // but doing it later on keeps the code more lean. - let result = InMemoryNodeInner::estimate_gas_step( + let result = self.estimate_gas_step( l2_tx.clone(), gas_per_pubdata_byte, BATCH_GAS_LIMIT, @@ -860,7 +863,7 @@ impl InMemoryNodeInner { ); let try_gas_limit = additional_gas_for_pubdata + mid; - let estimate_gas_result = InMemoryNodeInner::estimate_gas_step( + let estimate_gas_result = self.estimate_gas_step( l2_tx.clone(), gas_per_pubdata_byte, try_gas_limit, @@ -892,7 +895,7 @@ impl InMemoryNodeInner { * self.fee_input_provider.estimate_gas_scale_factor) as u64; - let estimate_gas_result = InMemoryNodeInner::estimate_gas_step( + let estimate_gas_result = self.estimate_gas_step( l2_tx.clone(), gas_per_pubdata_byte, suggested_gas_limit, @@ -1008,6 +1011,7 @@ impl InMemoryNodeInner { /// Runs fee estimation against a sandbox vm with the given gas_limit. #[allow(clippy::too_many_arguments)] fn estimate_gas_step( + &self, mut l2_tx: L2Tx, gas_per_pubdata_byte: u64, tx_gas_limit: u64, @@ -1033,8 +1037,9 @@ impl InMemoryNodeInner { // The nonce needs to be updated let nonce = l2_tx.nonce(); - let nonce_key = - StorageKeyLayout::get_nonce_key(zkos_config.use_zkos, &l2_tx.initiator_account()); + let nonce_key = self + .storage_key_layout + .get_nonce_key(&l2_tx.initiator_account()); let full_nonce = storage.borrow_mut().read_value(&nonce_key); let (_, deployment_nonce) = decompose_full_nonce(h256_to_u256(full_nonce)); let enforced_full_nonce = nonces_to_full_nonce(U256::from(nonce.0), deployment_nonce); @@ -1044,8 +1049,9 @@ impl InMemoryNodeInner { // We need to explicitly put enough balance into the account of the users let payer = l2_tx.payer(); - let balance_key = - StorageKeyLayout::get_storage_key_for_base_token(zkos_config.use_zkos, &payer); + let balance_key = self + .storage_key_layout + .get_storage_key_for_base_token(&payer); let mut current_balance = h256_to_u256(storage.borrow_mut().read_value(&balance_key)); let added_balance = l2_tx.common_data.fee.gas_limit * l2_tx.common_data.fee.max_fee_per_gas; current_balance += added_balance; @@ -1321,10 +1327,9 @@ impl InMemoryNodeInner { /// Adds a lot of tokens to a given account with a specified balance. pub fn set_rich_account(&mut self, address: H160, balance: U256) { - let key = StorageKeyLayout::get_storage_key_for_base_token( - self.system_contracts.use_zkos(), - &address, - ); + let key = self + .storage_key_layout + .get_storage_key_for_base_token(&address); let keys = { let mut storage_view = StorageView::new(&self.fork_storage); @@ -1338,6 +1343,15 @@ impl InMemoryNodeInner { } self.rich_accounts.insert(address); } + + pub fn read_storage(&self) -> Box { + Box::new(&self.fork_storage) + } + + // TODO: Remove, this should also be made available from somewhere else + pub fn chain_id(&self) -> L2ChainId { + self.fork_storage.chain_id + } } #[derive(Debug)] @@ -1389,6 +1403,11 @@ impl InMemoryNodeInner { config.use_evm_emulator, config.zkos_config.clone(), ); + let storage_key_layout = if config.zkos_config.use_zkos { + StorageKeyLayout::ZkOs + } else { + StorageKeyLayout::ZkEra + }; let (inner, _, _, _) = InMemoryNodeInner::init( None, fee_provider, @@ -1396,6 +1415,7 @@ impl InMemoryNodeInner { config, impersonation.clone(), system_contracts.clone(), + storage_key_layout, ); inner } @@ -1611,7 +1631,7 @@ mod tests { let system_contracts = node .system_contracts .system_contracts_for_initiator(&node.impersonation, &tx.initiator_account()); - let (block_ctx, batch_env, mut vm) = test_vm(&mut *node, system_contracts).await; + let (block_ctx, batch_env, mut vm) = test_vm(&mut node, system_contracts).await; let err = node .run_l2_tx(tx, 0, &block_ctx, &batch_env, &mut vm) .unwrap_err(); @@ -1630,7 +1650,7 @@ mod tests { let system_contracts = node .system_contracts .system_contracts_for_initiator(&node.impersonation, &tx.initiator_account()); - let (block_ctx, batch_env, mut vm) = test_vm(&mut *node, system_contracts).await; + let (block_ctx, batch_env, mut vm) = test_vm(&mut node, system_contracts).await; let err = node .run_l2_tx(tx, 0, &block_ctx, &batch_env, &mut vm) .unwrap_err(); @@ -1653,7 +1673,7 @@ mod tests { let system_contracts = node .system_contracts .system_contracts_for_initiator(&node.impersonation, &tx.initiator_account()); - let (block_ctx, batch_env, mut vm) = test_vm(&mut *node, system_contracts).await; + let (block_ctx, batch_env, mut vm) = test_vm(&mut node, system_contracts).await; let err = node .run_l2_tx(tx, 0, &block_ctx, &batch_env, &mut vm) .unwrap_err(); @@ -1714,6 +1734,7 @@ mod tests { TestNodeConfig::default(), impersonation, node.system_contracts.clone(), + node.storage_key_layout, ); let mut node = node.write().await; @@ -1722,7 +1743,7 @@ mod tests { let system_contracts = node .system_contracts .system_contracts_for_initiator(&node.impersonation, &tx.initiator_account()); - let (_, _, mut vm) = test_vm(&mut *node, system_contracts).await; + let (_, _, mut vm) = test_vm(&mut node, system_contracts).await; node.run_l2_tx_raw(tx, &mut vm) .expect("transaction must pass with external storage"); } @@ -1772,7 +1793,7 @@ mod tests { let system_contracts = node .system_contracts .system_contracts_for_initiator(&node.impersonation, &tx.initiator_account()); - let (_, _, mut vm) = test_vm(&mut *node, system_contracts).await; + let (_, _, mut vm) = test_vm(&mut node, system_contracts).await; let TxExecutionOutput { result, .. } = node.run_l2_tx_raw(tx, &mut vm).expect("failed tx"); match result.result { diff --git a/crates/core/src/node/inner/mod.rs b/crates/core/src/node/inner/mod.rs index 760af367..3b80f0c3 100644 --- a/crates/core/src/node/inner/mod.rs +++ b/crates/core/src/node/inner/mod.rs @@ -13,12 +13,15 @@ pub mod blockchain; pub mod fork; mod in_memory_inner; pub mod node_executor; +pub mod storage; pub mod time; pub use in_memory_inner::{InMemoryNodeInner, TxExecutionOutput}; use crate::filters::EthFilters; use crate::node::blockchain::Blockchain; +use crate::node::inner::storage::ReadStorageDyn; +use crate::node::keys::StorageKeyLayout; use crate::node::{ImpersonationManager, TestNodeFeeInputProvider}; use crate::system_contracts::SystemContracts; use anvil_zksync_config::constants::NON_FORK_FIRST_BLOCK_TIMESTAMP; @@ -39,9 +42,10 @@ impl InMemoryNodeInner { config: TestNodeConfig, impersonation: ImpersonationManager, system_contracts: SystemContracts, + storage_key_layout: StorageKeyLayout, ) -> ( Arc>, - ForkStorage, + Box, Box, Box, ) { @@ -72,11 +76,12 @@ impl InMemoryNodeInner { config.clone(), impersonation.clone(), system_contracts.clone(), + storage_key_layout, ); ( Arc::new(RwLock::new(node_inner)), - fork_storage, + Box::new(fork_storage), Box::new(blockchain), Box::new(time), ) diff --git a/crates/core/src/node/inner/node_executor.rs b/crates/core/src/node/inner/node_executor.rs index b06bd9d4..f963c8ba 100644 --- a/crates/core/src/node/inner/node_executor.rs +++ b/crates/core/src/node/inner/node_executor.rs @@ -1,27 +1,33 @@ use super::InMemoryNodeInner; +use crate::node::keys::StorageKeyLayout; use crate::node::pool::TxBatch; use crate::system_contracts::SystemContracts; use std::sync::Arc; use tokio::sync::{mpsc, oneshot, RwLock}; use zksync_multivm::interface::TxExecutionMode; -use zksync_types::L2BlockNumber; +use zksync_types::bytecode::BytecodeHash; +use zksync_types::utils::nonces_to_full_nonce; +use zksync_types::{get_code_key, u256_to_h256, Address, L2BlockNumber, StorageKey, U256}; pub struct NodeExecutor { node_inner: Arc>, system_contracts: SystemContracts, command_receiver: mpsc::Receiver, + storage_key_layout: StorageKeyLayout, } impl NodeExecutor { pub fn new( node_inner: Arc>, system_contracts: SystemContracts, + storage_key_layout: StorageKeyLayout, ) -> (Self, NodeExecutorHandle) { let (command_sender, command_receiver) = mpsc::channel(128); let this = Self { node_inner, system_contracts, command_receiver, + storage_key_layout, }; let handle = NodeExecutorHandle { command_sender }; (this, handle) @@ -36,6 +42,18 @@ impl NodeExecutor { Command::SealBlocks(tx_batches, interval, reply) => { self.seal_blocks(tx_batches, interval, reply).await; } + Command::SetCode(address, code, reply) => { + self.set_code(address, code, reply).await; + } + Command::SetStorage(key, value, reply) => { + self.set_storage(key, value, reply).await; + } + Command::SetBalance(address, balance, reply) => { + self.set_balance(address, balance, reply).await; + } + Command::SetNonce(address, nonce, reply) => { + self.set_nonce(address, nonce, reply).await; + } Command::IncreaseTime(delta, reply) => { self.increase_time(delta, reply).await; } @@ -138,6 +156,66 @@ impl NodeExecutor { } } + async fn set_code(&self, address: Address, bytecode: Vec, reply: oneshot::Sender<()>) { + let code_key = get_code_key(&address); + let bytecode_hash = BytecodeHash::for_bytecode(&bytecode).value(); + // TODO: Likely fork_storage can be moved to `NodeExecutor` instead + let node_inner = self.node_inner.read().await; + node_inner + .fork_storage + .store_factory_dep(bytecode_hash, bytecode); + node_inner.fork_storage.set_value(code_key, bytecode_hash); + drop(node_inner); + // Reply to sender if we can + if reply.send(()).is_err() { + tracing::info!("failed to reply as receiver has been dropped"); + } + } + + async fn set_storage(&self, key: StorageKey, value: U256, reply: oneshot::Sender<()>) { + // TODO: Likely fork_storage can be moved to `NodeExecutor` instead + self.node_inner + .read() + .await + .fork_storage + .set_value(key, u256_to_h256(value)); + // Reply to sender if we can + if reply.send(()).is_err() { + tracing::info!("failed to reply as receiver has been dropped"); + } + } + + async fn set_balance(&self, address: Address, balance: U256, reply: oneshot::Sender<()>) { + let balance_key = self + .storage_key_layout + .get_storage_key_for_base_token(&address); + // TODO: Likely fork_storage can be moved to `NodeExecutor` instead + self.node_inner + .read() + .await + .fork_storage + .set_value(balance_key, u256_to_h256(balance)); + // Reply to sender if we can + if reply.send(()).is_err() { + tracing::info!("failed to reply as receiver has been dropped"); + } + } + + async fn set_nonce(&self, address: Address, nonce: U256, reply: oneshot::Sender<()>) { + let nonce_key = self.storage_key_layout.get_nonce_key(&address); + let enforced_full_nonce = nonces_to_full_nonce(nonce, nonce); + // TODO: Likely fork_storage can be moved to `NodeExecutor` instead + self.node_inner + .read() + .await + .fork_storage + .set_value(nonce_key, u256_to_h256(enforced_full_nonce)); + // Reply to sender if we can + if reply.send(()).is_err() { + tracing::info!("failed to reply as receiver has been dropped"); + } + } + async fn increase_time(&self, delta: u64, reply: oneshot::Sender<()>) { self.node_inner.write().await.time.increase_time(delta); // Reply to sender if we can @@ -267,6 +345,68 @@ impl NodeExecutorHandle { } } + /// Request [`NodeExecutor`] to set bytecode for given address. Waits for the change to take place. + pub async fn set_code_sync(&self, address: Address, bytecode: Vec) -> anyhow::Result<()> { + let (response_sender, response_receiver) = oneshot::channel(); + self.command_sender + .send(Command::SetCode(address, bytecode, response_sender)) + .await + .map_err(|_| anyhow::anyhow!("failed to set code as node executor is dropped"))?; + match response_receiver.await { + Ok(()) => Ok(()), + Err(_) => { + anyhow::bail!("failed to set code as node executor is dropped") + } + } + } + + /// Request [`NodeExecutor`] to set storage key-value pair. Waits for the change to take place. + pub async fn set_storage_sync(&self, key: StorageKey, value: U256) -> anyhow::Result<()> { + let (response_sender, response_receiver) = oneshot::channel(); + self.command_sender + .send(Command::SetStorage(key, value, response_sender)) + .await + .map_err(|_| anyhow::anyhow!("failed to set storage as node executor is dropped"))?; + match response_receiver.await { + Ok(()) => Ok(()), + Err(_) => { + anyhow::bail!("failed to set storage as node executor is dropped") + } + } + } + + /// Request [`NodeExecutor`] to set account's balance to the given value. Waits for the change + /// to take place. + pub async fn set_balance_sync(&self, address: Address, balance: U256) -> anyhow::Result<()> { + let (response_sender, response_receiver) = oneshot::channel(); + self.command_sender + .send(Command::SetBalance(address, balance, response_sender)) + .await + .map_err(|_| anyhow::anyhow!("failed to set balance as node executor is dropped"))?; + match response_receiver.await { + Ok(()) => Ok(()), + Err(_) => { + anyhow::bail!("failed to set balance as node executor is dropped") + } + } + } + + /// Request [`NodeExecutor`] to set account's nonce to the given value. Waits for the change + /// to take place. + pub async fn set_nonce_sync(&self, address: Address, nonce: U256) -> anyhow::Result<()> { + let (response_sender, response_receiver) = oneshot::channel(); + self.command_sender + .send(Command::SetNonce(address, nonce, response_sender)) + .await + .map_err(|_| anyhow::anyhow!("failed to set nonce as node executor is dropped"))?; + match response_receiver.await { + Ok(()) => Ok(()), + Err(_) => { + anyhow::bail!("failed to set nonce as node executor is dropped") + } + } + } + /// Request [`NodeExecutor`] to increase time by the given delta (in seconds). Waits for the /// change to take place. pub async fn increase_time_sync(&self, delta: u64) -> anyhow::Result<()> { @@ -357,6 +497,11 @@ enum Command { u64, oneshot::Sender>>, ), + // Storage manipulation commands + SetCode(Address, Vec, oneshot::Sender<()>), + SetStorage(StorageKey, U256, oneshot::Sender<()>), + SetBalance(Address, U256, oneshot::Sender<()>), + SetNonce(Address, U256, oneshot::Sender<()>), // Time manipulation commands. Caveat: reply-able commands can hold user connections alive for // a long time (until the command is processed). IncreaseTime(u64, oneshot::Sender<()>), diff --git a/crates/core/src/node/inner/storage.rs b/crates/core/src/node/inner/storage.rs new file mode 100644 index 00000000..eaa3c5b2 --- /dev/null +++ b/crates/core/src/node/inner/storage.rs @@ -0,0 +1,23 @@ +use async_trait::async_trait; +use zksync_multivm::interface::storage::ReadStorage; +use zksync_types::{StorageKey, StorageValue, H256}; + +#[async_trait] +pub trait ReadStorageDyn: ReadStorage + Send + Sync { + /// Alternative for [`Clone::clone`] that is object safe. + fn dyn_cloned(&self) -> Box; + + /// Alternative version of [`ReadStorage::read_value`] that is fallible, async and does not + /// require `&mut self`. + async fn read_value_alt(&self, key: &StorageKey) -> anyhow::Result; + + /// Alternative version of [`ReadStorage::load_factory_dep`] that is fallible, async and does not + /// require `&mut self`. + async fn load_factory_dep_alt(&self, hash: H256) -> anyhow::Result>>; +} + +impl Clone for Box { + fn clone(&self) -> Self { + self.dyn_cloned() + } +} diff --git a/crates/core/src/node/keys.rs b/crates/core/src/node/keys.rs index 776a459b..adb06f33 100644 --- a/crates/core/src/node/keys.rs +++ b/crates/core/src/node/keys.rs @@ -1,24 +1,23 @@ use zksync_types::{Address, StorageKey}; -pub struct StorageKeyLayout {} +#[derive(Copy, Clone)] +pub enum StorageKeyLayout { + ZkEra, + ZkOs, +} impl StorageKeyLayout { - pub fn get_nonce_key(is_zkos: bool, account: &Address) -> StorageKey { - if is_zkos { - crate::node::zkos::zkos_get_nonce_key(account) - } else { - zksync_types::get_nonce_key(account) + pub fn get_nonce_key(&self, account: &Address) -> StorageKey { + match self { + StorageKeyLayout::ZkEra => zksync_types::get_nonce_key(account), + StorageKeyLayout::ZkOs => crate::node::zkos::zkos_get_nonce_key(account), } } - pub fn get_storage_key_for_base_token(is_zkos: bool, address: &Address) -> StorageKey { - if is_zkos { - crate::node::zkos::zkos_storage_key_for_eth_balance(address) - } else { - zksync_types::utils::storage_key_for_standard_token_balance( - zksync_types::AccountTreeId::new(zksync_types::L2_BASE_TOKEN_ADDRESS), - address, - ) + pub fn get_storage_key_for_base_token(&self, address: &Address) -> StorageKey { + match self { + StorageKeyLayout::ZkEra => zksync_types::utils::storage_key_for_eth_balance(address), + StorageKeyLayout::ZkOs => crate::node::zkos::zkos_storage_key_for_eth_balance(address), } } } diff --git a/crates/core/src/node/mod.rs b/crates/core/src/node/mod.rs index 32717844..f5bb0d30 100644 --- a/crates/core/src/node/mod.rs +++ b/crates/core/src/node/mod.rs @@ -19,7 +19,7 @@ mod zkos; mod zks; pub use self::{ - fee_model::TestNodeFeeInputProvider, impersonate::ImpersonationManager, + fee_model::TestNodeFeeInputProvider, impersonate::ImpersonationManager, keys::StorageKeyLayout, node_executor::NodeExecutor, pool::TxPool, sealer::BlockSealer, sealer::BlockSealerMode, state::VersionedState, }; diff --git a/crates/core/src/node/zks.rs b/crates/core/src/node/zks.rs index f8973875..5c776c14 100644 --- a/crates/core/src/node/zks.rs +++ b/crates/core/src/node/zks.rs @@ -125,22 +125,14 @@ impl InMemoryNode { let tokens = self.get_confirmed_tokens_impl(0, 100).await?; let balances = { - let writer = self.inner.write().await; let mut balances = HashMap::new(); for token in tokens { + // TODO: Use StorageKeyLayout once zkos can lookup other tokens let balance_key = storage_key_for_standard_token_balance( AccountTreeId::new(token.l2_address), &address, ); - let balance = match writer.fork_storage.read_value_internal(&balance_key) { - Ok(balance) => balance, - Err(error) => { - return Err(Web3Error::InternalError(anyhow::anyhow!( - "failed reading value: {:?}", - error - ))); - } - }; + let balance = self.storage.read_value_alt(&balance_key).await?; if !balance.is_zero() { balances.insert(token.l2_address, h256_to_u256(balance)); } @@ -223,19 +215,11 @@ impl InMemoryNode { } pub async fn get_bytecode_by_hash_impl(&self, hash: H256) -> anyhow::Result>> { - let writer = self.inner.write().await; - - let maybe_bytecode = match writer.fork_storage.load_factory_dep_internal(hash) { - Ok(maybe_bytecode) => maybe_bytecode, - Err(error) => { - return Err(anyhow::anyhow!("failed to load factory dep: {:?}", error)); - } - }; - - if maybe_bytecode.is_some() { - return Ok(maybe_bytecode); + if let Some(bytecode) = self.storage.load_factory_dep_alt(hash).await? { + return Ok(Some(bytecode)); } + let writer = self.inner.write().await; let maybe_fork_details = &writer .fork_storage .inner diff --git a/crates/core/src/utils.rs b/crates/core/src/utils.rs index 9e688b85..f84d2b54 100644 --- a/crates/core/src/utils.rs +++ b/crates/core/src/utils.rs @@ -39,14 +39,6 @@ pub fn to_human_size(input: U256) -> String { tmp.iter().rev().collect() } -// TODO: Approach to encoding bytecode has changed in the core, so this function is likely no -// longer needed. See `zksync_contracts::SystemContractCode` for general approach. -pub fn bytecode_to_factory_dep(bytecode: Vec) -> Result<(H256, Vec), anyhow::Error> { - zksync_types::bytecode::validate_bytecode(&bytecode).context("Invalid bytecode")?; - let bytecode_hash = zksync_types::bytecode::BytecodeHash::for_bytecode(&bytecode).value(); - Ok((bytecode_hash, bytecode)) -} - /// Returns the actual [U64] block number from [BlockNumber]. /// /// # Arguments diff --git a/e2e-tests-rust/Cargo.lock b/e2e-tests-rust/Cargo.lock index d704cb5c..20e2dc5f 100644 --- a/e2e-tests-rust/Cargo.lock +++ b/e2e-tests-rust/Cargo.lock @@ -956,6 +956,7 @@ dependencies = [ "tempdir", "tokio", "tower 0.5.1", + "tower-http", ] [[package]] @@ -6285,6 +6286,20 @@ dependencies = [ "tower-service", ] +[[package]] +name = "tower-http" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "403fa3b783d4b626a8ad51d766ab03cb6d2dbfc46b1c5d4448395e6628dc9697" +dependencies = [ + "bitflags 2.6.0", + "bytes", + "http 1.1.0", + "pin-project-lite", + "tower-layer", + "tower-service", +] + [[package]] name = "tower-layer" version = "0.3.3" diff --git a/e2e-tests-rust/src/lib.rs b/e2e-tests-rust/src/lib.rs index f0423224..596ff525 100644 --- a/e2e-tests-rust/src/lib.rs +++ b/e2e-tests-rust/src/lib.rs @@ -10,4 +10,4 @@ pub use provider::{ init_testing_provider, init_testing_provider_with_client, AnvilZKsyncApi, TestingProvider, DEFAULT_TX_VALUE, }; -pub use utils::get_node_binary_path; +pub use utils::{get_node_binary_path, LockedPort}; diff --git a/e2e-tests-rust/tests/lib.rs b/e2e-tests-rust/tests/lib.rs index 23fc97f5..9ae3d38f 100644 --- a/e2e-tests-rust/tests/lib.rs +++ b/e2e-tests-rust/tests/lib.rs @@ -4,11 +4,12 @@ use alloy::providers::Provider; use alloy::{ network::primitives::BlockTransactionsKind, primitives::U256, signers::local::PrivateKeySigner, }; +use alloy_zksync::node_bindings::AnvilZKsync; use anvil_zksync_core::node::VersionedState; use anvil_zksync_core::utils::write_json_file; use anvil_zksync_e2e_tests::{ get_node_binary_path, init_testing_provider, init_testing_provider_with_client, AnvilZKsyncApi, - ReceiptExt, ZksyncWalletProviderExt, DEFAULT_TX_VALUE, + LockedPort, ReceiptExt, ZksyncWalletProviderExt, DEFAULT_TX_VALUE, }; use anyhow::Context; use flate2::read::GzDecoder; @@ -778,3 +779,30 @@ async fn load_state_on_fork() -> anyhow::Result<()> { Ok(()) } + +#[tokio::test] +async fn test_server_port_fallback() -> anyhow::Result<()> { + let locked_port = LockedPort::acquire_unused().await?; + + let node1 = AnvilZKsync::new() + .path(get_node_binary_path()) + .port(locked_port.port) + .spawn(); + let port1 = node1.port(); + + let node2 = AnvilZKsync::new() + .path(get_node_binary_path()) + .port(locked_port.port) + .spawn(); + let port2 = node2.port(); + + assert_ne!( + port1, port2, + "The second instance should have a different port due to fallback" + ); + + drop(node1); + drop(node2); + + Ok(()) +} diff --git a/scripts/refresh_contracts.sh b/scripts/refresh_contracts.sh index 34bf88fc..a1319a8f 100755 --- a/scripts/refresh_contracts.sh +++ b/scripts/refresh_contracts.sh @@ -3,7 +3,7 @@ set -xe SRC_DIR=contracts/system-contracts/artifacts-zk/contracts-preprocessed DEV_CONTRACTS_SRC_DIR=contracts/l2-contracts/artifacts-zk/contracts/dev-contracts/ -DST_DIR=src/deps/contracts/ +DST_DIR=crates/core/src/deps/contracts/ mkdir -p $DST_DIR