@@ -8,20 +8,20 @@ use crate::{
88use alloy_evm:: eth:: receipt_builder:: ReceiptBuilderCtx ;
99use alloy_primitives:: B64 ;
1010use eyre:: { WrapErr as _, bail} ;
11- use op_alloy_consensus:: OpTxEnvelope ;
1211use op_alloy_rpc_types_engine:: OpFlashblockPayload ;
1312use op_revm:: L1BlockInfo ;
1413use reth:: revm:: { State , database:: StateProviderDatabase } ;
1514use reth_basic_payload_builder:: PayloadConfig ;
16- use reth_evm:: FromRecoveredTx ;
1715use reth_node_builder:: Events ;
1816use reth_optimism_chainspec:: OpChainSpec ;
19- use reth_optimism_evm:: { OpEvmConfig , OpNextBlockEnvAttributes } ;
17+ use reth_optimism_consensus:: OpBeaconConsensus ;
18+ use reth_optimism_evm:: OpNextBlockEnvAttributes ;
2019use reth_optimism_forks:: OpHardforks ;
2120use reth_optimism_node:: { OpEngineTypes , OpPayloadBuilderAttributes } ;
2221use reth_optimism_payload_builder:: OpBuiltPayload ;
2322use reth_optimism_primitives:: { OpReceipt , OpTransactionSigned } ;
2423use reth_payload_builder:: EthPayloadBuilderAttributes ;
24+ use reth_primitives_traits:: SealedHeader ;
2525use std:: sync:: Arc ;
2626use tokio:: sync:: mpsc;
2727use tracing:: warn;
@@ -156,6 +156,11 @@ where
156156 . wrap_err ( "failed to get parent header" ) ?
157157 . ok_or_else ( || eyre:: eyre!( "parent header not found" ) ) ?;
158158
159+ // For X Layer, validate header and parent relationship before execution
160+ let chain_spec = client. chain_spec ( ) ;
161+ validate_pre_execution ( & payload, & parent_header, parent_hash, chain_spec. clone ( ) )
162+ . wrap_err ( "pre-execution validation failed" ) ?;
163+
159164 let state_provider = client
160165 . state_by_block_hash ( parent_hash)
161166 . wrap_err ( "failed to get state for parent hash" ) ?;
@@ -165,7 +170,6 @@ where
165170 . with_bundle_update ( )
166171 . build ( ) ;
167172
168- let chain_spec = client. chain_spec ( ) ;
169173 let timestamp = payload. block ( ) . header ( ) . timestamp ( ) ;
170174 let block_env_attributes = OpNextBlockEnvAttributes {
171175 timestamp,
@@ -249,17 +253,14 @@ where
249253 ) ;
250254
251255 execute_transactions (
256+ & ctx,
252257 & mut info,
253258 & mut state,
254259 payload. block ( ) . body ( ) . transactions . clone ( ) ,
255260 payload. block ( ) . header ( ) . gas_used ,
256261 timestamp,
257- ctx. evm_config ( ) ,
258262 evm_env. clone ( ) ,
259263 chain_spec. clone ( ) ,
260- ctx. max_gas_per_txn ( ) ,
261- is_canyon_active ( & chain_spec, timestamp) ,
262- is_regolith_active ( & chain_spec, timestamp) ,
263264 )
264265 . wrap_err ( "failed to execute best transactions" ) ?;
265266
@@ -301,91 +302,56 @@ where
301302
302303#[ allow( clippy:: too_many_arguments) ]
303304fn execute_transactions (
305+ ctx : & OpPayloadSyncerCtx ,
304306 info : & mut ExecutionInfo < FlashblocksExecutionInfo > ,
305307 state : & mut State < impl alloy_evm:: Database > ,
306308 txs : Vec < op_alloy_consensus:: OpTxEnvelope > ,
307309 gas_limit : u64 ,
308310 timestamp : u64 ,
309- evm_config : & reth_optimism_evm:: OpEvmConfig ,
310311 evm_env : alloy_evm:: EvmEnv < op_revm:: OpSpecId > ,
311312 chain_spec : Arc < OpChainSpec > ,
312- max_gas_per_txn : Option < u64 > ,
313- is_canyon_active : bool ,
314- is_regolith_active : bool ,
315313) -> eyre:: Result < ( ) > {
316- use alloy_evm:: { Evm as _, EvmError as _} ;
317- use op_revm:: { OpTransaction , transaction:: deposit:: DepositTransactionParts } ;
314+ use alloy_evm:: Evm as _;
318315 use reth_evm:: ConfigureEvm as _;
319- use reth_primitives_traits:: SignerRecoverable as _;
320- use revm:: {
321- DatabaseCommit as _,
322- context:: { TxEnv , result:: ResultAndState } ,
323- } ;
316+ use reth_primitives_traits:: SignedTransaction ;
317+ use revm:: { DatabaseCommit as _, context:: result:: ResultAndState } ;
324318
325- let mut evm = evm_config. evm_with_env ( & mut * state, evm_env) ;
319+ let mut evm = ctx . evm_config ( ) . evm_with_env ( & mut * state, evm_env) ;
326320
327321 for tx in txs {
328- let sender = tx
329- . recover_signer ( )
330- . wrap_err ( "failed to recover tx signer" ) ?;
331- let tx_env = TxEnv :: from_recovered_tx ( & tx, sender) ;
332- let executable_tx = match tx {
333- OpTxEnvelope :: Deposit ( ref tx) => {
334- let deposit = DepositTransactionParts {
335- mint : Some ( tx. mint ) ,
336- source_hash : tx. source_hash ,
337- is_system_transaction : tx. is_system_transaction ,
338- } ;
339- OpTransaction {
340- base : tx_env,
341- enveloped_tx : None ,
342- deposit,
343- }
344- }
345- OpTxEnvelope :: Legacy ( _) => {
346- let mut tx = OpTransaction :: new ( tx_env) ;
347- tx. enveloped_tx = Some ( vec ! [ 0x00 ] . into ( ) ) ;
348- tx
349- }
350- OpTxEnvelope :: Eip2930 ( _) => {
351- let mut tx = OpTransaction :: new ( tx_env) ;
352- tx. enveloped_tx = Some ( vec ! [ 0x00 ] . into ( ) ) ;
353- tx
354- }
355- OpTxEnvelope :: Eip1559 ( _) => {
356- let mut tx = OpTransaction :: new ( tx_env) ;
357- tx. enveloped_tx = Some ( vec ! [ 0x00 ] . into ( ) ) ;
358- tx
359- }
360- OpTxEnvelope :: Eip7702 ( _) => {
361- let mut tx = OpTransaction :: new ( tx_env) ;
362- tx. enveloped_tx = Some ( vec ! [ 0x00 ] . into ( ) ) ;
363- tx
364- }
365- } ;
322+ // Convert to recovered transaction
323+ let tx_recovered = tx
324+ . try_clone_into_recovered ( )
325+ . wrap_err ( "failed to recover tx" ) ?;
326+ let sender = tx_recovered. signer ( ) ;
327+
328+ // Cache the depositor account prior to the state transition for the deposit nonce.
329+ //
330+ // Note that this *only* needs to be done post-regolith hardfork, as deposit nonces
331+ // were not introduced in Bedrock. In addition, regular transactions don't have deposit
332+ // nonces, so we don't need to touch the DB for those.
333+ let depositor_nonce = ( ctx. is_regolith_active ( timestamp) && tx_recovered. is_deposit ( ) )
334+ . then ( || {
335+ evm. db_mut ( )
336+ . load_cache_account ( sender)
337+ . map ( |acc| acc. account_info ( ) . unwrap_or_default ( ) . nonce )
338+ } )
339+ . transpose ( )
340+ . wrap_err ( "failed to get depositor nonce" ) ?;
366341
367- let ResultAndState { result, state } = match evm. transact_raw ( executable_tx) {
368- Ok ( res) => res,
369- Err ( err) => {
370- if let Some ( err) = err. as_invalid_tx_err ( ) {
371- // TODO: what invalid txs are allowed in the block?
372- // reverting txs should be allowed (?) but not straight up invalid ones
373- tracing:: error!( error = %err, "skipping invalid transaction in flashblock" ) ;
374- continue ;
375- }
376- return Err ( err) . wrap_err ( "failed to execute flashblock transaction" ) ;
377- }
378- } ;
342+ let ResultAndState { result, state } = evm
343+ . transact ( & tx_recovered)
344+ . wrap_err ( "failed to execute transaction" ) ?;
379345
380- if let Some ( max_gas_per_txn) = max_gas_per_txn
381- && result. gas_used ( ) > max_gas_per_txn
346+ let tx_gas_used = result. gas_used ( ) ;
347+ if let Some ( max_gas_per_txn) = ctx. max_gas_per_txn ( )
348+ && tx_gas_used > max_gas_per_txn
382349 {
383350 return Err ( eyre:: eyre!(
384351 "transaction exceeded max gas per txn limit in flashblock"
385352 ) ) ;
386353 }
387354
388- let tx_gas_used = result. gas_used ( ) ;
389355 info. cumulative_gas_used = info
390356 . cumulative_gas_used
391357 . checked_add ( tx_gas_used)
@@ -396,29 +362,16 @@ fn execute_transactions(
396362 bail ! ( "flashblock exceeded gas limit when executing transactions" ) ;
397363 }
398364
399- let depositor_nonce = ( is_regolith_active && tx. is_deposit ( ) )
400- . then ( || {
401- evm. db_mut ( )
402- . load_cache_account ( sender)
403- . map ( |acc| acc. account_info ( ) . unwrap_or_default ( ) . nonce )
404- } )
405- . transpose ( )
406- . wrap_err ( "failed to get depositor nonce" ) ?;
407-
408- let ctx = ReceiptBuilderCtx {
365+ let receipt_ctx = ReceiptBuilderCtx {
409366 tx : & tx,
410367 evm : & evm,
411368 result,
412369 state : & state,
413370 cumulative_gas_used : info. cumulative_gas_used ,
414371 } ;
415372
416- info. receipts . push ( build_receipt (
417- evm_config,
418- ctx,
419- depositor_nonce,
420- is_canyon_active,
421- ) ) ;
373+ info. receipts
374+ . push ( build_receipt ( ctx, receipt_ctx, depositor_nonce, timestamp) ) ;
422375
423376 evm. db_mut ( ) . commit ( state) ;
424377
@@ -440,26 +393,26 @@ fn execute_transactions(
440393}
441394
442395fn build_receipt < E : alloy_evm:: Evm > (
443- evm_config : & OpEvmConfig ,
444- ctx : ReceiptBuilderCtx < ' _ , OpTransactionSigned , E > ,
396+ ctx : & OpPayloadSyncerCtx ,
397+ receipt_ctx : ReceiptBuilderCtx < ' _ , OpTransactionSigned , E > ,
445398 deposit_nonce : Option < u64 > ,
446- is_canyon_active : bool ,
399+ timestamp : u64 ,
447400) -> OpReceipt {
448401 use alloy_consensus:: Eip658Value ;
449402 use alloy_op_evm:: block:: receipt_builder:: OpReceiptBuilder as _;
450403 use op_alloy_consensus:: OpDepositReceipt ;
451404 use reth_evm:: ConfigureEvm as _;
452405
453- let receipt_builder = evm_config. block_executor_factory ( ) . receipt_builder ( ) ;
454- match receipt_builder. build_receipt ( ctx ) {
406+ let receipt_builder = ctx . evm_config ( ) . block_executor_factory ( ) . receipt_builder ( ) ;
407+ match receipt_builder. build_receipt ( receipt_ctx ) {
455408 Ok ( receipt) => receipt,
456- Err ( ctx ) => {
409+ Err ( receipt_ctx ) => {
457410 let receipt = alloy_consensus:: Receipt {
458411 // Success flag was added in `EIP-658: Embedding transaction status code
459412 // in receipts`.
460- status : Eip658Value :: Eip658 ( ctx . result . is_success ( ) ) ,
461- cumulative_gas_used : ctx . cumulative_gas_used ,
462- logs : ctx . result . into_logs ( ) ,
413+ status : Eip658Value :: Eip658 ( receipt_ctx . result . is_success ( ) ) ,
414+ cumulative_gas_used : receipt_ctx . cumulative_gas_used ,
415+ logs : receipt_ctx . result . into_logs ( ) ,
463416 } ;
464417
465418 receipt_builder. build_deposit_receipt ( OpDepositReceipt {
@@ -470,18 +423,36 @@ fn build_receipt<E: alloy_evm::Evm>(
470423 // when set. The state transition process ensures
471424 // this is only set for post-Canyon deposit
472425 // transactions.
473- deposit_receipt_version : is_canyon_active. then_some ( 1 ) ,
426+ deposit_receipt_version : ctx . is_canyon_active ( timestamp ) . then_some ( 1 ) ,
474427 } )
475428 }
476429 }
477430}
478431
479- fn is_canyon_active ( chain_spec : & OpChainSpec , timestamp : u64 ) -> bool {
480- use reth_optimism_chainspec:: OpHardforks as _;
481- chain_spec. is_canyon_active_at_timestamp ( timestamp)
482- }
432+ /// Validates the payload header and its relationship with the parent before execution.
433+ /// This performs consensus rule validation including:
434+ /// - Header field validation (timestamp, gas limit, etc.)
435+ /// - Parent relationship validation (block number increment, timestamp progression)
436+ fn validate_pre_execution (
437+ payload : & OpBuiltPayload ,
438+ parent_header : & reth_primitives_traits:: Header ,
439+ parent_hash : alloy_primitives:: B256 ,
440+ chain_spec : Arc < OpChainSpec > ,
441+ ) -> eyre:: Result < ( ) > {
442+ use reth:: consensus:: HeaderValidator ;
443+
444+ let consensus = OpBeaconConsensus :: new ( chain_spec) ;
445+ let parent_sealed = SealedHeader :: new ( parent_header. clone ( ) , parent_hash) ;
446+
447+ // Validate incoming header
448+ consensus
449+ . validate_header ( payload. block ( ) . sealed_header ( ) )
450+ . wrap_err ( "header validation failed" ) ?;
483451
484- fn is_regolith_active ( chain_spec : & OpChainSpec , timestamp : u64 ) -> bool {
485- use reth_optimism_chainspec:: OpHardforks as _;
486- chain_spec. is_regolith_active_at_timestamp ( timestamp)
452+ // Validate incoming header against parent
453+ consensus
454+ . validate_header_against_parent ( payload. block ( ) . sealed_header ( ) , & parent_sealed)
455+ . wrap_err ( "header validation against parent failed" ) ?;
456+
457+ Ok ( ( ) )
487458}
0 commit comments