Skip to content

Commit

Permalink
chore: optimize tx l1 fetches (#1967)
Browse files Browse the repository at this point in the history
* chore: optimize tx l1 fetches

* add std box
  • Loading branch information
rakita authored Jan 6, 2025
1 parent 226f059 commit 9dd9f9e
Show file tree
Hide file tree
Showing 3 changed files with 152 additions and 25 deletions.
2 changes: 1 addition & 1 deletion crates/revm/src/optimism.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ mod l1block;
mod precompile;

pub use handler_register::{
deduct_caller, end, last_frame_return, load_accounts, load_precompiles,
clear, deduct_caller, end, last_frame_return, load_accounts, load_precompiles,
optimism_handle_register, output, refund, reimburse_caller, reward_beneficiary, validate_env,
validate_tx_against_state,
};
Expand Down
137 changes: 121 additions & 16 deletions crates/revm/src/optimism/handler_register.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,9 @@ use crate::{
},
Context, ContextPrecompiles, FrameResult,
};
use core::ops::Mul;
use core::{cmp::Ordering, ops::Mul};
use revm_precompile::PrecompileSpecId;
use std::string::ToString;
use std::sync::Arc;
use std::{boxed::Box, string::ToString, sync::Arc};

use super::l1block::OPERATOR_FEE_RECIPIENT;

Expand All @@ -41,6 +40,7 @@ pub fn optimism_handle_register<DB: Database, EXT>(handler: &mut EvmHandler<'_,
// In case of halt of deposit transaction return Error.
handler.post_execution.output = Arc::new(output::<SPEC, EXT, DB>);
handler.post_execution.end = Arc::new(end::<SPEC, EXT, DB>);
handler.post_execution.clear = Arc::new(clear::<EXT, DB>);
});
}

