From 3deafe2ad90d8dfe3d7a42ca3c30fba4880f89bd Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Fri, 15 Nov 2024 16:55:37 +0100 Subject: [PATCH 1/2] feat: add helpers to obtain the execution witness for a payload --- Cargo.lock | 1 + crates/optimism/payload/Cargo.toml | 3 +- crates/optimism/payload/src/builder.rs | 96 ++++++++++++++++++++------ 3 files changed, 76 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6ccf3cdabb18..9dabe6fa4596 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8344,6 +8344,7 @@ dependencies = [ "alloy-eips", "alloy-primitives", "alloy-rlp", + "alloy-rpc-types-debug", "alloy-rpc-types-engine", "op-alloy-consensus", "op-alloy-rpc-types-engine", diff --git a/crates/optimism/payload/Cargo.toml b/crates/optimism/payload/Cargo.toml index 839355b2158e..646df040e19f 100644 --- a/crates/optimism/payload/Cargo.toml +++ b/crates/optimism/payload/Cargo.toml @@ -15,7 +15,7 @@ workspace = true # reth reth-chainspec.workspace = true reth-primitives.workspace = true -reth-revm.workspace = true +reth-revm = { workspace = true, features = ["witness"] } reth-transaction-pool.workspace = true reth-provider.workspace = true reth-rpc-types-compat.workspace = true @@ -41,6 +41,7 @@ alloy-rlp.workspace = true op-alloy-rpc-types-engine.workspace = true op-alloy-consensus.workspace = true alloy-rpc-types-engine.workspace = true +alloy-rpc-types-debug.workspace = true alloy-consensus.workspace = true # misc diff --git a/crates/optimism/payload/src/builder.rs b/crates/optimism/payload/src/builder.rs index beb9a5c4ae21..250c8980fae3 100644 --- a/crates/optimism/payload/src/builder.rs +++ b/crates/optimism/payload/src/builder.rs @@ -5,6 +5,7 @@ use std::{fmt::Display, sync::Arc}; use alloy_consensus::{Header, Transaction, EMPTY_OMMER_ROOT_HASH}; use alloy_eips::merge::BEACON_NONCE; use alloy_primitives::{Address, Bytes, U256}; +use alloy_rpc_types_debug::ExecutionWitness; use alloy_rpc_types_engine::PayloadId; use reth_basic_payload_builder::*; use reth_chain_state::ExecutedBlock; @@ -20,7 +21,7 @@ use reth_primitives::{ revm_primitives::{BlockEnv, CfgEnvWithHandlerCfg}, Block, BlockBody, Receipt, SealedHeader, TransactionSigned, TxType, }; -use reth_provider::{ProviderError, StateProviderFactory, StateRootProvider}; +use reth_provider::{ProviderError, StateProofProvider, StateProviderFactory, StateRootProvider}; use reth_revm::database::StateProviderDatabase; use reth_transaction_pool::{ noop::NoopTransactionPool, BestTransactionsAttributes, PayloadTransactions, TransactionPool, @@ -38,6 +39,7 @@ use crate::{ payload::{OpBuiltPayload, OpPayloadBuilderAttributes}, }; use op_alloy_consensus::DepositTransaction; +use reth_revm::witness::ExecutionWitnessRecord; use reth_transaction_pool::pool::BestPayloadTransactions; /// Optimism's payload builder @@ -234,36 +236,33 @@ where Pool: TransactionPool, Txs: OpPayloadTransactions, { - /// Builds the payload on top of the state. - pub fn build( + /// Executes the payload and returns the outcome. + pub fn execute( self, - mut db: State, - ctx: OpPayloadBuilderCtx, - ) -> Result, PayloadBuilderError> + db: &mut State, + ctx: &OpPayloadBuilderCtx, + ) -> Result, PayloadBuilderError> where EvmConfig: ConfigureEvm
, - DB: Database + AsRef

, - P: StateRootProvider, + DB: Database, { let Self { pool, best } = self; debug!(target: "payload_builder", id=%ctx.payload_id(), parent_header = ?ctx.parent().hash(), parent_number = ctx.parent().number, "building new payload"); // 1. apply eip-4788 pre block contract call - ctx.apply_pre_beacon_root_contract_call(&mut db)?; + ctx.apply_pre_beacon_root_contract_call(db)?; // 2. ensure create2deployer is force deployed - ctx.ensure_create2_deployer(&mut db)?; + ctx.ensure_create2_deployer(db)?; // 3. execute sequencer transactions - let mut info = ctx.execute_sequencer_transactions(&mut db)?; + let mut info = ctx.execute_sequencer_transactions(db)?; // 4. if mem pool transactions are requested we execute them if !ctx.attributes().no_tx_pool { let best_txs = best.best_transactions(pool, ctx.best_transaction_attributes()); - if let Some(cancelled) = - ctx.execute_best_transactions::<_, Pool>(&mut info, &mut db, best_txs)? - { - return Ok(cancelled) + if ctx.execute_best_transactions::<_, Pool>(&mut info, db, best_txs)?.is_some() { + return Ok(BuildOutcomeKind::Cancelled) } // check if the new payload is even more valuable @@ -273,16 +272,38 @@ where } } - let WithdrawalsOutcome { withdrawals_root, withdrawals } = - ctx.commit_withdrawals(&mut db)?; + let withdrawals_outcome = ctx.commit_withdrawals(db)?; + + Ok(BuildOutcomeKind::Better { payload: ExecutedPayload { info, withdrawals_outcome } }) + } + + /// Builds the payload on top of the state. + pub fn build( + self, + mut state: State, + ctx: OpPayloadBuilderCtx, + ) -> Result, PayloadBuilderError> + where + EvmConfig: ConfigureEvm

, + DB: Database + AsRef

, + P: StateRootProvider, + { + let ExecutedPayload { + info, + withdrawals_outcome: WithdrawalsOutcome { withdrawals, withdrawals_root }, + } = match self.execute(&mut state, &ctx)? { + BuildOutcomeKind::Better { payload } | BuildOutcomeKind::Freeze(payload) => payload, + BuildOutcomeKind::Cancelled => return Ok(BuildOutcomeKind::Cancelled), + BuildOutcomeKind::Aborted { fees } => return Ok(BuildOutcomeKind::Aborted { fees }), + }; // merge all transitions into bundle state, this would apply the withdrawal balance changes // and 4788 contract call - db.merge_transitions(BundleRetention::Reverts); + state.merge_transitions(BundleRetention::Reverts); let block_number = ctx.block_number(); let execution_outcome = ExecutionOutcome::new( - db.take_bundle(), + state.take_bundle(), vec![info.receipts.clone()].into(), block_number, Vec::new(), @@ -302,7 +323,7 @@ where // // calculate the state root let hashed_state = HashedPostState::from_bundle_state(&execution_outcome.state().state); let (state_root, trie_output) = { - db.database.as_ref().state_root_with_updates(hashed_state.clone()).inspect_err( + state.database.as_ref().state_root_with_updates(hashed_state.clone()).inspect_err( |err| { warn!(target: "payload_builder", parent_header=%ctx.parent().hash(), @@ -388,6 +409,24 @@ where Ok(BuildOutcomeKind::Better { payload }) } } + + /// Builds the payload and returns its [`ExecutionWitness`] based on the state after execution. + pub fn witness( + self, + state: &mut State, + ctx: &OpPayloadBuilderCtx, + ) -> Result + where + EvmConfig: ConfigureEvm

, + DB: Database + AsRef

, + P: StateProofProvider, + { + let _ = self.execute(state, ctx)?; + let ExecutionWitnessRecord { hashed_state, codes, keys } = + ExecutionWitnessRecord::from_executed_state(state); + let state = state.database.as_ref().witness(Default::default(), hashed_state)?; + Ok(ExecutionWitness { state: state.into_iter().collect(), codes, keys }) + } } /// A type that returns a the [`PayloadTransactions`] that should be included in the pool. @@ -411,6 +450,15 @@ impl OpPayloadTransactions for () { } } +/// Holds the state after execution +#[derive(Debug)] +pub struct ExecutedPayload { + /// Tracked execution info + pub info: ExecutionInfo, + /// Outcome after committing withdrawals. + pub withdrawals_outcome: WithdrawalsOutcome, +} + /// This acts as the container for executed transactions and its byproducts (receipts, gas used) #[derive(Default, Debug)] pub struct ExecutionInfo { @@ -725,13 +773,15 @@ where Ok(info) } - /// Executes the given best transactions and updates the execution info + /// Executes the given best transactions and updates the execution info. + /// + /// Returns `Ok(Some(())` if the job was cancelled. pub fn execute_best_transactions( &self, info: &mut ExecutionInfo, db: &mut State, mut best_txs: impl PayloadTransactions, - ) -> Result>, PayloadBuilderError> + ) -> Result, PayloadBuilderError> where DB: Database, Pool: TransactionPool, @@ -764,7 +814,7 @@ where // check if the job was cancelled, if so we can exit early if self.cancel.is_cancelled() { - return Ok(Some(BuildOutcomeKind::Cancelled)) + return Ok(Some(())) } // Configure the environment for the tx. From 2c04cef839b137d2a1652208c3c7b3c874c87f43 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Fri, 15 Nov 2024 17:01:36 +0100 Subject: [PATCH 2/2] move merge transitions --- crates/optimism/payload/src/builder.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/crates/optimism/payload/src/builder.rs b/crates/optimism/payload/src/builder.rs index 250c8980fae3..2542ff527df0 100644 --- a/crates/optimism/payload/src/builder.rs +++ b/crates/optimism/payload/src/builder.rs @@ -239,7 +239,7 @@ where /// Executes the payload and returns the outcome. pub fn execute( self, - db: &mut State, + state: &mut State, ctx: &OpPayloadBuilderCtx, ) -> Result, PayloadBuilderError> where @@ -250,18 +250,18 @@ where debug!(target: "payload_builder", id=%ctx.payload_id(), parent_header = ?ctx.parent().hash(), parent_number = ctx.parent().number, "building new payload"); // 1. apply eip-4788 pre block contract call - ctx.apply_pre_beacon_root_contract_call(db)?; + ctx.apply_pre_beacon_root_contract_call(state)?; // 2. ensure create2deployer is force deployed - ctx.ensure_create2_deployer(db)?; + ctx.ensure_create2_deployer(state)?; // 3. execute sequencer transactions - let mut info = ctx.execute_sequencer_transactions(db)?; + let mut info = ctx.execute_sequencer_transactions(state)?; // 4. if mem pool transactions are requested we execute them if !ctx.attributes().no_tx_pool { let best_txs = best.best_transactions(pool, ctx.best_transaction_attributes()); - if ctx.execute_best_transactions::<_, Pool>(&mut info, db, best_txs)?.is_some() { + if ctx.execute_best_transactions::<_, Pool>(&mut info, state, best_txs)?.is_some() { return Ok(BuildOutcomeKind::Cancelled) } @@ -272,7 +272,11 @@ where } } - let withdrawals_outcome = ctx.commit_withdrawals(db)?; + let withdrawals_outcome = ctx.commit_withdrawals(state)?; + + // merge all transitions into bundle state, this would apply the withdrawal balance changes + // and 4788 contract call + state.merge_transitions(BundleRetention::Reverts); Ok(BuildOutcomeKind::Better { payload: ExecutedPayload { info, withdrawals_outcome } }) } @@ -297,10 +301,6 @@ where BuildOutcomeKind::Aborted { fees } => return Ok(BuildOutcomeKind::Aborted { fees }), }; - // merge all transitions into bundle state, this would apply the withdrawal balance changes - // and 4788 contract call - state.merge_transitions(BundleRetention::Reverts); - let block_number = ctx.block_number(); let execution_outcome = ExecutionOutcome::new( state.take_bundle(),