Skip to content

Commit 9dd9f9e

Browse files
authored
chore: optimize tx l1 fetches (#1967)
* chore: optimize tx l1 fetches * add std box
1 parent 226f059 commit 9dd9f9e

File tree

3 files changed

+152
-25
lines changed

3 files changed

+152
-25
lines changed

crates/revm/src/optimism.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ mod l1block;
77
mod precompile;
88

99
pub use handler_register::{
10-
deduct_caller, end, last_frame_return, load_accounts, load_precompiles,
10+
clear, deduct_caller, end, last_frame_return, load_accounts, load_precompiles,
1111
optimism_handle_register, output, refund, reimburse_caller, reward_beneficiary, validate_env,
1212
validate_tx_against_state,
1313
};

crates/revm/src/optimism/handler_register.rs

Lines changed: 121 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,9 @@ use crate::{
1414
},
1515
Context, ContextPrecompiles, FrameResult,
1616
};
17-
use core::ops::Mul;
17+
use core::{cmp::Ordering, ops::Mul};
1818
use revm_precompile::PrecompileSpecId;
19-
use std::string::ToString;
20-
use std::sync::Arc;
19+
use std::{boxed::Box, string::ToString, sync::Arc};
2120

2221
use super::l1block::OPERATOR_FEE_RECIPIENT;
2322

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

