From d72b6fca929009f6b6c783f5e069dd75b6fa1122 Mon Sep 17 00:00:00 2001 From: Marcos Nicolau Date: Fri, 23 Aug 2024 11:26:53 -0300 Subject: [PATCH] Implement refund tracer --- core/lib/multivm/src/versions/era_vm/hook.rs | 2 +- core/lib/multivm/src/versions/era_vm/mod.rs | 1 - .../multivm/src/versions/era_vm/refunds.rs | 66 -------- .../src/versions/era_vm/tracers/manager.rs | 34 ++++- .../versions/era_vm/tracers/refunds_tracer.rs | 143 +++++++++++++++++- core/lib/multivm/src/versions/era_vm/vm.rs | 126 ++++----------- 6 files changed, 201 insertions(+), 171 deletions(-) delete mode 100644 core/lib/multivm/src/versions/era_vm/refunds.rs diff --git a/core/lib/multivm/src/versions/era_vm/hook.rs b/core/lib/multivm/src/versions/era_vm/hook.rs index 39cf3267df6..348ae11c532 100644 --- a/core/lib/multivm/src/versions/era_vm/hook.rs +++ b/core/lib/multivm/src/versions/era_vm/hook.rs @@ -1,4 +1,4 @@ -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum Hook { AccountValidationEntered, diff --git a/core/lib/multivm/src/versions/era_vm/mod.rs b/core/lib/multivm/src/versions/era_vm/mod.rs index 11925ae46f4..edb8ee88eec 100644 --- a/core/lib/multivm/src/versions/era_vm/mod.rs +++ b/core/lib/multivm/src/versions/era_vm/mod.rs @@ -4,7 +4,6 @@ mod event; mod hook; mod initial_bootloader_memory; mod logs; -mod refunds; mod snapshot; #[cfg(test)] mod tests; diff --git a/core/lib/multivm/src/versions/era_vm/refunds.rs b/core/lib/multivm/src/versions/era_vm/refunds.rs deleted file mode 100644 index 524a6ca4c3b..00000000000 --- a/core/lib/multivm/src/versions/era_vm/refunds.rs +++ /dev/null @@ -1,66 +0,0 @@ -use zksync_types::{H256, U256}; -use zksync_utils::ceil_div_u256; - -use crate::vm_latest::{utils::fee::get_batch_base_fee, L1BatchEnv}; - -pub(crate) fn compute_refund( - l1_batch: &L1BatchEnv, - bootloader_refund: u64, - gas_spent_on_pubdata: u64, - tx_gas_limit: u64, - current_ergs_per_pubdata_byte: u32, - pubdata_published: u32, - tx_hash: H256, -) -> u64 { - let total_gas_spent = tx_gas_limit - bootloader_refund; - - let gas_spent_on_computation = total_gas_spent - .checked_sub(gas_spent_on_pubdata) - .unwrap_or_else(|| { - tracing::error!( - "Gas spent on pubdata is greater than total gas spent. On pubdata: {}, total: {}", - gas_spent_on_pubdata, - total_gas_spent - ); - 0 - }); - - // For now, bootloader charges only for base fee. - let effective_gas_price = get_batch_base_fee(l1_batch); - - let bootloader_eth_price_per_pubdata_byte = - U256::from(effective_gas_price) * U256::from(current_ergs_per_pubdata_byte); - - let fair_eth_price_per_pubdata_byte = U256::from(l1_batch.fee_input.fair_pubdata_price()); - - // For now, L1 originated transactions are allowed to pay less than fair fee per pubdata, - // so we should take it into account. - let eth_price_per_pubdata_byte_for_calculation = std::cmp::min( - bootloader_eth_price_per_pubdata_byte, - fair_eth_price_per_pubdata_byte, - ); - - let fair_fee_eth = U256::from(gas_spent_on_computation) - * U256::from(l1_batch.fee_input.fair_l2_gas_price()) - + U256::from(pubdata_published) * eth_price_per_pubdata_byte_for_calculation; - let pre_paid_eth = U256::from(tx_gas_limit) * U256::from(effective_gas_price); - let refund_eth = pre_paid_eth.checked_sub(fair_fee_eth).unwrap_or_else(|| { - tracing::error!( - "Fair fee is greater than pre paid. Fair fee: {} wei, pre paid: {} wei", - fair_fee_eth, - pre_paid_eth - ); - U256::zero() - }); - - tracing::trace!( - "Fee benchmark for transaction with hash {}", - hex::encode(tx_hash.as_bytes()) - ); - tracing::trace!("Gas Limit: {}", tx_gas_limit); - tracing::trace!("Gas spent on computation: {}", gas_spent_on_computation); - tracing::trace!("Gas spent on pubdata: {}", gas_spent_on_pubdata); - tracing::trace!("Pubdata published: {}", pubdata_published); - - ceil_div_u256(refund_eth, effective_gas_price.into()).as_u64() -} diff --git a/core/lib/multivm/src/versions/era_vm/tracers/manager.rs b/core/lib/multivm/src/versions/era_vm/tracers/manager.rs index 0c10f2e0297..f3f68355d32 100644 --- a/core/lib/multivm/src/versions/era_vm/tracers/manager.rs +++ b/core/lib/multivm/src/versions/era_vm/tracers/manager.rs @@ -13,11 +13,11 @@ use crate::era_vm::vm::Vm; // this tracer manager is the one that gets called when running the vm pub struct VmTracerManager { - dispatcher: TracerDispatcher, - result_tracer: ResultTracer, - refund_tracer: Option, - pubdata_tracer: PubdataTracer, - circuits_tracer: CircuitsTracer, + pub dispatcher: TracerDispatcher, + pub result_tracer: ResultTracer, + pub refund_tracer: Option, + pub pubdata_tracer: PubdataTracer, + pub circuits_tracer: CircuitsTracer, storage: StoragePtr, } @@ -103,7 +103,7 @@ impl Tracer for VmTracerManager { } } -impl VmTracer for VmTracerManager { +impl VmTracer for VmTracerManager { fn before_bootloader_execution(&mut self, state: &mut Vm) { // Call the dispatcher to handle all the tracers added to it self.dispatcher.before_bootloader_execution(state); @@ -134,4 +134,26 @@ impl VmTracer for VmTracerManager { self.circuits_tracer .after_bootloader_execution(state, stop_reason.clone()); } + + fn bootloader_hook_call( + &mut self, + state: &mut Vm, + hook: crate::era_vm::hook::Hook, + hook_params: &[zksync_types::U256; 3], + ) { + // Call the dispatcher to handle all the tracers added to it + self.dispatcher + .bootloader_hook_call(state, hook.clone(), hook_params); + + // Individual tracers + self.result_tracer + .bootloader_hook_call(state, hook.clone(), hook_params); + if let Some(refunds_tracer) = &mut self.refund_tracer { + refunds_tracer.bootloader_hook_call(state, hook.clone(), hook_params); + } + self.pubdata_tracer + .bootloader_hook_call(state, hook.clone(), hook_params); + self.circuits_tracer + .bootloader_hook_call(state, hook.clone(), hook_params); + } } diff --git a/core/lib/multivm/src/versions/era_vm/tracers/refunds_tracer.rs b/core/lib/multivm/src/versions/era_vm/tracers/refunds_tracer.rs index 109def19643..f22c782ef95 100644 --- a/core/lib/multivm/src/versions/era_vm/tracers/refunds_tracer.rs +++ b/core/lib/multivm/src/versions/era_vm/tracers/refunds_tracer.rs @@ -1,15 +1,152 @@ use zksync_state::ReadStorage; +use zksync_types::{H256, U256}; +use zksync_utils::ceil_div_u256; use super::traits::{Tracer, VmTracer}; +use crate::{ + era_vm::hook::Hook, + vm_latest::{ + constants::{OPERATOR_REFUNDS_OFFSET, TX_GAS_LIMIT_OFFSET}, + utils::fee::get_batch_base_fee, + L1BatchEnv, Refunds, + }, +}; -pub struct RefundsTracer {} +#[derive(Default)] +pub struct RefundsTracer { + pub gas_refunded: u64, + pub operator_suggested_refund: u64, + pubdata_before: u32, +} impl RefundsTracer { pub fn new() -> Self { - Self {} + Self { + gas_refunded: 0, + operator_suggested_refund: 0, + pubdata_before: 0, + } + } +} + +impl Into for RefundsTracer { + fn into(self) -> Refunds { + Refunds { + gas_refunded: self.gas_refunded, + operator_suggested_refund: self.operator_suggested_refund, + } } } impl Tracer for RefundsTracer {} -impl VmTracer for RefundsTracer {} +impl VmTracer for RefundsTracer { + fn before_bootloader_execution(&mut self, vm: &mut super::traits::Vm) { + self.pubdata_before = vm.inner.state.pubdata() as u32; + } + + fn bootloader_hook_call( + &mut self, + vm: &mut super::traits::Vm, + hook: crate::era_vm::hook::Hook, + hook_params: &[zksync_types::U256; 3], + ) { + match hook { + Hook::NotifyAboutRefund => self.gas_refunded = hook_params[0].low_u64(), + Hook::AskOperatorForRefund => { + println!("ENTERINg HERE!"); + let [bootloader_refund, gas_spent_on_pubdata, gas_per_pubdata_byte] = hook_params; + let current_tx_index = vm.bootloader_state.current_tx(); + let tx_description_offset = vm + .bootloader_state + .get_tx_description_offset(current_tx_index); + let tx_gas_limit = vm + .read_heap_word(tx_description_offset + TX_GAS_LIMIT_OFFSET) + .as_u64(); + + let pubdata_published = vm.inner.state.pubdata() as u32; + + self.operator_suggested_refund = compute_refund( + &vm.batch_env, + bootloader_refund.as_u64(), + gas_spent_on_pubdata.as_u64(), + tx_gas_limit, + gas_per_pubdata_byte.low_u32(), + pubdata_published.saturating_sub(self.pubdata_before), + vm.bootloader_state.last_l2_block().txs.last().unwrap().hash, + ); + + self.pubdata_before = pubdata_published; + let refund_value = self.operator_suggested_refund; + vm.write_to_bootloader_heap([( + OPERATOR_REFUNDS_OFFSET + current_tx_index, + refund_value.into(), + )]); + vm.bootloader_state.set_refund_for_current_tx(refund_value); + } + _ => {} + }; + } +} + +pub(crate) fn compute_refund( + l1_batch: &L1BatchEnv, + bootloader_refund: u64, + gas_spent_on_pubdata: u64, + tx_gas_limit: u64, + current_ergs_per_pubdata_byte: u32, + pubdata_published: u32, + tx_hash: H256, +) -> u64 { + let total_gas_spent = tx_gas_limit - bootloader_refund; + + let gas_spent_on_computation = total_gas_spent + .checked_sub(gas_spent_on_pubdata) + .unwrap_or_else(|| { + tracing::error!( + "Gas spent on pubdata is greater than total gas spent. On pubdata: {}, total: {}", + gas_spent_on_pubdata, + total_gas_spent + ); + 0 + }); + + // For now, bootloader charges only for base fee. + let effective_gas_price = get_batch_base_fee(l1_batch); + + let bootloader_eth_price_per_pubdata_byte = + U256::from(effective_gas_price) * U256::from(current_ergs_per_pubdata_byte); + + let fair_eth_price_per_pubdata_byte = U256::from(l1_batch.fee_input.fair_pubdata_price()); + + // For now, L1 originated transactions are allowed to pay less than fair fee per pubdata, + // so we should take it into account. + let eth_price_per_pubdata_byte_for_calculation = std::cmp::min( + bootloader_eth_price_per_pubdata_byte, + fair_eth_price_per_pubdata_byte, + ); + + let fair_fee_eth = U256::from(gas_spent_on_computation) + * U256::from(l1_batch.fee_input.fair_l2_gas_price()) + + U256::from(pubdata_published) * eth_price_per_pubdata_byte_for_calculation; + let pre_paid_eth = U256::from(tx_gas_limit) * U256::from(effective_gas_price); + let refund_eth = pre_paid_eth.checked_sub(fair_fee_eth).unwrap_or_else(|| { + tracing::error!( + "Fair fee is greater than pre paid. Fair fee: {} wei, pre paid: {} wei", + fair_fee_eth, + pre_paid_eth + ); + U256::zero() + }); + + tracing::trace!( + "Fee benchmark for transaction with hash {}", + hex::encode(tx_hash.as_bytes()) + ); + tracing::trace!("Gas Limit: {}", tx_gas_limit); + tracing::trace!("Gas spent on computation: {}", gas_spent_on_computation); + tracing::trace!("Gas spent on pubdata: {}", gas_spent_on_pubdata); + tracing::trace!("Pubdata published: {}", pubdata_published); + + ceil_div_u256(refund_eth, effective_gas_price.into()).as_u64() +} diff --git a/core/lib/multivm/src/versions/era_vm/vm.rs b/core/lib/multivm/src/versions/era_vm/vm.rs index 101f778f882..843525d1299 100644 --- a/core/lib/multivm/src/versions/era_vm/vm.rs +++ b/core/lib/multivm/src/versions/era_vm/vm.rs @@ -36,7 +36,6 @@ use super::{ hook::Hook, initial_bootloader_memory::bootloader_initial_memory, logs::IntoSystemLog, - refunds::compute_refund, snapshot::VmSnapshot, tracers::{ dispatcher::TracerDispatcher, manager::VmTracerManager, refunds_tracer::RefundsTracer, @@ -50,10 +49,9 @@ use crate::{ }, vm_latest::{ constants::{ - get_vm_hook_position, get_vm_hook_start_position_latest, OPERATOR_REFUNDS_OFFSET, - TX_GAS_LIMIT_OFFSET, VM_HOOK_PARAMS_COUNT, + get_vm_hook_position, get_vm_hook_start_position_latest, VM_HOOK_PARAMS_COUNT, }, - BootloaderMemory, CurrentExecutionState, ExecutionResult, L1BatchEnv, L2BlockEnv, Refunds, + BootloaderMemory, CurrentExecutionState, ExecutionResult, L1BatchEnv, L2BlockEnv, SystemEnv, VmExecutionLogs, VmExecutionMode, VmExecutionResultAndLogs, VmExecutionStatistics, }, @@ -161,53 +159,30 @@ impl Vm { pub fn run( &mut self, execution_mode: VmExecutionMode, - tracer: TracerDispatcher, - track_refunds: bool, - ) -> (ExecutionResult, Refunds) { - let mut refunds = Refunds { - gas_refunded: 0, - operator_suggested_refund: 0, - }; - let mut pubdata_before = self.inner.state.pubdata() as u32; + tracer: &mut impl VmTracer, + ) -> ExecutionResult { let mut last_tx_result = None; - let refund_tracer = if track_refunds { - Some(RefundsTracer::new()) - } else { - None - }; - - let mut tracer = VmTracerManager::new(self.storage.clone(), tracer, refund_tracer); - tracer.before_bootloader_execution(self); - let (stop_reason, refunds) = loop { - let result = self - .inner - .run_program_with_custom_bytecode(Some(&mut tracer)); + let stop_reason = loop { + let result = self.inner.run_program_with_custom_bytecode(Some(tracer)); let result = match result { - ExecutionOutput::Ok(output) => { - break (ExecutionResult::Success { output }, refunds) - } + ExecutionOutput::Ok(output) => break (ExecutionResult::Success { output }), ExecutionOutput::Revert(output) => match TxRevertReason::parse_error(&output) { TxRevertReason::TxReverted(output) => { - break (ExecutionResult::Revert { output }, refunds) - } - TxRevertReason::Halt(reason) => { - break (ExecutionResult::Halt { reason }, refunds) + break (ExecutionResult::Revert { output }) } + TxRevertReason::Halt(reason) => break (ExecutionResult::Halt { reason }), }, ExecutionOutput::Panic => { - break ( - ExecutionResult::Halt { - reason: if self.inner.execution.gas_left().unwrap() == 0 { - Halt::BootloaderOutOfGas - } else { - Halt::VMPanic - }, + break ExecutionResult::Halt { + reason: if self.inner.execution.gas_left().unwrap() == 0 { + Halt::BootloaderOutOfGas + } else { + Halt::VMPanic }, - refunds, - ) + } } ExecutionOutput::SuspendedOnHook { hook, @@ -273,55 +248,10 @@ impl Vm { } }); } - Hook::NotifyAboutRefund => { - if track_refunds { - refunds.gas_refunded = self.get_hook_params()[0].low_u64() - } - } - Hook::AskOperatorForRefund => { - if track_refunds { - let [bootloader_refund, gas_spent_on_pubdata, gas_per_pubdata_byte] = - self.get_hook_params(); - let current_tx_index = self.bootloader_state.current_tx(); - let tx_description_offset = self - .bootloader_state - .get_tx_description_offset(current_tx_index); - let tx_gas_limit = self - .read_heap_word(tx_description_offset + TX_GAS_LIMIT_OFFSET) - .as_u64(); - - let pubdata_published = self.inner.state.pubdata() as u32; - - refunds.operator_suggested_refund = compute_refund( - &self.batch_env, - bootloader_refund.as_u64(), - gas_spent_on_pubdata.as_u64(), - tx_gas_limit, - gas_per_pubdata_byte.low_u32(), - pubdata_published.saturating_sub(pubdata_before), - self.bootloader_state - .last_l2_block() - .txs - .last() - .unwrap() - .hash, - ); - - pubdata_before = pubdata_published; - let refund_value = refunds.operator_suggested_refund; - self.write_to_bootloader_heap([( - OPERATOR_REFUNDS_OFFSET + current_tx_index, - refund_value.into(), - )]); - self.bootloader_state - .set_refund_for_current_tx(refund_value); - } - } Hook::DebugLog => {} Hook::TxHasEnded => { if let VmExecutionMode::OneTx = execution_mode { - let tx_result = last_tx_result.take().unwrap(); - return (tx_result, refunds); + break last_tx_result.take().unwrap(); } } Hook::PubdataRequested => { @@ -366,12 +296,13 @@ impl Vm { apply_pubdata_to_memory(&mut memory_to_apply, pubdata_input); self.write_to_bootloader_heap(memory_to_apply); } + _ => {} } }; tracer.after_bootloader_execution(self, stop_reason.clone()); - (stop_reason, refunds) + stop_reason } pub(crate) fn insert_bytecodes<'a>(&mut self, bytecodes: impl IntoIterator) { @@ -520,19 +451,26 @@ impl VmInterface for Vm { _tracer: Self::TracerDispatcher, execution_mode: VmExecutionMode, ) -> VmExecutionResultAndLogs { - let mut enable_refund_tracer = false; + let mut track_refunds = false; if let VmExecutionMode::OneTx = execution_mode { // Move the pointer to the next transaction self.bootloader_state.move_tx_to_execute_pointer(); - enable_refund_tracer = true; + track_refunds = true; } - let snapshot = self.inner.state.snapshot(); - let (result, refunds) = self.run( - execution_mode, + let refund_tracer = if track_refunds { + Some(RefundsTracer::new()) + } else { + None + }; + let mut tracer = VmTracerManager::new( + self.storage.clone(), TracerDispatcher::new(vec![]), - enable_refund_tracer, + refund_tracer, ); + let snapshot = self.inner.state.snapshot(); + + let result = self.run(execution_mode, &mut tracer); let ignore_world_diff = matches!(execution_mode, VmExecutionMode::OneTx) && matches!(result, ExecutionResult::Halt { .. }); @@ -604,7 +542,7 @@ impl VmInterface for Vm { pubdata_published: (self.inner.state.pubdata() - snapshot.pubdata).max(0) as u32, circuit_statistic: Default::default(), }, - refunds, + refunds: tracer.refund_tracer.unwrap_or_default().into(), } }