diff --git a/src/node/eth.rs b/src/node/eth.rs index b7c87f7b..c8ce50e5 100644 --- a/src/node/eth.rs +++ b/src/node/eth.rs @@ -1471,6 +1471,8 @@ impl EthTestNod #[cfg(test)] mod tests { + use super::*; + use crate::node::NON_FORK_FIRST_BLOCK_TIMESTAMP; use crate::{ config::{cache::CacheConfig, gas::DEFAULT_L2_GAS_PRICE}, fork::ForkDetails, @@ -1482,16 +1484,18 @@ mod tests { }, }; use maplit::hashmap; + use zksync_basic_types::vm::VmVersion; use zksync_basic_types::{web3, Nonce}; + use zksync_multivm::utils::get_max_batch_gas_limit; + use zksync_types::l2::TransactionType; use zksync_types::{ + api, api::{BlockHashObject, BlockNumber, BlockNumberObject, TransactionReceipt}, utils::deployed_address_create, - K256PrivateKey, + Bloom, K256PrivateKey, EMPTY_UNCLES_HASH, }; use zksync_web3_decl::types::{SyncState, ValueOrArray}; - use super::*; - #[tokio::test] async fn test_eth_syncing() { let node = InMemoryNode::::default(); @@ -1639,7 +1643,13 @@ mod tests { #[tokio::test] async fn test_get_block_by_hash_for_produced_block() { let node = InMemoryNode::::default(); - let (expected_block_hash, _) = testing::apply_tx(&node, H256::repeat_byte(0x01)); + let tx_hash = H256::repeat_byte(0x01); + let (expected_block_hash, _, _) = testing::apply_tx(&node, tx_hash); + let genesis_block = node + .get_block_by_number(BlockNumber::from(0), false) + .await + .expect("failed fetching block by number") + .expect("no block"); let actual_block = node .get_block_by_hash(expected_block_hash, false) @@ -1647,9 +1657,40 @@ mod tests { .expect("failed fetching block by hash") .expect("no block"); - assert_eq!(expected_block_hash, actual_block.hash); - assert_eq!(U64::from(1), actual_block.number); - assert_eq!(Some(U64::from(1)), actual_block.l1_batch_number); + let expected_block: Block = Block { + hash: expected_block_hash, + parent_hash: genesis_block.hash, + uncles_hash: EMPTY_UNCLES_HASH, + author: Default::default(), + state_root: Default::default(), + transactions_root: Default::default(), + receipts_root: Default::default(), + number: U64::from(1), + l1_batch_number: Some(U64::from(1)), + gas_used: actual_block.gas_used, // Checked separately, see below + gas_limit: U256::from(get_max_batch_gas_limit(VmVersion::latest())), + base_fee_per_gas: actual_block.base_fee_per_gas, // Checked separately, see below + extra_data: Default::default(), + logs_bloom: actual_block.logs_bloom, // Checked separately, see below + timestamp: U256::from(NON_FORK_FIRST_BLOCK_TIMESTAMP + 1), + l1_batch_timestamp: Some(U256::from(NON_FORK_FIRST_BLOCK_TIMESTAMP + 1)), + difficulty: Default::default(), + total_difficulty: Default::default(), + seal_fields: vec![], + uncles: vec![], + transactions: vec![TransactionVariant::Hash(tx_hash)], + size: Default::default(), + mix_hash: Default::default(), + nonce: Default::default(), + }; + + assert_eq!(expected_block, actual_block); + + // It is hard to predict the values below without repeating the exact logic used to calculate + // them. We are resorting to some basic sanity checks instead. + assert!(actual_block.gas_used > U256::zero()); + assert!(actual_block.base_fee_per_gas > U256::zero()); + assert_ne!(actual_block.logs_bloom, Bloom::zero()); } #[tokio::test] @@ -1748,17 +1789,102 @@ mod tests { #[tokio::test] async fn test_get_block_by_number_for_produced_block() { let node = InMemoryNode::::default(); - testing::apply_tx(&node, H256::repeat_byte(0x01)); + let tx_hash = H256::repeat_byte(0x01); + let (expected_block_hash, _, _) = testing::apply_tx(&node, tx_hash); let expected_block_number = 1; + let genesis_block = node + .get_block_by_number(BlockNumber::from(0), false) + .await + .expect("failed fetching block by number") + .expect("no block"); let actual_block = node .get_block_by_number(BlockNumber::Number(U64::from(expected_block_number)), false) .await - .expect("failed fetching block by hash") + .expect("failed fetching block by number") .expect("no block"); - assert_eq!(U64::from(expected_block_number), actual_block.number); - assert_eq!(1, actual_block.transactions.len()); + let expected_block: Block = Block { + hash: expected_block_hash, + parent_hash: genesis_block.hash, + uncles_hash: EMPTY_UNCLES_HASH, + author: Default::default(), + state_root: Default::default(), + transactions_root: Default::default(), + receipts_root: Default::default(), + number: U64::from(expected_block_number), + l1_batch_number: Some(U64::from(1)), + gas_used: actual_block.gas_used, // Checked separately, see below + gas_limit: U256::from(get_max_batch_gas_limit(VmVersion::latest())), + base_fee_per_gas: actual_block.base_fee_per_gas, // Checked separately, see below + extra_data: Default::default(), + logs_bloom: actual_block.logs_bloom, // Checked separately, see below + timestamp: U256::from(NON_FORK_FIRST_BLOCK_TIMESTAMP + 1), + l1_batch_timestamp: Some(U256::from(NON_FORK_FIRST_BLOCK_TIMESTAMP + 1)), + difficulty: Default::default(), + total_difficulty: Default::default(), + seal_fields: vec![], + uncles: vec![], + transactions: vec![TransactionVariant::Hash(tx_hash)], + size: Default::default(), + mix_hash: Default::default(), + nonce: Default::default(), + }; + + assert_eq!(expected_block, actual_block); + + // It is hard to predict the values below without repeating the exact logic used to calculate + // them. We are resorting to some basic sanity checks instead. + assert!(actual_block.gas_used > U256::zero()); + assert!(actual_block.base_fee_per_gas > U256::zero()); + assert_ne!(actual_block.logs_bloom, Bloom::zero()); + } + + #[tokio::test] + async fn test_get_block_by_number_for_produced_block_full_txs() { + let node = InMemoryNode::::default(); + let tx_hash = H256::repeat_byte(0x01); + let (block_hash, _, tx) = testing::apply_tx(&node, tx_hash); + let expected_block_number = 1; + + let mut actual_block = node + .get_block_by_number(BlockNumber::Number(U64::from(expected_block_number)), true) + .await + .expect("failed fetching block by number") + .expect("no block"); + + assert_eq!(actual_block.transactions.len(), 1); + let actual_tx = match actual_block.transactions.remove(0) { + TransactionVariant::Full(tx) => tx, + TransactionVariant::Hash(_) => unreachable!(), + }; + let expected_tx = api::Transaction { + hash: tx_hash, + nonce: U256::from(0), + block_hash: Some(block_hash), + block_number: Some(U64::from(expected_block_number)), + transaction_index: Some(U64::from(0)), + from: Some(tx.initiator_account()), + to: tx.recipient_account(), + value: U256::from(1), + gas_price: Some(tx.common_data.fee.max_fee_per_gas), + gas: tx.common_data.fee.gas_limit, + input: Default::default(), + v: actual_tx.v, // Checked separately, see below + r: actual_tx.r, // Checked separately, see below + s: actual_tx.s, // Checked separately, see below + raw: None, + transaction_type: Some(U64::from(TransactionType::EIP712Transaction as u32)), + access_list: None, + max_fee_per_gas: Some(tx.common_data.fee.max_fee_per_gas), + max_priority_fee_per_gas: Some(tx.common_data.fee.max_priority_fee_per_gas), + chain_id: U256::from(260), + l1_batch_number: Some(U64::from(1)), + l1_batch_tx_index: Some(U64::from(0)), + }; + assert_eq!(expected_tx, actual_tx); + + // TODO: Verify that the TX is signed properly (use alloy to abstract from zksync-core code?) } #[tokio::test] @@ -1939,7 +2065,7 @@ mod tests { async fn test_get_block_transaction_count_by_hash_for_produced_block() { let node = InMemoryNode::::default(); - let (expected_block_hash, _) = testing::apply_tx(&node, H256::repeat_byte(0x01)); + let (expected_block_hash, _, _) = testing::apply_tx(&node, H256::repeat_byte(0x01)); let actual_transaction_count = node .get_block_transaction_count_by_hash(expected_block_hash) .await @@ -2150,7 +2276,7 @@ mod tests { async fn test_get_transaction_receipt_uses_produced_block_hash() { let node = InMemoryNode::::default(); let tx_hash = H256::repeat_byte(0x01); - let (expected_block_hash, _) = testing::apply_tx(&node, tx_hash); + let (expected_block_hash, _, _) = testing::apply_tx(&node, tx_hash); let actual_tx_receipt = node .get_transaction_receipt(tx_hash) @@ -2232,7 +2358,7 @@ mod tests { .new_block_filter() .await .expect("failed creating filter"); - let (block_hash, _) = testing::apply_tx(&node, H256::repeat_byte(0x1)); + let (block_hash, _, _) = testing::apply_tx(&node, H256::repeat_byte(0x1)); match node .get_filter_changes(filter_id) @@ -3048,7 +3174,7 @@ mod tests { async fn test_get_transaction_by_block_hash_and_index_returns_none_for_invalid_block_hash() { let node = InMemoryNode::::default(); let input_tx_hash = H256::repeat_byte(0x01); - let (input_block_hash, _) = testing::apply_tx(&node, input_tx_hash); + let (input_block_hash, _, _) = testing::apply_tx(&node, input_tx_hash); let invalid_block_hash = H256::repeat_byte(0xab); assert_ne!(input_block_hash, invalid_block_hash); @@ -3064,7 +3190,7 @@ mod tests { async fn test_get_transaction_by_block_hash_and_index_returns_none_for_invalid_index() { let node = InMemoryNode::::default(); let input_tx_hash = H256::repeat_byte(0x01); - let (input_block_hash, _) = testing::apply_tx(&node, input_tx_hash); + let (input_block_hash, _, _) = testing::apply_tx(&node, input_tx_hash); let result = node .get_transaction_by_block_hash_and_index(input_block_hash, U64::from(10)) @@ -3078,7 +3204,7 @@ mod tests { async fn test_get_transaction_by_block_hash_and_index_returns_transaction_for_valid_input() { let node = InMemoryNode::::default(); let input_tx_hash = H256::repeat_byte(0x01); - let (input_block_hash, _) = testing::apply_tx(&node, input_tx_hash); + let (input_block_hash, _, _) = testing::apply_tx(&node, input_tx_hash); let actual_tx = node .get_transaction_by_block_hash_and_index(input_block_hash, U64::from(0)) @@ -3201,7 +3327,7 @@ mod tests { { let node = InMemoryNode::::default(); let input_tx_hash = H256::repeat_byte(0x01); - let (input_block_hash, _) = testing::apply_tx(&node, input_tx_hash); + let (input_block_hash, _, _) = testing::apply_tx(&node, input_tx_hash); let invalid_block_hash = H256::repeat_byte(0xab); assert_ne!(input_block_hash, invalid_block_hash); @@ -3234,7 +3360,7 @@ mod tests { async fn test_get_transaction_by_block_number_and_index_returns_transaction_for_valid_input() { let node = InMemoryNode::::default(); let input_tx_hash = H256::repeat_byte(0x01); - let (_, input_block_number) = testing::apply_tx(&node, input_tx_hash); + let (_, input_block_number, _) = testing::apply_tx(&node, input_tx_hash); let actual_tx = node .get_transaction_by_block_number_and_index( diff --git a/src/node/in_memory.rs b/src/node/in_memory.rs index 933db585..f7e12cb4 100644 --- a/src/node/in_memory.rs +++ b/src/node/in_memory.rs @@ -1,77 +1,75 @@ //! In-memory node, that supports forking other networks. -use crate::{ - bootloader_debug::{BootloaderDebug, BootloaderDebugTracer}, - config::{ - cache::CacheConfig, - gas::{self, GasConfig}, - node::{InMemoryNodeConfig, ShowCalls, ShowGasDetails, ShowStorageLogs, ShowVMDetails}, - }, - console_log::ConsoleLogHandler, - constants::{LEGACY_RICH_WALLETS, RICH_WALLETS}, - deps::{storage_view::StorageView, InMemoryStorage}, - filters::EthFilters, - fork::{ForkDetails, ForkSource, ForkStorage}, - formatter, - node::{ - call_error_tracer::CallErrorTracer, fee_model::TestNodeFeeInputProvider, - storage_logs::print_storage_logs_details, - }, - observability::Observability, - system_contracts::{self, SystemContracts}, - utils::{bytecode_to_factory_dep, create_debug_output, into_jsrpc_error, to_human_size}, -}; -use anyhow::Context as _; -use colored::Colorize; -use indexmap::IndexMap; -use once_cell::sync::OnceCell; use std::{ collections::{HashMap, HashSet}, + convert::TryInto, str::FromStr, sync::{Arc, RwLock}, }; -use std::convert::TryInto; +use anyhow::Context as _; +use colored::Colorize; +use indexmap::IndexMap; +use once_cell::sync::OnceCell; use zksync_basic_types::{ - web3::keccak256, web3::Bytes, AccountTreeId, Address, L1BatchNumber, L2BlockNumber, H160, H256, - U256, U64, + web3::{keccak256, Bytes, Index}, + AccountTreeId, Address, L1BatchNumber, L2BlockNumber, H160, H256, H64, U256, U64, }; use zksync_contracts::BaseSystemContracts; -use zksync_multivm::interface::storage::{ReadStorage, StoragePtr, WriteStorage}; use zksync_multivm::{ interface::{ + storage::{ReadStorage, StoragePtr, WriteStorage}, Call, ExecutionResult, L1BatchEnv, L2Block, L2BlockEnv, SystemEnv, TxExecutionMode, VmExecutionMode, VmExecutionResultAndLogs, VmFactory, VmInterface, VmInterfaceExt, }, - vm_latest::constants::BATCH_COMPUTATIONAL_GAS_LIMIT, - VmVersion, -}; -use zksync_multivm::{ tracers::CallTracer, utils::{ adjust_pubdata_price_for_tx, derive_base_fee_and_gas_per_pubdata, derive_overhead, - get_max_gas_per_pubdata_byte, + get_batch_base_fee, get_max_batch_gas_limit, get_max_gas_per_pubdata_byte, }, - vm_latest::HistoryDisabled, vm_latest::{ - constants::{BATCH_GAS_LIMIT, MAX_VM_PUBDATA_PER_BATCH}, + constants::{BATCH_COMPUTATIONAL_GAS_LIMIT, BATCH_GAS_LIMIT, MAX_VM_PUBDATA_PER_BATCH}, utils::l2_blocks::load_last_l2_block, - ToTracerPointer, Vm, + HistoryDisabled, ToTracerPointer, Vm, }, + VmVersion, }; use zksync_types::{ api::{Block, DebugCall, Log, TransactionReceipt, TransactionVariant}, - block::{unpack_block_info, L2BlockHasher}, + block::{build_bloom, unpack_block_info, L2BlockHasher}, fee::Fee, fee_model::{BatchFeeInput, PubdataIndependentBatchFeeModelInput}, get_code_key, get_nonce_key, l2::{L2Tx, TransactionType}, utils::{decompose_full_nonce, nonces_to_full_nonce, storage_key_for_eth_balance}, - PackedEthSignature, StorageKey, StorageValue, Transaction, ACCOUNT_CODE_STORAGE_ADDRESS, - MAX_L2_TX_GAS_LIMIT, SYSTEM_CONTEXT_ADDRESS, SYSTEM_CONTEXT_BLOCK_INFO_POSITION, + BloomInput, PackedEthSignature, StorageKey, StorageValue, Transaction, + ACCOUNT_CODE_STORAGE_ADDRESS, EMPTY_UNCLES_HASH, MAX_L2_TX_GAS_LIMIT, SYSTEM_CONTEXT_ADDRESS, + SYSTEM_CONTEXT_BLOCK_INFO_POSITION, }; use zksync_utils::{bytecode::hash_bytecode, h256_to_account_address, h256_to_u256, u256_to_h256}; use zksync_web3_decl::error::Web3Error; +use crate::{ + bootloader_debug::{BootloaderDebug, BootloaderDebugTracer}, + config::{ + cache::CacheConfig, + gas::{self, GasConfig}, + node::{InMemoryNodeConfig, ShowCalls, ShowGasDetails, ShowStorageLogs, ShowVMDetails}, + }, + console_log::ConsoleLogHandler, + constants::{LEGACY_RICH_WALLETS, RICH_WALLETS}, + deps::{storage_view::StorageView, InMemoryStorage}, + filters::EthFilters, + fork::{ForkDetails, ForkSource, ForkStorage}, + formatter, + node::{ + call_error_tracer::CallErrorTracer, fee_model::TestNodeFeeInputProvider, + storage_logs::print_storage_logs_details, + }, + observability::Observability, + system_contracts::{self, SystemContracts}, + utils::{bytecode_to_factory_dep, create_debug_output, into_jsrpc_error, to_human_size}, +}; + /// Max possible size of an ABI encoded tx (in bytes). pub const MAX_TX_SIZE: usize = 1_000_000; /// Timestamp of the first block (if not running in fork mode). @@ -1461,8 +1459,11 @@ impl InMemoryNode { let hash = compute_hash(block_ctx.miniblock, l2_tx.hash()); let mut transaction = zksync_types::api::Transaction::from(l2_tx); - transaction.block_hash = Some(inner.current_miniblock_hash); - transaction.block_number = Some(U64::from(inner.current_miniblock)); + transaction.block_hash = Some(hash); + transaction.block_number = Some(U64::from(block_ctx.miniblock)); + transaction.transaction_index = Some(Index::zero()); + transaction.l1_batch_number = Some(U64::from(batch_env.number.0)); + transaction.l1_batch_tx_index = Some(Index::zero()); let parent_block_hash = inner .block_hashes @@ -1470,16 +1471,40 @@ impl InMemoryNode { .cloned() .unwrap_or_default(); + let iter = tx_result.logs.events.iter().flat_map(|event| { + event + .indexed_topics + .iter() + .map(|topic| BloomInput::Raw(topic.as_bytes())) + .chain([BloomInput::Raw(event.address.as_bytes())]) + }); + let logs_bloom = build_bloom(iter); + let block = Block { hash, parent_hash: parent_block_hash, + uncles_hash: EMPTY_UNCLES_HASH, // Static for non-PoW chains, see EIP-3675 number: U64::from(block_ctx.miniblock), - timestamp: U256::from(batch_env.timestamp), l1_batch_number: Some(U64::from(batch_env.number.0)), + base_fee_per_gas: U256::from(get_batch_base_fee(&batch_env, VmVersion::latest())), + timestamp: U256::from(batch_env.timestamp), + l1_batch_timestamp: Some(U256::from(batch_env.timestamp)), transactions: vec![TransactionVariant::Full(transaction)], gas_used: U256::from(tx_result.statistics.gas_used), - gas_limit: U256::from(BATCH_GAS_LIMIT), - ..Default::default() + gas_limit: U256::from(get_max_batch_gas_limit(VmVersion::latest())), + logs_bloom, + author: Address::default(), // Matches core's behavior, irrelevant for ZKsync + state_root: H256::default(), // Intentionally empty as blocks in ZKsync don't have state - batches do + transactions_root: H256::default(), // Intentionally empty as blocks in ZKsync don't have state - batches do + receipts_root: H256::default(), // Intentionally empty as blocks in ZKsync don't have state - batches do + extra_data: Bytes::default(), // Matches core's behavior, not used in ZKsync + difficulty: U256::default(), // Empty for non-PoW chains, see EIP-3675, TODO: should be 2500000000000000 to match DIFFICULTY opcode + total_difficulty: U256::default(), // Empty for non-PoW chains, see EIP-3675 + seal_fields: vec![], // Matches core's behavior, TODO: remove + uncles: vec![], // Empty for non-PoW chains, see EIP-3675 + size: U256::default(), // Matches core's behavior, TODO: perhaps it should be computed + mix_hash: H256::default(), // Empty for non-PoW chains, see EIP-3675 + nonce: H64::default(), // Empty for non-PoW chains, see EIP-3675 }; let mut bytecodes = HashMap::new(); diff --git a/src/testing.rs b/src/testing.rs index 4ed4388f..bd078830 100644 --- a/src/testing.rs +++ b/src/testing.rs @@ -452,7 +452,10 @@ impl TransactionBuilder { Default::default(), ) .unwrap(); - tx.set_input(vec![], self.tx_hash); + tx.set_input( + tx.common_data.input_data().unwrap_or_default().into(), + self.tx_hash, + ); tx } } @@ -461,7 +464,7 @@ impl TransactionBuilder { pub fn apply_tx( node: &InMemoryNode, tx_hash: H256, -) -> (H256, U64) { +) -> (H256, U64, L2Tx) { let next_miniblock = node .get_inner() .read() @@ -472,9 +475,10 @@ pub fn apply_tx( let tx = TransactionBuilder::new().set_hash(tx_hash).build(); node.set_rich_account(tx.common_data.initiator_address); - node.apply_txs(vec![tx]).expect("failed applying tx"); + node.apply_txs(vec![tx.clone()]) + .expect("failed applying tx"); - (produced_block_hash, U64::from(next_miniblock)) + (produced_block_hash, U64::from(next_miniblock), tx) } /// Deploys a contract with the given bytecode. @@ -964,7 +968,7 @@ mod test { #[tokio::test] async fn test_apply_tx() { let node = InMemoryNode::::default(); - let (actual_block_hash, actual_block_number) = apply_tx(&node, H256::repeat_byte(0x01)); + let (actual_block_hash, actual_block_number, _) = apply_tx(&node, H256::repeat_byte(0x01)); assert_eq!( H256::from_str("0xd97ba6a5ab0f2d7fbfc697251321cce20bff3da2b0ddaf12c80f80f0ab270b15")