@@ -70,10 +70,113 @@ pub fn validate_env<SPEC: Spec, DB: Database>(env: &Env) -> Result<(), EVMError<
7070
pub fn validate_tx_against_state<SPEC: Spec, EXT, DB: Database>(
7171
context: &mut Context<EXT, DB>,
7272
) -> Result<(), EVMError<DB::Error>> {
73-
if context.evm.inner.env.tx.optimism.source_hash.is_some() {
73+
let env @ Env { cfg, tx, .. } = context.evm.inner.env.as_ref();
74+
75+
// No validation is needed for deposit transactions, as they are pre-verified on L1.
76+
if tx.optimism.source_hash.is_some() {
7477
return Ok(());
7578
}
76-
mainnet::validate_tx_against_state::<SPEC, EXT, DB>(context)
79+
80+
// load acc
81+
let tx_caller = tx.caller;
82+
let account = context
83+
.evm
84+
.inner
85+
.journaled_state
86+
.load_code(tx_caller, &mut context.evm.inner.db)?
87+
.data;
88+
89+
// EIP-3607: Reject transactions from senders with deployed code
90+
// This EIP is introduced after london but there was no collision in past
91+
// so we can leave it enabled always
92+
if !cfg.is_eip3607_disabled() {
93+
let bytecode = &account.info.code.as_ref().unwrap();
94+
// allow EOAs whose code is a valid delegation designation,
95+
// i.e. 0xef0100 || address, to continue to originate transactions.
96+
if !bytecode.is_empty() && !bytecode.is_eip7702() {
97+
return Err(EVMError::Transaction(
98+
InvalidTransaction::RejectCallerWithCode,
99+
));
100+
}
101+
}
102+
103+
// Check that the transaction's nonce is correct
104+
if let Some(tx) = tx.nonce {
105+
let state = account.info.nonce;
106+
match tx.cmp(&state) {
107+
Ordering::Greater => {
108+
return Err(EVMError::Transaction(InvalidTransaction::NonceTooHigh {
109+
tx,
110+
state,
111+
}));
112+
}
113+
Ordering::Less => {
114+
return Err(EVMError::Transaction(InvalidTransaction::NonceTooLow {
115+
tx,
116+
state,
117+
}));
118+
}
119+
_ => {}
120+
}
121+
}
122+
123+
// get envelope
124+
let Some(enveloped_tx) = &tx.optimism.enveloped_tx else {
125+
return Err(EVMError::Custom(
126+
"[OPTIMISM] Failed to load enveloped transaction.".to_string(),
127+
));
128+
};
129+
130+
// compute L1 cost
131+
let tx_l1_cost = context
132+
.evm
133+
.inner
134+
.l1_block_info
135+
.as_mut()
136+
.expect("L1BlockInfo should be loaded")
137+
.calculate_tx_l1_cost(enveloped_tx, SPEC::SPEC_ID);
138+
139+
let gas_limit = U256::from(tx.gas_limit);
140+
let operator_fee_charge = context
141+
.evm
142+
.inner
143+
.l1_block_info
144+
.as_ref()
145+
.expect("L1BlockInfo should be loaded")
146+
.operator_fee_charge(gas_limit, SPEC::SPEC_ID);
147+
148+
let mut balance_check = gas_limit
149+
.checked_mul(tx.gas_price)
150+
.and_then(|gas_cost| gas_cost.checked_add(tx.value))
151+
.and_then(|total_cost| total_cost.checked_add(tx_l1_cost))
152+
.and_then(|total_cost| total_cost.checked_add(operator_fee_charge))
153+
.ok_or(InvalidTransaction::OverflowPaymentInTransaction)?;
154+
155+
if SPEC::enabled(SpecId::CANCUN) {
156+
// if the tx is not a blob tx, this will be None, so we add zero
157+
let data_fee = env.calc_max_data_fee().unwrap_or_default();
158+
balance_check = balance_check
159+
.checked_add(U256::from(data_fee))
160+
.ok_or(InvalidTransaction::OverflowPaymentInTransaction)?;
161+
}
162+
163+
// Check if account has enough balance for gas_limit*gas_price and value transfer.
164+
// Transfer will be done inside `*_inner` functions.
165+
if balance_check > account.info.balance {
166+
if cfg.is_balance_check_disabled() {
167+
// Add transaction cost to balance to ensure execution doesn't fail.
168+
account.info.balance = balance_check;
169+
} else {
170+
return Err(EVMError::Transaction(
171+
InvalidTransaction::LackOfFundForMaxFee {
172+
fee: Box::new(balance_check),
173+
balance: Box::new(account.info.balance),
174+
},
175+
));
176+
}
177+
}
178+
179+
Ok(())
77180
}
78181

79182
/// Handle output of the transaction
@@ -266,17 +369,9 @@ pub fn deduct_caller<SPEC: Spec, EXT, DB: Database>(
266369
.evm
267370
.inner
268371
.l1_block_info
269-
.as_ref()
372+
.as_mut()
270373
.expect("L1BlockInfo should be loaded")
271374
.calculate_tx_l1_cost(enveloped_tx, SPEC::SPEC_ID);
272-
if tx_l1_cost.gt(&caller_account.info.balance) {
273-
return Err(EVMError::Transaction(
274-
InvalidTransaction::LackOfFundForMaxFee {
275-
fee: tx_l1_cost.into(),
276-
balance: caller_account.info.balance.into(),
277-
},
278-
));
279-
}
280375
caller_account.info.balance = caller_account.info.balance.saturating_sub(tx_l1_cost);
281376

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

557+
/// Clears cache OP l1 value.
558+
#[inline]
559+
pub fn clear<EXT, DB: Database>(context: &mut Context<EXT, DB>) {
560+
// clear error and journaled state.
561+
mainnet::clear(context);
562+
if let Some(l1_block) = &mut context.evm.inner.l1_block_info {
563+
l1_block.clear_tx_l1_cost();
564+
}
565+
}
566+
462567
#[cfg(test)]
463568
mod tests {
464569
use revm_interpreter::{CallOutcome, InterpreterResult};
@@ -714,7 +819,7 @@ mod tests {
714819
context.evm.inner.env.tx.optimism.enveloped_tx = Some(bytes!("FACADE"));
715820

716821
assert_eq!(
717-
deduct_caller::<RegolithSpec, (), _>(&mut context),
822+
validate_tx_against_state::<RegolithSpec, (), _>(&mut context),
718823
Err(EVMError::Transaction(
719824
InvalidTransaction::LackOfFundForMaxFee {
720825
fee: Box::new(U256::from(1048)),

crates/revm/src/optimism/l1block.rs

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ pub struct L1BlockInfo {
9494
pub operator_fee_constant: Option<U256>,
9595
/// True if Ecotone is activated, but the L1 fee scalars have not yet been set.
9696
pub(crate) empty_ecotone_scalars: bool,
97+
/// Last calculated l1 fee cost. Uses as a cache between validation and pre execution stages.
98+
pub tx_l1_cost: Option<U256>,
9799
}
98100

99101
impl L1BlockInfo {
@@ -169,6 +171,7 @@ impl L1BlockInfo {
169171
l1_fee_overhead,
170172
operator_fee_scalar: Some(operator_fee_scalar),
171173
operator_fee_constant: Some(operator_fee_constant),
174+
tx_l1_cost: None,
172175
})
173176
} else {
174177
// Pre-isthmus L1 block info
@@ -269,20 +272,30 @@ impl L1BlockInfo {
269272
)
270273
}
271274

275+
/// Clears the cached L1 cost of the transaction.
276+
pub fn clear_tx_l1_cost(&mut self) {
277+
self.tx_l1_cost = None;
278+
}
279+
272280
/// Calculate the gas cost of a transaction based on L1 block data posted on L2, depending on the [SpecId] passed.
273-
pub fn calculate_tx_l1_cost(&self, input: &[u8], spec_id: SpecId) -> U256 {
281+
/// And cache the result for future use.
282+
pub fn calculate_tx_l1_cost(&mut self, input: &[u8], spec_id: SpecId) -> U256 {
283+
if let Some(tx_l1_cost) = self.tx_l1_cost {
284+
return tx_l1_cost;
285+
}
274286
// If the input is a deposit transaction or empty, the default value is zero.
275-
if input.is_empty() || input.first() == Some(&0x7F) {
287+
let tx_l1_cost = if input.is_empty() || input.first() == Some(&0x7F) {
276288
return U256::ZERO;
277-
}
278-
279-
if spec_id.is_enabled_in(SpecId::FJORD) {
289+
} else if spec_id.is_enabled_in(SpecId::FJORD) {
280290
self.calculate_tx_l1_cost_fjord(input)
281291
} else if spec_id.is_enabled_in(SpecId::ECOTONE) {
282292
self.calculate_tx_l1_cost_ecotone(input, spec_id)
283293
} else {
284294
self.calculate_tx_l1_cost_bedrock(input, spec_id)
285-
}
295+
};
296+
297+
self.tx_l1_cost = Some(tx_l1_cost);
298+
tx_l1_cost
286299
}
287300

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

417430
#[test]
418431
fn test_calculate_tx_l1_cost() {
419-
let l1_block_info = L1BlockInfo {
432+
let mut l1_block_info = L1BlockInfo {
420433
l1_base_fee: U256::from(1_000),
421434
l1_fee_overhead: Some(U256::from(1_000)),
422435
l1_base_fee_scalar: U256::from(1_000),
@@ -426,16 +439,19 @@ mod tests {
426439
let input = bytes!("FACADE");
427440
let gas_cost = l1_block_info.calculate_tx_l1_cost(&input, SpecId::REGOLITH);
428441
assert_eq!(gas_cost, U256::from(1048));
442+
l1_block_info.clear_tx_l1_cost();
429443

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

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

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

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

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

469488
// If the scalars are empty, the bedrock cost function should be used.
470489
l1_block_info.empty_ecotone_scalars = true;
@@ -478,7 +497,7 @@ mod tests {
478497
// l1FeeScaled = baseFeeScalar*l1BaseFee*16 + blobFeeScalar*l1BlobBaseFee
479498
// = 1000 * 1000 * 16 + 1000 * 1000
480499
// = 17e6
481-
let l1_block_info = L1BlockInfo {
500+
let mut l1_block_info = L1BlockInfo {
482501
l1_base_fee: U256::from(1_000),
483502
l1_base_fee_scalar: U256::from(1_000),
484503
l1_blob_base_fee: Some(U256::from(1_000)),
@@ -496,6 +515,7 @@ mod tests {
496515
// = 1700
497516
let gas_cost = l1_block_info.calculate_tx_l1_cost(&input, SpecId::FJORD);
498517
assert_eq!(gas_cost, U256::from(1700));
518+
l1_block_info.clear_tx_l1_cost();
499519

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

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

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

0 commit comments

Comments
 (0)