From bc8c10dd2ab1a211b3ae0607fbaadb15f480d636 Mon Sep 17 00:00:00 2001 From: Dustin Brickwood Date: Fri, 4 Aug 2023 09:29:55 -0500 Subject: [PATCH] chore: handle panics in `node.rs` and add crate-level documentation (#24) * chore: update eth api to handle panics * chore add crate level docs * chore: adds L2TxResult type due to clippy errors * chore: update get tx hash * chore: cargo clippy --fix * chore: made requested changes * chore change line spacing based on requested changes * chore: added printlns to not implemented match cases --- Makefile | 4 + src/main.rs | 46 +++- src/node.rs | 705 ++++++++++++++++++++++++++++++++-------------------- 3 files changed, 485 insertions(+), 270 deletions(-) diff --git a/Makefile b/Makefile index 6821d178..87d01de9 100644 --- a/Makefile +++ b/Makefile @@ -17,6 +17,10 @@ rebuild-contracts: rust-build: cargo build --release +# Build the Rust documentation +rust-doc: + cargo doc --no-deps --open + # Lint checks for Rust code lint: cargo fmt --all -- --check diff --git a/src/main.rs b/src/main.rs index 80bf7ad5..b33bc4c9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,47 @@ +//! zkSync Era In-Memory Node +//! +//! The `era-test-node` crate provides an in-memory node designed primarily for local testing. +//! It supports forking the state from other networks, making it a valuable tool for integration testing, +//! bootloader and system contract testing, and prototyping. +//! +//! ## Overview +//! +//! - **In-Memory Database**: The node uses an in-memory database for storing state information, +//! and employs simplified hashmaps for tracking blocks and transactions. +//! +//! - **Forking**: In fork mode, the node fetches missing storage data from a remote source if not available locally. +//! +//! - **Remote Server Interaction**: The node can use the remote server (openchain) to resolve the ABI and topics +//! to human-readable names. +//! +//! - **Local Testing**: Designed for local testing, this node is not intended for production use. +//! +//! ## Features +//! +//! - Fork the state of mainnet, testnet, or a custom network. +//! - Replay existing mainnet or testnet transactions. +//! - Use local bootloader and system contracts. +//! - Operate deterministically in non-fork mode. +//! - Start quickly with pre-configured 'rich' accounts. +//! - Resolve names of ABI functions and Events using openchain. +//! +//! ## Limitations +//! +//! - No communication between Layer 1 and Layer 2. +//! - Many APIs are not yet implemented. +//! - No support for accessing historical data. +//! - Only one transaction allowed per Layer 1 batch. +//! - Fixed values returned for zk Gas estimation. +//! +//! ## Usage +//! +//! To start the node, use the command `era_test_node run`. For more advanced functionalities like forking or +//! replaying transactions, refer to the official documentation. +//! +//! ## Contributions +//! +//! Contributions to improve `era-test-node` are welcome. Please refer to the contribution guidelines for more details. + use clap::{Parser, Subcommand}; use configuration_api::ConfigurationApiNamespaceT; use fork::ForkDetails; @@ -234,7 +278,7 @@ async fn main() -> anyhow::Result<()> { ); if !transactions_to_replay.is_empty() { - node.apply_txs(transactions_to_replay); + let _ = node.apply_txs(transactions_to_replay); } println!("\nRich Accounts"); diff --git a/src/node.rs b/src/node.rs index 156513a9..2eeaeb35 100644 --- a/src/node.rs +++ b/src/node.rs @@ -7,19 +7,31 @@ use crate::{ utils::IntoBoxedFuture, ShowCalls, }; - use colored::Colorize; +use futures::FutureExt; +use jsonrpc_core::BoxFuture; use std::{ collections::HashMap, convert::TryInto, sync::{Arc, RwLock}, }; +use vm::{ + utils::{BLOCK_GAS_LIMIT, ETH_CALL_GAS_LIMIT}, + vm::VmTxExecutionResult, + vm_with_bootloader::{ + init_vm_inner, push_transaction_to_bootloader_memory, BlockContext, BlockContextMode, + BootloaderJobType, TxExecutionMode, + }, + HistoryEnabled, OracleTools, +}; use zksync_basic_types::{AccountTreeId, Bytes, H160, H256, U256, U64}; use zksync_contracts::{ read_playground_block_bootloader_bytecode, read_sys_contract_bytecode, BaseSystemContracts, ContractLanguage, SystemContractCode, }; -use zksync_core::api_server::web3::backend_jsonrpc::namespaces::eth::EthNamespaceT; +use zksync_core::api_server::web3::backend_jsonrpc::{ + error::into_jsrpc_error, namespaces::eth::EthNamespaceT, +}; use zksync_state::{ReadStorage, StorageView, WriteStorage}; use zksync_types::{ api::{Log, TransactionReceipt, TransactionVariant}, @@ -37,20 +49,13 @@ use zksync_utils::{ bytecode::hash_bytecode, bytes_to_be_words, h256_to_account_address, h256_to_u256, h256_to_u64, u256_to_h256, }; - -use vm::{ - utils::{BLOCK_GAS_LIMIT, ETH_CALL_GAS_LIMIT}, - vm::VmTxExecutionResult, - vm_with_bootloader::{ - init_vm_inner, push_transaction_to_bootloader_memory, BlockContext, BlockContextMode, - BootloaderJobType, TxExecutionMode, - }, - HistoryEnabled, OracleTools, +use zksync_web3_decl::{ + error::Web3Error, + types::{Filter, FilterChanges}, }; -use zksync_web3_decl::types::{Filter, FilterChanges}; pub const MAX_TX_SIZE: usize = 1000000; -// Timestamp of the first block (if not running in fork mode). +/// Timestamp of the first block (if not running in fork mode). pub const NON_FORK_FIRST_BLOCK_TIMESTAMP: u64 = 1000; /// Network ID we use for the test node. pub const TEST_NODE_NETWORK_ID: u16 = 260; @@ -96,6 +101,13 @@ pub struct InMemoryNodeInner { pub playground_contracts: BaseSystemContracts, } +type L2TxResult = ( + HashMap, + VmTxExecutionResult, + BlockInfo, + HashMap>, +); + impl InMemoryNodeInner { fn create_block_context(&self) -> BlockContext { BlockContext { @@ -232,18 +244,28 @@ impl InMemoryNode { } /// Applies multiple transactions - but still one per L1 batch. - pub fn apply_txs(&self, txs: Vec) { + pub fn apply_txs(&self, txs: Vec) -> Result<(), String> { println!("Running {:?} transactions (one per batch)", txs.len()); for tx in txs { - self.run_l2_tx(tx, TxExecutionMode::VerifyExecute); + self.run_l2_tx(tx, TxExecutionMode::VerifyExecute)?; } + + Ok(()) } /// Adds a lot of tokens to a given account. pub fn set_rich_account(&self, address: H160) { let key = storage_key_for_eth_balance(&address); - let mut inner = self.inner.write().unwrap(); + + let mut inner = match self.inner.write() { + Ok(guard) => guard, + Err(e) => { + println!("Failed to acquire write lock: {}", e); + return; + } + }; + let keys = { let mut storage_view = StorageView::new(&inner.fork_storage); storage_view.set_value(key, u256_to_h256(U256::from(10u128.pow(22)))); @@ -256,12 +278,15 @@ impl InMemoryNode { } /// Runs L2 'eth call' method - that doesn't commit to a block. - fn run_l2_call(&self, l2_tx: L2Tx) -> Vec { + fn run_l2_call(&self, l2_tx: L2Tx) -> Result, String> { let execution_mode = TxExecutionMode::EthCall { missed_storage_invocation_limit: 1000000, }; - let inner = self.inner.write().unwrap(); + let inner = self + .inner + .write() + .map_err(|e| format!("Failed to acquire write lock: {}", e))?; let mut storage_view = StorageView::new(&inner.fork_storage); @@ -307,8 +332,8 @@ impl InMemoryNode { } match vm_block_result.full_result.revert_reason { - Some(result) => result.original_data, - None => vm_block_result + Some(result) => Ok(result.original_data), + None => Ok(vm_block_result .full_result .return_data .into_iter() @@ -316,7 +341,7 @@ impl InMemoryNode { let bytes: [u8; 32] = val.into(); bytes.to_vec() }) - .collect::>(), + .collect::>()), } } @@ -324,13 +349,11 @@ impl InMemoryNode { &self, l2_tx: L2Tx, execution_mode: TxExecutionMode, - ) -> ( - HashMap, - VmTxExecutionResult, - BlockInfo, - HashMap>, - ) { - let inner = self.inner.write().unwrap(); + ) -> Result { + let inner = self + .inner + .write() + .map_err(|e| format!("Failed to acquire write lock: {}", e))?; let mut storage_view = StorageView::new(&inner.fork_storage); @@ -363,7 +386,9 @@ impl InMemoryNode { let tx: Transaction = l2_tx.into(); push_transaction_to_bootloader_memory(&mut vm, &tx, execution_mode, None); - let tx_result = vm.execute_next_tx(u32::MAX, true).unwrap(); + let tx_result = vm + .execute_next_tx(u32::MAX, true) + .map_err(|e| format!("Failed to execute next transaction: {}", e))?; match tx_result.status { TxExecutionStatus::Success => println!("Transaction: {}", "SUCCESS".green()), @@ -419,16 +444,20 @@ impl InMemoryNode { .clone(); let modified_keys = storage_view.modified_storage_keys().clone(); - (modified_keys, tx_result, block, bytecodes) + Ok((modified_keys, tx_result, block, bytecodes)) } /// Runs L2 transaction and commits it to a new block. - fn run_l2_tx(&self, l2_tx: L2Tx, execution_mode: TxExecutionMode) { + fn run_l2_tx(&self, l2_tx: L2Tx, execution_mode: TxExecutionMode) -> Result<(), String> { let tx_hash = l2_tx.hash(); println!("\nExecuting {}", format!("{:?}", tx_hash).bold()); - let (keys, result, block, bytecodes) = self.run_l2_tx_inner(l2_tx.clone(), execution_mode); + let (keys, result, block, bytecodes) = + self.run_l2_tx_inner(l2_tx.clone(), execution_mode)?; // Write all the mutated keys (storage slots). - let mut inner = self.inner.write().unwrap(); + let mut inner = self + .inner + .write() + .map_err(|e| format!("Failed to acquire write lock: {}", e))?; for (key, value) in keys.iter() { inner.fork_storage.set_value(*key, *value); } @@ -462,195 +491,238 @@ impl InMemoryNode { inner.current_batch += 1; inner.current_miniblock += 1; } + + Ok(()) } } impl EthNamespaceT for InMemoryNode { + /// Returns the chain ID of the node. fn chain_id(&self) -> jsonrpc_core::BoxFuture> { - let inner = self.inner.read().unwrap(); - Ok(U64::from(inner.fork_storage.chain_id.0 as u64)).into_boxed_future() + match self.inner.read() { + Ok(inner) => Ok(U64::from(inner.fork_storage.chain_id.0 as u64)).into_boxed_future(), + Err(_) => Err(into_jsrpc_error(Web3Error::InternalError)).into_boxed_future(), + } } + /// Calls the specified function on the L2 contract with the given arguments. + /// + /// # Arguments + /// + /// * `req` - The call request containing the function name and arguments. + /// * `_block` - The block ID variant (unused). + /// + /// # Returns + /// + /// A boxed future containing the result of the function call. fn call( &self, req: zksync_types::transaction_request::CallRequest, _block: Option, ) -> jsonrpc_core::BoxFuture> { - let mut tx = l2_tx_from_call_req(req, MAX_TX_SIZE).unwrap(); - tx.common_data.fee.gas_limit = ETH_CALL_GAS_LIMIT.into(); - let result = self.run_l2_call(tx); - - Ok(result.into()).into_boxed_future() + match l2_tx_from_call_req(req, MAX_TX_SIZE) { + Ok(mut tx) => { + tx.common_data.fee.gas_limit = ETH_CALL_GAS_LIMIT.into(); + let result = self.run_l2_call(tx); + + match result { + Ok(vec) => Ok(vec.into()).into_boxed_future(), + Err(e) => { + let error = + Web3Error::InvalidTransactionData(ethabi::Error::InvalidName(e)); + Err(into_jsrpc_error(error)).into_boxed_future() + } + } + } + Err(e) => { + let error = Web3Error::SerializationError(e); + Err(into_jsrpc_error(error)).into_boxed_future() + } + } } + /// Returns the balance of the specified address. + /// + /// # Arguments + /// + /// * `address` - The address to get the balance of. + /// * `_block` - The block ID variant (optional). + /// + /// # Returns + /// + /// A `BoxFuture` that resolves to a `Result` containing the balance of the specified address as a `U256` or a `jsonrpc_core::Error` if an error occurred. fn get_balance( &self, address: zksync_basic_types::Address, _block: Option, - ) -> jsonrpc_core::BoxFuture> { - let balance_key = storage_key_for_standard_token_balance( - AccountTreeId::new(L2_ETH_TOKEN_ADDRESS), - &address, - ); - - let balance = self - .inner - .write() - .unwrap() - .fork_storage - .read_value(&balance_key); - - Ok(h256_to_u256(balance)).into_boxed_future() + ) -> BoxFuture> { + let inner = Arc::clone(&self.inner); + + Box::pin(async move { + let balance_key = storage_key_for_standard_token_balance( + AccountTreeId::new(L2_ETH_TOKEN_ADDRESS), + &address, + ); + + match inner.write() { + Ok(mut inner_guard) => { + let balance = inner_guard.fork_storage.read_value(&balance_key); + Ok(h256_to_u256(balance)) + } + Err(_) => { + let web3_error = Web3Error::InternalError; + Err(into_jsrpc_error(web3_error)) + } + } + }) } + /// Returns a block by its number. + /// + /// # Arguments + /// + /// * `block_number` - A `BlockNumber` enum variant representing the block number to retrieve. + /// * `_full_transactions` - A boolean value indicating whether to retrieve full transactions or not. + /// + /// # Returns + /// + /// A `BoxFuture` containing a `jsonrpc_core::Result` that resolves to an `Option` of `zksync_types::api::Block`. fn get_block_by_number( &self, block_number: zksync_types::api::BlockNumber, _full_transactions: bool, - ) -> jsonrpc_core::BoxFuture< + ) -> BoxFuture< jsonrpc_core::Result< Option>, >, > { - // Currently we support only the 'most recent' block. - let reader = self.inner.read().unwrap(); - - match block_number { - zksync_types::api::BlockNumber::Committed - | zksync_types::api::BlockNumber::Finalized - | zksync_types::api::BlockNumber::Latest => {} - zksync_types::api::BlockNumber::Earliest => { - return not_implemented("get_block_by_number__Earliest") - } - zksync_types::api::BlockNumber::Pending => { - return not_implemented("get_block_by_number__Pending") - } - zksync_types::api::BlockNumber::Number(ask_number) => { - if ask_number != U64::from(reader.current_miniblock) { - let not_implemented_format = format!("get_block_by_number__{}", ask_number); - return not_implemented(¬_implemented_format); + let inner = Arc::clone(&self.inner); + + Box::pin(async move { + let reader = match inner.read() { + Ok(r) => r, + Err(_) => return Err(into_jsrpc_error(Web3Error::InternalError)), + }; + + match block_number { + zksync_types::api::BlockNumber::Earliest => { + println!( + "Method get_block_by_number with BlockNumber::Earliest is not implemented" + ); + return Err(into_jsrpc_error(Web3Error::NotImplemented)); + } + zksync_types::api::BlockNumber::Pending => { + println!( + "Method get_block_by_number with BlockNumber::Pending is not implemented" + ); + return Err(into_jsrpc_error(Web3Error::NotImplemented)); } + zksync_types::api::BlockNumber::Number(ask_number) + if ask_number != U64::from(reader.current_miniblock) => + { + println!("Method get_block_by_number with BlockNumber::Number({}) is not implemented", ask_number); + return Err(into_jsrpc_error(Web3Error::NotImplemented)); + } + _ => {} } - } - let txn: Vec = vec![]; - - let block = zksync_types::api::Block { - transactions: txn, - hash: Default::default(), - parent_hash: Default::default(), - uncles_hash: Default::default(), - author: Default::default(), - state_root: Default::default(), - transactions_root: Default::default(), - receipts_root: Default::default(), - number: U64::from(reader.current_miniblock), - l1_batch_number: Some(U64::from(reader.current_batch)), - gas_used: Default::default(), - gas_limit: U256::from(ETH_CALL_GAS_LIMIT), - base_fee_per_gas: Default::default(), - extra_data: Default::default(), - logs_bloom: Default::default(), - timestamp: Default::default(), - l1_batch_timestamp: Default::default(), - difficulty: Default::default(), - total_difficulty: Default::default(), - seal_fields: Default::default(), - uncles: Default::default(), - size: Default::default(), - mix_hash: Default::default(), - nonce: Default::default(), - }; + let block = zksync_types::api::Block { + transactions: vec![], + number: U64::from(reader.current_miniblock), + l1_batch_number: Some(U64::from(reader.current_batch)), + gas_limit: U256::from(ETH_CALL_GAS_LIMIT), + ..Default::default() + }; - Ok(Some(block)).into_boxed_future() + Ok(Some(block)) + }) } + /// Returns the code stored at the specified address. + /// + /// # Arguments + /// + /// * `address` - The address to retrieve the code from. + /// * `_block` - An optional block ID variant. + /// + /// # Returns + /// + /// A `BoxFuture` containing the result of the operation, which is a `jsonrpc_core::Result` containing + /// the code as a `zksync_basic_types::Bytes` object. fn get_code( &self, address: zksync_basic_types::Address, _block: Option, - ) -> jsonrpc_core::BoxFuture> { - let code_key = get_code_key(&address); + ) -> BoxFuture> { + let inner = Arc::clone(&self.inner); - let code_hash = self - .inner - .write() - .unwrap() - .fork_storage - .read_value(&code_key); + Box::pin(async move { + let code_key = get_code_key(&address); - Ok(Bytes::from(code_hash.as_bytes())).into_boxed_future() + match inner.write() { + Ok(mut guard) => { + let code_hash = guard.fork_storage.read_value(&code_key); + Ok(Bytes::from(code_hash.as_bytes())) + } + Err(_) => Err(into_jsrpc_error(Web3Error::InternalError)), + } + }) } + /// Returns the transaction count for a given address. + /// + /// # Arguments + /// + /// * `address` - The address to get the transaction count for. + /// * `_block` - Optional block ID variant. + /// + /// # Returns + /// + /// Returns a `BoxFuture` containing the transaction count as a `U256` wrapped in a `jsonrpc_core::Result`. fn get_transaction_count( &self, address: zksync_basic_types::Address, _block: Option, - ) -> jsonrpc_core::BoxFuture> { - let nonce_key = get_nonce_key(&address); + ) -> BoxFuture> { + let inner = Arc::clone(&self.inner); - let result = self - .inner - .write() - .unwrap() - .fork_storage - .read_value(&nonce_key); - Ok(h256_to_u64(result).into()).into_boxed_future() + Box::pin(async move { + let nonce_key = get_nonce_key(&address); + + match inner.write() { + Ok(mut guard) => { + let result = guard.fork_storage.read_value(&nonce_key); + Ok(h256_to_u64(result).into()) + } + Err(_) => Err(into_jsrpc_error(Web3Error::InternalError)), + } + }) } + /// Retrieves the transaction receipt for a given transaction hash. + /// + /// # Arguments + /// + /// * `hash` - The hash of the transaction to retrieve the receipt for. + /// + /// # Returns + /// + /// A `BoxFuture` that resolves to an `Option` of a `TransactionReceipt` or an error. fn get_transaction_receipt( &self, hash: zksync_basic_types::H256, - ) -> jsonrpc_core::BoxFuture>> - { - let reader = self.inner.read().unwrap(); - let tx_result = reader.tx_results.get(&hash); - - let receipt = tx_result.map(|info| { - let status = if info.result.status == TxExecutionStatus::Success { - U64::from(1) - } else { - U64::from(0) + ) -> BoxFuture>> { + let inner = Arc::clone(&self.inner); + + Box::pin(async move { + let reader = match inner.read() { + Ok(r) => r, + Err(_) => return Err(into_jsrpc_error(Web3Error::InternalError)), }; - let logs: Vec = info - .result - .result - .logs - .events - .iter() - .map(|log| { - let address = log.address; - let topics = &log.indexed_topics; - let data = log.value.clone(); - let block_hash = Some(hash); - let block_number = Some(U64::from(info.miniblock_number)); - let l1_batch_number = Some(U64::from(info.batch_number as u64)); - let transaction_hash = Some(hash); - let transaction_index = Some(U64::from(1)); - let log_index = Some(U256::default()); - let transaction_log_index = Some(U256::default()); - let log_type = None; - let removed = None; - - Log { - address, - topics: topics.to_vec(), - data: zksync_types::Bytes(data), - block_hash, - block_number, - l1_batch_number, - transaction_hash, - transaction_index, - log_index, - transaction_log_index, - log_type, - removed, - } - }) - .collect(); + let tx_result = reader.tx_results.get(&hash); - TransactionReceipt { + let receipt = tx_result.map(|info| TransactionReceipt { transaction_hash: hash, transaction_index: U64::from(1), block_hash: Some(hash), @@ -662,40 +734,109 @@ impl EthNamespaceT for InMemoryNode { cumulative_gas_used: Default::default(), gas_used: Some(info.tx.common_data.fee.gas_limit - info.result.gas_refunded), contract_address: contract_address_from_tx_result(&info.result), - logs, + logs: info + .result + .result + .logs + .events + .iter() + .map(|log| Log { + address: log.address, + topics: log.indexed_topics.clone(), + data: zksync_types::Bytes(log.value.clone()), + block_hash: Some(hash), + block_number: Some(U64::from(info.miniblock_number)), + l1_batch_number: Some(U64::from(info.batch_number as u64)), + transaction_hash: Some(hash), + transaction_index: Some(U64::from(1)), + log_index: Some(U256::default()), + transaction_log_index: Some(U256::default()), + log_type: None, + removed: None, + }) + .collect(), l2_to_l1_logs: vec![], - status: Some(status), - root: None, - logs_bloom: Default::default(), - transaction_type: None, + status: Some(if info.result.status == TxExecutionStatus::Success { + U64::from(1) + } else { + U64::from(0) + }), effective_gas_price: Some(500.into()), - } - }); + ..Default::default() + }); - Ok(receipt).into_boxed_future() + Ok(receipt).map_err(|_: jsonrpc_core::Error| into_jsrpc_error(Web3Error::InternalError)) + }) } + /// Sends a raw transaction to the L2 network. + /// + /// # Arguments + /// + /// * `tx_bytes` - The transaction bytes to send. + /// + /// # Returns + /// + /// A future that resolves to the hash of the transaction if successful, or an error if the transaction is invalid or execution fails. fn send_raw_transaction( &self, tx_bytes: zksync_basic_types::Bytes, ) -> jsonrpc_core::BoxFuture> { - let chain_id = { - let reader = self.inner.read().unwrap(); - reader.fork_storage.chain_id + let chain_id = match self.inner.read() { + Ok(reader) => reader.fork_storage.chain_id, + Err(_) => { + return futures::future::err(into_jsrpc_error(Web3Error::InternalError)).boxed() + } }; let (tx_req, hash) = - TransactionRequest::from_bytes(&tx_bytes.0, chain_id.0, MAX_TX_SIZE).unwrap(); + match TransactionRequest::from_bytes(&tx_bytes.0, chain_id.0, MAX_TX_SIZE) { + Ok(result) => result, + Err(e) => { + return futures::future::err(into_jsrpc_error(Web3Error::SerializationError(e))) + .boxed() + } + }; - let mut l2_tx: L2Tx = tx_req.try_into().unwrap(); - l2_tx.set_input(tx_bytes.0, hash); - assert_eq!(hash, l2_tx.hash()); + let mut l2_tx: L2Tx = match tx_req.try_into() { + Ok(tx) => tx, + Err(e) => { + return futures::future::err(into_jsrpc_error(Web3Error::SerializationError(e))) + .boxed() + } + }; - self.run_l2_tx(l2_tx, TxExecutionMode::VerifyExecute); + l2_tx.set_input(tx_bytes.0, hash); + if hash != l2_tx.hash() { + return futures::future::err(into_jsrpc_error(Web3Error::InvalidTransactionData( + zksync_types::ethabi::Error::InvalidData, + ))) + .boxed(); + }; - Ok(hash).into_boxed_future() + match self.run_l2_tx(l2_tx.clone(), TxExecutionMode::VerifyExecute) { + Ok(_) => Ok(hash).into_boxed_future(), + Err(e) => { + let error_message = format!("Execution error: {}", e); + futures::future::err(into_jsrpc_error(Web3Error::SubmitTransactionError( + error_message, + l2_tx.hash().as_bytes().to_vec(), + ))) + .boxed() + } + } } + /// Returns a block by its hash. Currently, only hashes for blocks in memory are supported. + /// + /// # Arguments + /// + /// * `hash` - A `H256` type representing the hash of the block to retrieve. + /// * `_full_transactions` - A boolean value indicating whether to retrieve full transactions or not. + /// + /// # Returns + /// + /// A `BoxFuture` that resolves to a `Result` containing an `Option` of a `Block` with its transactions and other details. fn get_block_by_hash( &self, hash: zksync_basic_types::H256, @@ -705,106 +846,130 @@ impl EthNamespaceT for InMemoryNode { Option>, >, > { - // Currently we support only hashes for blocks in memory - let reader = self.inner.read().unwrap(); - let not_implemented_format = format!("get_block_by_hash__{}", hash); - - let matching_transaction = reader.tx_results.get(&hash); - if matching_transaction.is_none() { - return not_implemented(¬_implemented_format); - } + let inner = Arc::clone(&self.inner); + + Box::pin(async move { + // Currently we support only hashes for blocks in memory + let reader = inner + .read() + .map_err(|_| into_jsrpc_error(Web3Error::InternalError))?; + + let matching_transaction = reader.tx_results.get(&hash); + if matching_transaction.is_none() { + return Err(into_jsrpc_error(Web3Error::InvalidTransactionData( + zksync_types::ethabi::Error::InvalidData, + ))); + } - let matching_block = reader - .blocks - .get(&matching_transaction.unwrap().batch_number); - if matching_block.is_none() { - return not_implemented(¬_implemented_format); - } + let matching_block = reader + .blocks + .get(&matching_transaction.unwrap().batch_number); + if matching_block.is_none() { + return Err(into_jsrpc_error(Web3Error::NoBlock)); + } - let txn: Vec = vec![]; - let block = zksync_types::api::Block { - transactions: txn, - hash: Default::default(), - parent_hash: Default::default(), - uncles_hash: Default::default(), - author: Default::default(), - state_root: Default::default(), - transactions_root: Default::default(), - receipts_root: Default::default(), - number: U64::from(matching_block.unwrap().batch_number), - l1_batch_number: Some(U64::from(reader.current_batch)), - gas_used: Default::default(), - gas_limit: U256::from(ETH_CALL_GAS_LIMIT), - base_fee_per_gas: Default::default(), - extra_data: Default::default(), - logs_bloom: Default::default(), - timestamp: Default::default(), - l1_batch_timestamp: Default::default(), - difficulty: Default::default(), - total_difficulty: Default::default(), - seal_fields: Default::default(), - uncles: Default::default(), - size: Default::default(), - mix_hash: Default::default(), - nonce: Default::default(), - }; + let txn: Vec = vec![]; + let block = zksync_types::api::Block { + transactions: txn, + number: U64::from(matching_block.unwrap().batch_number), + l1_batch_number: Some(U64::from(reader.current_batch)), + gas_limit: U256::from(ETH_CALL_GAS_LIMIT), + ..Default::default() + }; - Ok(Some(block)).into_boxed_future() + Ok(Some(block)) + }) } + /// Returns a future that resolves to an optional transaction with the given hash. + /// + /// # Arguments + /// + /// * `hash` - A 32-byte hash of the transaction. + /// + /// # Returns + /// + /// A `jsonrpc_core::BoxFuture` that resolves to a `jsonrpc_core::Result` containing an optional `zksync_types::api::Transaction`. fn get_transaction_by_hash( &self, hash: zksync_basic_types::H256, ) -> jsonrpc_core::BoxFuture>> { - let reader = self.inner.read().unwrap(); - let tx_result = reader.tx_results.get(&hash); - - let tx = tx_result.map(|info| zksync_types::api::Transaction { - hash, - nonce: U256::from(info.tx.common_data.nonce.0), - block_hash: Some(hash), - block_number: Some(U64::from(info.miniblock_number)), - transaction_index: Some(U64::from(1)), - from: Some(info.tx.initiator_account()), - to: Some(info.tx.recipient_account()), - value: info.tx.execute.value, - gas_price: Default::default(), - gas: Default::default(), - input: info.tx.common_data.input.clone().unwrap().data.into(), - v: Some(info.tx.extract_chain_id().unwrap().into()), - r: Some(U256::zero()), - s: Some(U256::zero()), - raw: None, - transaction_type: { - let tx_type = match info.tx.common_data.transaction_type { - zksync_types::l2::TransactionType::LegacyTransaction => 0, - zksync_types::l2::TransactionType::EIP2930Transaction => 1, - zksync_types::l2::TransactionType::EIP1559Transaction => 2, - zksync_types::l2::TransactionType::EIP712Transaction => 113, - zksync_types::l2::TransactionType::PriorityOpTransaction => 255, - }; - Some(tx_type.into()) - }, - access_list: None, - max_fee_per_gas: Some(info.tx.common_data.fee.max_fee_per_gas), - max_priority_fee_per_gas: Some(info.tx.common_data.fee.max_priority_fee_per_gas), - chain_id: info.tx.extract_chain_id().unwrap().into(), - l1_batch_number: Some(U64::from(info.batch_number as u64)), - l1_batch_tx_index: None, - }); - - Ok(tx).into_boxed_future() + let inner = Arc::clone(&self.inner); + + Box::pin(async move { + let reader = inner + .read() + .map_err(|_| into_jsrpc_error(Web3Error::InternalError))?; + let tx_result = reader.tx_results.get(&hash); + + Ok(tx_result.and_then(|info| { + let input_data = info.tx.common_data.input.clone().or(None)?; + + let chain_id = info.tx.extract_chain_id().or(None)?; + + Some(zksync_types::api::Transaction { + hash, + nonce: U256::from(info.tx.common_data.nonce.0), + block_hash: Some(hash), + block_number: Some(U64::from(info.miniblock_number)), + transaction_index: Some(U64::from(1)), + from: Some(info.tx.initiator_account()), + to: Some(info.tx.recipient_account()), + value: info.tx.execute.value, + gas_price: Default::default(), + gas: Default::default(), + input: input_data.data.into(), + v: Some(chain_id.into()), + r: Some(U256::zero()), + s: Some(U256::zero()), + raw: None, + transaction_type: { + let tx_type = match info.tx.common_data.transaction_type { + zksync_types::l2::TransactionType::LegacyTransaction => 0, + zksync_types::l2::TransactionType::EIP2930Transaction => 1, + zksync_types::l2::TransactionType::EIP1559Transaction => 2, + zksync_types::l2::TransactionType::EIP712Transaction => 113, + zksync_types::l2::TransactionType::PriorityOpTransaction => 255, + }; + Some(tx_type.into()) + }, + access_list: None, + max_fee_per_gas: Some(info.tx.common_data.fee.max_fee_per_gas), + max_priority_fee_per_gas: Some( + info.tx.common_data.fee.max_priority_fee_per_gas, + ), + chain_id: chain_id.into(), + l1_batch_number: Some(U64::from(info.batch_number as u64)), + l1_batch_tx_index: None, + }) + })) + }) } - // Methods below are not currently implemented. - + /// Returns the current block number as a `U64` wrapped in a `BoxFuture`. fn get_block_number( &self, ) -> jsonrpc_core::BoxFuture> { - let reader = self.inner.read().unwrap(); - Ok(U64::from(reader.current_miniblock)).into_boxed_future() + let inner = Arc::clone(&self.inner); + + Box::pin(async move { + let reader = inner + .read() + .map_err(|_| into_jsrpc_error(Web3Error::InternalError))?; + Ok(U64::from(reader.current_miniblock)) + }) } + /// Estimates the gas required for a given call request. + /// + /// # Arguments + /// + /// * `_req` - A `CallRequest` struct representing the call request to estimate gas for. + /// * `_block` - An optional `BlockNumber` struct representing the block number to estimate gas for. + /// + /// # Returns + /// + /// A `BoxFuture` containing a `Result` with a `U256` representing the estimated gas required. fn estimate_gas( &self, _req: zksync_types::transaction_request::CallRequest, @@ -813,12 +978,14 @@ impl EthNamespaceT for InMemoryNode { let gas_used = U256::from(ETH_CALL_GAS_LIMIT); Ok(gas_used).into_boxed_future() } - + /// Returns the current gas price in U256 format. fn gas_price(&self) -> jsonrpc_core::BoxFuture> { let fair_l2_gas_price: u64 = 250_000_000; // 0.25 gwei Ok(U256::from(fair_l2_gas_price)).into_boxed_future() } + // Methods below are not currently implemented. + fn new_filter(&self, _filter: Filter) -> jsonrpc_core::BoxFuture> { not_implemented("new_filter") }