From 78011f38983814e76b231b7de0709cd59525a5b0 Mon Sep 17 00:00:00 2001 From: MexicanAce Date: Thu, 3 Aug 2023 10:12:46 +0100 Subject: [PATCH 01/22] feat: Implement eth_estimateGas --- .vscode/extensions.json | 1 + src/deps/mod.rs | 2 +- src/fork.rs | 21 +++++++ src/node.rs | 134 +++++++++++++++++++++++++++++++++++++--- test_endpoints.http | 12 ++++ 5 files changed, 161 insertions(+), 9 deletions(-) diff --git a/.vscode/extensions.json b/.vscode/extensions.json index f3877065..1aad874b 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -2,5 +2,6 @@ "recommendations": [ "rust-lang.rust-analyzer", "humao.rest-client", + "vadimcn.vscode-lldb" ], } \ No newline at end of file diff --git a/src/deps/mod.rs b/src/deps/mod.rs index 038de725..ff22da07 100644 --- a/src/deps/mod.rs +++ b/src/deps/mod.rs @@ -11,7 +11,7 @@ use std::fmt; use self::system_contracts::COMPILED_IN_SYSTEM_CONTRACTS; /// In-memory storage. -#[derive(Debug, Default)] +#[derive(Debug, Default, Clone)] pub struct InMemoryStorage { pub(crate) state: HashMap, pub(crate) factory_deps: HashMap>, diff --git a/src/fork.rs b/src/fork.rs index 12c81178..3b14e6e4 100644 --- a/src/fork.rs +++ b/src/fork.rs @@ -52,6 +52,16 @@ pub struct ForkStorage { pub chain_id: L2ChainId, } +impl Clone for ForkStorage { + fn clone(&self) -> Self { + let inner = Arc::new(RwLock::new(self.inner.read().unwrap().clone())); + Self { + inner, + chain_id: self.chain_id.clone(), + } + } +} + #[derive(Debug)] pub struct ForkStorageInner { // Underlying local storage @@ -65,6 +75,17 @@ pub struct ForkStorageInner { pub fork: Option, } +impl Clone for ForkStorageInner { + fn clone(&self) -> Self { + Self { + raw_storage: self.raw_storage.clone(), + value_read_cache: self.value_read_cache.clone(), + factory_dep_cache: self.factory_dep_cache.clone(), + fork: self.fork.clone(), + } + } +} + impl ForkStorage { pub fn new(fork: Option, dev_use_local_contracts: bool) -> Self { let chain_id = fork diff --git a/src/node.rs b/src/node.rs index 8cb4d6c2..260b81c3 100644 --- a/src/node.rs +++ b/src/node.rs @@ -45,7 +45,7 @@ use vm::{ init_vm_inner, push_transaction_to_bootloader_memory, BlockContext, BlockContextMode, BootloaderJobType, TxExecutionMode, }, - HistoryEnabled, OracleTools, + HistoryEnabled, OracleTools, HistoryDisabled, TxRevertReason, }; use zksync_web3_decl::types::{Filter, FilterChanges}; @@ -257,6 +257,7 @@ impl InMemoryNode { /// Runs L2 'eth call' method - that doesn't commit to a block. fn run_l2_call(&self, l2_tx: L2Tx) -> Vec { + println!("Running run_l2_call..."); let execution_mode = TxExecutionMode::EthCall { missed_storage_invocation_limit: 1000000, }; @@ -305,6 +306,7 @@ impl InMemoryNode { formatter::print_call(call, 0, &inner.show_calls, inner.resolve_hashes); } } + println!("run_l2_call complete"); match vm_block_result.full_result.revert_reason { Some(result) => result.original_data, @@ -320,13 +322,80 @@ impl InMemoryNode { } } + /// Runs L2 'eth call' method but with estimate fee execution mode. + fn run_l2_call_for_fee_estimate(&self, l2_tx: L2Tx, gas_limit: u32) -> Result { + // println!("Running run_l2_call_for_fee_estimate..."); + let execution_mode = TxExecutionMode::EstimateFee { + missed_storage_invocation_limit: 1000000, + }; + + let inner = self.inner.read().unwrap(); + let fork_storage_copy = inner.fork_storage.clone(); + let mut storage_view = StorageView::new(&fork_storage_copy); + + let mut oracle_tools = OracleTools::new(&mut storage_view, HistoryDisabled); + + let bootloader_code = &inner.playground_contracts; + + let block_context = inner.create_block_context(); + let block_properties = InMemoryNodeInner::create_block_properties(bootloader_code); + + // init vm + let mut vm = init_vm_inner( + &mut oracle_tools, + BlockContextMode::OverrideCurrent(block_context.into()), + &block_properties, + // BLOCK_GAS_LIMIT, + gas_limit, + bootloader_code, + execution_mode, + ); + + let tx: Transaction = l2_tx.into(); + + push_transaction_to_bootloader_memory(&mut vm, &tx, execution_mode, None); + + let vm_block_result = vm.execute_till_block_end_with_call_tracer(BootloaderJobType::TransactionExecution); + // let tx_result = match vm.execute_next_tx(u32::MAX, false) { + // Ok(value) => value, + // Err(err) => { + // println!("An error occurred: {:?}", err); + // return (Default::default(), Default::default(), block, Default::default()) + // } + // }; + + let result = match vm_block_result.full_result.revert_reason { + None => { + println!("Found {} gas_used", vm_block_result.full_result.gas_used); + Ok(vm_block_result.full_result.gas_used) + }, + Some(revert) => { + match revert.revert_reason { + TxRevertReason::ValidationFailed(_) => { + // This is the "unexpected signature length" error, ignoring for gas estimation + return Ok(vm_block_result.full_result.gas_used) + }, + _ => { + Err(revert.revert_reason) + } + } + }, + }; + + // println!("run_l2_call_for_fee_estimate complete"); + result + // (vm_block_result.full_result.gas_used, None) + // println!("returning {} gas_used instead", ETH_CALL_GAS_LIMIT); + // ETH_CALL_GAS_LIMIT + } + fn run_l2_tx_inner( &self, l2_tx: L2Tx, execution_mode: TxExecutionMode, ) -> ( HashMap, - VmTxExecutionResult, + Option, BlockInfo, HashMap>, ) { @@ -363,7 +432,14 @@ 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 = match vm.execute_next_tx(u32::MAX, true) { + Ok(value) => value, + Err(err) => { + println!("An error occurred: {:?}", err); + return (Default::default(), Default::default(), block, Default::default()) + } + }; + // let tx_result = vm.execute_next_tx(u32::MAX, true).unwrap(); match tx_result.status { TxExecutionStatus::Success => println!("Transaction: {}", "SUCCESS".green()), @@ -419,11 +495,12 @@ impl InMemoryNode { .clone(); let modified_keys = storage_view.modified_storage_keys().clone(); - (modified_keys, tx_result, block, bytecodes) + (modified_keys, Some(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) { + println!("Running run_l2_tx..."); 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); @@ -453,7 +530,7 @@ impl InMemoryNode { tx: l2_tx, batch_number: block.batch_number, miniblock_number: current_miniblock, - result, + result: result.unwrap(), }, ); inner.blocks.insert(block.batch_number, block); @@ -462,6 +539,7 @@ impl InMemoryNode { inner.current_batch += 1; inner.current_miniblock += 1; } + println!("run_l2_tx complete"); } } @@ -763,11 +841,51 @@ impl EthNamespaceT for InMemoryNode { fn estimate_gas( &self, - _req: zksync_types::transaction_request::CallRequest, + req: zksync_types::transaction_request::CallRequest, _block: Option, ) -> jsonrpc_core::BoxFuture> { - let gas_used = U256::from(ETH_CALL_GAS_LIMIT); - Ok(gas_used).into_boxed_future() + let mut tx = l2_tx_from_call_req(req, MAX_TX_SIZE).unwrap(); + tx.common_data.fee.gas_limit = ETH_CALL_GAS_LIMIT.into(); + + // This needs to be greater than fair_l2_gas_price (250_000_000) + tx.common_data.fee.max_fee_per_gas = U256::from(250_000_000) + 1; + + println!("Starting initial estimate_gas"); + let mut temp_gas_price = BLOCK_GAS_LIMIT; + let mut estimate_gas_result = self.run_l2_call_for_fee_estimate(tx.clone(), temp_gas_price); + + if estimate_gas_result.is_err() { + // THIS CONTRACT WILL NEVER WORK... + println!("Initial estimate_gas FAILED!!!"); + return Ok(U256::from(0)).into_boxed_future(); + } + println!("Initial estimate_gas complete w/ no errors"); + temp_gas_price = estimate_gas_result.unwrap(); + println!("Initial gas used: {}", temp_gas_price); + + // 1. Run the transaction with `temp_gas_limit` + // 2. If it fails, `temp_gas_limit = temp_gas_limit * 1.5` and go back to **`step i`** + // 3. If it succeeds, use `temp_gas_limit` as the `estimated_gas` + let mut successful_min_gas_found = false; + let mut iterations = 0; + + println!("Incrementing estimate_gas calls starting..."); + while !successful_min_gas_found && iterations < 30 { + temp_gas_price = ((temp_gas_price as f32) * 1.5) as u32; + + println!("Attempt {}: Trying to estimate transaction with {} gas", iterations, temp_gas_price); + estimate_gas_result = self.run_l2_call_for_fee_estimate(tx.clone(), temp_gas_price); + + iterations += 1; + if estimate_gas_result.is_err() { + // println!("Didn't work this time..."); + } else { + successful_min_gas_found = true; + } + } + + println!("Incrementing estimate_gas calls completed"); + Ok(U256::from(temp_gas_price)).into_boxed_future() } fn gas_price(&self) -> jsonrpc_core::BoxFuture> { diff --git a/test_endpoints.http b/test_endpoints.http index f4796f72..6b2ecf75 100644 --- a/test_endpoints.http +++ b/test_endpoints.http @@ -8,6 +8,18 @@ content-type: application/json "params": [] } +### + +POST http://localhost:8011 +content-type: application/json + +{ + "jsonrpc": "2.0", + "id": "1", + "method": "eth_gasPrice", + "params": [] +} + ### POST http://localhost:8011 content-type: application/json From eba8189990905202fddaabd81a004803ec20f751 Mon Sep 17 00:00:00 2001 From: MexicanAce Date: Fri, 4 Aug 2023 13:42:15 +0100 Subject: [PATCH 02/22] Implement binary search algorithm and basic multiplier --- src/node.rs | 89 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 50 insertions(+), 39 deletions(-) diff --git a/src/node.rs b/src/node.rs index 260b81c3..ff03c000 100644 --- a/src/node.rs +++ b/src/node.rs @@ -9,10 +9,11 @@ use crate::{ }; use colored::Colorize; +use jsonrpc_core::Error; use std::{ collections::HashMap, convert::TryInto, - sync::{Arc, RwLock}, + sync::{Arc, RwLock}, cmp::min, }; use zksync_basic_types::{AccountTreeId, Bytes, H160, H256, U256, U64}; use zksync_contracts::{ @@ -322,9 +323,8 @@ impl InMemoryNode { } } - /// Runs L2 'eth call' method but with estimate fee execution mode. + /// Runs L2 'eth call' method but with estimate fee execution mode against a sandbox vm. fn run_l2_call_for_fee_estimate(&self, l2_tx: L2Tx, gas_limit: u32) -> Result { - // println!("Running run_l2_call_for_fee_estimate..."); let execution_mode = TxExecutionMode::EstimateFee { missed_storage_invocation_limit: 1000000, }; @@ -345,7 +345,6 @@ impl InMemoryNode { &mut oracle_tools, BlockContextMode::OverrideCurrent(block_context.into()), &block_properties, - // BLOCK_GAS_LIMIT, gas_limit, bootloader_code, execution_mode, @@ -355,14 +354,7 @@ impl InMemoryNode { push_transaction_to_bootloader_memory(&mut vm, &tx, execution_mode, None); - let vm_block_result = vm.execute_till_block_end_with_call_tracer(BootloaderJobType::TransactionExecution); - // let tx_result = match vm.execute_next_tx(u32::MAX, false) { - // Ok(value) => value, - // Err(err) => { - // println!("An error occurred: {:?}", err); - // return (Default::default(), Default::default(), block, Default::default()) - // } - // }; + let vm_block_result = vm.execute_till_block_end(BootloaderJobType::TransactionExecution); let result = match vm_block_result.full_result.revert_reason { None => { @@ -382,11 +374,7 @@ impl InMemoryNode { }, }; - // println!("run_l2_call_for_fee_estimate complete"); result - // (vm_block_result.full_result.gas_used, None) - // println!("returning {} gas_used instead", ETH_CALL_GAS_LIMIT); - // ETH_CALL_GAS_LIMIT } fn run_l2_tx_inner( @@ -850,42 +838,65 @@ impl EthNamespaceT for InMemoryNode { // This needs to be greater than fair_l2_gas_price (250_000_000) tx.common_data.fee.max_fee_per_gas = U256::from(250_000_000) + 1; - println!("Starting initial estimate_gas"); + // Running initial gas estimate with max amount to deduce if there's a normal execution error let mut temp_gas_price = BLOCK_GAS_LIMIT; let mut estimate_gas_result = self.run_l2_call_for_fee_estimate(tx.clone(), temp_gas_price); if estimate_gas_result.is_err() { - // THIS CONTRACT WILL NEVER WORK... - println!("Initial estimate_gas FAILED!!!"); - return Ok(U256::from(0)).into_boxed_future(); + // There's a normal execution error + return Err(Error { + code: jsonrpc_core::ErrorCode::ServerError(0), + message: "Estimate failed even with maximum possible gas".to_string(), + data: None, + }).into_boxed_future(); } - println!("Initial estimate_gas complete w/ no errors"); temp_gas_price = estimate_gas_result.unwrap(); println!("Initial gas used: {}", temp_gas_price); - - // 1. Run the transaction with `temp_gas_limit` - // 2. If it fails, `temp_gas_limit = temp_gas_limit * 1.5` and go back to **`step i`** - // 3. If it succeeds, use `temp_gas_limit` as the `estimated_gas` - let mut successful_min_gas_found = false; - let mut iterations = 0; - - println!("Incrementing estimate_gas calls starting..."); - while !successful_min_gas_found && iterations < 30 { - temp_gas_price = ((temp_gas_price as f32) * 1.5) as u32; - - println!("Attempt {}: Trying to estimate transaction with {} gas", iterations, temp_gas_price); - estimate_gas_result = self.run_l2_call_for_fee_estimate(tx.clone(), temp_gas_price); - iterations += 1; + + ///// For L2 transactions we need a properly formatted signature + // if let ExecuteTransactionCommon::L2(l2_common_data) = &mut tx.common_data { + // if l2_common_data.signature.is_empty() { + // l2_common_data.signature = vec![0u8; 65]; + // l2_common_data.signature[64] = 27; + // } + + // l2_common_data.fee.gas_per_pubdata_limit = MAX_GAS_PER_PUBDATA_BYTE.into(); + // } + + + + // We are using binary search to find the minimal values of gas_limit under which + // the transaction succeedes + let mut lower_bound = temp_gas_price; + let mut upper_bound = ETH_CALL_GAS_LIMIT as u32; + let acceptable_overestimation = 1_000; + let max_attempts = 30usize; + + println!("Trying binary search with lower_bound: {}, and upper_bound: {}", lower_bound, upper_bound); + let mut number_of_iterations = 0usize; + while lower_bound + acceptable_overestimation < upper_bound && number_of_iterations < max_attempts{ + let mid = (lower_bound + upper_bound) / 2; + let try_gas_limit = acceptable_overestimation + mid; + estimate_gas_result = self.run_l2_call_for_fee_estimate(tx.clone(), try_gas_limit); if estimate_gas_result.is_err() { - // println!("Didn't work this time..."); + lower_bound = mid + 1; + println!("Attempt {}: Failed, trying again with lower_bound: {}, and upper_bound: {}", number_of_iterations, lower_bound, upper_bound); } else { - successful_min_gas_found = true; + upper_bound = mid; + println!("Attempt {}: Succeeded, trying again with lower_bound: {}, and upper_bound: {}", number_of_iterations, lower_bound, upper_bound); + println!("Gas Used: {}", estimate_gas_result.unwrap()) } + + number_of_iterations += 1; } - println!("Incrementing estimate_gas calls completed"); - Ok(U256::from(temp_gas_price)).into_boxed_future() + println!("COMPLETE after {} attempts and with lower_bound: {}, and upper_bound: {}", number_of_iterations, lower_bound, upper_bound); + let mut estimation_value = ((upper_bound + acceptable_overestimation) as f32 * 1.3) as u32; + + estimation_value = min(ETH_CALL_GAS_LIMIT as u32, estimation_value); + println!("VALUE RETURNING: {}", estimation_value); + Ok(U256::from(estimation_value)).into_boxed_future() } fn gas_price(&self) -> jsonrpc_core::BoxFuture> { From ddcf967eb3c105d281af023ad9bab28e578d2d59 Mon Sep 17 00:00:00 2001 From: MexicanAce Date: Fri, 4 Aug 2023 13:43:26 +0100 Subject: [PATCH 03/22] Add println when call fails even with max gas --- src/node.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/node.rs b/src/node.rs index ff03c000..721d591c 100644 --- a/src/node.rs +++ b/src/node.rs @@ -844,6 +844,7 @@ impl EthNamespaceT for InMemoryNode { if estimate_gas_result.is_err() { // There's a normal execution error + println!("Call failed with maximum gas provided"); return Err(Error { code: jsonrpc_core::ErrorCode::ServerError(0), message: "Estimate failed even with maximum possible gas".to_string(), From 2b85742494e34c4ddc3123e3cff640fdddbce1a9 Mon Sep 17 00:00:00 2001 From: MexicanAce Date: Tue, 8 Aug 2023 16:21:58 +0100 Subject: [PATCH 04/22] Fix estimateGas --- src/node.rs | 199 ++++++++++++++++++++++++++++++++++------------------ src/zks.rs | 1 + 2 files changed, 132 insertions(+), 68 deletions(-) diff --git a/src/node.rs b/src/node.rs index 721d591c..ec3793ef 100644 --- a/src/node.rs +++ b/src/node.rs @@ -9,7 +9,6 @@ use crate::{ }; use colored::Colorize; -use jsonrpc_core::Error; use std::{ collections::HashMap, convert::TryInto, @@ -28,14 +27,14 @@ use zksync_types::{ l2::L2Tx, transaction_request::{l2_tx_from_call_req, TransactionRequest}, tx::tx_execution_info::TxExecutionStatus, - utils::{storage_key_for_eth_balance, storage_key_for_standard_token_balance}, + utils::{storage_key_for_eth_balance, storage_key_for_standard_token_balance, decompose_full_nonce, nonces_to_full_nonce}, vm_trace::VmTrace, zk_evm::block_properties::BlockProperties, StorageKey, StorageLogQueryType, Transaction, ACCOUNT_CODE_STORAGE_ADDRESS, L2_ETH_TOKEN_ADDRESS, }; use zksync_utils::{ - bytecode::hash_bytecode, bytes_to_be_words, h256_to_account_address, h256_to_u256, h256_to_u64, + bytecode::{hash_bytecode, compress_bytecode}, bytes_to_be_words, h256_to_account_address, h256_to_u256, h256_to_u64, u256_to_h256, }; @@ -44,9 +43,9 @@ use vm::{ vm::VmTxExecutionResult, vm_with_bootloader::{ init_vm_inner, push_transaction_to_bootloader_memory, BlockContext, BlockContextMode, - BootloaderJobType, TxExecutionMode, + BootloaderJobType, TxExecutionMode, derive_base_fee_and_gas_per_pubdata, }, - HistoryEnabled, OracleTools, HistoryDisabled, TxRevertReason, + HistoryEnabled, OracleTools, HistoryDisabled, TxRevertReason, VmBlockResult, }; use zksync_web3_decl::types::{Filter, FilterChanges}; @@ -258,7 +257,6 @@ impl InMemoryNode { /// Runs L2 'eth call' method - that doesn't commit to a block. fn run_l2_call(&self, l2_tx: L2Tx) -> Vec { - println!("Running run_l2_call..."); let execution_mode = TxExecutionMode::EthCall { missed_storage_invocation_limit: 1000000, }; @@ -307,7 +305,6 @@ impl InMemoryNode { formatter::print_call(call, 0, &inner.show_calls, inner.resolve_hashes); } } - println!("run_l2_call complete"); match vm_block_result.full_result.revert_reason { Some(result) => result.original_data, @@ -323,8 +320,8 @@ impl InMemoryNode { } } - /// Runs L2 'eth call' method but with estimate fee execution mode against a sandbox vm. - fn run_l2_call_for_fee_estimate(&self, l2_tx: L2Tx, gas_limit: u32) -> Result { + /// Runs fee estimation against a sandbox vm with the given gas_limit. + fn estimate_gas_step(&self, mut l2_tx: L2Tx, gas_limit: u32) -> Result { let execution_mode = TxExecutionMode::EstimateFee { missed_storage_invocation_limit: 1000000, }; @@ -333,20 +330,38 @@ impl InMemoryNode { let fork_storage_copy = inner.fork_storage.clone(); let mut storage_view = StorageView::new(&fork_storage_copy); - let mut oracle_tools = OracleTools::new(&mut storage_view, HistoryDisabled); + let nonce = l2_tx.nonce(); + let nonce_key = get_nonce_key(&l2_tx.initiator_account()); + let full_nonce = storage_view.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); + storage_view.set_value(nonce_key, u256_to_h256(enforced_full_nonce)); + + // // // let payer = tx.payer(); + // // // let balance_key = storage_key_for_eth_balance(&payer); + // // // let mut current_balance = h256_to_u256(storage_view.read_value(&balance_key)); + // // // current_balance += execution_args.added_balance; + // // // storage_view.set_value(balance_key, u256_to_h256(current_balance)); - let bootloader_code = &inner.playground_contracts; + let mut oracle_tools = OracleTools::new(&mut storage_view, HistoryDisabled); let block_context = inner.create_block_context(); - let block_properties = InMemoryNodeInner::create_block_properties(bootloader_code); + + // Use the fee_estimate bootloader code, as it sets ENSURE_RETURNED_MAGIC to 0 and BOOTLOADER_TYPE to 'playground_block' + let fee_estimate_bytecode = include_bytes!("deps/contracts/fee_estimate.yul.zbin").to_vec(); + let fee_estimate_code = bsc_load_with_bootloader(fee_estimate_bytecode, false); + let block_properties = InMemoryNodeInner::create_block_properties(&fee_estimate_code); + + // Set gas_limit for transaction + l2_tx.common_data.fee.gas_limit = gas_limit.into(); // init vm let mut vm = init_vm_inner( &mut oracle_tools, BlockContextMode::OverrideCurrent(block_context.into()), &block_properties, - gas_limit, - bootloader_code, + BLOCK_GAS_LIMIT, + &fee_estimate_code, execution_mode, ); @@ -358,19 +373,10 @@ impl InMemoryNode { let result = match vm_block_result.full_result.revert_reason { None => { - println!("Found {} gas_used", vm_block_result.full_result.gas_used); - Ok(vm_block_result.full_result.gas_used) + Ok(vm_block_result) }, Some(revert) => { - match revert.revert_reason { - TxRevertReason::ValidationFailed(_) => { - // This is the "unexpected signature length" error, ignoring for gas estimation - return Ok(vm_block_result.full_result.gas_used) - }, - _ => { - Err(revert.revert_reason) - } - } + Err(revert.revert_reason) }, }; @@ -488,7 +494,6 @@ impl InMemoryNode { /// Runs L2 transaction and commits it to a new block. fn run_l2_tx(&self, l2_tx: L2Tx, execution_mode: TxExecutionMode) { - println!("Running run_l2_tx..."); 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); @@ -527,7 +532,6 @@ impl InMemoryNode { inner.current_batch += 1; inner.current_miniblock += 1; } - println!("run_l2_tx complete"); } } @@ -832,71 +836,130 @@ impl EthNamespaceT for InMemoryNode { 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(); - - // This needs to be greater than fair_l2_gas_price (250_000_000) - tx.common_data.fee.max_fee_per_gas = U256::from(250_000_000) + 1; + let mut l2_tx = l2_tx_from_call_req(req, MAX_TX_SIZE).unwrap(); + let tx: Transaction = l2_tx.clone().into(); + + // TODO: Un-hardcode these values + let fair_l1_gas_price = 50_000_000_000; + let fair_l2_gas_price = 250_000_000; + + // Calculate Adjusted L1 Price + let (_, adjusted_current_pubdata_price) = derive_base_fee_and_gas_per_pubdata((fair_l1_gas_price as f64 * 1.2) as u64, fair_l2_gas_price); + let adjusted_l1_gas_price = if U256::from(adjusted_current_pubdata_price) <= tx.gas_per_pubdata_byte_limit() { + // The current pubdata price is small enough + (fair_l1_gas_price as f64 * 1.2) as u64 + } else { + let l1_gas_price = U256::from(fair_l2_gas_price) + * (tx.gas_per_pubdata_byte_limit() - U256::from(1u32)) + / U256::from(17); + + l1_gas_price.as_u64() + }; - // Running initial gas estimate with max amount to deduce if there's a normal execution error - let mut temp_gas_price = BLOCK_GAS_LIMIT; - let mut estimate_gas_result = self.run_l2_call_for_fee_estimate(tx.clone(), temp_gas_price); - - if estimate_gas_result.is_err() { - // There's a normal execution error - println!("Call failed with maximum gas provided"); - return Err(Error { - code: jsonrpc_core::ErrorCode::ServerError(0), - message: "Estimate failed even with maximum possible gas".to_string(), - data: None, - }).into_boxed_future(); - } - temp_gas_price = estimate_gas_result.unwrap(); - println!("Initial gas used: {}", temp_gas_price); + let (base_fee, gas_per_pubdata_byte) = derive_base_fee_and_gas_per_pubdata( + fair_l1_gas_price, + fair_l2_gas_price, + ); + // TODO: Implement check for "account: {} does not have enough funds for for transferring tx.value: {}." - ///// For L2 transactions we need a properly formatted signature - // if let ExecuteTransactionCommon::L2(l2_common_data) = &mut tx.common_data { - // if l2_common_data.signature.is_empty() { - // l2_common_data.signature = vec![0u8; 65]; - // l2_common_data.signature[64] = 27; - // } + // Check for properly formatted signature + if l2_tx.common_data.signature.is_empty() { + l2_tx.common_data.signature = vec![0u8; 65]; + l2_tx.common_data.signature[64] = 27; + } - // l2_common_data.fee.gas_per_pubdata_limit = MAX_GAS_PER_PUBDATA_BYTE.into(); - // } + // MAX_GAS_PER_PUBDATA_BYTE = 20_000 + l2_tx.common_data.fee.gas_per_pubdata_limit = 20_000.into(); + l2_tx.common_data.fee.max_fee_per_gas = base_fee.into(); + l2_tx.common_data.fee.max_priority_fee_per_gas = base_fee.into(); + // Calculate gas_for_bytecodes_pubdata + let inner = self.inner.read().unwrap(); + let mut storage_view = StorageView::new(&inner.fork_storage); + let pubdata_for_factory_deps = l2_tx.execute.factory_deps.as_deref().unwrap_or_default().iter().map(|bytecode| { + if storage_view.is_bytecode_known(&hash_bytecode(bytecode)) { + return 0; + } + let length = if let Ok(compressed) = compress_bytecode(bytecode) { + compressed.len() + } else { + bytecode.len() + }; + let publish_byte_overhead = 100; + length as u32 + publish_byte_overhead + }); + let gas_for_bytecodes_pubdata: u32 = pubdata_for_factory_deps.sum::() * (gas_per_pubdata_byte as u32); - // We are using binary search to find the minimal values of gas_limit under which - // the transaction succeedes - let mut lower_bound = temp_gas_price; + // We are using binary search to find the minimal values of gas_limit under which the transaction succeeds + let mut lower_bound = 0; let mut upper_bound = ETH_CALL_GAS_LIMIT as u32; let acceptable_overestimation = 1_000; let max_attempts = 30usize; - println!("Trying binary search with lower_bound: {}, and upper_bound: {}", lower_bound, upper_bound); let mut number_of_iterations = 0usize; while lower_bound + acceptable_overestimation < upper_bound && number_of_iterations < max_attempts{ let mid = (lower_bound + upper_bound) / 2; - let try_gas_limit = acceptable_overestimation + mid; - estimate_gas_result = self.run_l2_call_for_fee_estimate(tx.clone(), try_gas_limit); + let try_gas_limit = gas_for_bytecodes_pubdata + mid; + + let overhead = vec![ + // Single instance circuits overhead + // 0.1 is a scaling coefficient L2 transactions + // 0.065 is from max_block_overhead / MAX_TX_ERGS_LIMIT + (0.1 * 0.065 * (try_gas_limit + gas_for_bytecodes_pubdata) as f32) as u32, + + // Bootloader overhead + // 11 is from max_block_overhead / BOOTLOADER_TX_ENCODING_SPACE + 11 * tx.encoding_len() as u32, + + // Slot overhead + // 5_078 is from max_block_overhead / MAX_TXS_IN_BLOCK + 5_078 as u32, + ] + .into_iter() + .max() + .unwrap(); + + let estimate_gas_result = self.estimate_gas_step(l2_tx.clone(), try_gas_limit + overhead); if estimate_gas_result.is_err() { lower_bound = mid + 1; - println!("Attempt {}: Failed, trying again with lower_bound: {}, and upper_bound: {}", number_of_iterations, lower_bound, upper_bound); + // println!("Attempt {}: Failed with {}, trying again with lower_bound: {}, and upper_bound: {}", number_of_iterations, try_gas_limit + overhead, lower_bound, upper_bound); } else { upper_bound = mid; - println!("Attempt {}: Succeeded, trying again with lower_bound: {}, and upper_bound: {}", number_of_iterations, lower_bound, upper_bound); - println!("Gas Used: {}", estimate_gas_result.unwrap()) + // println!("Attempt {}: Succeeded with {}, trying again with lower_bound: {}, and upper_bound: {}", number_of_iterations, try_gas_limit + overhead, lower_bound, upper_bound); } number_of_iterations += 1; } - println!("COMPLETE after {} attempts and with lower_bound: {}, and upper_bound: {}", number_of_iterations, lower_bound, upper_bound); - let mut estimation_value = ((upper_bound + acceptable_overestimation) as f32 * 1.3) as u32; - + // println!("COMPLETE after {} attempts and with lower_bound: {}, and upper_bound: {}", number_of_iterations, lower_bound, upper_bound); + let estimated_fee_scale_factor = 1.3; + let mut estimation_value = (upper_bound as f32 * estimated_fee_scale_factor) as u32; + estimation_value = min(ETH_CALL_GAS_LIMIT as u32, estimation_value); - println!("VALUE RETURNING: {}", estimation_value); + + let overhead = vec![ + // Single instance circuits overhead + // 0.1 is a scaling coefficient L2 transactions + // 0.065 is from max_block_overhead / MAX_TX_ERGS_LIMIT + (0.1 * 0.065 * (estimation_value + gas_for_bytecodes_pubdata) as f32) as u32, + + // Bootloader overhead + // 11 is from max_block_overhead / BOOTLOADER_TX_ENCODING_SPACE + 11 * tx.encoding_len() as u32, + + // Slot overhead + // 5_078 is from max_block_overhead / MAX_TXS_IN_BLOCK + 5_078 as u32, + ] + .into_iter() + .max() + .unwrap(); + + // Add overhead and gas_for_bytecodes_pubdata + estimation_value += gas_for_bytecodes_pubdata + overhead; + Ok(U256::from(estimation_value)).into_boxed_future() } diff --git a/src/zks.rs b/src/zks.rs index 859f5565..a2a93446 100644 --- a/src/zks.rs +++ b/src/zks.rs @@ -19,6 +19,7 @@ impl ZksNamespaceT for ZkMockNamespaceImpl { _req: zksync_types::transaction_request::CallRequest, ) -> jsonrpc_core::BoxFuture> { Box::pin(async move { + // TODO: FIX THIS Ok(zksync_types::fee::Fee { gas_limit: U256::from(1000000000), max_fee_per_gas: U256::from(1000000000), From 0922ba074b570f047386913c574b0444b24f6e80 Mon Sep 17 00:00:00 2001 From: MexicanAce Date: Wed, 9 Aug 2023 11:09:11 +0100 Subject: [PATCH 05/22] Only load the fee_estimate contract once. Implement balance adjusted of payer for fee estimation sandbox. Start using adjusted l1_gas_price. --- src/node.rs | 47 +++++++++++++++++++++++++++++++---------------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/src/node.rs b/src/node.rs index ec3793ef..b0b0d689 100644 --- a/src/node.rs +++ b/src/node.rs @@ -17,7 +17,7 @@ use std::{ 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, + ContractLanguage, SystemContractCode, read_zbin_bytecode, }; use zksync_core::api_server::web3::backend_jsonrpc::namespaces::eth::EthNamespaceT; use zksync_state::{ReadStorage, StorageView, WriteStorage}; @@ -94,6 +94,7 @@ pub struct InMemoryNodeInner { pub dev_use_local_contracts: bool, pub baseline_contracts: BaseSystemContracts, pub playground_contracts: BaseSystemContracts, + pub fee_estimate_contracts: BaseSystemContracts, } impl InMemoryNodeInner { @@ -175,6 +176,16 @@ pub fn playground(use_local_contracts: bool) -> BaseSystemContracts { bsc_load_with_bootloader(bootloader_bytecode, use_local_contracts) } +/// BaseSystemContracts with fee_estimate bootloader - used for handling 'eth_estimateGas'. +pub fn fee_estimate_contracts(use_local_contracts: bool) -> BaseSystemContracts { + let bootloader_bytecode = if use_local_contracts { + read_zbin_bytecode("etc/system-contracts/bootloader/build/artifacts/fee_estimate.yul/fee_estimate.yul.zbin") + } else { + include_bytes!("deps/contracts/fee_estimate.yul.zbin").to_vec() + }; + bsc_load_with_bootloader(bootloader_bytecode, use_local_contracts) +} + pub fn baseline_contracts(use_local_contracts: bool) -> BaseSystemContracts { let bootloader_bytecode = if use_local_contracts { read_playground_block_bootloader_bytecode() @@ -223,6 +234,7 @@ impl InMemoryNode { dev_use_local_contracts, playground_contracts: playground(dev_use_local_contracts), baseline_contracts: baseline_contracts(dev_use_local_contracts), + fee_estimate_contracts: fee_estimate_contracts(dev_use_local_contracts), })), } } @@ -326,10 +338,14 @@ impl InMemoryNode { missed_storage_invocation_limit: 1000000, }; + // Set gas_limit for transaction + l2_tx.common_data.fee.gas_limit = gas_limit.into(); + let inner = self.inner.read().unwrap(); let fork_storage_copy = inner.fork_storage.clone(); let mut storage_view = StorageView::new(&fork_storage_copy); + // The nonce needs to be updated let nonce = l2_tx.nonce(); let nonce_key = get_nonce_key(&l2_tx.initiator_account()); let full_nonce = storage_view.read_value(&nonce_key); @@ -337,23 +353,21 @@ impl InMemoryNode { let enforced_full_nonce = nonces_to_full_nonce(U256::from(nonce.0), deployment_nonce); storage_view.set_value(nonce_key, u256_to_h256(enforced_full_nonce)); - // // // let payer = tx.payer(); - // // // let balance_key = storage_key_for_eth_balance(&payer); - // // // let mut current_balance = h256_to_u256(storage_view.read_value(&balance_key)); - // // // current_balance += execution_args.added_balance; - // // // storage_view.set_value(balance_key, u256_to_h256(current_balance)); + // We need to explicitly put enough balance into the account of the users + let payer = l2_tx.payer(); + let balance_key = storage_key_for_eth_balance(&payer); + let mut current_balance = h256_to_u256(storage_view.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; + storage_view.set_value(balance_key, u256_to_h256(current_balance)); let mut oracle_tools = OracleTools::new(&mut storage_view, HistoryDisabled); let block_context = inner.create_block_context(); // Use the fee_estimate bootloader code, as it sets ENSURE_RETURNED_MAGIC to 0 and BOOTLOADER_TYPE to 'playground_block' - let fee_estimate_bytecode = include_bytes!("deps/contracts/fee_estimate.yul.zbin").to_vec(); - let fee_estimate_code = bsc_load_with_bootloader(fee_estimate_bytecode, false); - let block_properties = InMemoryNodeInner::create_block_properties(&fee_estimate_code); - - // Set gas_limit for transaction - l2_tx.common_data.fee.gas_limit = gas_limit.into(); + let bootloader_code = &inner.fee_estimate_contracts; + let block_properties = InMemoryNodeInner::create_block_properties(&bootloader_code); // init vm let mut vm = init_vm_inner( @@ -361,7 +375,7 @@ impl InMemoryNode { BlockContextMode::OverrideCurrent(block_context.into()), &block_properties, BLOCK_GAS_LIMIT, - &fee_estimate_code, + bootloader_code, execution_mode, ); @@ -840,11 +854,12 @@ impl EthNamespaceT for InMemoryNode { let tx: Transaction = l2_tx.clone().into(); // TODO: Un-hardcode these values - let fair_l1_gas_price = 50_000_000_000; + let fair_l1_gas_price = 50_000_000_000u64; let fair_l2_gas_price = 250_000_000; // Calculate Adjusted L1 Price - let (_, adjusted_current_pubdata_price) = derive_base_fee_and_gas_per_pubdata((fair_l1_gas_price as f64 * 1.2) as u64, fair_l2_gas_price); + let gas_price_scale_factor = 1.2; + let (_, adjusted_current_pubdata_price) = derive_base_fee_and_gas_per_pubdata((fair_l1_gas_price as f64 * gas_price_scale_factor) as u64, fair_l2_gas_price); let adjusted_l1_gas_price = if U256::from(adjusted_current_pubdata_price) <= tx.gas_per_pubdata_byte_limit() { // The current pubdata price is small enough (fair_l1_gas_price as f64 * 1.2) as u64 @@ -857,7 +872,7 @@ impl EthNamespaceT for InMemoryNode { }; let (base_fee, gas_per_pubdata_byte) = derive_base_fee_and_gas_per_pubdata( - fair_l1_gas_price, + adjusted_l1_gas_price, fair_l2_gas_price, ); From 363965ef0379d38f33e10b4180c16b97feb2df49 Mon Sep 17 00:00:00 2001 From: MexicanAce Date: Wed, 9 Aug 2023 11:26:08 +0100 Subject: [PATCH 06/22] Add launch configurations for local debugging. --- .gitignore | 2 ++ .vscode/launch.json | 49 +++++++++++++++++++++++++++++++++++++++++++++ .vscode/tasks.json | 13 ++++++++++++ 3 files changed, 64 insertions(+) create mode 100644 .vscode/launch.json create mode 100644 .vscode/tasks.json diff --git a/.gitignore b/.gitignore index 3a5e52c4..44eb39b3 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,5 @@ etc/system-contracts/bootloader/build etc/**/*.zbin .vscode/* !.vscode/extensions.json +!.vscode/launch.json +!.vscode/tasks.json diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..c1c8635f --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,49 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "Debug era_test_node", + "cargo": { + "args": [ + "build", + "--bin=era_test_node", + "--package=era_test_node" + ], + "filter": { + "name": "era_test_node", + "kind": "bin" + } + }, + "args": ["run"], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug era_test_node w/ system-contracts", + "cargo": { + "args": [ + "build", + "--bin=era_test_node", + "--package=era_test_node" + ], + "filter": { + "name": "era_test_node", + "kind": "bin" + } + }, + "env": { + "RUST_LOG": "vm=trace", + "ZKSYNC_HOME": "${workspaceFolder}" + }, + "args": ["--dev-use-local-contracts", "run"], + "preLaunchTask": "compile-yul-preprocess", + "cwd": "${workspaceFolder}" + }, + ] +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 00000000..ff68f8d2 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,13 @@ +{ + "version": "2.0.0", + "tasks": [{ + "label": "compile-yul-preprocess", + "command": "cd etc/system-contracts && yarn preprocess && yarn hardhat run ./scripts/compile-yul.ts && cd ../..", + "type": "shell", + "presentation": { + "reveal": "silent", + "revealProblems": "onProblem", + "close": true + } + }] +} From 3230e08b170798e3e32bf8626051696094041028 Mon Sep 17 00:00:00 2001 From: MexicanAce Date: Wed, 9 Aug 2023 17:32:23 +0100 Subject: [PATCH 07/22] Update code to closer match what we do in production. Moved helper functions into utils. --- src/node.rs | 258 ++++++++++++++++++++++++++++++++------------------- src/utils.rs | 66 +++++++++++++ 2 files changed, 226 insertions(+), 98 deletions(-) diff --git a/src/node.rs b/src/node.rs index 5f0fc162..9afdf5b9 100644 --- a/src/node.rs +++ b/src/node.rs @@ -4,7 +4,7 @@ use crate::{ deps::system_contracts::bytecode_from_slice, fork::{ForkDetails, ForkStorage}, formatter, - utils::IntoBoxedFuture, + utils::{IntoBoxedFuture, derive_gas_estimation_overhead, adjust_l1_gas_price_for_tx}, ShowCalls, }; use colored::Colorize; @@ -13,14 +13,14 @@ use jsonrpc_core::BoxFuture; use std::{ collections::HashMap, convert::TryInto, - sync::{Arc, RwLock}, cmp::min, + sync::{Arc, RwLock}, cmp::{self}, }; 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, derive_base_fee_and_gas_per_pubdata, + BootloaderJobType, TxExecutionMode, derive_base_fee_and_gas_per_pubdata, DerivedBlockContext, }, HistoryEnabled, OracleTools, HistoryDisabled, TxRevertReason, VmBlockResult, }; @@ -41,9 +41,9 @@ use zksync_types::{ tx::tx_execution_info::TxExecutionStatus, utils::{storage_key_for_eth_balance, storage_key_for_standard_token_balance, decompose_full_nonce, nonces_to_full_nonce}, vm_trace::VmTrace, - zk_evm::block_properties::BlockProperties, + zk_evm::{block_properties::BlockProperties, zkevm_opcode_defs::system_params::MAX_PUBDATA_PER_BLOCK}, StorageKey, StorageLogQueryType, Transaction, ACCOUNT_CODE_STORAGE_ADDRESS, - L2_ETH_TOKEN_ADDRESS, + L2_ETH_TOKEN_ADDRESS, MAX_GAS_PER_PUBDATA_BYTE, MAX_L2_TX_GAS_LIMIT, fee::Fee, }; use zksync_utils::{ bytecode::{hash_bytecode, compress_bytecode}, bytes_to_be_words, h256_to_account_address, h256_to_u256, h256_to_u64, @@ -59,6 +59,15 @@ pub const MAX_TX_SIZE: usize = 1000000; 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; +/// L1 Gas Price +pub const L1_GAS_PRICE: u64 = 50_000_000_000; +/// L2 Gas Price (0.25 gwei) +pub const L2_GAS_PRICE: u64 = 250_000_000; +// pub const MAX_TX_ERGS_LIMIT: u32 = 80_000_000; +// pub const BLOCK_OVERHEAD_GAS: u32 = 1_200_000; +// pub const BLOCK_OVERHEAD_L1_GAS: u32 = 1_000_000; +// pub const L1_GAS_PER_PUBDATA_BYTE: u32 = 17; +// pub const BLOCK_OVERHEAD_PUBDATA: U256 = BLOCK_OVERHEAD_L1_GAS / L1_GAS_PER_PUBDATA_BYTE; /// Basic information about the generated block (which is block l1 batch and miniblock). /// Currently, this test node supports exactly one transaction per block. @@ -115,7 +124,7 @@ impl InMemoryNodeInner { block_number: self.current_batch, block_timestamp: self.current_timestamp, l1_gas_price: self.l1_gas_price, - fair_l2_gas_price: 250_000_000, // 0.25 gwei + fair_l2_gas_price: L2_GAS_PRICE, operator_address: H160::zero(), } } @@ -236,7 +245,7 @@ impl InMemoryNode { l1_gas_price: fork .as_ref() .map(|f| f.l1_gas_price) - .unwrap_or(50_000_000_000), + .unwrap_or(L1_GAS_PRICE), tx_results: Default::default(), blocks: Default::default(), fork_storage: ForkStorage::new(fork, dev_use_local_contracts), @@ -358,16 +367,36 @@ impl InMemoryNode { } /// Runs fee estimation against a sandbox vm with the given gas_limit. - fn estimate_gas_step(&self, mut l2_tx: L2Tx, gas_limit: u32) -> Result { + fn estimate_gas_step( + &self, + mut l2_tx: L2Tx, + gas_per_pubdata_byte: u64, + tx_gas_limit: u32, + l1_gas_price: u64, + base_fee: u64, + ) -> Result { + let reader = match self.inner.read() { + Ok(r) => r, + Err(_) => return Err(TxRevertReason::InnerTxError), + }; + let execution_mode = TxExecutionMode::EstimateFee { missed_storage_invocation_limit: 1000000, }; + let tx: Transaction = l2_tx.clone().into(); + let l1_gas_price = adjust_l1_gas_price_for_tx(l1_gas_price, L2_GAS_PRICE, tx.gas_per_pubdata_byte_limit()); + // Set gas_limit for transaction - l2_tx.common_data.fee.gas_limit = gas_limit.into(); + let gas_limit_with_overhead = tx_gas_limit + + derive_gas_estimation_overhead( + tx_gas_limit, + gas_per_pubdata_byte as u32, + tx.encoding_len(), + ); + l2_tx.common_data.fee.gas_limit = gas_limit_with_overhead.into(); - let inner = self.inner.read().unwrap(); - let fork_storage_copy = inner.fork_storage.clone(); + let fork_storage_copy = reader.fork_storage.clone(); let mut storage_view = StorageView::new(&fork_storage_copy); // The nonce needs to be updated @@ -388,16 +417,21 @@ impl InMemoryNode { let mut oracle_tools = OracleTools::new(&mut storage_view, HistoryDisabled); - let block_context = inner.create_block_context(); + let mut block_context = reader.create_block_context(); + block_context.l1_gas_price = l1_gas_price; + let derived_block_context = DerivedBlockContext { + context: reader.create_block_context(), + base_fee: base_fee, + }; // Use the fee_estimate bootloader code, as it sets ENSURE_RETURNED_MAGIC to 0 and BOOTLOADER_TYPE to 'playground_block' - let bootloader_code = &inner.fee_estimate_contracts; + let bootloader_code = &reader.fee_estimate_contracts; let block_properties = InMemoryNodeInner::create_block_properties(&bootloader_code); // init vm let mut vm = init_vm_inner( &mut oracle_tools, - BlockContextMode::OverrideCurrent(block_context.into()), + BlockContextMode::OverrideCurrent(derived_block_context.into()), &block_properties, BLOCK_GAS_LIMIT, bootloader_code, @@ -1041,7 +1075,7 @@ impl EthNamespaceT for InMemoryNode { /// /// # Arguments /// - /// * `_req` - A `CallRequest` struct representing the call request to estimate gas for. + /// * `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 @@ -1052,66 +1086,84 @@ impl EthNamespaceT for InMemoryNode { req: zksync_types::transaction_request::CallRequest, _block: Option, ) -> jsonrpc_core::BoxFuture> { - let mut l2_tx = l2_tx_from_call_req(req, MAX_TX_SIZE).unwrap(); - let tx: Transaction = l2_tx.clone().into(); + let inner = Arc::clone(&self.inner); + let reader = match inner.read() { + Ok(r) => r, + Err(_) => return futures::future::err(into_jsrpc_error(Web3Error::InternalError)).boxed(), + }; - // TODO: Un-hardcode these values - let fair_l1_gas_price = 50_000_000_000u64; - let fair_l2_gas_price = 250_000_000; + let mut l2_tx = match l2_tx_from_call_req(req, MAX_TX_SIZE) { + Ok(tx) => {tx} + Err(e) => { + let error = Web3Error::SerializationError(e); + return Err(into_jsrpc_error(error)).into_boxed_future() + } + }; + + let tx: Transaction = l2_tx.clone().into(); + let fair_l2_gas_price = L2_GAS_PRICE; // Calculate Adjusted L1 Price let gas_price_scale_factor = 1.2; - let (_, adjusted_current_pubdata_price) = derive_base_fee_and_gas_per_pubdata((fair_l1_gas_price as f64 * gas_price_scale_factor) as u64, fair_l2_gas_price); - let adjusted_l1_gas_price = if U256::from(adjusted_current_pubdata_price) <= tx.gas_per_pubdata_byte_limit() { - // The current pubdata price is small enough - (fair_l1_gas_price as f64 * 1.2) as u64 - } else { - let l1_gas_price = U256::from(fair_l2_gas_price) - * (tx.gas_per_pubdata_byte_limit() - U256::from(1u32)) - / U256::from(17); - - l1_gas_price.as_u64() + let l1_gas_price = { + let current_l1_gas_price = + ((reader.l1_gas_price as f64) * gas_price_scale_factor) as u64; + + // In order for execution to pass smoothly, we need to ensure that block's required gasPerPubdata will be + // <= to the one in the transaction itself. + adjust_l1_gas_price_for_tx( + current_l1_gas_price, + L2_GAS_PRICE, + tx.gas_per_pubdata_byte_limit(), + ) }; let (base_fee, gas_per_pubdata_byte) = derive_base_fee_and_gas_per_pubdata( - adjusted_l1_gas_price, + l1_gas_price, fair_l2_gas_price, ); - // TODO: Implement check for "account: {} does not have enough funds for for transferring tx.value: {}." - // Check for properly formatted signature if l2_tx.common_data.signature.is_empty() { l2_tx.common_data.signature = vec![0u8; 65]; l2_tx.common_data.signature[64] = 27; } - // MAX_GAS_PER_PUBDATA_BYTE = 20_000 - l2_tx.common_data.fee.gas_per_pubdata_limit = 20_000.into(); + l2_tx.common_data.fee.gas_per_pubdata_limit = MAX_GAS_PER_PUBDATA_BYTE.into(); l2_tx.common_data.fee.max_fee_per_gas = base_fee.into(); l2_tx.common_data.fee.max_priority_fee_per_gas = base_fee.into(); + + let mut storage_view = StorageView::new(&reader.fork_storage); // Calculate gas_for_bytecodes_pubdata - let inner = self.inner.read().unwrap(); - let mut storage_view = StorageView::new(&inner.fork_storage); - let pubdata_for_factory_deps = l2_tx.execute.factory_deps.as_deref().unwrap_or_default().iter().map(|bytecode| { - if storage_view.is_bytecode_known(&hash_bytecode(bytecode)) { - return 0; - } + let pubdata_for_factory_deps = l2_tx.execute.factory_deps + .as_deref() + .unwrap_or_default() + .iter() + .map(|bytecode| { + if storage_view.is_bytecode_known(&hash_bytecode(bytecode)) { + return 0; + } - let length = if let Ok(compressed) = compress_bytecode(bytecode) { - compressed.len() - } else { - bytecode.len() - }; - let publish_byte_overhead = 100; - length as u32 + publish_byte_overhead - }); - let gas_for_bytecodes_pubdata: u32 = pubdata_for_factory_deps.sum::() * (gas_per_pubdata_byte as u32); + let length = if let Ok(compressed) = compress_bytecode(bytecode) { + compressed.len() + } else { + bytecode.len() + }; + let publish_byte_overhead = 100; + length as u32 + publish_byte_overhead + }) + .sum::(); + + if pubdata_for_factory_deps > MAX_PUBDATA_PER_BLOCK { + return Err(into_jsrpc_error(Web3Error::SubmitTransactionError("exceeds limit for published pubdata".into(), Default::default()))).into_boxed_future() + } + + let gas_for_bytecodes_pubdata: u32 = pubdata_for_factory_deps * (gas_per_pubdata_byte as u32); // We are using binary search to find the minimal values of gas_limit under which the transaction succeeds let mut lower_bound = 0; - let mut upper_bound = ETH_CALL_GAS_LIMIT as u32; + let mut upper_bound = MAX_L2_TX_GAS_LIMIT as u32; let acceptable_overestimation = 1_000; let max_attempts = 30usize; @@ -1120,31 +1172,18 @@ impl EthNamespaceT for InMemoryNode { let mid = (lower_bound + upper_bound) / 2; let try_gas_limit = gas_for_bytecodes_pubdata + mid; - let overhead = vec![ - // Single instance circuits overhead - // 0.1 is a scaling coefficient L2 transactions - // 0.065 is from max_block_overhead / MAX_TX_ERGS_LIMIT - (0.1 * 0.065 * (try_gas_limit + gas_for_bytecodes_pubdata) as f32) as u32, - - // Bootloader overhead - // 11 is from max_block_overhead / BOOTLOADER_TX_ENCODING_SPACE - 11 * tx.encoding_len() as u32, - - // Slot overhead - // 5_078 is from max_block_overhead / MAX_TXS_IN_BLOCK - 5_078 as u32, - ] - .into_iter() - .max() - .unwrap(); - - let estimate_gas_result = self.estimate_gas_step(l2_tx.clone(), try_gas_limit + overhead); + let estimate_gas_result = self.estimate_gas_step( + l2_tx.clone(), + gas_per_pubdata_byte, + try_gas_limit, + l1_gas_price, + base_fee); if estimate_gas_result.is_err() { lower_bound = mid + 1; - // println!("Attempt {}: Failed with {}, trying again with lower_bound: {}, and upper_bound: {}", number_of_iterations, try_gas_limit + overhead, lower_bound, upper_bound); + // println!("Attempt {}: Failed with {}, trying again with lower_bound: {}, and upper_bound: {}", number_of_iterations, try_gas_limit, lower_bound, upper_bound); } else { upper_bound = mid; - // println!("Attempt {}: Succeeded with {}, trying again with lower_bound: {}, and upper_bound: {}", number_of_iterations, try_gas_limit + overhead, lower_bound, upper_bound); + // println!("Attempt {}: Succeeded with {}, trying again with lower_bound: {}, and upper_bound: {}", number_of_iterations, try_gas_limit, lower_bound, upper_bound); } number_of_iterations += 1; @@ -1152,36 +1191,59 @@ impl EthNamespaceT for InMemoryNode { // println!("COMPLETE after {} attempts and with lower_bound: {}, and upper_bound: {}", number_of_iterations, lower_bound, upper_bound); let estimated_fee_scale_factor = 1.3; - let mut estimation_value = (upper_bound as f32 * estimated_fee_scale_factor) as u32; - - estimation_value = min(ETH_CALL_GAS_LIMIT as u32, estimation_value); - - let overhead = vec![ - // Single instance circuits overhead - // 0.1 is a scaling coefficient L2 transactions - // 0.065 is from max_block_overhead / MAX_TX_ERGS_LIMIT - (0.1 * 0.065 * (estimation_value + gas_for_bytecodes_pubdata) as f32) as u32, - - // Bootloader overhead - // 11 is from max_block_overhead / BOOTLOADER_TX_ENCODING_SPACE - 11 * tx.encoding_len() as u32, - - // Slot overhead - // 5_078 is from max_block_overhead / MAX_TXS_IN_BLOCK - 5_078 as u32, - ] - .into_iter() - .max() - .unwrap(); + let tx_body_gas_limit = cmp::min( + MAX_L2_TX_GAS_LIMIT as u32, + (upper_bound as f32 * estimated_fee_scale_factor) as u32, + ); + let suggested_gas_limit = tx_body_gas_limit + gas_for_bytecodes_pubdata; + + let estimate_gas_result = self.estimate_gas_step( + l2_tx.clone(), + gas_per_pubdata_byte, + suggested_gas_limit, + l1_gas_price, + base_fee); - // Add overhead and gas_for_bytecodes_pubdata - estimation_value += gas_for_bytecodes_pubdata + overhead; - - Ok(U256::from(estimation_value)).into_boxed_future() + match estimate_gas_result { + Err(_) => futures::future::err(into_jsrpc_error( + Web3Error::SubmitTransactionError( + "Transaction is unexecutable".into(), + Default::default(), + ) + )).boxed(), + Ok(_) => { + let overhead: u32 = derive_gas_estimation_overhead( + suggested_gas_limit, + gas_per_pubdata_byte as u32, + tx.encoding_len() + ); + + let full_gas_limit = + match tx_body_gas_limit.overflowing_add(gas_for_bytecodes_pubdata + overhead) { + (value, false) => value, + (_, true) => { + return Err(into_jsrpc_error( + Web3Error::SubmitTransactionError( + "exceeds block gas limit".into(), + Default::default() + ) + )).into_boxed_future() + } + }; + + let fee = Fee { + max_fee_per_gas: base_fee.into(), + max_priority_fee_per_gas: 0u32.into(), + gas_limit: full_gas_limit.into(), + gas_per_pubdata_limit: gas_per_pubdata_byte.into(), + }; + Ok(fee.gas_limit).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 + let fair_l2_gas_price: u64 = L2_GAS_PRICE; Ok(U256::from(fair_l2_gas_price)).into_boxed_future() } diff --git a/src/utils.rs b/src/utils.rs index 76f5017e..86faf7e5 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,6 +1,10 @@ use std::pin::Pin; use futures::Future; +use vm::vm_with_bootloader::{BOOTLOADER_TX_ENCODING_SPACE, BLOCK_OVERHEAD_GAS, BLOCK_OVERHEAD_PUBDATA, derive_base_fee_and_gas_per_pubdata}; +use zksync_basic_types::U256; +use zksync_types::{zk_evm::zkevm_opcode_defs::system_params::MAX_TX_ERGS_LIMIT, MAX_TXS_IN_BLOCK}; +use zksync_utils::ceil_div_u256; pub(crate) trait IntoBoxedFuture: Sized + Send + 'static { fn into_boxed_future(self) -> Pin + Send>> { @@ -14,3 +18,65 @@ where U: Send + 'static, { } + +pub fn derive_gas_estimation_overhead( + gas_limit: u32, + gas_price_per_pubdata: u32, + encoded_len: usize, +) -> u32 { + // Even if the gas limit is greater than the MAX_TX_ERGS_LIMIT, we assume that everything beyond MAX_TX_ERGS_LIMIT + // will be spent entirely on publishing bytecodes and so we derive the overhead solely based on the capped value + let gas_limit = std::cmp::min(MAX_TX_ERGS_LIMIT, gas_limit); + + // Using large U256 type to avoid overflow + let max_block_overhead = U256::from(block_overhead_gas(gas_price_per_pubdata)); + let gas_limit = U256::from(gas_limit); + let encoded_len = U256::from(encoded_len); + + // The MAX_TX_ERGS_LIMIT is formed in a way that may fullfills a single-instance circuits + // if used in full. That is, within MAX_TX_ERGS_LIMIT it is possible to fully saturate all the single-instance + // circuits. + let overhead_for_single_instance_circuits = + ceil_div_u256(gas_limit * max_block_overhead, MAX_TX_ERGS_LIMIT.into()); + + // The overhead for occupying the bootloader memory + let overhead_for_length = ceil_div_u256( + encoded_len * max_block_overhead, + BOOTLOADER_TX_ENCODING_SPACE.into(), + ); + + // The overhead for occupying a single tx slot + let tx_slot_overhead = ceil_div_u256(max_block_overhead, MAX_TXS_IN_BLOCK.into()); + + vec![ + (0.1 * overhead_for_single_instance_circuits.as_u32() as f64).floor() as u32, + overhead_for_length.as_u32(), + tx_slot_overhead.as_u32(), + ] + .into_iter() + .max() + .unwrap() +} + +pub fn block_overhead_gas(gas_per_pubdata_byte: u32) -> u32 { + BLOCK_OVERHEAD_GAS + BLOCK_OVERHEAD_PUBDATA * gas_per_pubdata_byte +} + +pub fn adjust_l1_gas_price_for_tx( + l1_gas_price: u64, + fair_l2_gas_price: u64, + tx_gas_per_pubdata_limit: U256, +) -> u64 { + let (_, current_pubdata_price) = + derive_base_fee_and_gas_per_pubdata(l1_gas_price, fair_l2_gas_price); + if U256::from(current_pubdata_price) <= tx_gas_per_pubdata_limit { + // The current pubdata price is small enough + l1_gas_price + } else { + let l1_gas_price = U256::from(fair_l2_gas_price) + * (tx_gas_per_pubdata_limit - U256::from(1u32)) + / U256::from(17); + + l1_gas_price.as_u64() + } +} From d46589a20027b322d22d2fff27f5304c15a92f3d Mon Sep 17 00:00:00 2001 From: MexicanAce Date: Wed, 9 Aug 2023 17:46:48 +0100 Subject: [PATCH 08/22] Migrate all fee estimate code into a helper function on the InnerNode so it can be called from zks_estimateFee --- src/node.rs | 327 ++++++++++++++++++++++++++++------------------------ 1 file changed, 176 insertions(+), 151 deletions(-) diff --git a/src/node.rs b/src/node.rs index 9afdf5b9..d965e1eb 100644 --- a/src/node.rs +++ b/src/node.rs @@ -366,6 +366,175 @@ impl InMemoryNode { } } + /// Estimates the gas required for a given call request. + /// + /// # Arguments + /// + /// * `req` - A `CallRequest` struct representing the call request to estimate gas for. + /// + /// # Returns + /// + /// A `BoxFuture` containing a `Result` with a `Fee` representing the estimated gas related data. + fn estimate_gas_impl( + &self, + req: zksync_types::transaction_request::CallRequest, + ) -> jsonrpc_core::Result { + let inner = Arc::clone(&self.inner); + let reader = match inner.read() { + Ok(r) => r, + Err(_) => return Err(into_jsrpc_error(Web3Error::InternalError)), + }; + + let mut l2_tx = match l2_tx_from_call_req(req, MAX_TX_SIZE) { + Ok(tx) => {tx} + Err(e) => { + let error = Web3Error::SerializationError(e); + return Err(into_jsrpc_error(error)) + } + }; + + let tx: Transaction = l2_tx.clone().into(); + let fair_l2_gas_price = L2_GAS_PRICE; + + // Calculate Adjusted L1 Price + let gas_price_scale_factor = 1.2; + let l1_gas_price = { + let current_l1_gas_price = + ((reader.l1_gas_price as f64) * gas_price_scale_factor) as u64; + + // In order for execution to pass smoothly, we need to ensure that block's required gasPerPubdata will be + // <= to the one in the transaction itself. + adjust_l1_gas_price_for_tx( + current_l1_gas_price, + L2_GAS_PRICE, + tx.gas_per_pubdata_byte_limit(), + ) + }; + + let (base_fee, gas_per_pubdata_byte) = derive_base_fee_and_gas_per_pubdata( + l1_gas_price, + fair_l2_gas_price, + ); + + // Check for properly formatted signature + if l2_tx.common_data.signature.is_empty() { + l2_tx.common_data.signature = vec![0u8; 65]; + l2_tx.common_data.signature[64] = 27; + } + + l2_tx.common_data.fee.gas_per_pubdata_limit = MAX_GAS_PER_PUBDATA_BYTE.into(); + l2_tx.common_data.fee.max_fee_per_gas = base_fee.into(); + l2_tx.common_data.fee.max_priority_fee_per_gas = base_fee.into(); + + let mut storage_view = StorageView::new(&reader.fork_storage); + + // Calculate gas_for_bytecodes_pubdata + let pubdata_for_factory_deps = l2_tx.execute.factory_deps + .as_deref() + .unwrap_or_default() + .iter() + .map(|bytecode| { + if storage_view.is_bytecode_known(&hash_bytecode(bytecode)) { + return 0; + } + + let length = if let Ok(compressed) = compress_bytecode(bytecode) { + compressed.len() + } else { + bytecode.len() + }; + let publish_byte_overhead = 100; + length as u32 + publish_byte_overhead + }) + .sum::(); + + if pubdata_for_factory_deps > MAX_PUBDATA_PER_BLOCK { + return Err(into_jsrpc_error(Web3Error::SubmitTransactionError("exceeds limit for published pubdata".into(), Default::default()))) + } + + let gas_for_bytecodes_pubdata: u32 = pubdata_for_factory_deps * (gas_per_pubdata_byte as u32); + + // We are using binary search to find the minimal values of gas_limit under which the transaction succeeds + let mut lower_bound = 0; + let mut upper_bound = MAX_L2_TX_GAS_LIMIT as u32; + let acceptable_overestimation = 1_000; + let max_attempts = 30usize; + + let mut number_of_iterations = 0usize; + while lower_bound + acceptable_overestimation < upper_bound && number_of_iterations < max_attempts{ + let mid = (lower_bound + upper_bound) / 2; + let try_gas_limit = gas_for_bytecodes_pubdata + mid; + + let estimate_gas_result = self.estimate_gas_step( + l2_tx.clone(), + gas_per_pubdata_byte, + try_gas_limit, + l1_gas_price, + base_fee); + if estimate_gas_result.is_err() { + lower_bound = mid + 1; + // println!("Attempt {}: Failed with {}, trying again with lower_bound: {}, and upper_bound: {}", number_of_iterations, try_gas_limit, lower_bound, upper_bound); + } else { + upper_bound = mid; + // println!("Attempt {}: Succeeded with {}, trying again with lower_bound: {}, and upper_bound: {}", number_of_iterations, try_gas_limit, lower_bound, upper_bound); + } + + number_of_iterations += 1; + } + + // println!("COMPLETE after {} attempts and with lower_bound: {}, and upper_bound: {}", number_of_iterations, lower_bound, upper_bound); + let estimated_fee_scale_factor = 1.3; + let tx_body_gas_limit = cmp::min( + MAX_L2_TX_GAS_LIMIT as u32, + (upper_bound as f32 * estimated_fee_scale_factor) as u32, + ); + let suggested_gas_limit = tx_body_gas_limit + gas_for_bytecodes_pubdata; + + let estimate_gas_result = self.estimate_gas_step( + l2_tx.clone(), + gas_per_pubdata_byte, + suggested_gas_limit, + l1_gas_price, + base_fee); + + match estimate_gas_result { + Err(_) => Err(into_jsrpc_error( + Web3Error::SubmitTransactionError( + "Transaction is unexecutable".into(), + Default::default(), + ) + )), + Ok(_) => { + let overhead: u32 = derive_gas_estimation_overhead( + suggested_gas_limit, + gas_per_pubdata_byte as u32, + tx.encoding_len() + ); + + let full_gas_limit = + match tx_body_gas_limit.overflowing_add(gas_for_bytecodes_pubdata + overhead) { + (value, false) => value, + (_, true) => { + return Err(into_jsrpc_error( + Web3Error::SubmitTransactionError( + "exceeds block gas limit".into(), + Default::default() + ) + )) + } + }; + + let fee = Fee { + max_fee_per_gas: base_fee.into(), + max_priority_fee_per_gas: 0u32.into(), + gas_limit: full_gas_limit.into(), + gas_per_pubdata_limit: gas_per_pubdata_byte.into(), + }; + Ok(fee) + } + } + } + /// Runs fee estimation against a sandbox vm with the given gas_limit. fn estimate_gas_step( &self, @@ -1086,161 +1255,17 @@ impl EthNamespaceT for InMemoryNode { req: zksync_types::transaction_request::CallRequest, _block: Option, ) -> jsonrpc_core::BoxFuture> { - let inner = Arc::clone(&self.inner); - let reader = match inner.read() { - Ok(r) => r, - Err(_) => return futures::future::err(into_jsrpc_error(Web3Error::InternalError)).boxed(), - }; - - let mut l2_tx = match l2_tx_from_call_req(req, MAX_TX_SIZE) { - Ok(tx) => {tx} - Err(e) => { - let error = Web3Error::SerializationError(e); - return Err(into_jsrpc_error(error)).into_boxed_future() - } - }; - - let tx: Transaction = l2_tx.clone().into(); - let fair_l2_gas_price = L2_GAS_PRICE; - - // Calculate Adjusted L1 Price - let gas_price_scale_factor = 1.2; - let l1_gas_price = { - let current_l1_gas_price = - ((reader.l1_gas_price as f64) * gas_price_scale_factor) as u64; - - // In order for execution to pass smoothly, we need to ensure that block's required gasPerPubdata will be - // <= to the one in the transaction itself. - adjust_l1_gas_price_for_tx( - current_l1_gas_price, - L2_GAS_PRICE, - tx.gas_per_pubdata_byte_limit(), - ) - }; - - let (base_fee, gas_per_pubdata_byte) = derive_base_fee_and_gas_per_pubdata( - l1_gas_price, - fair_l2_gas_price, - ); - - // Check for properly formatted signature - if l2_tx.common_data.signature.is_empty() { - l2_tx.common_data.signature = vec![0u8; 65]; - l2_tx.common_data.signature[64] = 27; - } - - l2_tx.common_data.fee.gas_per_pubdata_limit = MAX_GAS_PER_PUBDATA_BYTE.into(); - l2_tx.common_data.fee.max_fee_per_gas = base_fee.into(); - l2_tx.common_data.fee.max_priority_fee_per_gas = base_fee.into(); - - let mut storage_view = StorageView::new(&reader.fork_storage); - - // Calculate gas_for_bytecodes_pubdata - let pubdata_for_factory_deps = l2_tx.execute.factory_deps - .as_deref() - .unwrap_or_default() - .iter() - .map(|bytecode| { - if storage_view.is_bytecode_known(&hash_bytecode(bytecode)) { - return 0; - } - - let length = if let Ok(compressed) = compress_bytecode(bytecode) { - compressed.len() - } else { - bytecode.len() - }; - let publish_byte_overhead = 100; - length as u32 + publish_byte_overhead - }) - .sum::(); - - if pubdata_for_factory_deps > MAX_PUBDATA_PER_BLOCK { - return Err(into_jsrpc_error(Web3Error::SubmitTransactionError("exceeds limit for published pubdata".into(), Default::default()))).into_boxed_future() - } - - let gas_for_bytecodes_pubdata: u32 = pubdata_for_factory_deps * (gas_per_pubdata_byte as u32); - - // We are using binary search to find the minimal values of gas_limit under which the transaction succeeds - let mut lower_bound = 0; - let mut upper_bound = MAX_L2_TX_GAS_LIMIT as u32; - let acceptable_overestimation = 1_000; - let max_attempts = 30usize; - - let mut number_of_iterations = 0usize; - while lower_bound + acceptable_overestimation < upper_bound && number_of_iterations < max_attempts{ - let mid = (lower_bound + upper_bound) / 2; - let try_gas_limit = gas_for_bytecodes_pubdata + mid; - - let estimate_gas_result = self.estimate_gas_step( - l2_tx.clone(), - gas_per_pubdata_byte, - try_gas_limit, - l1_gas_price, - base_fee); - if estimate_gas_result.is_err() { - lower_bound = mid + 1; - // println!("Attempt {}: Failed with {}, trying again with lower_bound: {}, and upper_bound: {}", number_of_iterations, try_gas_limit, lower_bound, upper_bound); - } else { - upper_bound = mid; - // println!("Attempt {}: Succeeded with {}, trying again with lower_bound: {}, and upper_bound: {}", number_of_iterations, try_gas_limit, lower_bound, upper_bound); - } - - number_of_iterations += 1; - } - - // println!("COMPLETE after {} attempts and with lower_bound: {}, and upper_bound: {}", number_of_iterations, lower_bound, upper_bound); - let estimated_fee_scale_factor = 1.3; - let tx_body_gas_limit = cmp::min( - MAX_L2_TX_GAS_LIMIT as u32, - (upper_bound as f32 * estimated_fee_scale_factor) as u32, - ); - let suggested_gas_limit = tx_body_gas_limit + gas_for_bytecodes_pubdata; - - let estimate_gas_result = self.estimate_gas_step( - l2_tx.clone(), - gas_per_pubdata_byte, - suggested_gas_limit, - l1_gas_price, - base_fee); - - match estimate_gas_result { - Err(_) => futures::future::err(into_jsrpc_error( - Web3Error::SubmitTransactionError( - "Transaction is unexecutable".into(), - Default::default(), - ) - )).boxed(), - Ok(_) => { - let overhead: u32 = derive_gas_estimation_overhead( - suggested_gas_limit, - gas_per_pubdata_byte as u32, - tx.encoding_len() - ); - - let full_gas_limit = - match tx_body_gas_limit.overflowing_add(gas_for_bytecodes_pubdata + overhead) { - (value, false) => value, - (_, true) => { - return Err(into_jsrpc_error( - Web3Error::SubmitTransactionError( - "exceeds block gas limit".into(), - Default::default() - ) - )).into_boxed_future() - } - }; - - let fee = Fee { - max_fee_per_gas: base_fee.into(), - max_priority_fee_per_gas: 0u32.into(), - gas_limit: full_gas_limit.into(), - gas_per_pubdata_limit: gas_per_pubdata_byte.into(), - }; + let result: jsonrpc_core::Result = self.estimate_gas_impl(req); + match result { + Ok(fee) => { Ok(fee.gas_limit).into_boxed_future() + }, + Err(err) => { + return futures::future::err(err).boxed() } } } + /// Returns the current gas price in U256 format. fn gas_price(&self) -> jsonrpc_core::BoxFuture> { let fair_l2_gas_price: u64 = L2_GAS_PRICE; From 7de10d80fce0a6d8d9b23c19234f26198a3cea59 Mon Sep 17 00:00:00 2001 From: MexicanAce Date: Wed, 9 Aug 2023 18:28:43 +0100 Subject: [PATCH 09/22] Move functions so they can be accessed by the zks namespace. Implemente zks_estimateFee. --- src/main.rs | 6 +- src/node.rs | 498 ++++++++++++++++++++++++++-------------------------- src/zks.rs | 49 ++++-- 3 files changed, 287 insertions(+), 266 deletions(-) diff --git a/src/main.rs b/src/main.rs index b33bc4c9..ffbd4e3e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -108,6 +108,7 @@ async fn build_json_http( node: InMemoryNode, net: NetNamespace, config_api: ConfigurationApiNamespace, + zks: ZkMockNamespaceImpl, ) -> tokio::task::JoinHandle<()> { let (sender, recv) = oneshot::channel::<()>(); @@ -116,7 +117,7 @@ async fn build_json_http( io.extend_with(node.to_delegate()); io.extend_with(net.to_delegate()); io.extend_with(config_api.to_delegate()); - io.extend_with(ZkMockNamespaceImpl.to_delegate()); + io.extend_with(zks.to_delegate()); io }; @@ -292,14 +293,15 @@ async fn main() -> anyhow::Result<()> { } let net = NetNamespace::new(L2ChainId(TEST_NODE_NETWORK_ID)); - let config_api = ConfigurationApiNamespace::new(node.get_inner()); + let zks = ZkMockNamespaceImpl::new(node.get_inner()); let threads = build_json_http( SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), opt.port), node, net, config_api, + zks, ) .await; diff --git a/src/node.rs b/src/node.rs index d965e1eb..e0c2b0a1 100644 --- a/src/node.rs +++ b/src/node.rs @@ -128,243 +128,13 @@ impl InMemoryNodeInner { operator_address: H160::zero(), } } + fn create_block_properties(contracts: &BaseSystemContracts) -> BlockProperties { BlockProperties { default_aa_code_hash: h256_to_u256(contracts.default_aa.hash), zkporter_is_available: false, } } -} - -fn not_implemented( - method_name: &str, -) -> jsonrpc_core::BoxFuture> { - println!("Method {} is not implemented", method_name); - Err(jsonrpc_core::Error { - data: None, - code: jsonrpc_core::ErrorCode::MethodNotFound, - message: format!("Method {} is not implemented", method_name), - }) - .into_boxed_future() -} - -/// In-memory node, that can be used for local & unit testing. -/// It also supports the option of forking testnet/mainnet. -/// All contents are removed when object is destroyed. -pub struct InMemoryNode { - inner: Arc>, -} - -fn bsc_load_with_bootloader( - bootloader_bytecode: Vec, - use_local_contracts: bool, -) -> BaseSystemContracts { - let hash = hash_bytecode(&bootloader_bytecode); - - let bootloader = SystemContractCode { - code: bytes_to_be_words(bootloader_bytecode), - hash, - }; - - let bytecode = if use_local_contracts { - read_sys_contract_bytecode("", "DefaultAccount", ContractLanguage::Sol) - } else { - bytecode_from_slice( - "DefaultAccount", - include_bytes!("deps/contracts/DefaultAccount.json"), - ) - }; - let hash = hash_bytecode(&bytecode); - - let default_aa = SystemContractCode { - code: bytes_to_be_words(bytecode), - hash, - }; - - BaseSystemContracts { - bootloader, - default_aa, - } -} - -/// BaseSystemContracts with playground bootloader - used for handling 'eth_calls'. -pub fn playground(use_local_contracts: bool) -> BaseSystemContracts { - let bootloader_bytecode = if use_local_contracts { - read_playground_block_bootloader_bytecode() - } else { - include_bytes!("deps/contracts/playground_block.yul.zbin").to_vec() - }; - bsc_load_with_bootloader(bootloader_bytecode, use_local_contracts) -} - -/// BaseSystemContracts with fee_estimate bootloader - used for handling 'eth_estimateGas'. -pub fn fee_estimate_contracts(use_local_contracts: bool) -> BaseSystemContracts { - let bootloader_bytecode = if use_local_contracts { - read_zbin_bytecode("etc/system-contracts/bootloader/build/artifacts/fee_estimate.yul/fee_estimate.yul.zbin") - } else { - include_bytes!("deps/contracts/fee_estimate.yul.zbin").to_vec() - }; - bsc_load_with_bootloader(bootloader_bytecode, use_local_contracts) -} - -pub fn baseline_contracts(use_local_contracts: bool) -> BaseSystemContracts { - let bootloader_bytecode = if use_local_contracts { - read_playground_block_bootloader_bytecode() - } else { - include_bytes!("deps/contracts/proved_block.yul.zbin").to_vec() - }; - bsc_load_with_bootloader(bootloader_bytecode, use_local_contracts) -} - -fn contract_address_from_tx_result(execution_result: &VmTxExecutionResult) -> Option { - for query in execution_result.result.logs.storage_logs.iter().rev() { - if query.log_type == StorageLogQueryType::InitialWrite - && query.log_query.address == ACCOUNT_CODE_STORAGE_ADDRESS - { - return Some(h256_to_account_address(&u256_to_h256(query.log_query.key))); - } - } - None -} - -impl InMemoryNode { - pub fn new( - fork: Option, - show_calls: ShowCalls, - resolve_hashes: bool, - dev_use_local_contracts: bool, - ) -> Self { - InMemoryNode { - inner: Arc::new(RwLock::new(InMemoryNodeInner { - current_timestamp: fork - .as_ref() - .map(|f| f.block_timestamp + 1) - .unwrap_or(NON_FORK_FIRST_BLOCK_TIMESTAMP), - current_batch: fork.as_ref().map(|f| f.l1_block.0 + 1).unwrap_or(1), - current_miniblock: fork.as_ref().map(|f| f.l2_miniblock + 1).unwrap_or(1), - l1_gas_price: fork - .as_ref() - .map(|f| f.l1_gas_price) - .unwrap_or(L1_GAS_PRICE), - tx_results: Default::default(), - blocks: Default::default(), - fork_storage: ForkStorage::new(fork, dev_use_local_contracts), - show_calls, - resolve_hashes, - console_log_handler: ConsoleLogHandler::default(), - dev_use_local_contracts, - playground_contracts: playground(dev_use_local_contracts), - baseline_contracts: baseline_contracts(dev_use_local_contracts), - fee_estimate_contracts: fee_estimate_contracts(dev_use_local_contracts), - })), - } - } - - pub fn get_inner(&self) -> Arc> { - self.inner.clone() - } - - /// Applies multiple transactions - but still one per L1 batch. - 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)?; - } - - 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 = 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)))); - storage_view.modified_storage_keys().clone() - }; - - for (key, value) in keys.iter() { - inner.fork_storage.set_value(*key, *value); - } - } - - /// Runs L2 'eth call' method - that doesn't commit to a block. - 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() - .map_err(|e| format!("Failed to acquire write lock: {}", e))?; - - let mut storage_view = StorageView::new(&inner.fork_storage); - - let mut oracle_tools = OracleTools::new(&mut storage_view, HistoryEnabled); - - let bootloader_code = &inner.playground_contracts; - - let block_context = inner.create_block_context(); - let block_properties = InMemoryNodeInner::create_block_properties(bootloader_code); - - // init vm - let mut vm = init_vm_inner( - &mut oracle_tools, - BlockContextMode::NewBlock(block_context.into(), Default::default()), - &block_properties, - BLOCK_GAS_LIMIT, - bootloader_code, - execution_mode, - ); - - let tx: Transaction = l2_tx.into(); - - push_transaction_to_bootloader_memory(&mut vm, &tx, execution_mode, None); - - let vm_block_result = - vm.execute_till_block_end_with_call_tracer(BootloaderJobType::TransactionExecution); - - if let Some(revert_reason) = &vm_block_result.full_result.revert_reason { - println!("Call {} {:?}", "FAILED".red(), revert_reason.revert_reason); - } else { - println!("Call {}", "SUCCESS".green()); - } - if let VmTrace::CallTrace(call_trace) = &vm_block_result.full_result.trace { - println!("=== Console Logs: "); - for call in call_trace { - inner.console_log_handler.handle_call_recurive(call); - } - - println!("=== Call traces:"); - for call in call_trace { - formatter::print_call(call, 0, &inner.show_calls, inner.resolve_hashes); - } - } - - match vm_block_result.full_result.revert_reason { - Some(result) => Ok(result.original_data), - None => Ok(vm_block_result - .full_result - .return_data - .into_iter() - .flat_map(|val| { - let bytes: [u8; 32] = val.into(); - bytes.to_vec() - }) - .collect::>()), - } - } /// Estimates the gas required for a given call request. /// @@ -375,16 +145,10 @@ impl InMemoryNode { /// # Returns /// /// A `BoxFuture` containing a `Result` with a `Fee` representing the estimated gas related data. - fn estimate_gas_impl( + pub fn estimate_gas_impl( &self, req: zksync_types::transaction_request::CallRequest, ) -> jsonrpc_core::Result { - let inner = Arc::clone(&self.inner); - let reader = match inner.read() { - Ok(r) => r, - Err(_) => return Err(into_jsrpc_error(Web3Error::InternalError)), - }; - let mut l2_tx = match l2_tx_from_call_req(req, MAX_TX_SIZE) { Ok(tx) => {tx} Err(e) => { @@ -400,7 +164,7 @@ impl InMemoryNode { let gas_price_scale_factor = 1.2; let l1_gas_price = { let current_l1_gas_price = - ((reader.l1_gas_price as f64) * gas_price_scale_factor) as u64; + ((self.l1_gas_price as f64) * gas_price_scale_factor) as u64; // In order for execution to pass smoothly, we need to ensure that block's required gasPerPubdata will be // <= to the one in the transaction itself. @@ -426,7 +190,7 @@ impl InMemoryNode { l2_tx.common_data.fee.max_fee_per_gas = base_fee.into(); l2_tx.common_data.fee.max_priority_fee_per_gas = base_fee.into(); - let mut storage_view = StorageView::new(&reader.fork_storage); + let mut storage_view = StorageView::new(&self.fork_storage); // Calculate gas_for_bytecodes_pubdata let pubdata_for_factory_deps = l2_tx.execute.factory_deps @@ -543,12 +307,7 @@ impl InMemoryNode { tx_gas_limit: u32, l1_gas_price: u64, base_fee: u64, - ) -> Result { - let reader = match self.inner.read() { - Ok(r) => r, - Err(_) => return Err(TxRevertReason::InnerTxError), - }; - + ) -> Result { let execution_mode = TxExecutionMode::EstimateFee { missed_storage_invocation_limit: 1000000, }; @@ -565,7 +324,7 @@ impl InMemoryNode { ); l2_tx.common_data.fee.gas_limit = gas_limit_with_overhead.into(); - let fork_storage_copy = reader.fork_storage.clone(); + let fork_storage_copy = self.fork_storage.clone(); let mut storage_view = StorageView::new(&fork_storage_copy); // The nonce needs to be updated @@ -586,15 +345,15 @@ impl InMemoryNode { let mut oracle_tools = OracleTools::new(&mut storage_view, HistoryDisabled); - let mut block_context = reader.create_block_context(); + let mut block_context = self.create_block_context(); block_context.l1_gas_price = l1_gas_price; let derived_block_context = DerivedBlockContext { - context: reader.create_block_context(), + context: self.create_block_context(), base_fee: base_fee, }; // Use the fee_estimate bootloader code, as it sets ENSURE_RETURNED_MAGIC to 0 and BOOTLOADER_TYPE to 'playground_block' - let bootloader_code = &reader.fee_estimate_contracts; + let bootloader_code = &self.fee_estimate_contracts; let block_properties = InMemoryNodeInner::create_block_properties(&bootloader_code); // init vm @@ -624,6 +383,237 @@ impl InMemoryNode { result } +} + +fn not_implemented( + method_name: &str, +) -> jsonrpc_core::BoxFuture> { + println!("Method {} is not implemented", method_name); + Err(jsonrpc_core::Error { + data: None, + code: jsonrpc_core::ErrorCode::MethodNotFound, + message: format!("Method {} is not implemented", method_name), + }) + .into_boxed_future() +} + +/// In-memory node, that can be used for local & unit testing. +/// It also supports the option of forking testnet/mainnet. +/// All contents are removed when object is destroyed. +pub struct InMemoryNode { + inner: Arc>, +} + +fn bsc_load_with_bootloader( + bootloader_bytecode: Vec, + use_local_contracts: bool, +) -> BaseSystemContracts { + let hash = hash_bytecode(&bootloader_bytecode); + + let bootloader = SystemContractCode { + code: bytes_to_be_words(bootloader_bytecode), + hash, + }; + + let bytecode = if use_local_contracts { + read_sys_contract_bytecode("", "DefaultAccount", ContractLanguage::Sol) + } else { + bytecode_from_slice( + "DefaultAccount", + include_bytes!("deps/contracts/DefaultAccount.json"), + ) + }; + let hash = hash_bytecode(&bytecode); + + let default_aa = SystemContractCode { + code: bytes_to_be_words(bytecode), + hash, + }; + + BaseSystemContracts { + bootloader, + default_aa, + } +} + +/// BaseSystemContracts with playground bootloader - used for handling 'eth_calls'. +pub fn playground(use_local_contracts: bool) -> BaseSystemContracts { + let bootloader_bytecode = if use_local_contracts { + read_playground_block_bootloader_bytecode() + } else { + include_bytes!("deps/contracts/playground_block.yul.zbin").to_vec() + }; + bsc_load_with_bootloader(bootloader_bytecode, use_local_contracts) +} + +/// BaseSystemContracts with fee_estimate bootloader - used for handling 'eth_estimateGas'. +pub fn fee_estimate_contracts(use_local_contracts: bool) -> BaseSystemContracts { + let bootloader_bytecode = if use_local_contracts { + read_zbin_bytecode("etc/system-contracts/bootloader/build/artifacts/fee_estimate.yul/fee_estimate.yul.zbin") + } else { + include_bytes!("deps/contracts/fee_estimate.yul.zbin").to_vec() + }; + bsc_load_with_bootloader(bootloader_bytecode, use_local_contracts) +} + +pub fn baseline_contracts(use_local_contracts: bool) -> BaseSystemContracts { + let bootloader_bytecode = if use_local_contracts { + read_playground_block_bootloader_bytecode() + } else { + include_bytes!("deps/contracts/proved_block.yul.zbin").to_vec() + }; + bsc_load_with_bootloader(bootloader_bytecode, use_local_contracts) +} + +fn contract_address_from_tx_result(execution_result: &VmTxExecutionResult) -> Option { + for query in execution_result.result.logs.storage_logs.iter().rev() { + if query.log_type == StorageLogQueryType::InitialWrite + && query.log_query.address == ACCOUNT_CODE_STORAGE_ADDRESS + { + return Some(h256_to_account_address(&u256_to_h256(query.log_query.key))); + } + } + None +} + +impl InMemoryNode { + pub fn new( + fork: Option, + show_calls: ShowCalls, + resolve_hashes: bool, + dev_use_local_contracts: bool, + ) -> Self { + InMemoryNode { + inner: Arc::new(RwLock::new(InMemoryNodeInner { + current_timestamp: fork + .as_ref() + .map(|f| f.block_timestamp + 1) + .unwrap_or(NON_FORK_FIRST_BLOCK_TIMESTAMP), + current_batch: fork.as_ref().map(|f| f.l1_block.0 + 1).unwrap_or(1), + current_miniblock: fork.as_ref().map(|f| f.l2_miniblock + 1).unwrap_or(1), + l1_gas_price: fork + .as_ref() + .map(|f| f.l1_gas_price) + .unwrap_or(L1_GAS_PRICE), + tx_results: Default::default(), + blocks: Default::default(), + fork_storage: ForkStorage::new(fork, dev_use_local_contracts), + show_calls, + resolve_hashes, + console_log_handler: ConsoleLogHandler::default(), + dev_use_local_contracts, + playground_contracts: playground(dev_use_local_contracts), + baseline_contracts: baseline_contracts(dev_use_local_contracts), + fee_estimate_contracts: fee_estimate_contracts(dev_use_local_contracts), + })), + } + } + + pub fn get_inner(&self) -> Arc> { + self.inner.clone() + } + + /// Applies multiple transactions - but still one per L1 batch. + 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)?; + } + + 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 = 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)))); + storage_view.modified_storage_keys().clone() + }; + + for (key, value) in keys.iter() { + inner.fork_storage.set_value(*key, *value); + } + } + + /// Runs L2 'eth call' method - that doesn't commit to a block. + 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() + .map_err(|e| format!("Failed to acquire write lock: {}", e))?; + + let mut storage_view = StorageView::new(&inner.fork_storage); + + let mut oracle_tools = OracleTools::new(&mut storage_view, HistoryEnabled); + + let bootloader_code = &inner.playground_contracts; + + let block_context = inner.create_block_context(); + let block_properties = InMemoryNodeInner::create_block_properties(bootloader_code); + + // init vm + let mut vm = init_vm_inner( + &mut oracle_tools, + BlockContextMode::NewBlock(block_context.into(), Default::default()), + &block_properties, + BLOCK_GAS_LIMIT, + bootloader_code, + execution_mode, + ); + + let tx: Transaction = l2_tx.into(); + + push_transaction_to_bootloader_memory(&mut vm, &tx, execution_mode, None); + + let vm_block_result = + vm.execute_till_block_end_with_call_tracer(BootloaderJobType::TransactionExecution); + + if let Some(revert_reason) = &vm_block_result.full_result.revert_reason { + println!("Call {} {:?}", "FAILED".red(), revert_reason.revert_reason); + } else { + println!("Call {}", "SUCCESS".green()); + } + if let VmTrace::CallTrace(call_trace) = &vm_block_result.full_result.trace { + println!("=== Console Logs: "); + for call in call_trace { + inner.console_log_handler.handle_call_recurive(call); + } + + println!("=== Call traces:"); + for call in call_trace { + formatter::print_call(call, 0, &inner.show_calls, inner.resolve_hashes); + } + } + + match vm_block_result.full_result.revert_reason { + Some(result) => Ok(result.original_data), + None => Ok(vm_block_result + .full_result + .return_data + .into_iter() + .flat_map(|val| { + let bytes: [u8; 32] = val.into(); + bytes.to_vec() + }) + .collect::>()), + } + } fn run_l2_tx_inner( &self, @@ -1255,7 +1245,13 @@ impl EthNamespaceT for InMemoryNode { req: zksync_types::transaction_request::CallRequest, _block: Option, ) -> jsonrpc_core::BoxFuture> { - let result: jsonrpc_core::Result = self.estimate_gas_impl(req); + let inner = Arc::clone(&self.inner); + let reader = match inner.read() { + Ok(r) => r, + Err(_) => return futures::future::err(into_jsrpc_error(Web3Error::InternalError)).boxed(), + }; + + let result: jsonrpc_core::Result = reader.estimate_gas_impl(req); match result { Ok(fee) => { Ok(fee.gas_limit).into_boxed_future() diff --git a/src/zks.rs b/src/zks.rs index a2a93446..ec228afd 100644 --- a/src/zks.rs +++ b/src/zks.rs @@ -1,10 +1,23 @@ +use std::sync::{Arc, RwLock}; + use bigdecimal::BigDecimal; +use futures::FutureExt; use zksync_basic_types::{MiniblockNumber, U256}; use zksync_core::api_server::web3::backend_jsonrpc::namespaces::zks::ZksNamespaceT; -use zksync_types::api::BridgeAddresses; +use zksync_types::{api::BridgeAddresses, fee::Fee}; + +use crate::{node::InMemoryNodeInner, utils::IntoBoxedFuture}; /// Mock implementation of ZksNamespace - used only in the test node. -pub struct ZkMockNamespaceImpl; +pub struct ZkMockNamespaceImpl { + node: Arc>, +} + +impl ZkMockNamespaceImpl { + pub fn new(node: Arc>) -> Self { + Self { node } + } +} macro_rules! not_implemented { () => { @@ -16,17 +29,19 @@ impl ZksNamespaceT for ZkMockNamespaceImpl { /// For now, returning a fake amount of gas. fn estimate_fee( &self, - _req: zksync_types::transaction_request::CallRequest, + req: zksync_types::transaction_request::CallRequest, ) -> jsonrpc_core::BoxFuture> { - Box::pin(async move { - // TODO: FIX THIS - Ok(zksync_types::fee::Fee { - gas_limit: U256::from(1000000000), - max_fee_per_gas: U256::from(1000000000), - max_priority_fee_per_gas: U256::from(1000000000), - gas_per_pubdata_limit: U256::from(1000000000), - }) - }) + let reader = self.node.read().unwrap(); + + let result: jsonrpc_core::Result = reader.estimate_gas_impl(req); + match result { + Ok(fee) => { + Ok(fee).into_boxed_future() + }, + Err(err) => { + return futures::future::err(err).boxed() + } + } } fn get_raw_block_transactions( @@ -175,12 +190,20 @@ impl ZksNamespaceT for ZkMockNamespaceImpl { #[cfg(test)] mod tests { + use crate::node::InMemoryNode; + use super::*; use zksync_types::transaction_request::CallRequest; #[tokio::test] async fn test_estimate_fee() { - let namespace = ZkMockNamespaceImpl; + let node = InMemoryNode::new( + Default::default(), + crate::ShowCalls::None, + false, + false, + ); + let namespace = ZkMockNamespaceImpl::new(node.get_inner()); let mock_request = CallRequest { from: Some( From 3594b9b1e6a0d13209b3b21373dc15ee3e6444e4 Mon Sep 17 00:00:00 2001 From: MexicanAce Date: Wed, 9 Aug 2023 18:46:36 +0100 Subject: [PATCH 10/22] Fix lint errors. Add lint-fix command to Makefile. --- Makefile | 5 ++ src/fork.rs | 2 +- src/node.rs | 143 ++++++++++++++++++++++++++------------------------- src/utils.rs | 5 +- src/zks.rs | 17 ++---- 5 files changed, 87 insertions(+), 85 deletions(-) diff --git a/Makefile b/Makefile index 87d01de9..e9fd149b 100644 --- a/Makefile +++ b/Makefile @@ -26,6 +26,11 @@ lint: cargo fmt --all -- --check cargo clippy -Zunstable-options -- -D warnings --allow clippy::unwrap_used +# Fix lint errors for Rust code +lint-fix: + cargo clippy --fix + cargo fmt + # Run unit tests for Rust code test: cargo test diff --git a/src/fork.rs b/src/fork.rs index 3b14e6e4..e2420b1d 100644 --- a/src/fork.rs +++ b/src/fork.rs @@ -57,7 +57,7 @@ impl Clone for ForkStorage { let inner = Arc::new(RwLock::new(self.inner.read().unwrap().clone())); Self { inner, - chain_id: self.chain_id.clone(), + chain_id: self.chain_id, } } } diff --git a/src/node.rs b/src/node.rs index e0c2b0a1..240de58d 100644 --- a/src/node.rs +++ b/src/node.rs @@ -4,30 +4,31 @@ use crate::{ deps::system_contracts::bytecode_from_slice, fork::{ForkDetails, ForkStorage}, formatter, - utils::{IntoBoxedFuture, derive_gas_estimation_overhead, adjust_l1_gas_price_for_tx}, + utils::{adjust_l1_gas_price_for_tx, derive_gas_estimation_overhead, IntoBoxedFuture}, ShowCalls, }; use colored::Colorize; use futures::FutureExt; use jsonrpc_core::BoxFuture; use std::{ + cmp::{self}, collections::HashMap, convert::TryInto, - sync::{Arc, RwLock}, cmp::{self}, + 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, derive_base_fee_and_gas_per_pubdata, DerivedBlockContext, + derive_base_fee_and_gas_per_pubdata, init_vm_inner, push_transaction_to_bootloader_memory, + BlockContext, BlockContextMode, BootloaderJobType, DerivedBlockContext, TxExecutionMode, }, - HistoryEnabled, OracleTools, HistoryDisabled, TxRevertReason, VmBlockResult, + HistoryDisabled, HistoryEnabled, OracleTools, TxRevertReason, VmBlockResult, }; 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, read_zbin_bytecode, + read_playground_block_bootloader_bytecode, read_sys_contract_bytecode, read_zbin_bytecode, + BaseSystemContracts, ContractLanguage, SystemContractCode, }; use zksync_core::api_server::web3::backend_jsonrpc::{ error::into_jsrpc_error, namespaces::eth::EthNamespaceT, @@ -35,19 +36,25 @@ use zksync_core::api_server::web3::backend_jsonrpc::{ use zksync_state::{ReadStorage, StorageView, WriteStorage}; use zksync_types::{ api::{Log, TransactionReceipt, TransactionVariant}, + fee::Fee, get_code_key, get_nonce_key, l2::L2Tx, transaction_request::{l2_tx_from_call_req, TransactionRequest}, tx::tx_execution_info::TxExecutionStatus, - utils::{storage_key_for_eth_balance, storage_key_for_standard_token_balance, decompose_full_nonce, nonces_to_full_nonce}, + utils::{ + decompose_full_nonce, nonces_to_full_nonce, storage_key_for_eth_balance, + storage_key_for_standard_token_balance, + }, vm_trace::VmTrace, - zk_evm::{block_properties::BlockProperties, zkevm_opcode_defs::system_params::MAX_PUBDATA_PER_BLOCK}, + zk_evm::{ + block_properties::BlockProperties, zkevm_opcode_defs::system_params::MAX_PUBDATA_PER_BLOCK, + }, StorageKey, StorageLogQueryType, Transaction, ACCOUNT_CODE_STORAGE_ADDRESS, - L2_ETH_TOKEN_ADDRESS, MAX_GAS_PER_PUBDATA_BYTE, MAX_L2_TX_GAS_LIMIT, fee::Fee, + L2_ETH_TOKEN_ADDRESS, MAX_GAS_PER_PUBDATA_BYTE, MAX_L2_TX_GAS_LIMIT, }; use zksync_utils::{ - bytecode::{hash_bytecode, compress_bytecode}, bytes_to_be_words, h256_to_account_address, h256_to_u256, h256_to_u64, - u256_to_h256, + bytecode::{compress_bytecode, hash_bytecode}, + bytes_to_be_words, h256_to_account_address, h256_to_u256, h256_to_u64, u256_to_h256, }; use zksync_web3_decl::{ error::Web3Error, @@ -150,10 +157,10 @@ impl InMemoryNodeInner { req: zksync_types::transaction_request::CallRequest, ) -> jsonrpc_core::Result { let mut l2_tx = match l2_tx_from_call_req(req, MAX_TX_SIZE) { - Ok(tx) => {tx} + Ok(tx) => tx, Err(e) => { let error = Web3Error::SerializationError(e); - return Err(into_jsrpc_error(error)) + return Err(into_jsrpc_error(error)); } }; @@ -163,9 +170,8 @@ impl InMemoryNodeInner { // Calculate Adjusted L1 Price let gas_price_scale_factor = 1.2; let l1_gas_price = { - let current_l1_gas_price = - ((self.l1_gas_price as f64) * gas_price_scale_factor) as u64; - + let current_l1_gas_price = ((self.l1_gas_price as f64) * gas_price_scale_factor) as u64; + // In order for execution to pass smoothly, we need to ensure that block's required gasPerPubdata will be // <= to the one in the transaction itself. adjust_l1_gas_price_for_tx( @@ -175,10 +181,8 @@ impl InMemoryNodeInner { ) }; - let (base_fee, gas_per_pubdata_byte) = derive_base_fee_and_gas_per_pubdata( - l1_gas_price, - fair_l2_gas_price, - ); + let (base_fee, gas_per_pubdata_byte) = + derive_base_fee_and_gas_per_pubdata(l1_gas_price, fair_l2_gas_price); // Check for properly formatted signature if l2_tx.common_data.signature.is_empty() { @@ -189,11 +193,13 @@ impl InMemoryNodeInner { l2_tx.common_data.fee.gas_per_pubdata_limit = MAX_GAS_PER_PUBDATA_BYTE.into(); l2_tx.common_data.fee.max_fee_per_gas = base_fee.into(); l2_tx.common_data.fee.max_priority_fee_per_gas = base_fee.into(); - + let mut storage_view = StorageView::new(&self.fork_storage); // Calculate gas_for_bytecodes_pubdata - let pubdata_for_factory_deps = l2_tx.execute.factory_deps + let pubdata_for_factory_deps = l2_tx + .execute + .factory_deps .as_deref() .unwrap_or_default() .iter() @@ -213,10 +219,14 @@ impl InMemoryNodeInner { .sum::(); if pubdata_for_factory_deps > MAX_PUBDATA_PER_BLOCK { - return Err(into_jsrpc_error(Web3Error::SubmitTransactionError("exceeds limit for published pubdata".into(), Default::default()))) + return Err(into_jsrpc_error(Web3Error::SubmitTransactionError( + "exceeds limit for published pubdata".into(), + Default::default(), + ))); } - let gas_for_bytecodes_pubdata: u32 = pubdata_for_factory_deps * (gas_per_pubdata_byte as u32); + let gas_for_bytecodes_pubdata: u32 = + pubdata_for_factory_deps * (gas_per_pubdata_byte as u32); // We are using binary search to find the minimal values of gas_limit under which the transaction succeeds let mut lower_bound = 0; @@ -225,7 +235,9 @@ impl InMemoryNodeInner { let max_attempts = 30usize; let mut number_of_iterations = 0usize; - while lower_bound + acceptable_overestimation < upper_bound && number_of_iterations < max_attempts{ + while lower_bound + acceptable_overestimation < upper_bound + && number_of_iterations < max_attempts + { let mid = (lower_bound + upper_bound) / 2; let try_gas_limit = gas_for_bytecodes_pubdata + mid; @@ -234,7 +246,8 @@ impl InMemoryNodeInner { gas_per_pubdata_byte, try_gas_limit, l1_gas_price, - base_fee); + base_fee, + ); if estimate_gas_result.is_err() { lower_bound = mid + 1; // println!("Attempt {}: Failed with {}, trying again with lower_bound: {}, and upper_bound: {}", number_of_iterations, try_gas_limit, lower_bound, upper_bound); @@ -242,10 +255,10 @@ impl InMemoryNodeInner { upper_bound = mid; // println!("Attempt {}: Succeeded with {}, trying again with lower_bound: {}, and upper_bound: {}", number_of_iterations, try_gas_limit, lower_bound, upper_bound); } - + number_of_iterations += 1; } - + // println!("COMPLETE after {} attempts and with lower_bound: {}, and upper_bound: {}", number_of_iterations, lower_bound, upper_bound); let estimated_fee_scale_factor = 1.3; let tx_body_gas_limit = cmp::min( @@ -259,32 +272,29 @@ impl InMemoryNodeInner { gas_per_pubdata_byte, suggested_gas_limit, l1_gas_price, - base_fee); - + base_fee, + ); + match estimate_gas_result { - Err(_) => Err(into_jsrpc_error( - Web3Error::SubmitTransactionError( - "Transaction is unexecutable".into(), - Default::default(), - ) - )), + Err(_) => Err(into_jsrpc_error(Web3Error::SubmitTransactionError( + "Transaction is unexecutable".into(), + Default::default(), + ))), Ok(_) => { let overhead: u32 = derive_gas_estimation_overhead( suggested_gas_limit, - gas_per_pubdata_byte as u32, - tx.encoding_len() + gas_per_pubdata_byte as u32, + tx.encoding_len(), ); let full_gas_limit = match tx_body_gas_limit.overflowing_add(gas_for_bytecodes_pubdata + overhead) { (value, false) => value, (_, true) => { - return Err(into_jsrpc_error( - Web3Error::SubmitTransactionError( - "exceeds block gas limit".into(), - Default::default() - ) - )) + return Err(into_jsrpc_error(Web3Error::SubmitTransactionError( + "exceeds block gas limit".into(), + Default::default(), + ))) } }; @@ -307,13 +317,14 @@ impl InMemoryNodeInner { tx_gas_limit: u32, l1_gas_price: u64, base_fee: u64, - ) -> Result { + ) -> Result { let execution_mode = TxExecutionMode::EstimateFee { missed_storage_invocation_limit: 1000000, }; let tx: Transaction = l2_tx.clone().into(); - let l1_gas_price = adjust_l1_gas_price_for_tx(l1_gas_price, L2_GAS_PRICE, tx.gas_per_pubdata_byte_limit()); + let l1_gas_price = + adjust_l1_gas_price_for_tx(l1_gas_price, L2_GAS_PRICE, tx.gas_per_pubdata_byte_limit()); // Set gas_limit for transaction let gas_limit_with_overhead = tx_gas_limit @@ -334,7 +345,7 @@ impl InMemoryNodeInner { 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); storage_view.set_value(nonce_key, u256_to_h256(enforced_full_nonce)); - + // We need to explicitly put enough balance into the account of the users let payer = l2_tx.payer(); let balance_key = storage_key_for_eth_balance(&payer); @@ -349,17 +360,17 @@ impl InMemoryNodeInner { block_context.l1_gas_price = l1_gas_price; let derived_block_context = DerivedBlockContext { context: self.create_block_context(), - base_fee: base_fee, + base_fee, }; - + // Use the fee_estimate bootloader code, as it sets ENSURE_RETURNED_MAGIC to 0 and BOOTLOADER_TYPE to 'playground_block' let bootloader_code = &self.fee_estimate_contracts; - let block_properties = InMemoryNodeInner::create_block_properties(&bootloader_code); + let block_properties = InMemoryNodeInner::create_block_properties(bootloader_code); // init vm let mut vm = init_vm_inner( &mut oracle_tools, - BlockContextMode::OverrideCurrent(derived_block_context.into()), + BlockContextMode::OverrideCurrent(derived_block_context), &block_properties, BLOCK_GAS_LIMIT, bootloader_code, @@ -372,16 +383,10 @@ impl InMemoryNodeInner { let vm_block_result = vm.execute_till_block_end(BootloaderJobType::TransactionExecution); - let result = match vm_block_result.full_result.revert_reason { - None => { - Ok(vm_block_result) - }, - Some(revert) => { - Err(revert.revert_reason) - }, - }; - - result + match vm_block_result.full_result.revert_reason { + None => Ok(vm_block_result), + Some(revert) => Err(revert.revert_reason), + } } } @@ -752,7 +757,7 @@ impl InMemoryNode { tx: l2_tx, batch_number: block.batch_number, miniblock_number: current_miniblock, - result: result, + result, }, ); inner.blocks.insert(block.batch_number, block); @@ -1248,17 +1253,15 @@ impl EthNamespaceT for InMemoryNode { let inner = Arc::clone(&self.inner); let reader = match inner.read() { Ok(r) => r, - Err(_) => return futures::future::err(into_jsrpc_error(Web3Error::InternalError)).boxed(), + Err(_) => { + return futures::future::err(into_jsrpc_error(Web3Error::InternalError)).boxed() + } }; let result: jsonrpc_core::Result = reader.estimate_gas_impl(req); match result { - Ok(fee) => { - Ok(fee.gas_limit).into_boxed_future() - }, - Err(err) => { - return futures::future::err(err).boxed() - } + Ok(fee) => Ok(fee.gas_limit).into_boxed_future(), + Err(err) => return futures::future::err(err).boxed(), } } diff --git a/src/utils.rs b/src/utils.rs index 86faf7e5..95d9e6ff 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,7 +1,10 @@ use std::pin::Pin; use futures::Future; -use vm::vm_with_bootloader::{BOOTLOADER_TX_ENCODING_SPACE, BLOCK_OVERHEAD_GAS, BLOCK_OVERHEAD_PUBDATA, derive_base_fee_and_gas_per_pubdata}; +use vm::vm_with_bootloader::{ + derive_base_fee_and_gas_per_pubdata, BLOCK_OVERHEAD_GAS, BLOCK_OVERHEAD_PUBDATA, + BOOTLOADER_TX_ENCODING_SPACE, +}; use zksync_basic_types::U256; use zksync_types::{zk_evm::zkevm_opcode_defs::system_params::MAX_TX_ERGS_LIMIT, MAX_TXS_IN_BLOCK}; use zksync_utils::ceil_div_u256; diff --git a/src/zks.rs b/src/zks.rs index ec228afd..9e4b8157 100644 --- a/src/zks.rs +++ b/src/zks.rs @@ -32,15 +32,11 @@ impl ZksNamespaceT for ZkMockNamespaceImpl { req: zksync_types::transaction_request::CallRequest, ) -> jsonrpc_core::BoxFuture> { let reader = self.node.read().unwrap(); - + let result: jsonrpc_core::Result = reader.estimate_gas_impl(req); match result { - Ok(fee) => { - Ok(fee).into_boxed_future() - }, - Err(err) => { - return futures::future::err(err).boxed() - } + Ok(fee) => Ok(fee).into_boxed_future(), + Err(err) => return futures::future::err(err).boxed(), } } @@ -197,12 +193,7 @@ mod tests { #[tokio::test] async fn test_estimate_fee() { - let node = InMemoryNode::new( - Default::default(), - crate::ShowCalls::None, - false, - false, - ); + let node = InMemoryNode::new(Default::default(), crate::ShowCalls::None, false, false); let namespace = ZkMockNamespaceImpl::new(node.get_inner()); let mock_request = CallRequest { From 0eda41a88e40635841e631d7a347f33269d92d1e Mon Sep 17 00:00:00 2001 From: MexicanAce Date: Wed, 9 Aug 2023 18:50:18 +0100 Subject: [PATCH 11/22] Update eth_estimateGas support in docs --- SUPPORTED_APIS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SUPPORTED_APIS.md b/SUPPORTED_APIS.md index f377a74b..36c0d243 100644 --- a/SUPPORTED_APIS.md +++ b/SUPPORTED_APIS.md @@ -43,7 +43,7 @@ | `EVM` | `evm_setTime` | NOT IMPLEMENTED | Sets the internal clock time to the given timestamp | | `EVM` | `evm_snapshot` | NOT IMPLEMENTED | Snapshot the state of the blockchain at the current block | | [`ETH`](#eth-namespace) | [`eth_chainId`](#eth_chainid) | SUPPORTED | Returns the currently configured chain id | -| [`ETH`](#eth-namespace) | [`eth_estimateGas`](#eth_estimategas) | PARTIALLY | Generates and returns an estimate of how much gas is necessary for the transaction to complete | +| [`ETH`](#eth-namespace) | [`eth_estimateGas`](#eth_estimategas) | SUPPORTED | Generates and returns an estimate of how much gas is necessary for the transaction to complete | | [`ETH`](#eth-namespace) | [`eth_gasPrice`](#eth_gasprice) | SUPPORTED | Returns the current price per gas in wei | | [`ETH`](#eth-namespace) | [`eth_getBalance`](#eth_getbalance) | SUPPORTED | Returns the balance of the account of given address | | [`ETH`](#eth-namespace) | [`eth_getBlockByNumber`](#eth_getblockbynumber) | PARTIALLY | Returns information about a block by block number | From e97b616dcb9455a4bebea67a9fc0e6d2e1cb20f9 Mon Sep 17 00:00:00 2001 From: MexicanAce Date: Wed, 9 Aug 2023 20:34:34 +0100 Subject: [PATCH 12/22] Fix test. Remove comments. Update comment docs for estimate_gas_impl. --- src/node.rs | 7 +------ src/zks.rs | 31 ++++++++++++++++--------------- 2 files changed, 17 insertions(+), 21 deletions(-) diff --git a/src/node.rs b/src/node.rs index 240de58d..00b08a5e 100644 --- a/src/node.rs +++ b/src/node.rs @@ -70,11 +70,6 @@ pub const TEST_NODE_NETWORK_ID: u16 = 260; pub const L1_GAS_PRICE: u64 = 50_000_000_000; /// L2 Gas Price (0.25 gwei) pub const L2_GAS_PRICE: u64 = 250_000_000; -// pub const MAX_TX_ERGS_LIMIT: u32 = 80_000_000; -// pub const BLOCK_OVERHEAD_GAS: u32 = 1_200_000; -// pub const BLOCK_OVERHEAD_L1_GAS: u32 = 1_000_000; -// pub const L1_GAS_PER_PUBDATA_BYTE: u32 = 17; -// pub const BLOCK_OVERHEAD_PUBDATA: U256 = BLOCK_OVERHEAD_L1_GAS / L1_GAS_PER_PUBDATA_BYTE; /// Basic information about the generated block (which is block l1 batch and miniblock). /// Currently, this test node supports exactly one transaction per block. @@ -151,7 +146,7 @@ impl InMemoryNodeInner { /// /// # Returns /// - /// A `BoxFuture` containing a `Result` with a `Fee` representing the estimated gas related data. + /// A `Result` with a `Fee` representing the estimated gas related data. pub fn estimate_gas_impl( &self, req: zksync_types::transaction_request::CallRequest, diff --git a/src/zks.rs b/src/zks.rs index 9e4b8157..2f7030dd 100644 --- a/src/zks.rs +++ b/src/zks.rs @@ -193,37 +193,38 @@ mod tests { #[tokio::test] async fn test_estimate_fee() { - let node = InMemoryNode::new(Default::default(), crate::ShowCalls::None, false, false); + let node = InMemoryNode::new(None, crate::ShowCalls::None, false, false); let namespace = ZkMockNamespaceImpl::new(node.get_inner()); let mock_request = CallRequest { from: Some( - "0x0000000000000000000000000000000000000000" + "0xa61464658afeaf65cccaafd3a512b69a83b77618" .parse() .unwrap(), ), to: Some( - "0x0000000000000000000000000000000000000001" + "0x36615cf349d7f6344891b1e7ca7c72883f5dc049" .parse() .unwrap(), ), - gas: Some(U256::from(21000)), - gas_price: Some(U256::from(20)), - max_fee_per_gas: Some(U256::from(30)), - max_priority_fee_per_gas: Some(U256::from(10)), - value: Some(U256::from(1000)), - data: Some(vec![1, 2, 3, 4].into()), - nonce: Some(U256::from(1)), - transaction_type: Some(zksync_basic_types::U64::from(1)), + gas: Some(U256::from(0)), + gas_price: Some(U256::from(0)), + max_fee_per_gas: None, + max_priority_fee_per_gas: None, + value: Some(U256::from(0)), + data: Some(vec![0, 0].into()), + nonce: Some(U256::from(0)), + transaction_type: None, access_list: None, eip712_meta: None, }; let result = namespace.estimate_fee(mock_request).await.unwrap(); + println!("result: {:?}", result); - assert_eq!(result.gas_limit, U256::from(1000000000)); - assert_eq!(result.max_fee_per_gas, U256::from(1000000000)); - assert_eq!(result.max_priority_fee_per_gas, U256::from(1000000000)); - assert_eq!(result.gas_per_pubdata_limit, U256::from(1000000000)); + assert_eq!(result.gas_limit, U256::from(904431)); + assert_eq!(result.max_fee_per_gas, U256::from(250000000)); + assert_eq!(result.max_priority_fee_per_gas, U256::from(0)); + assert_eq!(result.gas_per_pubdata_limit, U256::from(4080)); } } From cc7872fbe470b81493ca633e8761538c3e77d5c2 Mon Sep 17 00:00:00 2001 From: Nicolas Villanueva Date: Thu, 10 Aug 2023 07:54:31 +0100 Subject: [PATCH 13/22] Update src/node.rs Co-authored-by: Dustin Brickwood --- src/node.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/node.rs b/src/node.rs index 00b08a5e..4f8c03e9 100644 --- a/src/node.rs +++ b/src/node.rs @@ -446,7 +446,15 @@ pub fn playground(use_local_contracts: bool) -> BaseSystemContracts { bsc_load_with_bootloader(bootloader_bytecode, use_local_contracts) } -/// BaseSystemContracts with fee_estimate bootloader - used for handling 'eth_estimateGas'. +/// Returns the system contracts for fee estimation. +/// +/// # Arguments +/// +/// * `use_local_contracts` - A boolean indicating whether to use local contracts or not. +/// +/// # Returns +/// +/// A `BaseSystemContracts` struct containing the system contracts used for handling 'eth_estimateGas'. pub fn fee_estimate_contracts(use_local_contracts: bool) -> BaseSystemContracts { let bootloader_bytecode = if use_local_contracts { read_zbin_bytecode("etc/system-contracts/bootloader/build/artifacts/fee_estimate.yul/fee_estimate.yul.zbin") From 9ec6b78d426dd897487283bddd5f005436642c1e Mon Sep 17 00:00:00 2001 From: Nicolas Villanueva Date: Thu, 10 Aug 2023 07:55:26 +0100 Subject: [PATCH 14/22] Update src/utils.rs Co-authored-by: Dustin Brickwood --- src/utils.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/utils.rs b/src/utils.rs index 95d9e6ff..b10535c8 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -21,7 +21,17 @@ where U: Send + 'static, { } - +/// Derives the gas estimation overhead based on the given gas limit, gas price per pubdata, and encoded length. +/// +/// # Arguments +/// +/// * `gas_limit` - A `u32` representing the gas limit. +/// * `gas_price_per_pubdata` - A `u32` representing the gas price per pubdata. +/// * `encoded_len` - A `usize` representing the encoded length. +/// +/// # Returns +/// +/// A `u32` representing the derived gas estimation overhead. pub fn derive_gas_estimation_overhead( gas_limit: u32, gas_price_per_pubdata: u32, From a96b3dd53e0c92533a026433a8aa1e400c455406 Mon Sep 17 00:00:00 2001 From: Nicolas Villanueva Date: Thu, 10 Aug 2023 07:56:19 +0100 Subject: [PATCH 15/22] Update src/zks.rs Co-authored-by: Dustin Brickwood --- src/zks.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/zks.rs b/src/zks.rs index 2f7030dd..793656fe 100644 --- a/src/zks.rs +++ b/src/zks.rs @@ -14,6 +14,7 @@ pub struct ZkMockNamespaceImpl { } impl ZkMockNamespaceImpl { + /// Creates a new `Zks` instance with the given `node`. pub fn new(node: Arc>) -> Self { Self { node } } From 9b56216ef20a2e18a4d900f14fb88d47c645f28d Mon Sep 17 00:00:00 2001 From: Nicolas Villanueva Date: Thu, 10 Aug 2023 07:56:47 +0100 Subject: [PATCH 16/22] Update src/utils.rs Co-authored-by: Dustin Brickwood --- src/utils.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/utils.rs b/src/utils.rs index b10535c8..aa27b7ce 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -74,7 +74,19 @@ pub fn derive_gas_estimation_overhead( pub fn block_overhead_gas(gas_per_pubdata_byte: u32) -> u32 { BLOCK_OVERHEAD_GAS + BLOCK_OVERHEAD_PUBDATA * gas_per_pubdata_byte } - +/// Adjusts the L1 gas price for a transaction based on the current pubdata price and the fair L2 gas price. +/// If the current pubdata price is small enough, returns the original L1 gas price. +/// Otherwise, calculates a new L1 gas price based on the fair L2 gas price and the transaction gas per pubdata limit. +/// +/// # Arguments +/// +/// * `l1_gas_price` - The original L1 gas price. +/// * `fair_l2_gas_price` - The fair L2 gas price. +/// * `tx_gas_per_pubdata_limit` - The transaction gas per pubdata limit. +/// +/// # Returns +/// +/// The adjusted L1 gas price. pub fn adjust_l1_gas_price_for_tx( l1_gas_price: u64, fair_l2_gas_price: u64, From f5cd4b54c975a58a620fa12735f36fa9d236b39e Mon Sep 17 00:00:00 2001 From: Nicolas Villanueva Date: Thu, 10 Aug 2023 07:58:13 +0100 Subject: [PATCH 17/22] Update src/utils.rs Co-authored-by: Dustin Brickwood --- src/utils.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/utils.rs b/src/utils.rs index aa27b7ce..a5fdcc90 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -70,7 +70,15 @@ pub fn derive_gas_estimation_overhead( .max() .unwrap() } - +/// Calculates the total gas cost of the block overhead, including the gas cost of the public data. +/// +/// # Arguments +/// +/// * `gas_per_pubdata_byte` - The gas cost per byte of public data. +/// +/// # Returns +/// +/// The total gas cost of the block overhead, including the gas cost of the public data. pub fn block_overhead_gas(gas_per_pubdata_byte: u32) -> u32 { BLOCK_OVERHEAD_GAS + BLOCK_OVERHEAD_PUBDATA * gas_per_pubdata_byte } From ed30ec848a27f3a3f02177d4f80eb86c21f1e036 Mon Sep 17 00:00:00 2001 From: MexicanAce Date: Thu, 10 Aug 2023 11:38:10 +0100 Subject: [PATCH 18/22] Move const variables to top of file. Remove unnecessary println statements. Remove max number_of_iterations for binary search in gas estimation. Remove unnecessary clone within gas estimation. Remove unwrap(). Add more formatted comments. --- src/node.rs | 48 ++++++++++++++++++++++-------------------------- src/utils.rs | 5 ++++- src/zks.rs | 21 +++++++++++++++++---- 3 files changed, 43 insertions(+), 31 deletions(-) diff --git a/src/node.rs b/src/node.rs index 4f8c03e9..d5d6835e 100644 --- a/src/node.rs +++ b/src/node.rs @@ -61,15 +61,24 @@ use zksync_web3_decl::{ types::{Filter, FilterChanges}, }; -pub const MAX_TX_SIZE: usize = 1000000; +/// 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). -pub const NON_FORK_FIRST_BLOCK_TIMESTAMP: u64 = 1000; +pub const NON_FORK_FIRST_BLOCK_TIMESTAMP: u64 = 1_000; /// Network ID we use for the test node. pub const TEST_NODE_NETWORK_ID: u16 = 260; -/// L1 Gas Price +/// L1 Gas Price. pub const L1_GAS_PRICE: u64 = 50_000_000_000; -/// L2 Gas Price (0.25 gwei) +/// L2 Gas Price (0.25 gwei). pub const L2_GAS_PRICE: u64 = 250_000_000; +/// L1 Gas Price Scale Factor for gas estimation. +pub const ESTIMATE_GAS_L1_GAS_PRICE_SCALE_FACTOR: f64 = 1.2; +/// The max possible number of gas that `eth_estimateGas` is allowed to overestimate. +pub const ESTIMATE_GAS_PUBLISH_BYTE_OVERHEAD: u32 = 100; +/// Acceptable gas overestimation limit. +pub const ESTIMATE_GAS_ACCEPTABLE_OVERESTIMATION: u32 = 1_000; +/// The factor by which to scale the gasLimit. +pub const ESTIMATE_GAS_SCALE_FACTOR: f32 = 1.3; /// Basic information about the generated block (which is block l1 batch and miniblock). /// Currently, this test node supports exactly one transaction per block. @@ -163,9 +172,8 @@ impl InMemoryNodeInner { let fair_l2_gas_price = L2_GAS_PRICE; // Calculate Adjusted L1 Price - let gas_price_scale_factor = 1.2; let l1_gas_price = { - let current_l1_gas_price = ((self.l1_gas_price as f64) * gas_price_scale_factor) as u64; + let current_l1_gas_price = ((self.l1_gas_price as f64) * ESTIMATE_GAS_L1_GAS_PRICE_SCALE_FACTOR) as u64; // In order for execution to pass smoothly, we need to ensure that block's required gasPerPubdata will be // <= to the one in the transaction itself. @@ -208,8 +216,7 @@ impl InMemoryNodeInner { } else { bytecode.len() }; - let publish_byte_overhead = 100; - length as u32 + publish_byte_overhead + length as u32 + ESTIMATE_GAS_PUBLISH_BYTE_OVERHEAD }) .sum::(); @@ -226,12 +233,8 @@ impl InMemoryNodeInner { // We are using binary search to find the minimal values of gas_limit under which the transaction succeeds let mut lower_bound = 0; let mut upper_bound = MAX_L2_TX_GAS_LIMIT as u32; - let acceptable_overestimation = 1_000; - let max_attempts = 30usize; - let mut number_of_iterations = 0usize; - while lower_bound + acceptable_overestimation < upper_bound - && number_of_iterations < max_attempts + while lower_bound + ESTIMATE_GAS_ACCEPTABLE_OVERESTIMATION < upper_bound { let mid = (lower_bound + upper_bound) / 2; let try_gas_limit = gas_for_bytecodes_pubdata + mid; @@ -245,20 +248,14 @@ impl InMemoryNodeInner { ); if estimate_gas_result.is_err() { lower_bound = mid + 1; - // println!("Attempt {}: Failed with {}, trying again with lower_bound: {}, and upper_bound: {}", number_of_iterations, try_gas_limit, lower_bound, upper_bound); } else { upper_bound = mid; - // println!("Attempt {}: Succeeded with {}, trying again with lower_bound: {}, and upper_bound: {}", number_of_iterations, try_gas_limit, lower_bound, upper_bound); } - - number_of_iterations += 1; } - // println!("COMPLETE after {} attempts and with lower_bound: {}, and upper_bound: {}", number_of_iterations, lower_bound, upper_bound); - let estimated_fee_scale_factor = 1.3; let tx_body_gas_limit = cmp::min( MAX_L2_TX_GAS_LIMIT as u32, - (upper_bound as f32 * estimated_fee_scale_factor) as u32, + (upper_bound as f32 * ESTIMATE_GAS_SCALE_FACTOR) as u32, ); let suggested_gas_limit = tx_body_gas_limit + gas_for_bytecodes_pubdata; @@ -313,10 +310,6 @@ impl InMemoryNodeInner { l1_gas_price: u64, base_fee: u64, ) -> Result { - let execution_mode = TxExecutionMode::EstimateFee { - missed_storage_invocation_limit: 1000000, - }; - let tx: Transaction = l2_tx.clone().into(); let l1_gas_price = adjust_l1_gas_price_for_tx(l1_gas_price, L2_GAS_PRICE, tx.gas_per_pubdata_byte_limit()); @@ -330,8 +323,7 @@ impl InMemoryNodeInner { ); l2_tx.common_data.fee.gas_limit = gas_limit_with_overhead.into(); - let fork_storage_copy = self.fork_storage.clone(); - let mut storage_view = StorageView::new(&fork_storage_copy); + let mut storage_view = StorageView::new(&self.fork_storage); // The nonce needs to be updated let nonce = l2_tx.nonce(); @@ -362,6 +354,10 @@ impl InMemoryNodeInner { let bootloader_code = &self.fee_estimate_contracts; let block_properties = InMemoryNodeInner::create_block_properties(bootloader_code); + let execution_mode = TxExecutionMode::EstimateFee { + missed_storage_invocation_limit: 1000000, + }; + // init vm let mut vm = init_vm_inner( &mut oracle_tools, diff --git a/src/utils.rs b/src/utils.rs index a5fdcc90..242558d7 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -21,6 +21,7 @@ where U: Send + 'static, { } + /// Derives the gas estimation overhead based on the given gas limit, gas price per pubdata, and encoded length. /// /// # Arguments @@ -68,8 +69,9 @@ pub fn derive_gas_estimation_overhead( ] .into_iter() .max() - .unwrap() + .unwrap_or(0) } + /// Calculates the total gas cost of the block overhead, including the gas cost of the public data. /// /// # Arguments @@ -82,6 +84,7 @@ pub fn derive_gas_estimation_overhead( pub fn block_overhead_gas(gas_per_pubdata_byte: u32) -> u32 { BLOCK_OVERHEAD_GAS + BLOCK_OVERHEAD_PUBDATA * gas_per_pubdata_byte } + /// Adjusts the L1 gas price for a transaction based on the current pubdata price and the fair L2 gas price. /// If the current pubdata price is small enough, returns the original L1 gas price. /// Otherwise, calculates a new L1 gas price based on the fair L2 gas price and the transaction gas per pubdata limit. diff --git a/src/zks.rs b/src/zks.rs index 793656fe..be6899f8 100644 --- a/src/zks.rs +++ b/src/zks.rs @@ -3,8 +3,9 @@ use std::sync::{Arc, RwLock}; use bigdecimal::BigDecimal; use futures::FutureExt; use zksync_basic_types::{MiniblockNumber, U256}; -use zksync_core::api_server::web3::backend_jsonrpc::namespaces::zks::ZksNamespaceT; +use zksync_core::api_server::web3::backend_jsonrpc::{namespaces::zks::ZksNamespaceT, error::into_jsrpc_error}; use zksync_types::{api::BridgeAddresses, fee::Fee}; +use zksync_web3_decl::error::Web3Error; use crate::{node::InMemoryNodeInner, utils::IntoBoxedFuture}; @@ -26,13 +27,25 @@ macro_rules! not_implemented { }; } impl ZksNamespaceT for ZkMockNamespaceImpl { - /// We have to support this method, as zksync foundry depends on it. - /// For now, returning a fake amount of gas. + /// Estimates the gas fee data required for a given call request. + /// + /// # Arguments + /// + /// * `req` - A `CallRequest` struct representing the call request to estimate gas for. + /// + /// # Returns + /// + /// A `BoxFuture` containing a `Result` with a `Fee` representing the estimated gas data required. fn estimate_fee( &self, req: zksync_types::transaction_request::CallRequest, ) -> jsonrpc_core::BoxFuture> { - let reader = self.node.read().unwrap(); + let reader = match self.node.read() { + Ok(r) => r, + Err(_) => { + return futures::future::err(into_jsrpc_error(Web3Error::InternalError)).boxed() + } + }; let result: jsonrpc_core::Result = reader.estimate_gas_impl(req); match result { From c3d9f37a77a12f2565266531fbbb5458b20386a6 Mon Sep 17 00:00:00 2001 From: MexicanAce Date: Thu, 10 Aug 2023 11:39:01 +0100 Subject: [PATCH 19/22] Fix linting --- src/node.rs | 6 +++--- src/utils.rs | 16 ++++++++-------- src/zks.rs | 6 ++++-- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/node.rs b/src/node.rs index d5d6835e..d9b3798d 100644 --- a/src/node.rs +++ b/src/node.rs @@ -173,7 +173,8 @@ impl InMemoryNodeInner { // Calculate Adjusted L1 Price let l1_gas_price = { - let current_l1_gas_price = ((self.l1_gas_price as f64) * ESTIMATE_GAS_L1_GAS_PRICE_SCALE_FACTOR) as u64; + let current_l1_gas_price = + ((self.l1_gas_price as f64) * ESTIMATE_GAS_L1_GAS_PRICE_SCALE_FACTOR) as u64; // In order for execution to pass smoothly, we need to ensure that block's required gasPerPubdata will be // <= to the one in the transaction itself. @@ -234,8 +235,7 @@ impl InMemoryNodeInner { let mut lower_bound = 0; let mut upper_bound = MAX_L2_TX_GAS_LIMIT as u32; - while lower_bound + ESTIMATE_GAS_ACCEPTABLE_OVERESTIMATION < upper_bound - { + while lower_bound + ESTIMATE_GAS_ACCEPTABLE_OVERESTIMATION < upper_bound { let mid = (lower_bound + upper_bound) / 2; let try_gas_limit = gas_for_bytecodes_pubdata + mid; diff --git a/src/utils.rs b/src/utils.rs index 242558d7..e7add378 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -23,15 +23,15 @@ where } /// Derives the gas estimation overhead based on the given gas limit, gas price per pubdata, and encoded length. -/// +/// /// # Arguments -/// +/// /// * `gas_limit` - A `u32` representing the gas limit. /// * `gas_price_per_pubdata` - A `u32` representing the gas price per pubdata. /// * `encoded_len` - A `usize` representing the encoded length. -/// +/// /// # Returns -/// +/// /// A `u32` representing the derived gas estimation overhead. pub fn derive_gas_estimation_overhead( gas_limit: u32, @@ -73,13 +73,13 @@ pub fn derive_gas_estimation_overhead( } /// Calculates the total gas cost of the block overhead, including the gas cost of the public data. -/// +/// /// # Arguments -/// +/// /// * `gas_per_pubdata_byte` - The gas cost per byte of public data. -/// +/// /// # Returns -/// +/// /// The total gas cost of the block overhead, including the gas cost of the public data. pub fn block_overhead_gas(gas_per_pubdata_byte: u32) -> u32 { BLOCK_OVERHEAD_GAS + BLOCK_OVERHEAD_PUBDATA * gas_per_pubdata_byte diff --git a/src/zks.rs b/src/zks.rs index be6899f8..cb1cb212 100644 --- a/src/zks.rs +++ b/src/zks.rs @@ -3,7 +3,9 @@ use std::sync::{Arc, RwLock}; use bigdecimal::BigDecimal; use futures::FutureExt; use zksync_basic_types::{MiniblockNumber, U256}; -use zksync_core::api_server::web3::backend_jsonrpc::{namespaces::zks::ZksNamespaceT, error::into_jsrpc_error}; +use zksync_core::api_server::web3::backend_jsonrpc::{ + error::into_jsrpc_error, namespaces::zks::ZksNamespaceT, +}; use zksync_types::{api::BridgeAddresses, fee::Fee}; use zksync_web3_decl::error::Web3Error; @@ -15,7 +17,7 @@ pub struct ZkMockNamespaceImpl { } impl ZkMockNamespaceImpl { - /// Creates a new `Zks` instance with the given `node`. + /// Creates a new `Zks` instance with the given `node`. pub fn new(node: Arc>) -> Self { Self { node } } From 5b7e262c549429943b48ccaa333e46d67373b598 Mon Sep 17 00:00:00 2001 From: MexicanAce Date: Thu, 10 Aug 2023 17:26:40 +0100 Subject: [PATCH 20/22] Remove unused Clone logic for ForkStorage. Add more comments for variables. Remove unused println statement. Update estimate_gas_step to static method. Add more descriptive logging when gas estimation fails. --- src/fork.rs | 21 ------------------- src/node.rs | 58 +++++++++++++++++++++++++++++++++++----------------- src/utils.rs | 8 +++++++- src/zks.rs | 1 - 4 files changed, 46 insertions(+), 42 deletions(-) diff --git a/src/fork.rs b/src/fork.rs index e2420b1d..12c81178 100644 --- a/src/fork.rs +++ b/src/fork.rs @@ -52,16 +52,6 @@ pub struct ForkStorage { pub chain_id: L2ChainId, } -impl Clone for ForkStorage { - fn clone(&self) -> Self { - let inner = Arc::new(RwLock::new(self.inner.read().unwrap().clone())); - Self { - inner, - chain_id: self.chain_id, - } - } -} - #[derive(Debug)] pub struct ForkStorageInner { // Underlying local storage @@ -75,17 +65,6 @@ pub struct ForkStorageInner { pub fork: Option, } -impl Clone for ForkStorageInner { - fn clone(&self) -> Self { - Self { - raw_storage: self.raw_storage.clone(), - value_read_cache: self.value_read_cache.clone(), - factory_dep_cache: self.factory_dep_cache.clone(), - fork: self.fork.clone(), - } - } -} - impl ForkStorage { pub fn new(fork: Option, dev_use_local_contracts: bool) -> Self { let chain_id = fork diff --git a/src/node.rs b/src/node.rs index d9b3798d..515267c7 100644 --- a/src/node.rs +++ b/src/node.rs @@ -188,7 +188,7 @@ impl InMemoryNodeInner { let (base_fee, gas_per_pubdata_byte) = derive_base_fee_and_gas_per_pubdata(l1_gas_price, fair_l2_gas_price); - // Check for properly formatted signature + // Properly format signature if l2_tx.common_data.signature.is_empty() { l2_tx.common_data.signature = vec![0u8; 65]; l2_tx.common_data.signature[64] = 27; @@ -230,6 +230,9 @@ impl InMemoryNodeInner { let gas_for_bytecodes_pubdata: u32 = pubdata_for_factory_deps * (gas_per_pubdata_byte as u32); + + let block_context = self.create_block_context(); + let bootloader_code = &self.fee_estimate_contracts; // We are using binary search to find the minimal values of gas_limit under which the transaction succeeds let mut lower_bound = 0; @@ -239,13 +242,17 @@ impl InMemoryNodeInner { let mid = (lower_bound + upper_bound) / 2; let try_gas_limit = gas_for_bytecodes_pubdata + mid; - let estimate_gas_result = self.estimate_gas_step( + let estimate_gas_result = InMemoryNodeInner::estimate_gas_step( l2_tx.clone(), gas_per_pubdata_byte, try_gas_limit, l1_gas_price, base_fee, + block_context, + &self.fork_storage, + bootloader_code ); + if estimate_gas_result.is_err() { lower_bound = mid + 1; } else { @@ -259,30 +266,43 @@ impl InMemoryNodeInner { ); let suggested_gas_limit = tx_body_gas_limit + gas_for_bytecodes_pubdata; - let estimate_gas_result = self.estimate_gas_step( + let estimate_gas_result = InMemoryNodeInner::estimate_gas_step( l2_tx.clone(), gas_per_pubdata_byte, suggested_gas_limit, l1_gas_price, base_fee, + block_context, + &self.fork_storage, + bootloader_code, + ); + + let overhead: u32 = derive_gas_estimation_overhead( + suggested_gas_limit, + gas_per_pubdata_byte as u32, + tx.encoding_len(), ); match estimate_gas_result { - Err(_) => Err(into_jsrpc_error(Web3Error::SubmitTransactionError( - "Transaction is unexecutable".into(), - Default::default(), - ))), + Err(_) => { + println!("{}", format!("Unable to estimate gas for the request with our suggested gas limit of {}. The transaction is most likely unexecutable. Breakdown of estimation:", suggested_gas_limit + overhead).to_string().red()); + println!("{}", format!("\tEstimated transaction body gas cost: {}", tx_body_gas_limit).to_string().red()); + println!("{}", format!("\tGas for pubdata: {}", gas_for_bytecodes_pubdata).to_string().red()); + println!("{}", format!("\tOverhead: {}", overhead).to_string().red()); + Err(into_jsrpc_error(Web3Error::SubmitTransactionError( + "Transaction is unexecutable".into(), + Default::default(), + ))) + }, Ok(_) => { - let overhead: u32 = derive_gas_estimation_overhead( - suggested_gas_limit, - gas_per_pubdata_byte as u32, - tx.encoding_len(), - ); - let full_gas_limit = match tx_body_gas_limit.overflowing_add(gas_for_bytecodes_pubdata + overhead) { (value, false) => value, (_, true) => { + println!("{}", "Overflow when calculating gas estimation. We've exceeded the block gas limit by summing the following values:".red()); + println!("{}", format!("\tEstimated transaction body gas cost: {}", tx_body_gas_limit).to_string().red()); + println!("{}", format!("\tGas for pubdata: {}", gas_for_bytecodes_pubdata).to_string().red()); + println!("{}", format!("\tOverhead: {}", overhead).to_string().red()); return Err(into_jsrpc_error(Web3Error::SubmitTransactionError( "exceeds block gas limit".into(), Default::default(), @@ -303,12 +323,14 @@ impl InMemoryNodeInner { /// Runs fee estimation against a sandbox vm with the given gas_limit. fn estimate_gas_step( - &self, mut l2_tx: L2Tx, gas_per_pubdata_byte: u64, tx_gas_limit: u32, l1_gas_price: u64, base_fee: u64, + mut block_context: BlockContext, + fork_storage: &ForkStorage, + bootloader_code: &BaseSystemContracts, ) -> Result { let tx: Transaction = l2_tx.clone().into(); let l1_gas_price = @@ -323,7 +345,7 @@ impl InMemoryNodeInner { ); l2_tx.common_data.fee.gas_limit = gas_limit_with_overhead.into(); - let mut storage_view = StorageView::new(&self.fork_storage); + let mut storage_view = StorageView::new(fork_storage); // The nonce needs to be updated let nonce = l2_tx.nonce(); @@ -343,15 +365,12 @@ impl InMemoryNodeInner { let mut oracle_tools = OracleTools::new(&mut storage_view, HistoryDisabled); - let mut block_context = self.create_block_context(); block_context.l1_gas_price = l1_gas_price; let derived_block_context = DerivedBlockContext { - context: self.create_block_context(), + context: block_context, base_fee, }; - // Use the fee_estimate bootloader code, as it sets ENSURE_RETURNED_MAGIC to 0 and BOOTLOADER_TYPE to 'playground_block' - let bootloader_code = &self.fee_estimate_contracts; let block_properties = InMemoryNodeInner::create_block_properties(bootloader_code); let execution_mode = TxExecutionMode::EstimateFee { @@ -451,6 +470,7 @@ pub fn playground(use_local_contracts: bool) -> BaseSystemContracts { /// # Returns /// /// A `BaseSystemContracts` struct containing the system contracts used for handling 'eth_estimateGas'. +/// It sets ENSURE_RETURNED_MAGIC to 0 and BOOTLOADER_TYPE to 'playground_block' pub fn fee_estimate_contracts(use_local_contracts: bool) -> BaseSystemContracts { let bootloader_bytecode = if use_local_contracts { read_zbin_bytecode("etc/system-contracts/bootloader/build/artifacts/fee_estimate.yul/fee_estimate.yul.zbin") diff --git a/src/utils.rs b/src/utils.rs index e7add378..824bec43 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -62,8 +62,14 @@ pub fn derive_gas_estimation_overhead( // The overhead for occupying a single tx slot let tx_slot_overhead = ceil_div_u256(max_block_overhead, MAX_TXS_IN_BLOCK.into()); + // For L2 transactions we allow a certain default discount with regard to the number of ergs. + // Multiinstance circuits can in theory be spawned infinite times, while projected future limitations + // on gas per pubdata allow for roughly 800k gas per L1 batch, so the rough trust "discount" on the proof's part + // to be paid by the users is 0.1. + const ERGS_LIMIT_OVERHEAD_COEFFICIENT: f64 = 0.1; + vec![ - (0.1 * overhead_for_single_instance_circuits.as_u32() as f64).floor() as u32, + (ERGS_LIMIT_OVERHEAD_COEFFICIENT * overhead_for_single_instance_circuits.as_u32() as f64).floor() as u32, overhead_for_length.as_u32(), tx_slot_overhead.as_u32(), ] diff --git a/src/zks.rs b/src/zks.rs index cb1cb212..04145187 100644 --- a/src/zks.rs +++ b/src/zks.rs @@ -236,7 +236,6 @@ mod tests { }; let result = namespace.estimate_fee(mock_request).await.unwrap(); - println!("result: {:?}", result); assert_eq!(result.gas_limit, U256::from(904431)); assert_eq!(result.max_fee_per_gas, U256::from(250000000)); From 6d5ae3bffdd7d25c21d72488eb144f85f5debcd3 Mon Sep 17 00:00:00 2001 From: MexicanAce Date: Thu, 10 Aug 2023 17:39:32 +0100 Subject: [PATCH 21/22] Fix linting. Add too many arguments exception for estimate_gas_step now that it is static. --- src/node.rs | 66 +++++++++++++++++++++++++++++++++++++--------------- src/utils.rs | 3 ++- 2 files changed, 49 insertions(+), 20 deletions(-) diff --git a/src/node.rs b/src/node.rs index 515267c7..e55f9d9d 100644 --- a/src/node.rs +++ b/src/node.rs @@ -230,7 +230,7 @@ impl InMemoryNodeInner { let gas_for_bytecodes_pubdata: u32 = pubdata_for_factory_deps * (gas_per_pubdata_byte as u32); - + let block_context = self.create_block_context(); let bootloader_code = &self.fee_estimate_contracts; @@ -250,7 +250,7 @@ impl InMemoryNodeInner { base_fee, block_context, &self.fork_storage, - bootloader_code + bootloader_code, ); if estimate_gas_result.is_err() { @@ -286,29 +286,56 @@ impl InMemoryNodeInner { match estimate_gas_result { Err(_) => { println!("{}", format!("Unable to estimate gas for the request with our suggested gas limit of {}. The transaction is most likely unexecutable. Breakdown of estimation:", suggested_gas_limit + overhead).to_string().red()); - println!("{}", format!("\tEstimated transaction body gas cost: {}", tx_body_gas_limit).to_string().red()); - println!("{}", format!("\tGas for pubdata: {}", gas_for_bytecodes_pubdata).to_string().red()); + println!( + "{}", + format!( + "\tEstimated transaction body gas cost: {}", + tx_body_gas_limit + ) + .to_string() + .red() + ); + println!( + "{}", + format!("\tGas for pubdata: {}", gas_for_bytecodes_pubdata) + .to_string() + .red() + ); println!("{}", format!("\tOverhead: {}", overhead).to_string().red()); Err(into_jsrpc_error(Web3Error::SubmitTransactionError( "Transaction is unexecutable".into(), Default::default(), ))) - }, + } Ok(_) => { - let full_gas_limit = - match tx_body_gas_limit.overflowing_add(gas_for_bytecodes_pubdata + overhead) { - (value, false) => value, - (_, true) => { - println!("{}", "Overflow when calculating gas estimation. We've exceeded the block gas limit by summing the following values:".red()); - println!("{}", format!("\tEstimated transaction body gas cost: {}", tx_body_gas_limit).to_string().red()); - println!("{}", format!("\tGas for pubdata: {}", gas_for_bytecodes_pubdata).to_string().red()); - println!("{}", format!("\tOverhead: {}", overhead).to_string().red()); - return Err(into_jsrpc_error(Web3Error::SubmitTransactionError( - "exceeds block gas limit".into(), - Default::default(), - ))) - } - }; + let full_gas_limit = match tx_body_gas_limit + .overflowing_add(gas_for_bytecodes_pubdata + overhead) + { + (value, false) => value, + (_, true) => { + println!("{}", "Overflow when calculating gas estimation. We've exceeded the block gas limit by summing the following values:".red()); + println!( + "{}", + format!( + "\tEstimated transaction body gas cost: {}", + tx_body_gas_limit + ) + .to_string() + .red() + ); + println!( + "{}", + format!("\tGas for pubdata: {}", gas_for_bytecodes_pubdata) + .to_string() + .red() + ); + println!("{}", format!("\tOverhead: {}", overhead).to_string().red()); + return Err(into_jsrpc_error(Web3Error::SubmitTransactionError( + "exceeds block gas limit".into(), + Default::default(), + ))); + } + }; let fee = Fee { max_fee_per_gas: base_fee.into(), @@ -322,6 +349,7 @@ impl InMemoryNodeInner { } /// Runs fee estimation against a sandbox vm with the given gas_limit. + #[allow(clippy::too_many_arguments)] fn estimate_gas_step( mut l2_tx: L2Tx, gas_per_pubdata_byte: u64, diff --git a/src/utils.rs b/src/utils.rs index 824bec43..bc72b4fb 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -69,7 +69,8 @@ pub fn derive_gas_estimation_overhead( const ERGS_LIMIT_OVERHEAD_COEFFICIENT: f64 = 0.1; vec![ - (ERGS_LIMIT_OVERHEAD_COEFFICIENT * overhead_for_single_instance_circuits.as_u32() as f64).floor() as u32, + (ERGS_LIMIT_OVERHEAD_COEFFICIENT * overhead_for_single_instance_circuits.as_u32() as f64) + .floor() as u32, overhead_for_length.as_u32(), tx_slot_overhead.as_u32(), ] From 791e9d513e1c5326874e54145d09e81de856a94c Mon Sep 17 00:00:00 2001 From: MexicanAce Date: Thu, 10 Aug 2023 18:49:28 +0100 Subject: [PATCH 22/22] Fix unit test --- src/zks.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zks.rs b/src/zks.rs index 04145187..75e8714a 100644 --- a/src/zks.rs +++ b/src/zks.rs @@ -237,7 +237,7 @@ mod tests { let result = namespace.estimate_fee(mock_request).await.unwrap(); - assert_eq!(result.gas_limit, U256::from(904431)); + assert_eq!(result.gas_limit, U256::from(1083285)); assert_eq!(result.max_fee_per_gas, U256::from(250000000)); assert_eq!(result.max_priority_fee_per_gas, U256::from(0)); assert_eq!(result.gas_per_pubdata_limit, U256::from(4080));