Expand Down Expand Up @@ -70,10 +70,113 @@ pub fn validate_env<SPEC: Spec, DB: Database>(env: &Env) -> Result<(), EVMError<
pub fn validate_tx_against_state<SPEC: Spec, EXT, DB: Database>(
context: &mut Context<EXT, DB>,
) -> Result<(), EVMError<DB::Error>> {
if context.evm.inner.env.tx.optimism.source_hash.is_some() {
let env @ Env { cfg, tx, .. } = context.evm.inner.env.as_ref();

// No validation is needed for deposit transactions, as they are pre-verified on L1.
if tx.optimism.source_hash.is_some() {
return Ok(());
}
mainnet::validate_tx_against_state::<SPEC, EXT, DB>(context)

// load acc
let tx_caller = tx.caller;
let account = context
.evm
.inner
.journaled_state
.load_code(tx_caller, &mut context.evm.inner.db)?
.data;

// EIP-3607: Reject transactions from senders with deployed code
// This EIP is introduced after london but there was no collision in past
// so we can leave it enabled always
if !cfg.is_eip3607_disabled() {
let bytecode = &account.info.code.as_ref().unwrap();
// allow EOAs whose code is a valid delegation designation,
// i.e. 0xef0100 || address, to continue to originate transactions.
if !bytecode.is_empty() && !bytecode.is_eip7702() {
return Err(EVMError::Transaction(
InvalidTransaction::RejectCallerWithCode,
));
}
}

// Check that the transaction's nonce is correct
if let Some(tx) = tx.nonce {
let state = account.info.nonce;
match tx.cmp(&state) {
Ordering::Greater => {
return Err(EVMError::Transaction(InvalidTransaction::NonceTooHigh {
tx,
state,
}));
}
Ordering::Less => {
return Err(EVMError::Transaction(InvalidTransaction::NonceTooLow {
tx,
state,
}));
}
_ => {}
}
}

// get envelope
let Some(enveloped_tx) = &tx.optimism.enveloped_tx else {
return Err(EVMError::Custom(
"[OPTIMISM] Failed to load enveloped transaction.".to_string(),
));
};

// compute L1 cost
let tx_l1_cost = context
.evm
.inner
.l1_block_info
.as_mut()
.expect("L1BlockInfo should be loaded")
.calculate_tx_l1_cost(enveloped_tx, SPEC::SPEC_ID);

let gas_limit = U256::from(tx.gas_limit);
let operator_fee_charge = context
.evm
.inner
.l1_block_info
.as_ref()
.expect("L1BlockInfo should be loaded")
.operator_fee_charge(gas_limit, SPEC::SPEC_ID);

let mut balance_check = gas_limit
.checked_mul(tx.gas_price)
.and_then(|gas_cost| gas_cost.checked_add(tx.value))
.and_then(|total_cost| total_cost.checked_add(tx_l1_cost))
.and_then(|total_cost| total_cost.checked_add(operator_fee_charge))
.ok_or(InvalidTransaction::OverflowPaymentInTransaction)?;

if SPEC::enabled(SpecId::CANCUN) {
// if the tx is not a blob tx, this will be None, so we add zero
let data_fee = env.calc_max_data_fee().unwrap_or_default();
balance_check = balance_check
.checked_add(U256::from(data_fee))
.ok_or(InvalidTransaction::OverflowPaymentInTransaction)?;
}

// Check if account has enough balance for gas_limit*gas_price and value transfer.
// Transfer will be done inside `*_inner` functions.
if balance_check > account.info.balance {
if cfg.is_balance_check_disabled() {
// Add transaction cost to balance to ensure execution doesn't fail.
account.info.balance = balance_check;
} else {
return Err(EVMError::Transaction(
InvalidTransaction::LackOfFundForMaxFee {
fee: Box::new(balance_check),
balance: Box::new(account.info.balance),
},
));
}
}

Ok(())
}

/// Handle output of the transaction
Expand Down Expand Up @@ -266,17 +369,9 @@ pub fn deduct_caller<SPEC: Spec, EXT, DB: Database>(
.evm
.inner
.l1_block_info
.as_ref()
.as_mut()
.expect("L1BlockInfo should be loaded")
.calculate_tx_l1_cost(enveloped_tx, SPEC::SPEC_ID);
if tx_l1_cost.gt(&caller_account.info.balance) {
return Err(EVMError::Transaction(
InvalidTransaction::LackOfFundForMaxFee {
fee: tx_l1_cost.into(),
balance: caller_account.info.balance.into(),
},
));
}
caller_account.info.balance = caller_account.info.balance.saturating_sub(tx_l1_cost);

// Deduct the operator fee from the caller's account.
Expand Down Expand Up @@ -314,7 +409,7 @@ pub fn reward_beneficiary<SPEC: Spec, EXT, DB: Database>(
if !is_deposit {
// If the transaction is not a deposit transaction, fees are paid out
// to both the Base Fee Vault as well as the L1 Fee Vault.
let Some(l1_block_info) = &context.evm.inner.l1_block_info else {
let Some(l1_block_info) = &mut context.evm.inner.l1_block_info else {
return Err(EVMError::Custom(
"[OPTIMISM] Failed to load L1 block information.".to_string(),
));
Expand Down Expand Up @@ -459,6 +554,16 @@ pub fn end<SPEC: Spec, EXT, DB: Database>(
})
}

/// Clears cache OP l1 value.
#[inline]
pub fn clear<EXT, DB: Database>(context: &mut Context<EXT, DB>) {
// clear error and journaled state.
mainnet::clear(context);
if let Some(l1_block) = &mut context.evm.inner.l1_block_info {
l1_block.clear_tx_l1_cost();
}
}

#[cfg(test)]
mod tests {
use revm_interpreter::{CallOutcome, InterpreterResult};
Expand Down Expand Up @@ -714,7 +819,7 @@ mod tests {
context.evm.inner.env.tx.optimism.enveloped_tx = Some(bytes!("FACADE"));

assert_eq!(
deduct_caller::<RegolithSpec, (), _>(&mut context),
validate_tx_against_state::<RegolithSpec, (), _>(&mut context),
Err(EVMError::Transaction(
InvalidTransaction::LackOfFundForMaxFee {
fee: Box::new(U256::from(1048)),
Expand Down
38 changes: 30 additions & 8 deletions crates/revm/src/optimism/l1block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ pub struct L1BlockInfo {
pub operator_fee_constant: Option<U256>,
/// True if Ecotone is activated, but the L1 fee scalars have not yet been set.
pub(crate) empty_ecotone_scalars: bool,
/// Last calculated l1 fee cost. Uses as a cache between validation and pre execution stages.
pub tx_l1_cost: Option<U256>,
}

impl L1BlockInfo {
Expand Down Expand Up @@ -169,6 +171,7 @@ impl L1BlockInfo {
l1_fee_overhead,
operator_fee_scalar: Some(operator_fee_scalar),
operator_fee_constant: Some(operator_fee_constant),
tx_l1_cost: None,
})
} else {
// Pre-isthmus L1 block info
Expand Down Expand Up @@ -269,20 +272,30 @@ impl L1BlockInfo {
)
}

/// Clears the cached L1 cost of the transaction.
pub fn clear_tx_l1_cost(&mut self) {
self.tx_l1_cost = None;
}

/// Calculate the gas cost of a transaction based on L1 block data posted on L2, depending on the [SpecId] passed.
pub fn calculate_tx_l1_cost(&self, input: &[u8], spec_id: SpecId) -> U256 {
/// And cache the result for future use.
pub fn calculate_tx_l1_cost(&mut self, input: &[u8], spec_id: SpecId) -> U256 {
if let Some(tx_l1_cost) = self.tx_l1_cost {
return tx_l1_cost;
}
// If the input is a deposit transaction or empty, the default value is zero.
if input.is_empty() || input.first() == Some(&0x7F) {
let tx_l1_cost = if input.is_empty() || input.first() == Some(&0x7F) {
return U256::ZERO;
}

if spec_id.is_enabled_in(SpecId::FJORD) {
} else if spec_id.is_enabled_in(SpecId::FJORD) {
self.calculate_tx_l1_cost_fjord(input)
} else if spec_id.is_enabled_in(SpecId::ECOTONE) {
self.calculate_tx_l1_cost_ecotone(input, spec_id)
} else {
self.calculate_tx_l1_cost_bedrock(input, spec_id)
}
};

self.tx_l1_cost = Some(tx_l1_cost);
tx_l1_cost
}

/// Calculate the gas cost of a transaction based on L1 block data posted on L2, pre-Ecotone.
Expand Down Expand Up @@ -416,7 +429,7 @@ mod tests {

#[test]
fn test_calculate_tx_l1_cost() {
let l1_block_info = L1BlockInfo {
let mut l1_block_info = L1BlockInfo {
l1_base_fee: U256::from(1_000),
l1_fee_overhead: Some(U256::from(1_000)),
l1_base_fee_scalar: U256::from(1_000),
Expand All @@ -426,16 +439,19 @@ mod tests {
let input = bytes!("FACADE");
let gas_cost = l1_block_info.calculate_tx_l1_cost(&input, SpecId::REGOLITH);
assert_eq!(gas_cost, U256::from(1048));
l1_block_info.clear_tx_l1_cost();

// Zero rollup data gas cost should result in zero
let input = bytes!("");
let gas_cost = l1_block_info.calculate_tx_l1_cost(&input, SpecId::REGOLITH);
assert_eq!(gas_cost, U256::ZERO);
l1_block_info.clear_tx_l1_cost();

// Deposit transactions with the EIP-2718 type of 0x7F should result in zero
let input = bytes!("7FFACADE");
let gas_cost = l1_block_info.calculate_tx_l1_cost(&input, SpecId::REGOLITH);
assert_eq!(gas_cost, U256::ZERO);
l1_block_info.clear_tx_l1_cost();
}

#[test]
Expand All @@ -455,16 +471,19 @@ mod tests {
let input = bytes!("FACADE");
let gas_cost = l1_block_info.calculate_tx_l1_cost(&input, SpecId::ECOTONE);
assert_eq!(gas_cost, U256::from(51));
l1_block_info.clear_tx_l1_cost();

// Zero rollup data gas cost should result in zero
let input = bytes!("");
let gas_cost = l1_block_info.calculate_tx_l1_cost(&input, SpecId::ECOTONE);
assert_eq!(gas_cost, U256::ZERO);
l1_block_info.clear_tx_l1_cost();

// Deposit transactions with the EIP-2718 type of 0x7F should result in zero
let input = bytes!("7FFACADE");
let gas_cost = l1_block_info.calculate_tx_l1_cost(&input, SpecId::ECOTONE);
assert_eq!(gas_cost, U256::ZERO);
l1_block_info.clear_tx_l1_cost();

// If the scalars are empty, the bedrock cost function should be used.
l1_block_info.empty_ecotone_scalars = true;
Expand All @@ -478,7 +497,7 @@ mod tests {
// l1FeeScaled = baseFeeScalar*l1BaseFee*16 + blobFeeScalar*l1BlobBaseFee
// = 1000 * 1000 * 16 + 1000 * 1000
// = 17e6
let l1_block_info = L1BlockInfo {
let mut l1_block_info = L1BlockInfo {
l1_base_fee: U256::from(1_000),
l1_base_fee_scalar: U256::from(1_000),
l1_blob_base_fee: Some(U256::from(1_000)),
Expand All @@ -496,6 +515,7 @@ mod tests {
// = 1700
let gas_cost = l1_block_info.calculate_tx_l1_cost(&input, SpecId::FJORD);
assert_eq!(gas_cost, U256::from(1700));
l1_block_info.clear_tx_l1_cost();

// fastLzSize = 202
// estimatedSize = max(minTransactionSize, intercept + fastlzCoef*fastlzSize)
Expand All @@ -507,11 +527,13 @@ mod tests {
// = 2148
let gas_cost = l1_block_info.calculate_tx_l1_cost(&input, SpecId::FJORD);
assert_eq!(gas_cost, U256::from(2148));
l1_block_info.clear_tx_l1_cost();

// Zero rollup data gas cost should result in zero
let input = bytes!("");
let gas_cost = l1_block_info.calculate_tx_l1_cost(&input, SpecId::FJORD);
assert_eq!(gas_cost, U256::ZERO);
l1_block_info.clear_tx_l1_cost();

// Deposit transactions with the EIP-2718 type of 0x7F should result in zero
let input = bytes!("7FFACADE");
Expand Down

0 comments on commit 9dd9f9e

Please sign in to comment.