Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: optimize tx l1 fetches #1967

Merged
merged 2 commits into from
Jan 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading