From 998af3ac377daf6bf8c22e7532bd6d7ac0e64b3e Mon Sep 17 00:00:00 2001 From: 0xKitsune <0xkitsune@protonmail.com> Date: Tue, 10 Sep 2024 14:39:44 -0400 Subject: [PATCH 01/32] wip --- crates/optimism/payload/src/builder.rs | 114 ++++++++++++++++++++++--- 1 file changed, 101 insertions(+), 13 deletions(-) diff --git a/crates/optimism/payload/src/builder.rs b/crates/optimism/payload/src/builder.rs index 702b8a98ca98..4f572e40df4c 100644 --- a/crates/optimism/payload/src/builder.rs +++ b/crates/optimism/payload/src/builder.rs @@ -60,6 +60,76 @@ impl OptimismPayloadBuilder { pub const fn is_compute_pending_block(&self) -> bool { self.compute_pending_block } + + fn build_payload( + &self, + evm_config: EvmConfig, + args: BuildArguments, + _compute_pending_block: bool, + ) -> Result, PayloadBuilderError> { + let BuildArguments { client, pool, mut cached_reads, config, cancel, best_payload } = args; + + let mut db = self.init_db_state(&config.parent_block.hash())?; + + let PayloadConfig { + initialized_block_env, + initialized_cfg, + parent_block, + attributes, + chain_spec, + .. + } = config; + + debug!(target: "payload_builder", id=%attributes.payload_attributes.payload_id(), parent_hash = ?parent_block.hash(), parent_number = parent_block.number, "building new payload"); + } + + fn init_db_state( + &self, + block_hash: &Hash, + ) -> Result, PayloadBuilderError> { + let state_provider = client.state_by_block_hash(block_hash)?; + let state = StateProviderDatabase::new(state_provider); + State::builder().with_database_ref(cached_reads.as_db(state)).with_bundle_update().build() + } + + fn init_pre_block_state(&self) {} + + fn pre_block_calls( + &self, + db: &mut State, + attributes: &OptimismPayloadBuilderAttributes, + ) -> Result<(), PayloadBuilderError> { + pre_block_beacon_root_contract_call( + &mut db, + &evm_config, + &chain_spec, + &initialized_cfg, + &initialized_block_env, + attributes.payload_attributes.parent_beacon_block_root, + ) + .map_err(|err| { + warn!(target: "payload_builder", + parent_hash=%parent_block.hash(), + %err, + "failed to apply beacon root contract call for empty payload" + ); + PayloadBuilderError::Internal(err.into()) + })?; + + // Ensure that the create2deployer is force-deployed at the canyon transition. Optimism + // blocks will always have at least a single transaction in them (the L1 info transaction), + // so we can safely assume that this will always be triggered upon the transition and that + // the above check for empty blocks will never be hit on OP chains. + reth_evm_optimism::ensure_create2_deployer( + chain_spec.clone(), + attributes.payload_attributes.timestamp, + &mut db, + ) + .map_err(|err| { + warn!(target: "payload_builder", %err, "missing create2 deployer, skipping block."); + PayloadBuilderError::other(OptimismPayloadBuilderError::ForceCreate2DeployerFail) + })?; + } } /// Implementation of the [`PayloadBuilder`] trait for [`OptimismPayloadBuilder`]. @@ -76,6 +146,9 @@ where &self, args: BuildArguments, ) -> Result, PayloadBuilderError> { + //NOTE: make this a self.build_payload() function? + // self.build_payload() + optimism_payload(self.evm_config.clone(), args, self.compute_pending_block) } @@ -118,6 +191,8 @@ where /// Given build arguments including an Ethereum client, transaction pool, /// and configuration, this function creates a transaction payload. Returns /// a result indicating success with the payload or an error in case of failure. + +// NOTE: it seems we could make these functions impl on the struct itself? #[inline] pub(crate) fn optimism_payload( evm_config: EvmConfig, @@ -133,8 +208,10 @@ where let state_provider = client.state_by_block_hash(config.parent_block.hash())?; let state = StateProviderDatabase::new(state_provider); + let mut db = State::builder().with_database_ref(cached_reads.as_db(state)).with_bundle_update().build(); + let extra_data = config.extra_data(); let PayloadConfig { initialized_block_env, @@ -147,6 +224,8 @@ where debug!(target: "payload_builder", id=%attributes.payload_attributes.payload_id(), parent_hash = ?parent_block.hash(), parent_number = parent_block.number, "building new payload"); + // NOTE: + let mut cumulative_gas_used = 0; let block_gas_limit: u64 = attributes.gas_limit.unwrap_or_else(|| { initialized_block_env.gas_limit.try_into().unwrap_or(chain_spec.max_gas_limit) @@ -171,6 +250,7 @@ where ); // apply eip-4788 pre block contract call + // NOTE: pre_block_beacon_root_contract_call( &mut db, &evm_config, @@ -202,18 +282,19 @@ where PayloadBuilderError::other(OptimismPayloadBuilderError::ForceCreate2DeployerFail) })?; + // NOTE: let mut receipts = Vec::with_capacity(attributes.transactions.len()); for sequencer_tx in &attributes.transactions { // Check if the job was cancelled, if so we can exit early. if cancel.is_cancelled() { - return Ok(BuildOutcome::Cancelled) + return Ok(BuildOutcome::Cancelled); } // A sequencer's block should never contain blob transactions. if sequencer_tx.value().is_eip4844() { return Err(PayloadBuilderError::other( OptimismPayloadBuilderError::BlobTransactionRejected, - )) + )); } // Convert the transaction to a [TransactionSignedEcRecovered]. This is @@ -255,11 +336,11 @@ where match err { EVMError::Transaction(err) => { trace!(target: "payload_builder", %err, ?sequencer_tx, "Error in sequencer transaction, skipping."); - continue + continue; } err => { // this is an error that we should treat as fatal for this attempt - return Err(PayloadBuilderError::EvmExecutionError(err)) + return Err(PayloadBuilderError::EvmExecutionError(err)); } } } @@ -299,6 +380,8 @@ where } if !attributes.no_tx_pool { + // NOTE: looks like this is already ordered in best_txs.next(), we will likely have to do our ordering there + while let Some(pool_tx) = best_txs.next() { // ensure we still have capacity for this transaction if cumulative_gas_used + pool_tx.gas_limit() > block_gas_limit { @@ -306,18 +389,18 @@ where // invalid which also removes all dependent transaction from // the iterator before we can continue best_txs.mark_invalid(&pool_tx); - continue + continue; } // A sequencer's block should never contain blob or deposit transactions from the pool. if pool_tx.is_eip4844() || pool_tx.tx_type() == TxType::Deposit as u8 { best_txs.mark_invalid(&pool_tx); - continue + continue; } // check if the job was cancelled, if so we can exit early if cancel.is_cancelled() { - return Ok(BuildOutcome::Cancelled) + return Ok(BuildOutcome::Cancelled); } // convert tx to a signed transaction @@ -346,15 +429,18 @@ where best_txs.mark_invalid(&pool_tx); } - continue + continue; } err => { // this is an error that we should treat as fatal for this attempt - return Err(PayloadBuilderError::EvmExecutionError(err)) + return Err(PayloadBuilderError::EvmExecutionError(err)); } } } }; + + // NOTE: this is the same as above this could be more dry + // drop evm so db is released. drop(evm); // commit changes @@ -391,9 +477,10 @@ where // check if we have a better block if !is_better_payload(best_payload.as_ref(), total_fees) { // can skip building the block - return Ok(BuildOutcome::Aborted { fees: total_fees, cached_reads }) + return Ok(BuildOutcome::Aborted { fees: total_fees, cached_reads }); } + // NOTE: break new function let WithdrawalsOutcome { withdrawals_root, withdrawals } = commit_withdrawals( &mut db, &chain_spec, @@ -405,6 +492,7 @@ where // and 4788 contract call db.merge_transitions(BundleRetention::PlainState); + // NOTE: break new function let execution_outcome = ExecutionOutcome::new( db.take_bundle(), vec![receipts.clone()].into(), @@ -433,7 +521,7 @@ where })? }; - // create the block header + // NOTE: create new function for create header let transactions_root = proofs::calculate_transaction_root(&executed_txs); // initialize empty blob sidecars. There are no blob transactions on L2. @@ -480,13 +568,13 @@ where requests_root: None, }; - // seal the block + // NOTE: create new function to seal the block let block = Block { header, body: executed_txs, ommers: vec![], withdrawals, requests: None }; let sealed_block = block.seal_slow(); debug!(target: "payload_builder", ?sealed_block, "sealed built block"); - // create the executed block data + // NOTE: into impl for built payload let executed = ExecutedBlock { block: Arc::new(sealed_block.clone()), senders: Arc::new(executed_senders), From e988521039de51904a53de91058c457b18214ecd Mon Sep 17 00:00:00 2001 From: 0xKitsune <0xkitsune@protonmail.com> Date: Wed, 11 Sep 2024 13:25:51 -0400 Subject: [PATCH 02/32] wip --- crates/optimism/payload/src/builder.rs | 69 +++++++++++++++----------- 1 file changed, 40 insertions(+), 29 deletions(-) diff --git a/crates/optimism/payload/src/builder.rs b/crates/optimism/payload/src/builder.rs index 4f572e40df4c..e89cc51d6a9f 100644 --- a/crates/optimism/payload/src/builder.rs +++ b/crates/optimism/payload/src/builder.rs @@ -13,20 +13,20 @@ use reth_execution_types::ExecutionOutcome; use reth_payload_builder::error::PayloadBuilderError; use reth_primitives::{ constants::BEACON_NONCE, eip4844::calculate_excess_blob_gas, proofs, Block, Header, - IntoRecoveredTransaction, Receipt, TxType, EMPTY_OMMER_ROOT_HASH, + IntoRecoveredTransaction, Receipt, TxType, B256, EMPTY_OMMER_ROOT_HASH, }; -use reth_provider::StateProviderFactory; +use reth_provider::{StateProvider, StateProviderFactory}; use reth_revm::database::StateProviderDatabase; use reth_transaction_pool::{ noop::NoopTransactionPool, BestTransactionsAttributes, TransactionPool, }; use reth_trie::HashedPostState; use revm::{ - db::states::bundle_state::BundleRetention, + db::{states::bundle_state::BundleRetention, WrapDatabaseRef}, primitives::{EVMError, EnvWithHandlerCfg, InvalidTransaction, ResultAndState}, - DatabaseCommit, State, + Database, DatabaseCommit, State, }; -use std::sync::Arc; +use std::{fmt::Display, sync::Arc}; use tracing::{debug, trace, warn}; /// Optimism's payload builder @@ -61,16 +61,44 @@ impl OptimismPayloadBuilder { self.compute_pending_block } - fn build_payload( + fn build_payload( &self, evm_config: EvmConfig, args: BuildArguments, _compute_pending_block: bool, - ) -> Result, PayloadBuilderError> { + ) -> Result, PayloadBuilderError> + where + EvmConfig: ConfigureEvm, + Client: StateProviderFactory, + Pool: TransactionPool, + { let BuildArguments { client, pool, mut cached_reads, config, cancel, best_payload } = args; - let mut db = self.init_db_state(&config.parent_block.hash())?; + let state_provider = client.state_by_block_hash(config.parent_block.hash())?; + let state = StateProviderDatabase::new(state_provider); + let mut db = State::builder() + .with_database_ref(cached_reads.as_db(state)) + .with_bundle_update() + .build(); + + self.init_pre_block_state(config, evm_config, db)?; + + //TODO: construct block + todo!() + } + + fn init_pre_block_state( + &self, + payload_config: PayloadConfig, + evm_config: EvmConfig, + db: &mut revm::State, + ) -> Result<(), PayloadBuilderError> + where + DB: Database + DatabaseCommit, + DB::Error: Display, + EvmConfig: ConfigureEvm, + { let PayloadConfig { initialized_block_env, initialized_cfg, @@ -78,29 +106,12 @@ impl OptimismPayloadBuilder { attributes, chain_spec, .. - } = config; + } = payload_config; debug!(target: "payload_builder", id=%attributes.payload_attributes.payload_id(), parent_hash = ?parent_block.hash(), parent_number = parent_block.number, "building new payload"); - } - - fn init_db_state( - &self, - block_hash: &Hash, - ) -> Result, PayloadBuilderError> { - let state_provider = client.state_by_block_hash(block_hash)?; - let state = StateProviderDatabase::new(state_provider); - State::builder().with_database_ref(cached_reads.as_db(state)).with_bundle_update().build() - } - - fn init_pre_block_state(&self) {} - fn pre_block_calls( - &self, - db: &mut State, - attributes: &OptimismPayloadBuilderAttributes, - ) -> Result<(), PayloadBuilderError> { pre_block_beacon_root_contract_call( - &mut db, + db, &evm_config, &chain_spec, &initialized_cfg, @@ -123,12 +134,12 @@ impl OptimismPayloadBuilder { reth_evm_optimism::ensure_create2_deployer( chain_spec.clone(), attributes.payload_attributes.timestamp, - &mut db, + db, ) .map_err(|err| { warn!(target: "payload_builder", %err, "missing create2 deployer, skipping block."); PayloadBuilderError::other(OptimismPayloadBuilderError::ForceCreate2DeployerFail) - })?; + }) } } From feda8da08fd7a1c1544fd2161407a8d7f013b338 Mon Sep 17 00:00:00 2001 From: 0xKitsune <0xkitsune@protonmail.com> Date: Wed, 11 Sep 2024 14:37:06 -0400 Subject: [PATCH 03/32] adding op block attributes --- crates/optimism/payload/src/builder.rs | 205 ++++++++++++++++++++++++- 1 file changed, 199 insertions(+), 6 deletions(-) diff --git a/crates/optimism/payload/src/builder.rs b/crates/optimism/payload/src/builder.rs index e89cc51d6a9f..2f80a4b3e937 100644 --- a/crates/optimism/payload/src/builder.rs +++ b/crates/optimism/payload/src/builder.rs @@ -7,13 +7,14 @@ use crate::{ use alloy_primitives::U256; use reth_basic_payload_builder::*; use reth_chain_state::ExecutedBlock; -use reth_chainspec::{EthereumHardforks, OptimismHardfork}; +use reth_chainspec::{ChainSpec, EthereumHardforks, OptimismHardfork}; use reth_evm::{system_calls::pre_block_beacon_root_contract_call, ConfigureEvm}; use reth_execution_types::ExecutionOutcome; use reth_payload_builder::error::PayloadBuilderError; use reth_primitives::{ - constants::BEACON_NONCE, eip4844::calculate_excess_blob_gas, proofs, Block, Header, - IntoRecoveredTransaction, Receipt, TxType, B256, EMPTY_OMMER_ROOT_HASH, + constants::BEACON_NONCE, eip4844::calculate_excess_blob_gas, proofs, transaction::WithEncoded, + Address, Block, Header, IntoRecoveredTransaction, Receipt, TransactionSigned, TxType, B256, + EMPTY_OMMER_ROOT_HASH, }; use reth_provider::{StateProvider, StateProviderFactory}; use reth_revm::database::StateProviderDatabase; @@ -23,7 +24,10 @@ use reth_transaction_pool::{ use reth_trie::HashedPostState; use revm::{ db::{states::bundle_state::BundleRetention, WrapDatabaseRef}, - primitives::{EVMError, EnvWithHandlerCfg, InvalidTransaction, ResultAndState}, + primitives::{ + BlockEnv, CfgEnvWithHandlerCfg, EVMError, EnvWithHandlerCfg, InvalidTransaction, + ResultAndState, + }, Database, DatabaseCommit, State, }; use std::{fmt::Display, sync::Arc}; @@ -81,9 +85,9 @@ impl OptimismPayloadBuilder { .with_bundle_update() .build(); - self.init_pre_block_state(config, evm_config, db)?; + self.init_pre_block_state(config, evm_config, &mut db)?; - //TODO: construct block + // self.construct_block(); todo!() } @@ -141,6 +145,195 @@ impl OptimismPayloadBuilder { PayloadBuilderError::other(OptimismPayloadBuilderError::ForceCreate2DeployerFail) }) } + + fn construct_block( + &self, + payload_config: PayloadConfig, + pool: Pool, + chain_spec: &ChainSpec, + db: &mut revm::State, + cancel: Cancelled, + ) -> Result, PayloadBuilderError> + where + EvmConfig: ConfigureEvm, + Pool: TransactionPool, + DB: Database + DatabaseCommit, + DB::Error: Display, + { + let mut op_block_attributes = + OptimismBlockAttributes::new(&payload_config, self.evm_config.clone()); + + // Add sequencer transactions to the block + for sequencer_tx in &payload_config.attributes.transactions { + // Check if the job was cancelled, if so we can exit early. + if cancel.is_cancelled() { + return Ok(BuildOutcome::Cancelled); + } + + op_block_attributes.add_sequencer_transaction(sequencer_tx, db); + + // TODO: add payload attributes txs + // TODO: return some executedTransaction including the receipt, sender, and transaction + + //TODO: add transactions from the pool + } + + todo!() + } +} + +#[derive(Debug)] +pub struct OptimismBlockAttributes { + receipts: Vec>, + executed_txs: Vec, + executed_senders: Vec
, + is_regolith: bool, + block_gas_limit: u64, + base_fee: u64, + cumulative_gas_used: u64, + initialized_cfg: CfgEnvWithHandlerCfg, + initialized_block_env: BlockEnv, + evm_config: EvmConfig, +} + +impl OptimismBlockAttributes +where + EvmConfig: ConfigureEvm, +{ + pub fn new( + payload_config: &PayloadConfig, + evm_config: EvmConfig, + ) -> Self { + let attributes = &payload_config.attributes; + let initialized_block_env = payload_config.initialized_block_env.clone(); + + let is_regolith = payload_config.chain_spec.is_fork_active_at_timestamp( + OptimismHardfork::Regolith, + attributes.payload_attributes.timestamp, + ); + + let block_gas_limit: u64 = attributes.gas_limit.unwrap_or_else(|| { + initialized_block_env + .gas_limit + .try_into() + .unwrap_or(payload_config.chain_spec.max_gas_limit) + }); + + let base_fee = initialized_block_env.basefee.to::(); + + Self { + executed_senders: Vec::with_capacity(attributes.transactions.len()), + executed_txs: Vec::with_capacity(attributes.transactions.len()), + receipts: Vec::with_capacity(attributes.transactions.len()), + is_regolith, + block_gas_limit, + base_fee, + cumulative_gas_used: 0, + initialized_cfg: payload_config.initialized_cfg.clone(), + initialized_block_env, + evm_config, + } + } + + pub fn add_sequencer_transaction( + &mut self, + sequencer_tx: &WithEncoded, + db: &mut revm::State, + ) -> Result<(), PayloadBuilderError> + where + DB: Database + DatabaseCommit, + DB::Error: Display, + { + // Payload attributes transactions should never contain blob transactions. + if sequencer_tx.value().is_eip4844() { + return Err(PayloadBuilderError::other( + OptimismPayloadBuilderError::BlobTransactionRejected, + )); + } + + // Convert the transaction to a [TransactionSignedEcRecovered]. This is + // purely for the purposes of utilizing the `evm_config.tx_env`` function. + // Deposit transactions do not have signatures, so if the tx is a deposit, this + // will just pull in its `from` address. + let sequencer_tx = sequencer_tx.value().clone().try_into_ecrecovered().map_err(|_| { + PayloadBuilderError::other(OptimismPayloadBuilderError::TransactionEcRecoverFailed) + })?; + + // Cache the depositor account prior to the state transition for the deposit nonce. + // + // Note that this *only* needs to be done post-regolith hardfork, as deposit nonces + // were not introduced in Bedrock. In addition, regular transactions don't have deposit + // nonces, so we don't need to touch the DB for those. + let depositor = (self.is_regolith && sequencer_tx.is_deposit()) + .then(|| { + db.load_cache_account(sequencer_tx.signer()) + .map(|acc| acc.account_info().unwrap_or_default()) + }) + .transpose() + .map_err(|_| { + PayloadBuilderError::other(OptimismPayloadBuilderError::AccountLoadFailed( + sequencer_tx.signer(), + )) + })?; + + let env = EnvWithHandlerCfg::new_with_cfg_env( + self.initialized_cfg.clone(), + self.initialized_block_env.clone(), + self.evm_config.tx_env(&sequencer_tx), + ); + + // let mut evm = evm_config.evm_with_env(&mut db, env); + + // let ResultAndState { result, state } = match evm.transact() { + // Ok(res) => res, + // Err(err) => { + // match err { + // EVMError::Transaction(err) => { + // trace!(target: "payload_builder", %err, ?sequencer_tx, "Error in sequencer transaction, skipping."); + // continue; + // } + // err => { + // // this is an error that we should treat as fatal for this attempt + // return Err(PayloadBuilderError::EvmExecutionError(err)); + // } + // } + // } + // }; + + // // to release the db reference drop evm. + // drop(evm); + // // commit changes + // db.commit(state); + + // let gas_used = result.gas_used(); + + // // add gas used by the transaction to cumulative gas used, before creating the receipt + // cumulative_gas_used += gas_used; + + // // Push transaction changeset and calculate header bloom filter for receipt. + // receipts.push(Some(Receipt { + // tx_type: sequencer_tx.tx_type(), + // success: result.is_success(), + // cumulative_gas_used, + // logs: result.into_logs().into_iter().map(Into::into).collect(), + // deposit_nonce: depositor.map(|account| account.nonce), + // // The deposit receipt version was introduced in Canyon to indicate an update to how + // // receipt hashes should be computed when set. The state transition process + // // ensures this is only set for post-Canyon deposit transactions. + // deposit_receipt_version: chain_spec + // .is_fork_active_at_timestamp( + // OptimismHardfork::Canyon, + // attributes.payload_attributes.timestamp, + // ) + // .then_some(1), + // })); + + // // append sender and transaction to the respective lists + // executed_senders.push(sequencer_tx.signer()); + // executed_txs.push(sequencer_tx.into_signed()); + + todo!() + } } /// Implementation of the [`PayloadBuilder`] trait for [`OptimismPayloadBuilder`]. From 229a27af99a4cf89f00ee4cb671e3770f45987dc Mon Sep 17 00:00:00 2001 From: 0xKitsune <0xkitsune@protonmail.com> Date: Wed, 11 Sep 2024 16:42:55 -0400 Subject: [PATCH 04/32] wip --- crates/optimism/payload/src/builder.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/optimism/payload/src/builder.rs b/crates/optimism/payload/src/builder.rs index 2f80a4b3e937..bd39c6bb457b 100644 --- a/crates/optimism/payload/src/builder.rs +++ b/crates/optimism/payload/src/builder.rs @@ -173,7 +173,6 @@ impl OptimismPayloadBuilder { op_block_attributes.add_sequencer_transaction(sequencer_tx, db); // TODO: add payload attributes txs - // TODO: return some executedTransaction including the receipt, sender, and transaction //TODO: add transactions from the pool } @@ -276,13 +275,14 @@ where )) })?; + // TODO: abstract into function let env = EnvWithHandlerCfg::new_with_cfg_env( self.initialized_cfg.clone(), self.initialized_block_env.clone(), self.evm_config.tx_env(&sequencer_tx), ); - // let mut evm = evm_config.evm_with_env(&mut db, env); + let mut evm = self.evm_config.evm_with_env(db, env); // let ResultAndState { result, state } = match evm.transact() { // Ok(res) => res, From b4a0552b34c8618cb9882ae96398612abd9aeaf4 Mon Sep 17 00:00:00 2001 From: 0xKitsune <0xkitsune@protonmail.com> Date: Thu, 12 Sep 2024 13:47:57 -0400 Subject: [PATCH 05/32] wip --- crates/optimism/payload/src/builder.rs | 85 ++++++++++++++++---------- 1 file changed, 53 insertions(+), 32 deletions(-) diff --git a/crates/optimism/payload/src/builder.rs b/crates/optimism/payload/src/builder.rs index bd39c6bb457b..680d21feebc2 100644 --- a/crates/optimism/payload/src/builder.rs +++ b/crates/optimism/payload/src/builder.rs @@ -13,10 +13,10 @@ use reth_execution_types::ExecutionOutcome; use reth_payload_builder::error::PayloadBuilderError; use reth_primitives::{ constants::BEACON_NONCE, eip4844::calculate_excess_blob_gas, proofs, transaction::WithEncoded, - Address, Block, Header, IntoRecoveredTransaction, Receipt, TransactionSigned, TxType, B256, - EMPTY_OMMER_ROOT_HASH, + Address, Block, Header, IntoRecoveredTransaction, Receipt, TransactionSigned, + TransactionSignedEcRecovered, TxType, B256, EMPTY_OMMER_ROOT_HASH, }; -use reth_provider::{StateProvider, StateProviderFactory}; +use reth_provider::{ProviderError, StateProvider, StateProviderFactory}; use reth_revm::database::StateProviderDatabase; use reth_transaction_pool::{ noop::NoopTransactionPool, BestTransactionsAttributes, TransactionPool, @@ -99,7 +99,7 @@ impl OptimismPayloadBuilder { db: &mut revm::State, ) -> Result<(), PayloadBuilderError> where - DB: Database + DatabaseCommit, + DB: revm::Database, DB::Error: Display, EvmConfig: ConfigureEvm, { @@ -170,13 +170,20 @@ impl OptimismPayloadBuilder { return Ok(BuildOutcome::Cancelled); } - op_block_attributes.add_sequencer_transaction(sequencer_tx, db); - - // TODO: add payload attributes txs + // TODO: handle and check the result, continue if evmerror transaction - //TODO: add transactions from the pool + + let ResultAndState { result, state } = op_block_attributes.add_sequencer_transaction(sequencer_tx, db) + .map_err(|err| match err { + EVMError::Transaction(inner_err) => { + trace!(target: "payload_builder", %inner_err, ?sequencer_tx, "Error in sequencer transaction, skipping."); + None + } + other => Some(PayloadBuilderError::EvmExecutionError(other_err)), + })?; } + //TODO: add transactions from the pool todo!() } } @@ -240,7 +247,7 @@ where db: &mut revm::State, ) -> Result<(), PayloadBuilderError> where - DB: Database + DatabaseCommit, + DB: revm::Database, DB::Error: Display, { // Payload attributes transactions should never contain blob transactions. @@ -275,30 +282,22 @@ where )) })?; - // TODO: abstract into function - let env = EnvWithHandlerCfg::new_with_cfg_env( + // let result_and_state = evm_transact( + // &sequencer_tx, + // self.initialized_cfg.clone(), + // self.initialized_block_env.clone(), + // self.evm_config.clone(), + // db, + // ) + // .map_err(|err| PayloadBuilderError::EvmExecutionError(err))?; + + let result_and_state = evm_transact( + &sequencer_tx, self.initialized_cfg.clone(), self.initialized_block_env.clone(), - self.evm_config.tx_env(&sequencer_tx), - ); - - let mut evm = self.evm_config.evm_with_env(db, env); - - // let ResultAndState { result, state } = match evm.transact() { - // Ok(res) => res, - // Err(err) => { - // match err { - // EVMError::Transaction(err) => { - // trace!(target: "payload_builder", %err, ?sequencer_tx, "Error in sequencer transaction, skipping."); - // continue; - // } - // err => { - // // this is an error that we should treat as fatal for this attempt - // return Err(PayloadBuilderError::EvmExecutionError(err)); - // } - // } - // } - // }; + self.evm_config.clone(), + db, + ).map_err(PayloadBuilderError::EvmExecutionError)?; // // to release the db reference drop evm. // drop(evm); @@ -387,6 +386,29 @@ where } } +fn evm_transact( + transaction: &TransactionSignedEcRecovered, + initialized_cfg: CfgEnvWithHandlerCfg, + initialized_block_env: BlockEnv, + evm_config: EvmConfig, + db: &mut revm::State, +) -> Result> +where + DB: revm::Database, + DB::Error: Display, + EvmConfig: ConfigureEvm, +{ + let env = EnvWithHandlerCfg::new_with_cfg_env( + initialized_cfg, + initialized_block_env, + evm_config.tx_env(transaction), + ); + let mut evm = evm_config.evm_with_env(db, env); + + evm.transact() + +} + /// Constructs an Ethereum transaction payload from the transactions sent through the /// Payload attributes by the sequencer. If the `no_tx_pool` argument is passed in /// the payload attributes, the transaction pool will be ignored and the only transactions @@ -396,7 +418,6 @@ where /// and configuration, this function creates a transaction payload. Returns /// a result indicating success with the payload or an error in case of failure. -// NOTE: it seems we could make these functions impl on the struct itself? #[inline] pub(crate) fn optimism_payload( evm_config: EvmConfig, From 10e6675287e1a2bc5e8ecb188b59dd1996c22c53 Mon Sep 17 00:00:00 2001 From: 0xKitsune <0xkitsune@protonmail.com> Date: Thu, 12 Sep 2024 14:14:28 -0400 Subject: [PATCH 06/32] updated build payload --- crates/optimism/payload/src/builder.rs | 131 ++++++++++--------------- 1 file changed, 53 insertions(+), 78 deletions(-) diff --git a/crates/optimism/payload/src/builder.rs b/crates/optimism/payload/src/builder.rs index 680d21feebc2..4bb220296445 100644 --- a/crates/optimism/payload/src/builder.rs +++ b/crates/optimism/payload/src/builder.rs @@ -85,16 +85,13 @@ impl OptimismPayloadBuilder { .with_bundle_update() .build(); - self.init_pre_block_state(config, evm_config, &mut db)?; - - // self.construct_block(); - - todo!() + self.init_pre_block_state(&config, evm_config, &mut db)?; + self.construct_block(&config, pool, &mut db, cancel) } fn init_pre_block_state( &self, - payload_config: PayloadConfig, + payload_config: &PayloadConfig, evm_config: EvmConfig, db: &mut revm::State, ) -> Result<(), PayloadBuilderError> @@ -148,17 +145,15 @@ impl OptimismPayloadBuilder { fn construct_block( &self, - payload_config: PayloadConfig, + payload_config: &PayloadConfig, pool: Pool, - chain_spec: &ChainSpec, db: &mut revm::State, cancel: Cancelled, ) -> Result, PayloadBuilderError> where EvmConfig: ConfigureEvm, Pool: TransactionPool, - DB: Database + DatabaseCommit, - DB::Error: Display, + DB: revm::Database, { let mut op_block_attributes = OptimismBlockAttributes::new(&payload_config, self.evm_config.clone()); @@ -170,17 +165,17 @@ impl OptimismPayloadBuilder { return Ok(BuildOutcome::Cancelled); } - // TODO: handle and check the result, continue if evmerror transaction - - - let ResultAndState { result, state } = op_block_attributes.add_sequencer_transaction(sequencer_tx, db) - .map_err(|err| match err { - EVMError::Transaction(inner_err) => { - trace!(target: "payload_builder", %inner_err, ?sequencer_tx, "Error in sequencer transaction, skipping."); - None + if let Err(err) = op_block_attributes.add_sequencer_transaction(sequencer_tx, db) { + match err { + PayloadBuilderError::EvmExecutionError(EVMError::Transaction(err)) => { + trace!(target: "payload_builder", %err, ?sequencer_tx, "Error in sequencer transaction, skipping."); + continue; + } + other => { + return Err(other); + } } - other => Some(PayloadBuilderError::EvmExecutionError(other_err)), - })?; + } } //TODO: add transactions from the pool @@ -189,7 +184,7 @@ impl OptimismPayloadBuilder { } #[derive(Debug)] -pub struct OptimismBlockAttributes { +pub struct OptimismBlockAttributes<'a, EvmConfig: ConfigureEvm> { receipts: Vec>, executed_txs: Vec, executed_senders: Vec
, @@ -197,17 +192,16 @@ pub struct OptimismBlockAttributes { block_gas_limit: u64, base_fee: u64, cumulative_gas_used: u64, - initialized_cfg: CfgEnvWithHandlerCfg, - initialized_block_env: BlockEnv, evm_config: EvmConfig, + payload_config: &'a PayloadConfig, } -impl OptimismBlockAttributes +impl<'a, EvmConfig> OptimismBlockAttributes<'a, EvmConfig> where EvmConfig: ConfigureEvm, { pub fn new( - payload_config: &PayloadConfig, + payload_config: &'a PayloadConfig, evm_config: EvmConfig, ) -> Self { let attributes = &payload_config.attributes; @@ -235,9 +229,8 @@ where block_gas_limit, base_fee, cumulative_gas_used: 0, - initialized_cfg: payload_config.initialized_cfg.clone(), - initialized_block_env, evm_config, + payload_config, } } @@ -247,8 +240,7 @@ where db: &mut revm::State, ) -> Result<(), PayloadBuilderError> where - DB: revm::Database, - DB::Error: Display, + DB: revm::Database, { // Payload attributes transactions should never contain blob transactions. if sequencer_tx.value().is_eip4844() { @@ -282,56 +274,41 @@ where )) })?; - // let result_and_state = evm_transact( - // &sequencer_tx, - // self.initialized_cfg.clone(), - // self.initialized_block_env.clone(), - // self.evm_config.clone(), - // db, - // ) - // .map_err(|err| PayloadBuilderError::EvmExecutionError(err))?; - - let result_and_state = evm_transact( + let ResultAndState { result, state } = evm_transact( &sequencer_tx, - self.initialized_cfg.clone(), - self.initialized_block_env.clone(), + self.payload_config.initialized_cfg.clone(), + self.payload_config.initialized_block_env.clone(), self.evm_config.clone(), db, - ).map_err(PayloadBuilderError::EvmExecutionError)?; - - // // to release the db reference drop evm. - // drop(evm); - // // commit changes - // db.commit(state); - - // let gas_used = result.gas_used(); - - // // add gas used by the transaction to cumulative gas used, before creating the receipt - // cumulative_gas_used += gas_used; - - // // Push transaction changeset and calculate header bloom filter for receipt. - // receipts.push(Some(Receipt { - // tx_type: sequencer_tx.tx_type(), - // success: result.is_success(), - // cumulative_gas_used, - // logs: result.into_logs().into_iter().map(Into::into).collect(), - // deposit_nonce: depositor.map(|account| account.nonce), - // // The deposit receipt version was introduced in Canyon to indicate an update to how - // // receipt hashes should be computed when set. The state transition process - // // ensures this is only set for post-Canyon deposit transactions. - // deposit_receipt_version: chain_spec - // .is_fork_active_at_timestamp( - // OptimismHardfork::Canyon, - // attributes.payload_attributes.timestamp, - // ) - // .then_some(1), - // })); - - // // append sender and transaction to the respective lists - // executed_senders.push(sequencer_tx.signer()); - // executed_txs.push(sequencer_tx.into_signed()); + ) + .map_err(PayloadBuilderError::EvmExecutionError)?; - todo!() + // commit changes + db.commit(state); + + self.cumulative_gas_used += result.gas_used(); + + let receipt = Receipt { + tx_type: sequencer_tx.tx_type(), + success: result.is_success(), + cumulative_gas_used: self.cumulative_gas_used, + logs: result.into_logs().into_iter().map(Into::into).collect(), + deposit_nonce: depositor.map(|account| account.nonce), + deposit_receipt_version: self + .payload_config + .chain_spec + .is_fork_active_at_timestamp( + OptimismHardfork::Canyon, + self.payload_config.attributes.payload_attributes.timestamp, + ) + .then_some(1), + }; + + self.receipts.push(Some(receipt)); + self.executed_senders.push(sequencer_tx.signer()); + self.executed_txs.push(sequencer_tx.into_signed()); + + Ok(()) } } @@ -394,8 +371,7 @@ fn evm_transact( db: &mut revm::State, ) -> Result> where - DB: revm::Database, - DB::Error: Display, + DB: revm::Database, EvmConfig: ConfigureEvm, { let env = EnvWithHandlerCfg::new_with_cfg_env( @@ -406,7 +382,6 @@ where let mut evm = evm_config.evm_with_env(db, env); evm.transact() - } /// Constructs an Ethereum transaction payload from the transactions sent through the From 2b397509dbc1b8434463dfb8274289ef9f089cfe Mon Sep 17 00:00:00 2001 From: 0xKitsune <0xkitsune@protonmail.com> Date: Thu, 12 Sep 2024 14:16:59 -0400 Subject: [PATCH 07/32] update to use transact commit --- crates/optimism/payload/src/builder.rs | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/crates/optimism/payload/src/builder.rs b/crates/optimism/payload/src/builder.rs index 4bb220296445..ae1559ab5691 100644 --- a/crates/optimism/payload/src/builder.rs +++ b/crates/optimism/payload/src/builder.rs @@ -25,8 +25,8 @@ use reth_trie::HashedPostState; use revm::{ db::{states::bundle_state::BundleRetention, WrapDatabaseRef}, primitives::{ - BlockEnv, CfgEnvWithHandlerCfg, EVMError, EnvWithHandlerCfg, InvalidTransaction, - ResultAndState, + BlockEnv, CfgEnvWithHandlerCfg, EVMError, EnvWithHandlerCfg, ExecutionResult, + InvalidTransaction, ResultAndState, }, Database, DatabaseCommit, State, }; @@ -274,7 +274,7 @@ where )) })?; - let ResultAndState { result, state } = evm_transact( + let execution_result = evm_transact_commit( &sequencer_tx, self.payload_config.initialized_cfg.clone(), self.payload_config.initialized_block_env.clone(), @@ -283,16 +283,13 @@ where ) .map_err(PayloadBuilderError::EvmExecutionError)?; - // commit changes - db.commit(state); - - self.cumulative_gas_used += result.gas_used(); + self.cumulative_gas_used += execution_result.gas_used(); let receipt = Receipt { tx_type: sequencer_tx.tx_type(), - success: result.is_success(), + success: execution_result.is_success(), cumulative_gas_used: self.cumulative_gas_used, - logs: result.into_logs().into_iter().map(Into::into).collect(), + logs: execution_result.into_logs().into_iter().map(Into::into).collect(), deposit_nonce: depositor.map(|account| account.nonce), deposit_receipt_version: self .payload_config @@ -363,13 +360,13 @@ where } } -fn evm_transact( +fn evm_transact_commit( transaction: &TransactionSignedEcRecovered, initialized_cfg: CfgEnvWithHandlerCfg, initialized_block_env: BlockEnv, evm_config: EvmConfig, db: &mut revm::State, -) -> Result> +) -> Result> where DB: revm::Database, EvmConfig: ConfigureEvm, @@ -381,7 +378,7 @@ where ); let mut evm = evm_config.evm_with_env(db, env); - evm.transact() + evm.transact_commit() } /// Constructs an Ethereum transaction payload from the transactions sent through the From 35998af755e12dcc3e1ec92a32484f07098d65c8 Mon Sep 17 00:00:00 2001 From: 0xKitsune <0xkitsune@protonmail.com> Date: Thu, 12 Sep 2024 14:18:30 -0400 Subject: [PATCH 08/32] update to use build payload --- crates/optimism/payload/src/builder.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/crates/optimism/payload/src/builder.rs b/crates/optimism/payload/src/builder.rs index ae1559ab5691..8c21bcfbc6ab 100644 --- a/crates/optimism/payload/src/builder.rs +++ b/crates/optimism/payload/src/builder.rs @@ -323,10 +323,7 @@ where &self, args: BuildArguments, ) -> Result, PayloadBuilderError> { - //NOTE: make this a self.build_payload() function? - // self.build_payload() - - optimism_payload(self.evm_config.clone(), args, self.compute_pending_block) + self.build_payload(self.evm_config.clone(), args, self.compute_pending_block) } fn on_missing_payload( @@ -354,7 +351,8 @@ where cancel: Default::default(), best_payload: None, }; - optimism_payload(self.evm_config.clone(), args, false)? + + self.build_payload(self.evm_config.clone(), args, false)? .into_payload() .ok_or_else(|| PayloadBuilderError::MissingPayload) } From de0cf38d455ec2338b46d0ff427e6318c910f815 Mon Sep 17 00:00:00 2001 From: 0xKitsune <0xkitsune@protonmail.com> Date: Thu, 12 Sep 2024 14:53:41 -0400 Subject: [PATCH 09/32] updating cancel logic --- crates/optimism/payload/src/builder.rs | 109 +++++++++++++++---------- 1 file changed, 66 insertions(+), 43 deletions(-) diff --git a/crates/optimism/payload/src/builder.rs b/crates/optimism/payload/src/builder.rs index 8c21bcfbc6ab..615fbdaa4a35 100644 --- a/crates/optimism/payload/src/builder.rs +++ b/crates/optimism/payload/src/builder.rs @@ -86,10 +86,11 @@ impl OptimismPayloadBuilder { .build(); self.init_pre_block_state(&config, evm_config, &mut db)?; + // NOTE: if cancelled, cancel self.construct_block(&config, pool, &mut db, cancel) } - fn init_pre_block_state( + pub fn init_pre_block_state( &self, payload_config: &PayloadConfig, evm_config: EvmConfig, @@ -156,29 +157,16 @@ impl OptimismPayloadBuilder { DB: revm::Database, { let mut op_block_attributes = - OptimismBlockAttributes::new(&payload_config, self.evm_config.clone()); + OptimismBlockAttributes::new(&payload_config, self.evm_config.clone(), cancel); - // Add sequencer transactions to the block - for sequencer_tx in &payload_config.attributes.transactions { - // Check if the job was cancelled, if so we can exit early. - if cancel.is_cancelled() { - return Ok(BuildOutcome::Cancelled); - } + //TODO: add transactions from the pool - if let Err(err) = op_block_attributes.add_sequencer_transaction(sequencer_tx, db) { - match err { - PayloadBuilderError::EvmExecutionError(EVMError::Transaction(err)) => { - trace!(target: "payload_builder", %err, ?sequencer_tx, "Error in sequencer transaction, skipping."); - continue; - } - other => { - return Err(other); - } - } - } - } + op_block_attributes + .add_sequencer_transactions(&payload_config.attributes.transactions, db)?; - //TODO: add transactions from the pool + //TODO: add pooled transactions + + // TODO: into built payload todo!() } } @@ -194,6 +182,7 @@ pub struct OptimismBlockAttributes<'a, EvmConfig: ConfigureEvm> { cumulative_gas_used: u64, evm_config: EvmConfig, payload_config: &'a PayloadConfig, + cancel: Cancelled, } impl<'a, EvmConfig> OptimismBlockAttributes<'a, EvmConfig> @@ -234,48 +223,82 @@ where } } - pub fn add_sequencer_transaction( + pub fn add_sequencer_transactions( &mut self, - sequencer_tx: &WithEncoded, + transactions: &[WithEncoded], db: &mut revm::State, + cancel: &Cancelled, ) -> Result<(), PayloadBuilderError> where DB: revm::Database, { - // Payload attributes transactions should never contain blob transactions. - if sequencer_tx.value().is_eip4844() { - return Err(PayloadBuilderError::other( - OptimismPayloadBuilderError::BlobTransactionRejected, - )); + // Add sequencer transactions to the block + for sequencer_tx in transactions { + // Check if the job was cancelled, if so we can exit early. + if cancel.is_cancelled() { + return Err(PayloadBuilderError::other()); + } + + // Payload attributes transactions should never contain blob transactions. + if sequencer_tx.value().is_eip4844() { + return Err(PayloadBuilderError::other( + OptimismPayloadBuilderError::BlobTransactionRejected, + )); + } + + // Convert the transaction to a [TransactionSignedEcRecovered]. This is + // purely for the purposes of utilizing the `evm_config.tx_env`` function. + // Deposit transactions do not have signatures, so if the tx is a deposit, this + // will just pull in its `from` address. + let sequencer_tx = + sequencer_tx.value().clone().try_into_ecrecovered().map_err(|_| { + PayloadBuilderError::other( + OptimismPayloadBuilderError::TransactionEcRecoverFailed, + ) + })?; + + if let Err(err) = self.insert_transaction(&sequencer_tx, db) { + match err { + PayloadBuilderError::EvmExecutionError(EVMError::Transaction(err)) => { + trace!(target: "payload_builder", %err, ?sequencer_tx, "Error in sequencer transaction, skipping."); + continue; + } + other => { + return Err(other); + } + } + } } - // Convert the transaction to a [TransactionSignedEcRecovered]. This is - // purely for the purposes of utilizing the `evm_config.tx_env`` function. - // Deposit transactions do not have signatures, so if the tx is a deposit, this - // will just pull in its `from` address. - let sequencer_tx = sequencer_tx.value().clone().try_into_ecrecovered().map_err(|_| { - PayloadBuilderError::other(OptimismPayloadBuilderError::TransactionEcRecoverFailed) - })?; + Ok(()) + } + pub fn insert_transaction( + &mut self, + tx: &TransactionSignedEcRecovered, + db: &mut revm::State, + ) -> Result<(), PayloadBuilderError> + where + DB: revm::Database, + { // Cache the depositor account prior to the state transition for the deposit nonce. // // Note that this *only* needs to be done post-regolith hardfork, as deposit nonces // were not introduced in Bedrock. In addition, regular transactions don't have deposit // nonces, so we don't need to touch the DB for those. - let depositor = (self.is_regolith && sequencer_tx.is_deposit()) + let depositor = (self.is_regolith && tx.is_deposit()) .then(|| { - db.load_cache_account(sequencer_tx.signer()) - .map(|acc| acc.account_info().unwrap_or_default()) + db.load_cache_account(tx.signer()).map(|acc| acc.account_info().unwrap_or_default()) }) .transpose() .map_err(|_| { PayloadBuilderError::other(OptimismPayloadBuilderError::AccountLoadFailed( - sequencer_tx.signer(), + tx.signer(), )) })?; let execution_result = evm_transact_commit( - &sequencer_tx, + &tx, self.payload_config.initialized_cfg.clone(), self.payload_config.initialized_block_env.clone(), self.evm_config.clone(), @@ -286,7 +309,7 @@ where self.cumulative_gas_used += execution_result.gas_used(); let receipt = Receipt { - tx_type: sequencer_tx.tx_type(), + tx_type: tx.tx_type(), success: execution_result.is_success(), cumulative_gas_used: self.cumulative_gas_used, logs: execution_result.into_logs().into_iter().map(Into::into).collect(), @@ -302,8 +325,8 @@ where }; self.receipts.push(Some(receipt)); - self.executed_senders.push(sequencer_tx.signer()); - self.executed_txs.push(sequencer_tx.into_signed()); + self.executed_senders.push(tx.signer()); + self.executed_txs.push(tx.to_owned().into_signed()); Ok(()) } From 1553f37f8fbba90ec727aef49fa2250874e98d37 Mon Sep 17 00:00:00 2001 From: 0xKitsune <0xkitsune@protonmail.com> Date: Thu, 12 Sep 2024 15:04:47 -0400 Subject: [PATCH 10/32] handle build outcome cancelled --- crates/optimism/payload/src/builder.rs | 22 +++++++++++++--------- crates/payload/builder/src/error.rs | 3 +++ 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/crates/optimism/payload/src/builder.rs b/crates/optimism/payload/src/builder.rs index 615fbdaa4a35..58e11caca9ed 100644 --- a/crates/optimism/payload/src/builder.rs +++ b/crates/optimism/payload/src/builder.rs @@ -86,8 +86,12 @@ impl OptimismPayloadBuilder { .build(); self.init_pre_block_state(&config, evm_config, &mut db)?; - // NOTE: if cancelled, cancel - self.construct_block(&config, pool, &mut db, cancel) + + match self.construct_block(&config, pool, &mut db, cancel) { + Ok(outcome) => Ok(outcome), + Err(PayloadBuilderError::BuildOutcomeCancelled) => Ok(BuildOutcome::Cancelled), + Err(err) => Err(err), + } } pub fn init_pre_block_state( @@ -157,12 +161,13 @@ impl OptimismPayloadBuilder { DB: revm::Database, { let mut op_block_attributes = - OptimismBlockAttributes::new(&payload_config, self.evm_config.clone(), cancel); + OptimismBlockAttributes::new(&payload_config, self.evm_config.clone()); - //TODO: add transactions from the pool - - op_block_attributes - .add_sequencer_transactions(&payload_config.attributes.transactions, db)?; + op_block_attributes.add_sequencer_transactions( + &payload_config.attributes.transactions, + db, + &cancel, + )?; //TODO: add pooled transactions @@ -182,7 +187,6 @@ pub struct OptimismBlockAttributes<'a, EvmConfig: ConfigureEvm> { cumulative_gas_used: u64, evm_config: EvmConfig, payload_config: &'a PayloadConfig, - cancel: Cancelled, } impl<'a, EvmConfig> OptimismBlockAttributes<'a, EvmConfig> @@ -236,7 +240,7 @@ where for sequencer_tx in transactions { // Check if the job was cancelled, if so we can exit early. if cancel.is_cancelled() { - return Err(PayloadBuilderError::other()); + return Err(PayloadBuilderError::BuildOutcomeCancelled); } // Payload attributes transactions should never contain blob transactions. diff --git a/crates/payload/builder/src/error.rs b/crates/payload/builder/src/error.rs index 8c430000f7c3..ef3874e74feb 100644 --- a/crates/payload/builder/src/error.rs +++ b/crates/payload/builder/src/error.rs @@ -14,6 +14,9 @@ pub enum PayloadBuilderError { /// An oneshot channels has been closed. #[error("sender has been dropped")] ChannelClosed, + /// Build cancelled + #[error("build outcome cancelled")] + BuildOutcomeCancelled, /// If there's no payload to resolve. #[error("missing payload")] MissingPayload, From fce950ef256ad88fc4c222fb709088f7af655aec Mon Sep 17 00:00:00 2001 From: 0xKitsune <0xkitsune@protonmail.com> Date: Thu, 12 Sep 2024 15:23:47 -0400 Subject: [PATCH 11/32] add pooled transactions to the block --- crates/optimism/payload/src/builder.rs | 105 ++++++++++++++++++++++++- 1 file changed, 101 insertions(+), 4 deletions(-) diff --git a/crates/optimism/payload/src/builder.rs b/crates/optimism/payload/src/builder.rs index 58e11caca9ed..92baea5976d1 100644 --- a/crates/optimism/payload/src/builder.rs +++ b/crates/optimism/payload/src/builder.rs @@ -163,13 +163,15 @@ impl OptimismPayloadBuilder { let mut op_block_attributes = OptimismBlockAttributes::new(&payload_config, self.evm_config.clone()); + // add sequencer transactions to block op_block_attributes.add_sequencer_transactions( &payload_config.attributes.transactions, db, &cancel, )?; - //TODO: add pooled transactions + // add pooled transactions to block + op_block_attributes.add_pooled_transactions(&pool, db, &cancel)?; // TODO: into built payload todo!() @@ -184,6 +186,7 @@ pub struct OptimismBlockAttributes<'a, EvmConfig: ConfigureEvm> { is_regolith: bool, block_gas_limit: u64, base_fee: u64, + total_fees: U256, cumulative_gas_used: u64, evm_config: EvmConfig, payload_config: &'a PayloadConfig, @@ -221,6 +224,7 @@ where is_regolith, block_gas_limit, base_fee, + total_fees: U256::ZERO, cumulative_gas_used: 0, evm_config, payload_config, @@ -261,7 +265,7 @@ where ) })?; - if let Err(err) = self.insert_transaction(&sequencer_tx, db) { + if let Err(err) = self.insert_sequencer_transaction(&sequencer_tx, db) { match err { PayloadBuilderError::EvmExecutionError(EVMError::Transaction(err)) => { trace!(target: "payload_builder", %err, ?sequencer_tx, "Error in sequencer transaction, skipping."); @@ -277,7 +281,58 @@ where Ok(()) } - pub fn insert_transaction( + pub fn add_pooled_transactions( + &mut self, + pool: &Pool, + db: &mut revm::State, + cancel: &Cancelled, + ) -> Result<(), PayloadBuilderError> + where + DB: revm::Database, + Pool: TransactionPool, + { + if self.payload_config.attributes.no_tx_pool { + return Ok(()); + } + + let mut best_txs = pool.best_transactions_with_attributes(BestTransactionsAttributes::new( + self.base_fee, + self.payload_config + .initialized_block_env + .get_blob_gasprice() + .map(|gasprice| gasprice as u64), + )); + + while let Some(pool_tx) = best_txs.next() { + // ensure we still have capacity for this transaction + if self.cumulative_gas_used + pool_tx.gas_limit() > self.block_gas_limit { + // we can't fit this transaction into the block, so we need to mark it as + // invalid which also removes all dependent transaction from + // the iterator before we can continue + best_txs.mark_invalid(&pool_tx); + continue; + } + + // A sequencer's block should never contain blob or deposit transactions from the pool. + if pool_tx.is_eip4844() || pool_tx.tx_type() == TxType::Deposit as u8 { + best_txs.mark_invalid(&pool_tx); + continue; + } + + // Check if the job was cancelled, if so we can exit early. + if cancel.is_cancelled() { + return Err(PayloadBuilderError::BuildOutcomeCancelled); + } + + // convert tx to a signed transaction + let tx = pool_tx.to_recovered_transaction(); + self.insert_pooled_transaction(&tx, db)?; + } + + Ok(()) + } + + pub fn insert_sequencer_transaction( &mut self, tx: &TransactionSignedEcRecovered, db: &mut revm::State, @@ -302,7 +357,7 @@ where })?; let execution_result = evm_transact_commit( - &tx, + tx, self.payload_config.initialized_cfg.clone(), self.payload_config.initialized_block_env.clone(), self.evm_config.clone(), @@ -334,6 +389,48 @@ where Ok(()) } + + pub fn insert_pooled_transaction( + &mut self, + tx: &TransactionSignedEcRecovered, + db: &mut revm::State, + ) -> Result<(), PayloadBuilderError> + where + DB: revm::Database, + { + let execution_result = evm_transact_commit( + tx, + self.payload_config.initialized_cfg.clone(), + self.payload_config.initialized_block_env.clone(), + self.evm_config.clone(), + db, + ) + .map_err(PayloadBuilderError::EvmExecutionError)?; + + let gas_used = execution_result.gas_used(); + self.cumulative_gas_used += gas_used; + + let receipt = Receipt { + tx_type: tx.tx_type(), + success: execution_result.is_success(), + cumulative_gas_used: self.cumulative_gas_used, + logs: execution_result.into_logs().into_iter().map(Into::into).collect(), + deposit_nonce: None, + deposit_receipt_version: None, + }; + + // update add to total fees + let miner_fee = tx + .effective_tip_per_gas(Some(self.base_fee)) + .expect("fee is always valid; execution succeeded"); + self.total_fees += U256::from(miner_fee) * U256::from(gas_used); + + self.receipts.push(Some(receipt)); + self.executed_senders.push(tx.signer()); + self.executed_txs.push(tx.to_owned().into_signed()); + + Ok(()) + } } /// Implementation of the [`PayloadBuilder`] trait for [`OptimismPayloadBuilder`]. From e26f45909b3cf4bf307f42a37401d95479b53f5d Mon Sep 17 00:00:00 2001 From: 0xKitsune <0xkitsune@protonmail.com> Date: Thu, 12 Sep 2024 15:41:13 -0400 Subject: [PATCH 12/32] updating block construction logic --- crates/optimism/payload/src/builder.rs | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/crates/optimism/payload/src/builder.rs b/crates/optimism/payload/src/builder.rs index 92baea5976d1..93bbf6959dd9 100644 --- a/crates/optimism/payload/src/builder.rs +++ b/crates/optimism/payload/src/builder.rs @@ -87,7 +87,7 @@ impl OptimismPayloadBuilder { self.init_pre_block_state(&config, evm_config, &mut db)?; - match self.construct_block(&config, pool, &mut db, cancel) { + match self.construct_block(args, &mut db) { Ok(outcome) => Ok(outcome), Err(PayloadBuilderError::BuildOutcomeCancelled) => Ok(BuildOutcome::Cancelled), Err(err) => Err(err), @@ -148,24 +148,24 @@ impl OptimismPayloadBuilder { }) } - fn construct_block( + fn construct_block( &self, - payload_config: &PayloadConfig, - pool: Pool, + args: BuildArguments, db: &mut revm::State, - cancel: Cancelled, ) -> Result, PayloadBuilderError> where EvmConfig: ConfigureEvm, Pool: TransactionPool, DB: revm::Database, { + let BuildArguments { pool, cached_reads, config, cancel, best_payload, .. } = args; + let mut op_block_attributes = - OptimismBlockAttributes::new(&payload_config, self.evm_config.clone()); + OptimismBlockAttributes::new(&config, self.evm_config.clone()); // add sequencer transactions to block op_block_attributes.add_sequencer_transactions( - &payload_config.attributes.transactions, + &config.attributes.transactions, db, &cancel, )?; @@ -173,6 +173,15 @@ impl OptimismPayloadBuilder { // add pooled transactions to block op_block_attributes.add_pooled_transactions(&pool, db, &cancel)?; + // check if we have a better block + if !is_better_payload(best_payload.as_ref(), op_block_attributes.total_fees) { + // can skip building the block + return Ok(BuildOutcome::Aborted { + fees: op_block_attributes.total_fees, + cached_reads, + }); + } + // TODO: into built payload todo!() } From e0c42337f490ff2003e8ec2d4bfdc3ac490628bc Mon Sep 17 00:00:00 2001 From: 0xKitsune <0xkitsune@protonmail.com> Date: Fri, 13 Sep 2024 19:01:38 -0400 Subject: [PATCH 13/32] update block attribute types --- crates/optimism/payload/src/builder.rs | 77 ++++++++++++++------------ 1 file changed, 41 insertions(+), 36 deletions(-) diff --git a/crates/optimism/payload/src/builder.rs b/crates/optimism/payload/src/builder.rs index 93bbf6959dd9..bbbe1ab72ef7 100644 --- a/crates/optimism/payload/src/builder.rs +++ b/crates/optimism/payload/src/builder.rs @@ -87,11 +87,24 @@ impl OptimismPayloadBuilder { self.init_pre_block_state(&config, evm_config, &mut db)?; - match self.construct_block(args, &mut db) { + let op_block_attributes = match self + .construct_block_attributes(pool, &config, &mut db, &cancel) + { Ok(outcome) => Ok(outcome), - Err(PayloadBuilderError::BuildOutcomeCancelled) => Ok(BuildOutcome::Cancelled), + Err(PayloadBuilderError::BuildOutcomeCancelled) => return Ok(BuildOutcome::Cancelled), Err(err) => Err(err), + }?; + + // check if we have a better block + if !is_better_payload(best_payload.as_ref(), op_block_attributes.total_fees) { + // can skip building the block + return Ok(BuildOutcome::Aborted { + fees: op_block_attributes.total_fees, + cached_reads, + }); } + + todo!() } pub fn init_pre_block_state( @@ -148,24 +161,24 @@ impl OptimismPayloadBuilder { }) } - fn construct_block( + fn construct_block_attributes( &self, - args: BuildArguments, + pool: Pool, + payload_config: &PayloadConfig, db: &mut revm::State, - ) -> Result, PayloadBuilderError> + cancel: &Cancelled, + ) -> Result, PayloadBuilderError> where EvmConfig: ConfigureEvm, Pool: TransactionPool, DB: revm::Database, { - let BuildArguments { pool, cached_reads, config, cancel, best_payload, .. } = args; - let mut op_block_attributes = - OptimismBlockAttributes::new(&config, self.evm_config.clone()); + OptimismBlockAttributes::new(payload_config, self.evm_config.clone()); // add sequencer transactions to block op_block_attributes.add_sequencer_transactions( - &config.attributes.transactions, + &payload_config.attributes.transactions, db, &cancel, )?; @@ -173,22 +186,12 @@ impl OptimismPayloadBuilder { // add pooled transactions to block op_block_attributes.add_pooled_transactions(&pool, db, &cancel)?; - // check if we have a better block - if !is_better_payload(best_payload.as_ref(), op_block_attributes.total_fees) { - // can skip building the block - return Ok(BuildOutcome::Aborted { - fees: op_block_attributes.total_fees, - cached_reads, - }); - } - - // TODO: into built payload - todo!() + Ok(op_block_attributes) } } #[derive(Debug)] -pub struct OptimismBlockAttributes<'a, EvmConfig: ConfigureEvm> { +pub struct OptimismBlockAttributes { receipts: Vec>, executed_txs: Vec, executed_senders: Vec
, @@ -198,15 +201,18 @@ pub struct OptimismBlockAttributes<'a, EvmConfig: ConfigureEvm> { total_fees: U256, cumulative_gas_used: u64, evm_config: EvmConfig, - payload_config: &'a PayloadConfig, + initialized_block_env: BlockEnv, + initialized_cfg: CfgEnvWithHandlerCfg, + chain_spec: Arc, + builder_attributes: OptimismPayloadBuilderAttributes, } -impl<'a, EvmConfig> OptimismBlockAttributes<'a, EvmConfig> +impl OptimismBlockAttributes where EvmConfig: ConfigureEvm, { pub fn new( - payload_config: &'a PayloadConfig, + payload_config: &PayloadConfig, evm_config: EvmConfig, ) -> Self { let attributes = &payload_config.attributes; @@ -236,7 +242,10 @@ where total_fees: U256::ZERO, cumulative_gas_used: 0, evm_config, - payload_config, + initialized_block_env, + initialized_cfg: payload_config.initialized_cfg.clone(), + chain_spec: payload_config.chain_spec.clone(), + builder_attributes: attributes.clone(), } } @@ -300,16 +309,13 @@ where DB: revm::Database, Pool: TransactionPool, { - if self.payload_config.attributes.no_tx_pool { + if self.builder_attributes.no_tx_pool { return Ok(()); } let mut best_txs = pool.best_transactions_with_attributes(BestTransactionsAttributes::new( self.base_fee, - self.payload_config - .initialized_block_env - .get_blob_gasprice() - .map(|gasprice| gasprice as u64), + self.initialized_block_env.get_blob_gasprice().map(|gasprice| gasprice as u64), )); while let Some(pool_tx) = best_txs.next() { @@ -367,8 +373,8 @@ where let execution_result = evm_transact_commit( tx, - self.payload_config.initialized_cfg.clone(), - self.payload_config.initialized_block_env.clone(), + self.initialized_cfg.clone(), + self.initialized_block_env.clone(), self.evm_config.clone(), db, ) @@ -383,11 +389,10 @@ where logs: execution_result.into_logs().into_iter().map(Into::into).collect(), deposit_nonce: depositor.map(|account| account.nonce), deposit_receipt_version: self - .payload_config .chain_spec .is_fork_active_at_timestamp( OptimismHardfork::Canyon, - self.payload_config.attributes.payload_attributes.timestamp, + self.builder_attributes.payload_attributes.timestamp, ) .then_some(1), }; @@ -409,8 +414,8 @@ where { let execution_result = evm_transact_commit( tx, - self.payload_config.initialized_cfg.clone(), - self.payload_config.initialized_block_env.clone(), + self.initialized_cfg.clone(), + self.initialized_block_env.clone(), self.evm_config.clone(), db, ) From b6d85de907625ba6e2a638bee8ae376ec20799ac Mon Sep 17 00:00:00 2001 From: 0xKitsune <0xkitsune@protonmail.com> Date: Fri, 13 Sep 2024 19:52:31 -0400 Subject: [PATCH 14/32] add into built payload --- crates/optimism/payload/src/builder.rs | 159 +++++++++++++++++++++++-- 1 file changed, 148 insertions(+), 11 deletions(-) diff --git a/crates/optimism/payload/src/builder.rs b/crates/optimism/payload/src/builder.rs index bbbe1ab72ef7..4057e034c49b 100644 --- a/crates/optimism/payload/src/builder.rs +++ b/crates/optimism/payload/src/builder.rs @@ -11,10 +11,11 @@ use reth_chainspec::{ChainSpec, EthereumHardforks, OptimismHardfork}; use reth_evm::{system_calls::pre_block_beacon_root_contract_call, ConfigureEvm}; use reth_execution_types::ExecutionOutcome; use reth_payload_builder::error::PayloadBuilderError; +use reth_payload_primitives::PayloadBuilderAttributes; use reth_primitives::{ constants::BEACON_NONCE, eip4844::calculate_excess_blob_gas, proofs, transaction::WithEncoded, - Address, Block, Header, IntoRecoveredTransaction, Receipt, TransactionSigned, - TransactionSignedEcRecovered, TxType, B256, EMPTY_OMMER_ROOT_HASH, + Address, Block, Bytes, Header, IntoRecoveredTransaction, Receipt, SealedBlock, + TransactionSigned, TransactionSignedEcRecovered, TxType, B256, EMPTY_OMMER_ROOT_HASH, }; use reth_provider::{ProviderError, StateProvider, StateProviderFactory}; use reth_revm::database::StateProviderDatabase; @@ -104,7 +105,13 @@ impl OptimismPayloadBuilder { }); } - todo!() + let payload = op_block_attributes.into_built_payload( + config.parent_block, + config.extra_data, + &mut db, + )?; + + Ok(BuildOutcome::Better { payload, cached_reads }) } pub fn init_pre_block_state( @@ -114,8 +121,7 @@ impl OptimismPayloadBuilder { db: &mut revm::State, ) -> Result<(), PayloadBuilderError> where - DB: revm::Database, - DB::Error: Display, + DB: revm::Database, EvmConfig: ConfigureEvm, { let PayloadConfig { @@ -161,7 +167,7 @@ impl OptimismPayloadBuilder { }) } - fn construct_block_attributes( + pub fn construct_block_attributes( &self, pool: Pool, payload_config: &PayloadConfig, @@ -169,9 +175,9 @@ impl OptimismPayloadBuilder { cancel: &Cancelled, ) -> Result, PayloadBuilderError> where - EvmConfig: ConfigureEvm, Pool: TransactionPool, DB: revm::Database, + EvmConfig: ConfigureEvm, { let mut op_block_attributes = OptimismBlockAttributes::new(payload_config, self.evm_config.clone()); @@ -204,7 +210,7 @@ pub struct OptimismBlockAttributes { initialized_block_env: BlockEnv, initialized_cfg: CfgEnvWithHandlerCfg, chain_spec: Arc, - builder_attributes: OptimismPayloadBuilderAttributes, + payload_attributes: OptimismPayloadBuilderAttributes, } impl OptimismBlockAttributes @@ -245,7 +251,7 @@ where initialized_block_env, initialized_cfg: payload_config.initialized_cfg.clone(), chain_spec: payload_config.chain_spec.clone(), - builder_attributes: attributes.clone(), + payload_attributes: attributes.clone(), } } @@ -309,7 +315,7 @@ where DB: revm::Database, Pool: TransactionPool, { - if self.builder_attributes.no_tx_pool { + if self.payload_attributes.no_tx_pool { return Ok(()); } @@ -392,7 +398,7 @@ where .chain_spec .is_fork_active_at_timestamp( OptimismHardfork::Canyon, - self.builder_attributes.payload_attributes.timestamp, + self.payload_attributes.payload_attributes.timestamp, ) .then_some(1), }; @@ -445,6 +451,137 @@ where Ok(()) } + + pub fn into_built_payload( + self, + parent_block: Arc, + extra_data: Bytes, + &mut db: &mut revm::State, + ) -> Result + where + DB: revm::Database, + { + let timestamp = self.payload_attributes.timestamp(); + + let WithdrawalsOutcome { withdrawals_root, withdrawals } = commit_withdrawals( + &mut db, + &self.chain_spec, + timestamp, + self.payload_attributes.withdrawals().to_owned(), + )?; + + // merge all transitions into bundle state, this would apply the withdrawal balance changes + // and 4788 contract call + db.merge_transitions(BundleRetention::PlainState); + + let block_number = self.initialized_block_env.number.to::(); + + let execution_outcome = ExecutionOutcome::new( + db.take_bundle(), + vec![self.receipts].into(), + block_number, + Vec::new(), + ); + + let chain_spec = self.chain_spec; + + let receipts_root = execution_outcome + .optimism_receipts_root_slow(block_number, chain_spec.as_ref(), timestamp) + .expect("Number is in range"); + + let logs_bloom = + execution_outcome.block_logs_bloom(block_number).expect("Number is in range"); + + // calculate the state root + let hashed_state = HashedPostState::from_bundle_state(&execution_outcome.state().state); + let (state_root, trie_output) = { + let state_provider = db.database.0.inner.borrow_mut(); + state_provider.db.state_root_with_updates(hashed_state.clone()).inspect_err(|err| { + warn!(target: "payload_builder", + parent_hash=%parent_block.hash(), + %err, + "failed to calculate state root for empty payload" + ); + })? + }; + + // create the block header + let transactions_root = proofs::calculate_transaction_root(&self.executed_txs); + + // initialize empty blob sidecars. There are no blob transactions on L2. + let blob_sidecars = Vec::new(); + let mut excess_blob_gas = None; + let mut blob_gas_used = None; + + // only determine cancun fields when active + if chain_spec.is_cancun_active_at_timestamp(timestamp) { + excess_blob_gas = if chain_spec.is_cancun_active_at_timestamp(parent_block.timestamp) { + let parent_excess_blob_gas = parent_block.excess_blob_gas.unwrap_or_default(); + let parent_blob_gas_used = parent_block.blob_gas_used.unwrap_or_default(); + Some(calculate_excess_blob_gas(parent_excess_blob_gas, parent_blob_gas_used)) + } else { + // for the first post-fork block, both parent.blob_gas_used and + // parent.excess_blob_gas are evaluated as 0 + Some(calculate_excess_blob_gas(0, 0)) + }; + + blob_gas_used = Some(0); + } + + let header = Header { + parent_hash: parent_block.hash(), + ommers_hash: EMPTY_OMMER_ROOT_HASH, + beneficiary: self.initialized_block_env.coinbase, + state_root, + transactions_root, + receipts_root, + withdrawals_root, + logs_bloom, + timestamp, + mix_hash: self.payload_attributes.prev_randao(), + nonce: BEACON_NONCE, + base_fee_per_gas: Some(self.base_fee), + number: parent_block.number + 1, + gas_limit: self.block_gas_limit, + difficulty: U256::ZERO, + gas_used: self.cumulative_gas_used, + extra_data, + parent_beacon_block_root: self.payload_attributes.parent_beacon_block_root(), + blob_gas_used, + excess_blob_gas, + requests_root: None, + }; + + // seal the block + let block = + Block { header, body: self.executed_txs, ommers: vec![], withdrawals, requests: None }; + + let sealed_block = block.seal_slow(); + debug!(target: "payload_builder", ?sealed_block, "sealed built block"); + + // create the executed block data + let executed = ExecutedBlock { + block: Arc::new(sealed_block.clone()), + senders: Arc::new(self.executed_senders), + execution_output: Arc::new(execution_outcome), + hashed_state: Arc::new(hashed_state), + trie: Arc::new(trie_output), + }; + + let mut payload = OptimismBuiltPayload::new( + self.payload_attributes.payload_id(), + sealed_block, + self.total_fees, + chain_spec, + self.payload_attributes, + Some(executed), + ); + + // extend the payload with the blob sidecars from the executed txs + payload.extend_sidecars(blob_sidecars); + + Ok(payload) + } } /// Implementation of the [`PayloadBuilder`] trait for [`OptimismPayloadBuilder`]. From 79cc6bc8bf8f7c3cd98472c28f8fb3b1ae91af30 Mon Sep 17 00:00:00 2001 From: 0xKitsune <0xkitsune@protonmail.com> Date: Fri, 13 Sep 2024 20:54:49 -0400 Subject: [PATCH 15/32] added functions to construct built payload --- crates/optimism/payload/src/builder.rs | 864 +++++++------------------ 1 file changed, 243 insertions(+), 621 deletions(-) diff --git a/crates/optimism/payload/src/builder.rs b/crates/optimism/payload/src/builder.rs index 4057e034c49b..7b16b02a3e58 100644 --- a/crates/optimism/payload/src/builder.rs +++ b/crates/optimism/payload/src/builder.rs @@ -17,21 +17,18 @@ use reth_primitives::{ Address, Block, Bytes, Header, IntoRecoveredTransaction, Receipt, SealedBlock, TransactionSigned, TransactionSignedEcRecovered, TxType, B256, EMPTY_OMMER_ROOT_HASH, }; -use reth_provider::{ProviderError, StateProvider, StateProviderFactory}; +use reth_provider::{ProviderError, StateProviderFactory}; use reth_revm::database::StateProviderDatabase; use reth_transaction_pool::{ noop::NoopTransactionPool, BestTransactionsAttributes, TransactionPool, }; -use reth_trie::HashedPostState; +use reth_trie::{updates::TrieUpdates, HashedPostState}; use revm::{ - db::{states::bundle_state::BundleRetention, WrapDatabaseRef}, - primitives::{ - BlockEnv, CfgEnvWithHandlerCfg, EVMError, EnvWithHandlerCfg, ExecutionResult, - InvalidTransaction, ResultAndState, - }, - Database, DatabaseCommit, State, + db::states::bundle_state::BundleRetention, + primitives::{BlockEnv, CfgEnvWithHandlerCfg, EVMError, EnvWithHandlerCfg, ExecutionResult}, + State, }; -use std::{fmt::Display, sync::Arc}; +use std::sync::Arc; use tracing::{debug, trace, warn}; /// Optimism's payload builder @@ -66,54 +63,6 @@ impl OptimismPayloadBuilder { self.compute_pending_block } - fn build_payload( - &self, - evm_config: EvmConfig, - args: BuildArguments, - _compute_pending_block: bool, - ) -> Result, PayloadBuilderError> - where - EvmConfig: ConfigureEvm, - Client: StateProviderFactory, - Pool: TransactionPool, - { - let BuildArguments { client, pool, mut cached_reads, config, cancel, best_payload } = args; - - let state_provider = client.state_by_block_hash(config.parent_block.hash())?; - let state = StateProviderDatabase::new(state_provider); - let mut db = State::builder() - .with_database_ref(cached_reads.as_db(state)) - .with_bundle_update() - .build(); - - self.init_pre_block_state(&config, evm_config, &mut db)?; - - let op_block_attributes = match self - .construct_block_attributes(pool, &config, &mut db, &cancel) - { - Ok(outcome) => Ok(outcome), - Err(PayloadBuilderError::BuildOutcomeCancelled) => return Ok(BuildOutcome::Cancelled), - Err(err) => Err(err), - }?; - - // check if we have a better block - if !is_better_payload(best_payload.as_ref(), op_block_attributes.total_fees) { - // can skip building the block - return Ok(BuildOutcome::Aborted { - fees: op_block_attributes.total_fees, - cached_reads, - }); - } - - let payload = op_block_attributes.into_built_payload( - config.parent_block, - config.extra_data, - &mut db, - )?; - - Ok(BuildOutcome::Better { payload, cached_reads }) - } - pub fn init_pre_block_state( &self, payload_config: &PayloadConfig, @@ -194,6 +143,224 @@ impl OptimismPayloadBuilder { Ok(op_block_attributes) } + + pub fn construct_built_payload( + &self, + op_block_attributes: OptimismBlockAttributes, + parent_block: Arc, + execution_outcome: ExecutionOutcome, + state_root: B256, + withdrawals_outcome: WithdrawalsOutcome, + hashed_state: HashedPostState, + trie_output: TrieUpdates, + extra_data: Bytes, + ) -> OptimismBuiltPayload + where + EvmConfig: ConfigureEvm, + { + let chain_spec = op_block_attributes.chain_spec; + let block_number = op_block_attributes.initialized_block_env.number.to::(); + let timestamp = op_block_attributes.payload_attributes.timestamp(); + + let receipts_root = execution_outcome + .optimism_receipts_root_slow(block_number, chain_spec.as_ref(), timestamp) + .expect("Number is in range"); + + let logs_bloom = + execution_outcome.block_logs_bloom(block_number).expect("Number is in range"); + + // create the block header + let transactions_root = + proofs::calculate_transaction_root(&op_block_attributes.executed_txs); + + let mut excess_blob_gas = None; + let mut blob_gas_used = None; + + // only determine cancun fields when active + if chain_spec.is_cancun_active_at_timestamp(timestamp) { + excess_blob_gas = if chain_spec.is_cancun_active_at_timestamp(parent_block.timestamp) { + let parent_excess_blob_gas = parent_block.excess_blob_gas.unwrap_or_default(); + let parent_blob_gas_used = parent_block.blob_gas_used.unwrap_or_default(); + Some(calculate_excess_blob_gas(parent_excess_blob_gas, parent_blob_gas_used)) + } else { + // for the first post-fork block, both parent.blob_gas_used and + // parent.excess_blob_gas are evaluated as 0 + Some(calculate_excess_blob_gas(0, 0)) + }; + + blob_gas_used = Some(0); + } + + let header = Header { + parent_hash: parent_block.hash(), + ommers_hash: EMPTY_OMMER_ROOT_HASH, + beneficiary: op_block_attributes.initialized_block_env.coinbase, + state_root, + transactions_root, + receipts_root, + withdrawals_root: withdrawals_outcome.withdrawals_root, + logs_bloom, + timestamp, + mix_hash: op_block_attributes.payload_attributes.prev_randao(), + nonce: BEACON_NONCE, + base_fee_per_gas: Some(op_block_attributes.base_fee), + number: parent_block.number + 1, + gas_limit: op_block_attributes.block_gas_limit, + difficulty: U256::ZERO, + gas_used: op_block_attributes.cumulative_gas_used, + extra_data, + parent_beacon_block_root: op_block_attributes + .payload_attributes + .parent_beacon_block_root(), + blob_gas_used, + excess_blob_gas, + requests_root: None, + }; + + let block = Block { + header, + body: op_block_attributes.executed_txs, + ommers: vec![], + withdrawals: withdrawals_outcome.withdrawals, + requests: None, + }; + + let sealed_block = block.seal_slow(); + debug!(target: "payload_builder", ?sealed_block, "sealed built block"); + + // create the executed block data + let executed = ExecutedBlock { + block: Arc::new(sealed_block.clone()), + senders: Arc::new(op_block_attributes.executed_senders), + execution_output: Arc::new(execution_outcome), + hashed_state: Arc::new(hashed_state), + trie: Arc::new(trie_output), + }; + + let payload = OptimismBuiltPayload::new( + op_block_attributes.payload_attributes.payload_id(), + sealed_block, + op_block_attributes.total_fees, + chain_spec, + op_block_attributes.payload_attributes, + Some(executed), + ); + + payload + } + + pub fn construct_outcome( + &self, + op_block_attributes: &OptimismBlockAttributes, + db: &mut revm::State, + ) -> Result<(WithdrawalsOutcome, ExecutionOutcome), ProviderError> + where + EvmConfig: ConfigureEvm, + DB: revm::Database, + { + let timestamp = op_block_attributes.payload_attributes.timestamp(); + + let withdrawals_outcome = commit_withdrawals( + db, + &op_block_attributes.chain_spec, + timestamp, + op_block_attributes.payload_attributes.withdrawals().to_owned(), + )?; + + // merge all transitions into bundle state, this would apply the withdrawal balance changes + // and 4788 contract call + db.merge_transitions(BundleRetention::PlainState); + + let block_number = op_block_attributes.initialized_block_env.number.to::(); + + let execution_outcome = ExecutionOutcome::new( + db.take_bundle(), + vec![op_block_attributes.receipts.to_owned()].into(), + block_number, + Vec::new(), + ); + + Ok((withdrawals_outcome, execution_outcome)) + } + + /// Constructs an Ethereum transaction payload from the transactions sent through the + /// Payload attributes by the sequencer. If the `no_tx_pool` argument is passed in + /// the payload attributes, the transaction pool will be ignored and the only transactions + /// included in the payload will be those sent through the attributes. + /// + /// Given build arguments including an Ethereum client, transaction pool, + /// and configuration, this function creates a transaction payload. Returns + /// a result indicating success with the payload or an error in case of failure. + fn build_payload( + &self, + evm_config: EvmConfig, + args: BuildArguments, + _compute_pending_block: bool, + ) -> Result, PayloadBuilderError> + where + EvmConfig: ConfigureEvm, + Client: StateProviderFactory, + Pool: TransactionPool, + { + let BuildArguments { client, pool, mut cached_reads, config, cancel, best_payload } = args; + + let state_provider = client.state_by_block_hash(config.parent_block.hash())?; + let state = StateProviderDatabase::new(state_provider); + let mut db = State::builder() + .with_database_ref(cached_reads.as_db(state)) + .with_bundle_update() + .build(); + + self.init_pre_block_state(&config, evm_config, &mut db)?; + + let op_block_attributes = match self + .construct_block_attributes(pool, &config, &mut db, &cancel) + { + Ok(outcome) => Ok(outcome), + Err(PayloadBuilderError::BuildOutcomeCancelled) => return Ok(BuildOutcome::Cancelled), + Err(err) => Err(err), + }?; + + // check if we have a better block + if !is_better_payload(best_payload.as_ref(), op_block_attributes.total_fees) { + // can skip building the block + return Ok(BuildOutcome::Aborted { + fees: op_block_attributes.total_fees, + cached_reads, + }); + } + + let parent_block = config.parent_block; + + let (withdrawals_outcome, execution_outcome) = + self.construct_outcome(&op_block_attributes, &mut db)?; + + // calculate the state root + let hashed_state = HashedPostState::from_bundle_state(&execution_outcome.state().state); + let (state_root, trie_output) = { + let state_provider = db.database.0.inner.borrow_mut(); + state_provider.db.state_root_with_updates(hashed_state.clone()).inspect_err(|err| { + warn!(target: "payload_builder", + parent_hash=%parent_block.hash(), + %err, + "failed to calculate state root for empty payload" + ); + })? + }; + + let payload = self.construct_built_payload( + op_block_attributes, + parent_block, + execution_outcome, + state_root, + withdrawals_outcome, + hashed_state, + trie_output, + config.extra_data, + ); + + Ok(BuildOutcome::Better { payload, cached_reads }) + } } #[derive(Debug)] @@ -451,137 +618,27 @@ where Ok(()) } +} - pub fn into_built_payload( - self, - parent_block: Arc, - extra_data: Bytes, - &mut db: &mut revm::State, - ) -> Result - where - DB: revm::Database, - { - let timestamp = self.payload_attributes.timestamp(); - - let WithdrawalsOutcome { withdrawals_root, withdrawals } = commit_withdrawals( - &mut db, - &self.chain_spec, - timestamp, - self.payload_attributes.withdrawals().to_owned(), - )?; - - // merge all transitions into bundle state, this would apply the withdrawal balance changes - // and 4788 contract call - db.merge_transitions(BundleRetention::PlainState); - - let block_number = self.initialized_block_env.number.to::(); - - let execution_outcome = ExecutionOutcome::new( - db.take_bundle(), - vec![self.receipts].into(), - block_number, - Vec::new(), - ); - - let chain_spec = self.chain_spec; - - let receipts_root = execution_outcome - .optimism_receipts_root_slow(block_number, chain_spec.as_ref(), timestamp) - .expect("Number is in range"); - - let logs_bloom = - execution_outcome.block_logs_bloom(block_number).expect("Number is in range"); - - // calculate the state root - let hashed_state = HashedPostState::from_bundle_state(&execution_outcome.state().state); - let (state_root, trie_output) = { - let state_provider = db.database.0.inner.borrow_mut(); - state_provider.db.state_root_with_updates(hashed_state.clone()).inspect_err(|err| { - warn!(target: "payload_builder", - parent_hash=%parent_block.hash(), - %err, - "failed to calculate state root for empty payload" - ); - })? - }; - - // create the block header - let transactions_root = proofs::calculate_transaction_root(&self.executed_txs); - - // initialize empty blob sidecars. There are no blob transactions on L2. - let blob_sidecars = Vec::new(); - let mut excess_blob_gas = None; - let mut blob_gas_used = None; - - // only determine cancun fields when active - if chain_spec.is_cancun_active_at_timestamp(timestamp) { - excess_blob_gas = if chain_spec.is_cancun_active_at_timestamp(parent_block.timestamp) { - let parent_excess_blob_gas = parent_block.excess_blob_gas.unwrap_or_default(); - let parent_blob_gas_used = parent_block.blob_gas_used.unwrap_or_default(); - Some(calculate_excess_blob_gas(parent_excess_blob_gas, parent_blob_gas_used)) - } else { - // for the first post-fork block, both parent.blob_gas_used and - // parent.excess_blob_gas are evaluated as 0 - Some(calculate_excess_blob_gas(0, 0)) - }; - - blob_gas_used = Some(0); - } - - let header = Header { - parent_hash: parent_block.hash(), - ommers_hash: EMPTY_OMMER_ROOT_HASH, - beneficiary: self.initialized_block_env.coinbase, - state_root, - transactions_root, - receipts_root, - withdrawals_root, - logs_bloom, - timestamp, - mix_hash: self.payload_attributes.prev_randao(), - nonce: BEACON_NONCE, - base_fee_per_gas: Some(self.base_fee), - number: parent_block.number + 1, - gas_limit: self.block_gas_limit, - difficulty: U256::ZERO, - gas_used: self.cumulative_gas_used, - extra_data, - parent_beacon_block_root: self.payload_attributes.parent_beacon_block_root(), - blob_gas_used, - excess_blob_gas, - requests_root: None, - }; - - // seal the block - let block = - Block { header, body: self.executed_txs, ommers: vec![], withdrawals, requests: None }; - - let sealed_block = block.seal_slow(); - debug!(target: "payload_builder", ?sealed_block, "sealed built block"); - - // create the executed block data - let executed = ExecutedBlock { - block: Arc::new(sealed_block.clone()), - senders: Arc::new(self.executed_senders), - execution_output: Arc::new(execution_outcome), - hashed_state: Arc::new(hashed_state), - trie: Arc::new(trie_output), - }; - - let mut payload = OptimismBuiltPayload::new( - self.payload_attributes.payload_id(), - sealed_block, - self.total_fees, - chain_spec, - self.payload_attributes, - Some(executed), - ); - - // extend the payload with the blob sidecars from the executed txs - payload.extend_sidecars(blob_sidecars); +fn evm_transact_commit( + transaction: &TransactionSignedEcRecovered, + initialized_cfg: CfgEnvWithHandlerCfg, + initialized_block_env: BlockEnv, + evm_config: EvmConfig, + db: &mut revm::State, +) -> Result> +where + DB: revm::Database, + EvmConfig: ConfigureEvm, +{ + let env = EnvWithHandlerCfg::new_with_cfg_env( + initialized_cfg, + initialized_block_env, + evm_config.tx_env(transaction), + ); + let mut evm = evm_config.evm_with_env(db, env); - Ok(payload) - } + evm.transact_commit() } /// Implementation of the [`PayloadBuilder`] trait for [`OptimismPayloadBuilder`]. @@ -632,438 +689,3 @@ where .ok_or_else(|| PayloadBuilderError::MissingPayload) } } - -fn evm_transact_commit( - transaction: &TransactionSignedEcRecovered, - initialized_cfg: CfgEnvWithHandlerCfg, - initialized_block_env: BlockEnv, - evm_config: EvmConfig, - db: &mut revm::State, -) -> Result> -where - DB: revm::Database, - EvmConfig: ConfigureEvm, -{ - let env = EnvWithHandlerCfg::new_with_cfg_env( - initialized_cfg, - initialized_block_env, - evm_config.tx_env(transaction), - ); - let mut evm = evm_config.evm_with_env(db, env); - - evm.transact_commit() -} - -/// Constructs an Ethereum transaction payload from the transactions sent through the -/// Payload attributes by the sequencer. If the `no_tx_pool` argument is passed in -/// the payload attributes, the transaction pool will be ignored and the only transactions -/// included in the payload will be those sent through the attributes. -/// -/// Given build arguments including an Ethereum client, transaction pool, -/// and configuration, this function creates a transaction payload. Returns -/// a result indicating success with the payload or an error in case of failure. - -#[inline] -pub(crate) fn optimism_payload( - evm_config: EvmConfig, - args: BuildArguments, - _compute_pending_block: bool, -) -> Result, PayloadBuilderError> -where - EvmConfig: ConfigureEvm, - Client: StateProviderFactory, - Pool: TransactionPool, -{ - let BuildArguments { client, pool, mut cached_reads, config, cancel, best_payload } = args; - - let state_provider = client.state_by_block_hash(config.parent_block.hash())?; - let state = StateProviderDatabase::new(state_provider); - - let mut db = - State::builder().with_database_ref(cached_reads.as_db(state)).with_bundle_update().build(); - - let extra_data = config.extra_data(); - let PayloadConfig { - initialized_block_env, - initialized_cfg, - parent_block, - attributes, - chain_spec, - .. - } = config; - - debug!(target: "payload_builder", id=%attributes.payload_attributes.payload_id(), parent_hash = ?parent_block.hash(), parent_number = parent_block.number, "building new payload"); - - // NOTE: - - let mut cumulative_gas_used = 0; - let block_gas_limit: u64 = attributes.gas_limit.unwrap_or_else(|| { - initialized_block_env.gas_limit.try_into().unwrap_or(chain_spec.max_gas_limit) - }); - let base_fee = initialized_block_env.basefee.to::(); - - let mut executed_txs = Vec::with_capacity(attributes.transactions.len()); - let mut executed_senders = Vec::with_capacity(attributes.transactions.len()); - - let mut best_txs = pool.best_transactions_with_attributes(BestTransactionsAttributes::new( - base_fee, - initialized_block_env.get_blob_gasprice().map(|gasprice| gasprice as u64), - )); - - let mut total_fees = U256::ZERO; - - let block_number = initialized_block_env.number.to::(); - - let is_regolith = chain_spec.is_fork_active_at_timestamp( - OptimismHardfork::Regolith, - attributes.payload_attributes.timestamp, - ); - - // apply eip-4788 pre block contract call - // NOTE: - pre_block_beacon_root_contract_call( - &mut db, - &evm_config, - &chain_spec, - &initialized_cfg, - &initialized_block_env, - attributes.payload_attributes.parent_beacon_block_root, - ) - .map_err(|err| { - warn!(target: "payload_builder", - parent_hash=%parent_block.hash(), - %err, - "failed to apply beacon root contract call for empty payload" - ); - PayloadBuilderError::Internal(err.into()) - })?; - - // Ensure that the create2deployer is force-deployed at the canyon transition. Optimism - // blocks will always have at least a single transaction in them (the L1 info transaction), - // so we can safely assume that this will always be triggered upon the transition and that - // the above check for empty blocks will never be hit on OP chains. - reth_evm_optimism::ensure_create2_deployer( - chain_spec.clone(), - attributes.payload_attributes.timestamp, - &mut db, - ) - .map_err(|err| { - warn!(target: "payload_builder", %err, "missing create2 deployer, skipping block."); - PayloadBuilderError::other(OptimismPayloadBuilderError::ForceCreate2DeployerFail) - })?; - - // NOTE: - let mut receipts = Vec::with_capacity(attributes.transactions.len()); - for sequencer_tx in &attributes.transactions { - // Check if the job was cancelled, if so we can exit early. - if cancel.is_cancelled() { - return Ok(BuildOutcome::Cancelled); - } - - // A sequencer's block should never contain blob transactions. - if sequencer_tx.value().is_eip4844() { - return Err(PayloadBuilderError::other( - OptimismPayloadBuilderError::BlobTransactionRejected, - )); - } - - // Convert the transaction to a [TransactionSignedEcRecovered]. This is - // purely for the purposes of utilizing the `evm_config.tx_env`` function. - // Deposit transactions do not have signatures, so if the tx is a deposit, this - // will just pull in its `from` address. - let sequencer_tx = sequencer_tx.value().clone().try_into_ecrecovered().map_err(|_| { - PayloadBuilderError::other(OptimismPayloadBuilderError::TransactionEcRecoverFailed) - })?; - - // Cache the depositor account prior to the state transition for the deposit nonce. - // - // Note that this *only* needs to be done post-regolith hardfork, as deposit nonces - // were not introduced in Bedrock. In addition, regular transactions don't have deposit - // nonces, so we don't need to touch the DB for those. - let depositor = (is_regolith && sequencer_tx.is_deposit()) - .then(|| { - db.load_cache_account(sequencer_tx.signer()) - .map(|acc| acc.account_info().unwrap_or_default()) - }) - .transpose() - .map_err(|_| { - PayloadBuilderError::other(OptimismPayloadBuilderError::AccountLoadFailed( - sequencer_tx.signer(), - )) - })?; - - let env = EnvWithHandlerCfg::new_with_cfg_env( - initialized_cfg.clone(), - initialized_block_env.clone(), - evm_config.tx_env(&sequencer_tx), - ); - - let mut evm = evm_config.evm_with_env(&mut db, env); - - let ResultAndState { result, state } = match evm.transact() { - Ok(res) => res, - Err(err) => { - match err { - EVMError::Transaction(err) => { - trace!(target: "payload_builder", %err, ?sequencer_tx, "Error in sequencer transaction, skipping."); - continue; - } - err => { - // this is an error that we should treat as fatal for this attempt - return Err(PayloadBuilderError::EvmExecutionError(err)); - } - } - } - }; - - // to release the db reference drop evm. - drop(evm); - // commit changes - db.commit(state); - - let gas_used = result.gas_used(); - - // add gas used by the transaction to cumulative gas used, before creating the receipt - cumulative_gas_used += gas_used; - - // Push transaction changeset and calculate header bloom filter for receipt. - receipts.push(Some(Receipt { - tx_type: sequencer_tx.tx_type(), - success: result.is_success(), - cumulative_gas_used, - logs: result.into_logs().into_iter().map(Into::into).collect(), - deposit_nonce: depositor.map(|account| account.nonce), - // The deposit receipt version was introduced in Canyon to indicate an update to how - // receipt hashes should be computed when set. The state transition process - // ensures this is only set for post-Canyon deposit transactions. - deposit_receipt_version: chain_spec - .is_fork_active_at_timestamp( - OptimismHardfork::Canyon, - attributes.payload_attributes.timestamp, - ) - .then_some(1), - })); - - // append sender and transaction to the respective lists - executed_senders.push(sequencer_tx.signer()); - executed_txs.push(sequencer_tx.into_signed()); - } - - if !attributes.no_tx_pool { - // NOTE: looks like this is already ordered in best_txs.next(), we will likely have to do our ordering there - - while let Some(pool_tx) = best_txs.next() { - // ensure we still have capacity for this transaction - if cumulative_gas_used + pool_tx.gas_limit() > block_gas_limit { - // we can't fit this transaction into the block, so we need to mark it as - // invalid which also removes all dependent transaction from - // the iterator before we can continue - best_txs.mark_invalid(&pool_tx); - continue; - } - - // A sequencer's block should never contain blob or deposit transactions from the pool. - if pool_tx.is_eip4844() || pool_tx.tx_type() == TxType::Deposit as u8 { - best_txs.mark_invalid(&pool_tx); - continue; - } - - // check if the job was cancelled, if so we can exit early - if cancel.is_cancelled() { - return Ok(BuildOutcome::Cancelled); - } - - // convert tx to a signed transaction - let tx = pool_tx.to_recovered_transaction(); - let env = EnvWithHandlerCfg::new_with_cfg_env( - initialized_cfg.clone(), - initialized_block_env.clone(), - evm_config.tx_env(&tx), - ); - - // Configure the environment for the block. - let mut evm = evm_config.evm_with_env(&mut db, env); - - let ResultAndState { result, state } = match evm.transact() { - Ok(res) => res, - Err(err) => { - match err { - EVMError::Transaction(err) => { - if matches!(err, InvalidTransaction::NonceTooLow { .. }) { - // if the nonce is too low, we can skip this transaction - trace!(target: "payload_builder", %err, ?tx, "skipping nonce too low transaction"); - } else { - // if the transaction is invalid, we can skip it and all of its - // descendants - trace!(target: "payload_builder", %err, ?tx, "skipping invalid transaction and its descendants"); - best_txs.mark_invalid(&pool_tx); - } - - continue; - } - err => { - // this is an error that we should treat as fatal for this attempt - return Err(PayloadBuilderError::EvmExecutionError(err)); - } - } - } - }; - - // NOTE: this is the same as above this could be more dry - - // drop evm so db is released. - drop(evm); - // commit changes - db.commit(state); - - let gas_used = result.gas_used(); - - // add gas used by the transaction to cumulative gas used, before creating the - // receipt - cumulative_gas_used += gas_used; - - // Push transaction changeset and calculate header bloom filter for receipt. - receipts.push(Some(Receipt { - tx_type: tx.tx_type(), - success: result.is_success(), - cumulative_gas_used, - logs: result.into_logs().into_iter().map(Into::into).collect(), - deposit_nonce: None, - deposit_receipt_version: None, - })); - - // update add to total fees - let miner_fee = tx - .effective_tip_per_gas(Some(base_fee)) - .expect("fee is always valid; execution succeeded"); - total_fees += U256::from(miner_fee) * U256::from(gas_used); - - // append sender and transaction to the respective lists - executed_senders.push(tx.signer()); - executed_txs.push(tx.into_signed()); - } - } - - // check if we have a better block - if !is_better_payload(best_payload.as_ref(), total_fees) { - // can skip building the block - return Ok(BuildOutcome::Aborted { fees: total_fees, cached_reads }); - } - - // NOTE: break new function - let WithdrawalsOutcome { withdrawals_root, withdrawals } = commit_withdrawals( - &mut db, - &chain_spec, - attributes.payload_attributes.timestamp, - attributes.clone().payload_attributes.withdrawals, - )?; - - // merge all transitions into bundle state, this would apply the withdrawal balance changes - // and 4788 contract call - db.merge_transitions(BundleRetention::PlainState); - - // NOTE: break new function - let execution_outcome = ExecutionOutcome::new( - db.take_bundle(), - vec![receipts.clone()].into(), - block_number, - Vec::new(), - ); - let receipts_root = execution_outcome - .optimism_receipts_root_slow( - block_number, - chain_spec.as_ref(), - attributes.payload_attributes.timestamp, - ) - .expect("Number is in range"); - let logs_bloom = execution_outcome.block_logs_bloom(block_number).expect("Number is in range"); - - // calculate the state root - let hashed_state = HashedPostState::from_bundle_state(&execution_outcome.state().state); - let (state_root, trie_output) = { - let state_provider = db.database.0.inner.borrow_mut(); - state_provider.db.state_root_with_updates(hashed_state.clone()).inspect_err(|err| { - warn!(target: "payload_builder", - parent_hash=%parent_block.hash(), - %err, - "failed to calculate state root for empty payload" - ); - })? - }; - - // NOTE: create new function for create header - let transactions_root = proofs::calculate_transaction_root(&executed_txs); - - // initialize empty blob sidecars. There are no blob transactions on L2. - let blob_sidecars = Vec::new(); - let mut excess_blob_gas = None; - let mut blob_gas_used = None; - - // only determine cancun fields when active - if chain_spec.is_cancun_active_at_timestamp(attributes.payload_attributes.timestamp) { - excess_blob_gas = if chain_spec.is_cancun_active_at_timestamp(parent_block.timestamp) { - let parent_excess_blob_gas = parent_block.excess_blob_gas.unwrap_or_default(); - let parent_blob_gas_used = parent_block.blob_gas_used.unwrap_or_default(); - Some(calculate_excess_blob_gas(parent_excess_blob_gas, parent_blob_gas_used)) - } else { - // for the first post-fork block, both parent.blob_gas_used and - // parent.excess_blob_gas are evaluated as 0 - Some(calculate_excess_blob_gas(0, 0)) - }; - - blob_gas_used = Some(0); - } - - let header = Header { - parent_hash: parent_block.hash(), - ommers_hash: EMPTY_OMMER_ROOT_HASH, - beneficiary: initialized_block_env.coinbase, - state_root, - transactions_root, - receipts_root, - withdrawals_root, - logs_bloom, - timestamp: attributes.payload_attributes.timestamp, - mix_hash: attributes.payload_attributes.prev_randao, - nonce: BEACON_NONCE, - base_fee_per_gas: Some(base_fee), - number: parent_block.number + 1, - gas_limit: block_gas_limit, - difficulty: U256::ZERO, - gas_used: cumulative_gas_used, - extra_data, - parent_beacon_block_root: attributes.payload_attributes.parent_beacon_block_root, - blob_gas_used, - excess_blob_gas, - requests_root: None, - }; - - // NOTE: create new function to seal the block - let block = Block { header, body: executed_txs, ommers: vec![], withdrawals, requests: None }; - - let sealed_block = block.seal_slow(); - debug!(target: "payload_builder", ?sealed_block, "sealed built block"); - - // NOTE: into impl for built payload - let executed = ExecutedBlock { - block: Arc::new(sealed_block.clone()), - senders: Arc::new(executed_senders), - execution_output: Arc::new(execution_outcome), - hashed_state: Arc::new(hashed_state), - trie: Arc::new(trie_output), - }; - - let mut payload = OptimismBuiltPayload::new( - attributes.payload_attributes.id, - sealed_block, - total_fees, - chain_spec, - attributes, - Some(executed), - ); - - // extend the payload with the blob sidecars from the executed txs - payload.extend_sidecars(blob_sidecars); - - Ok(BuildOutcome::Better { payload, cached_reads }) -} From c738930159d57a393b45c5b81d88b498d1afb8d4 Mon Sep 17 00:00:00 2001 From: 0xKitsune <0xkitsune@protonmail.com> Date: Fri, 13 Sep 2024 20:56:03 -0400 Subject: [PATCH 16/32] update function visibility --- crates/optimism/payload/src/builder.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/optimism/payload/src/builder.rs b/crates/optimism/payload/src/builder.rs index 7b16b02a3e58..7efda0dfbc79 100644 --- a/crates/optimism/payload/src/builder.rs +++ b/crates/optimism/payload/src/builder.rs @@ -620,7 +620,7 @@ where } } -fn evm_transact_commit( +pub fn evm_transact_commit( transaction: &TransactionSignedEcRecovered, initialized_cfg: CfgEnvWithHandlerCfg, initialized_block_env: BlockEnv, From ba0022b99d71d2e74b4a8d399dc8cfa340591684 Mon Sep 17 00:00:00 2001 From: 0xKitsune <0xkitsune@protonmail.com> Date: Fri, 13 Sep 2024 21:03:49 -0400 Subject: [PATCH 17/32] move block attributes --- crates/optimism/payload/src/builder.rs | 127 ++++++++++++------------- crates/optimism/payload/src/lib.rs | 1 + 2 files changed, 64 insertions(+), 64 deletions(-) diff --git a/crates/optimism/payload/src/builder.rs b/crates/optimism/payload/src/builder.rs index 7efda0dfbc79..e1d0432609af 100644 --- a/crates/optimism/payload/src/builder.rs +++ b/crates/optimism/payload/src/builder.rs @@ -330,12 +330,11 @@ impl OptimismPayloadBuilder { }); } - let parent_block = config.parent_block; - let (withdrawals_outcome, execution_outcome) = self.construct_outcome(&op_block_attributes, &mut db)?; // calculate the state root + let parent_block = config.parent_block; let hashed_state = HashedPostState::from_bundle_state(&execution_outcome.state().state); let (state_root, trie_output) = { let state_provider = db.database.0.inner.borrow_mut(); @@ -363,21 +362,70 @@ impl OptimismPayloadBuilder { } } +/// Implementation of the [`PayloadBuilder`] trait for [`OptimismPayloadBuilder`]. +impl PayloadBuilder for OptimismPayloadBuilder +where + Client: StateProviderFactory, + Pool: TransactionPool, + EvmConfig: ConfigureEvm, +{ + type Attributes = OptimismPayloadBuilderAttributes; + type BuiltPayload = OptimismBuiltPayload; + + fn try_build( + &self, + args: BuildArguments, + ) -> Result, PayloadBuilderError> { + self.build_payload(self.evm_config.clone(), args, self.compute_pending_block) + } + + fn on_missing_payload( + &self, + _args: BuildArguments, + ) -> MissingPayloadBehaviour { + // we want to await the job that's already in progress because that should be returned as + // is, there's no benefit in racing another job + MissingPayloadBehaviour::AwaitInProgress + } + + // NOTE: this should only be used for testing purposes because this doesn't have access to L1 + // system txs, hence on_missing_payload we return [MissingPayloadBehaviour::AwaitInProgress]. + fn build_empty_payload( + &self, + client: &Client, + config: PayloadConfig, + ) -> Result { + let args = BuildArguments { + client, + config, + // we use defaults here because for the empty payload we don't need to execute anything + pool: NoopTransactionPool::default(), + cached_reads: Default::default(), + cancel: Default::default(), + best_payload: None, + }; + + self.build_payload(self.evm_config.clone(), args, false)? + .into_payload() + .ok_or_else(|| PayloadBuilderError::MissingPayload) + } +} + #[derive(Debug)] pub struct OptimismBlockAttributes { - receipts: Vec>, - executed_txs: Vec, - executed_senders: Vec
, - is_regolith: bool, - block_gas_limit: u64, - base_fee: u64, - total_fees: U256, - cumulative_gas_used: u64, - evm_config: EvmConfig, - initialized_block_env: BlockEnv, - initialized_cfg: CfgEnvWithHandlerCfg, - chain_spec: Arc, - payload_attributes: OptimismPayloadBuilderAttributes, + pub receipts: Vec>, + pub executed_txs: Vec, + pub executed_senders: Vec
, + pub is_regolith: bool, + pub block_gas_limit: u64, + pub base_fee: u64, + pub total_fees: U256, + pub cumulative_gas_used: u64, + pub evm_config: EvmConfig, + pub initialized_block_env: BlockEnv, + pub initialized_cfg: CfgEnvWithHandlerCfg, + pub chain_spec: Arc, + pub payload_attributes: OptimismPayloadBuilderAttributes, } impl OptimismBlockAttributes @@ -640,52 +688,3 @@ where evm.transact_commit() } - -/// Implementation of the [`PayloadBuilder`] trait for [`OptimismPayloadBuilder`]. -impl PayloadBuilder for OptimismPayloadBuilder -where - Client: StateProviderFactory, - Pool: TransactionPool, - EvmConfig: ConfigureEvm, -{ - type Attributes = OptimismPayloadBuilderAttributes; - type BuiltPayload = OptimismBuiltPayload; - - fn try_build( - &self, - args: BuildArguments, - ) -> Result, PayloadBuilderError> { - self.build_payload(self.evm_config.clone(), args, self.compute_pending_block) - } - - fn on_missing_payload( - &self, - _args: BuildArguments, - ) -> MissingPayloadBehaviour { - // we want to await the job that's already in progress because that should be returned as - // is, there's no benefit in racing another job - MissingPayloadBehaviour::AwaitInProgress - } - - // NOTE: this should only be used for testing purposes because this doesn't have access to L1 - // system txs, hence on_missing_payload we return [MissingPayloadBehaviour::AwaitInProgress]. - fn build_empty_payload( - &self, - client: &Client, - config: PayloadConfig, - ) -> Result { - let args = BuildArguments { - client, - config, - // we use defaults here because for the empty payload we don't need to execute anything - pool: NoopTransactionPool::default(), - cached_reads: Default::default(), - cancel: Default::default(), - best_payload: None, - }; - - self.build_payload(self.evm_config.clone(), args, false)? - .into_payload() - .ok_or_else(|| PayloadBuilderError::MissingPayload) - } -} diff --git a/crates/optimism/payload/src/lib.rs b/crates/optimism/payload/src/lib.rs index 2bb60594287a..53bb3bebd238 100644 --- a/crates/optimism/payload/src/lib.rs +++ b/crates/optimism/payload/src/lib.rs @@ -15,6 +15,7 @@ pub mod builder; pub use builder::OptimismPayloadBuilder; pub mod error; pub mod payload; + pub use payload::{ OptimismBuiltPayload, OptimismPayloadAttributes, OptimismPayloadBuilderAttributes, }; From 1fa2149212d1edd657cd32df5ec01fa7dedad005 Mon Sep 17 00:00:00 2001 From: 0xKitsune <0xkitsune@protonmail.com> Date: Fri, 13 Sep 2024 23:53:02 -0400 Subject: [PATCH 18/32] add docs --- crates/optimism/payload/src/builder.rs | 157 +++++++++++++++++++++++++ 1 file changed, 157 insertions(+) diff --git a/crates/optimism/payload/src/builder.rs b/crates/optimism/payload/src/builder.rs index e1d0432609af..016d45aa0a62 100644 --- a/crates/optimism/payload/src/builder.rs +++ b/crates/optimism/payload/src/builder.rs @@ -63,6 +63,19 @@ impl OptimismPayloadBuilder { self.compute_pending_block } + /// Initializes the pre-block state for payload building. + /// + /// # Arguments + /// + /// * `payload_config` - Payload configuration containing attributes and environments for the block + /// * `evm_config` - EVM configuration for the execution environment + /// * `db` - Mutable reference to the database + /// + /// # Returns + /// + /// * `Ok(())` if the pre-block state is successfully initialized. + /// * `Err(PayloadBuilderError::Internal(_))` if applying the beacon root contract call fails. + /// * `Err(PayloadBuilderError::other(OptimismPayloadBuilderError::ForceCreate2DeployerFail))` if ensuring the create2 deployer fails. pub fn init_pre_block_state( &self, payload_config: &PayloadConfig, @@ -116,6 +129,24 @@ impl OptimismPayloadBuilder { }) } + /// Constructs new block attributes with a populated list of transactions + /// This method uses the provided payload attributes and transaction pool to populate the + /// block attributes. + /// + /// # Arguments + /// + /// * `pool` - The transaction pool from which to retrieve pooled transactions + /// * `payload_config` - Reference to the payload configuration containing attributes and environments + /// * `db` - Mutable reference to the database + /// * `cancel` - Marker used to signal that the payload builder should cancel building the block + /// + /// # Returns + /// + /// * `Ok(OptimismBlockAttributes)` with populated block attributes on success + /// * `Err(PayloadBuilderError::BuildOutcomeCancelled)` if the operation was cancelled + /// * `Err(PayloadBuilderError::BlobTransactionRejected)` if a blob transaction is encountered + /// * `Err(PayloadBuilderError::TransactionEcRecoverFailed)` if EC recovery fails for a transaction + /// * `Err(PayloadBuilderError::EvmExecutionError(_))` if an EVM execution error occurs pub fn construct_block_attributes( &self, pool: Pool, @@ -144,6 +175,26 @@ impl OptimismPayloadBuilder { Ok(op_block_attributes) } + /// Constructs a built payload from the block attributes and execution outcomes + /// + /// This method assembles the final `OptimismBuiltPayload` by combining the block attributes, + /// execution outcomes, state roots, and other necessary components. It ensures that all + /// parts of the payload are correctly integrated and sealed for submission. + /// + /// # Arguments + /// + /// * `op_block_attributes` - The populated `OptimismBlockAttributes` containing block details + /// * `parent_block` - Reference to the parent sealed block + /// * `execution_outcome` - The outcome of executing transactions within the block + /// * `state_root` - The resulting state root after transaction execution + /// * `withdrawals_outcome` - The outcome of processing withdrawals + /// * `hashed_state` - The hashed post-state after applying all transitions + /// * `trie_output` - Updates to the trie based on state changes + /// * `extra_data` - Additional data to include in the block header + /// + /// # Returns + /// + /// An `OptimismBuiltPayload` representing the fully constructed block payload. pub fn construct_built_payload( &self, op_block_attributes: OptimismBlockAttributes, @@ -249,6 +300,17 @@ impl OptimismPayloadBuilder { payload } + /// Constructs the execution and withdrawal outcomes resulting from transactions in the block. + /// + /// # Arguments + /// + /// * `op_block_attributes` - Reference to the `OptimismBlockAttributes` containing block details + /// * `db` - Mutable reference to the database + /// + /// # Returns + /// + /// * `Ok((WithdrawalsOutcome, ExecutionOutcome))` containing the outcomes of withdrawals and transaction executions. + /// * `Err(ProviderError)` if processing withdrawals fails or state merging encounters an error. pub fn construct_outcome( &self, op_block_attributes: &OptimismBlockAttributes, @@ -411,20 +473,38 @@ where } } +/// Represents the attributes and state required to build an Optimism block +/// +/// This struct holds all necessary data for constructing a block on the Optimism +/// network, including executed transactions, receipts, gas usage, and EVM-specific +/// configuration parameters #[derive(Debug)] pub struct OptimismBlockAttributes { + /// Receipts corresponding to each transaction in the block pub receipts: Vec>, + /// Signed transactions executed in the block pub executed_txs: Vec, + /// Addresses of the senders of the executed transactions pub executed_senders: Vec
, + /// Indicates if the Regolith hardfork is active at the block's timestamp pub is_regolith: bool, + /// Gas limit for the block pub block_gas_limit: u64, + /// Base fee per execution gas pub base_fee: u64, + /// Total fees collected from all transactions pub total_fees: U256, + /// Cumulative gas used by all transactions pub cumulative_gas_used: u64, + /// Configuration parameters for the EVM environment pub evm_config: EvmConfig, + /// Initialized block environment containing block-specific information pub initialized_block_env: BlockEnv, + /// Initialized configuration environment with handler settings pub initialized_cfg: CfgEnvWithHandlerCfg, + /// Shared reference to the chain specification pub chain_spec: Arc, + /// Payload attributes sent from op-node for block construction pub payload_attributes: OptimismPayloadBuilderAttributes, } @@ -432,6 +512,18 @@ impl OptimismBlockAttributes where EvmConfig: ConfigureEvm, { + /// Creates a new `OptimismBlockAttributes` instance. + /// + /// Initializes the block attributes based on the provided payload configuration + /// and EVM configuration. + /// + /// # Arguments + /// + /// * `payload_config` - The `PayloadConfig` containing + /// `OptimismPayloadBuilderAttributes`, initialized block environment, configuration + /// environment, and chain specification. + /// * `evm_config` - The EVM configuration implementing the `ConfigureEvm` trait, + /// which dictates how the EVM should behave during transaction execution. pub fn new( payload_config: &PayloadConfig, evm_config: EvmConfig, @@ -470,6 +562,21 @@ where } } + /// Adds sequencer transactions to the block + /// + /// # Arguments + /// + /// * `transactions` - Slice of encoded, signed transactions + /// * `db` - Mutable reference to the databsae + /// * `cancel` - Marker used to signal that the payload builder should cancel building the block + /// + /// # Returns + /// + /// * `Ok(())` on successful addition of all valid sequencer transactions + /// * `Err(PayloadBuilderError::BuildOutcomeCancelled)` if the operation was cancelled + /// * `Err(PayloadBuilderError::BlobTransactionRejected)` if a blob transaction is encountered + /// * `Err(PayloadBuilderError::TransactionEcRecoverFailed)` if EC recovery fails for a transaction + /// * `Err(PayloadBuilderError::EvmExecutionError(EVMError::Transaction(_)))` if an EVM execution error occurs pub fn add_sequencer_transactions( &mut self, transactions: &[WithEncoded], @@ -520,6 +627,19 @@ where Ok(()) } + /// Adds transactions to the block from the transaction pool + /// + /// # Arguments + /// + /// * `pool` - Reference to the transaction pool to retrieve transactions from + /// * `db` - Mutable reference to the database + /// * `cancel` - Marker used to signal that the payload builder should cancel building the block + /// + /// # Returns + /// + /// * `Ok(())` on successful addition of all valid pooled transactions + /// * `Err(PayloadBuilderError::BuildOutcomeCancelled)` if the operation was cancelled + /// * `Err(PayloadBuilderError::EvmExecutionError(_))` if an EVM execution error occurs pub fn add_pooled_transactions( &mut self, pool: &Pool, @@ -568,6 +688,18 @@ where Ok(()) } + /// Insert a transaction derived from payload attributes into the block, committing state to the db + /// + /// # Arguments + /// + /// * `tx` - Reference to the signed and recovered sequencer transaction + /// * `db` - Mutable reference to the database + /// + /// # Returns + /// + /// * `Ok(())` on successful execution and insertion of the sequencer transaction + /// * `Err(PayloadBuilderError::AccountLoadFailed(_))` if loading the depositor account fails + /// * `Err(PayloadBuilderError::EvmExecutionError(_))` if an EVM execution error occurs pub fn insert_sequencer_transaction( &mut self, tx: &TransactionSignedEcRecovered, @@ -625,6 +757,17 @@ where Ok(()) } + /// Inserts a transaction from the transaction pool into the block, committing state to the db + /// + /// # Arguments + /// + /// * `tx` - Reference to the signed and recovered pooled transaction + /// * `db` - Mutable reference to the database + /// + /// # Returns + /// + /// * `Ok(())` on successful execution and insertion of the pooled transaction + /// * `Err(PayloadBuilderError::EvmExecutionError(_))` if an EVM execution error occurs pub fn insert_pooled_transaction( &mut self, tx: &TransactionSignedEcRecovered, @@ -668,6 +811,20 @@ where } } +/// Executes a transaction within the EVM and commits the state changes to the db +/// +/// # Arguments +/// +/// * `transaction` - Reference to the recovered transaction to execute +/// * `initialized_cfg` - Initialized configuration environment with handler settings +/// * `initialized_block_env` - Initialized block environment containing block-specific information +/// * `evm_config` - EVM configuration +/// * `db` - Mutable reference to the database +/// +/// # Returns +/// +/// * `Ok(ExecutionResult)` with the result of the transaction execution on success +/// * `Err(EVMError)` if the transaction execution fails due to EVM-related errors pub fn evm_transact_commit( transaction: &TransactionSignedEcRecovered, initialized_cfg: CfgEnvWithHandlerCfg, From 4f821b2c0485b18511829717849b6279804ba20a Mon Sep 17 00:00:00 2001 From: 0xKitsune <0xkitsune@protonmail.com> Date: Sat, 14 Sep 2024 00:20:51 -0400 Subject: [PATCH 19/32] clippy and fmt --- crates/optimism/payload/src/builder.rs | 59 ++++++++++++++------------ 1 file changed, 31 insertions(+), 28 deletions(-) diff --git a/crates/optimism/payload/src/builder.rs b/crates/optimism/payload/src/builder.rs index 016d45aa0a62..6b81c6d8d316 100644 --- a/crates/optimism/payload/src/builder.rs +++ b/crates/optimism/payload/src/builder.rs @@ -67,7 +67,8 @@ impl OptimismPayloadBuilder { /// /// # Arguments /// - /// * `payload_config` - Payload configuration containing attributes and environments for the block + /// * `payload_config` - Payload configuration containing attributes and environments for the + /// block /// * `evm_config` - EVM configuration for the execution environment /// * `db` - Mutable reference to the database /// @@ -75,7 +76,8 @@ impl OptimismPayloadBuilder { /// /// * `Ok(())` if the pre-block state is successfully initialized. /// * `Err(PayloadBuilderError::Internal(_))` if applying the beacon root contract call fails. - /// * `Err(PayloadBuilderError::other(OptimismPayloadBuilderError::ForceCreate2DeployerFail))` if ensuring the create2 deployer fails. + /// * `Err(PayloadBuilderError::other(OptimismPayloadBuilderError::ForceCreate2DeployerFail))` + /// if ensuring the create2 deployer fails. pub fn init_pre_block_state( &self, payload_config: &PayloadConfig, @@ -100,9 +102,9 @@ impl OptimismPayloadBuilder { pre_block_beacon_root_contract_call( db, &evm_config, - &chain_spec, - &initialized_cfg, - &initialized_block_env, + chain_spec, + initialized_cfg, + initialized_block_env, attributes.payload_attributes.parent_beacon_block_root, ) .map_err(|err| { @@ -136,7 +138,8 @@ impl OptimismPayloadBuilder { /// # Arguments /// /// * `pool` - The transaction pool from which to retrieve pooled transactions - /// * `payload_config` - Reference to the payload configuration containing attributes and environments + /// * `payload_config` - Reference to the payload configuration containing attributes and + /// environments /// * `db` - Mutable reference to the database /// * `cancel` - Marker used to signal that the payload builder should cancel building the block /// @@ -145,7 +148,8 @@ impl OptimismPayloadBuilder { /// * `Ok(OptimismBlockAttributes)` with populated block attributes on success /// * `Err(PayloadBuilderError::BuildOutcomeCancelled)` if the operation was cancelled /// * `Err(PayloadBuilderError::BlobTransactionRejected)` if a blob transaction is encountered - /// * `Err(PayloadBuilderError::TransactionEcRecoverFailed)` if EC recovery fails for a transaction + /// * `Err(PayloadBuilderError::TransactionEcRecoverFailed)` if EC recovery fails for a + /// transaction /// * `Err(PayloadBuilderError::EvmExecutionError(_))` if an EVM execution error occurs pub fn construct_block_attributes( &self, @@ -166,11 +170,11 @@ impl OptimismPayloadBuilder { op_block_attributes.add_sequencer_transactions( &payload_config.attributes.transactions, db, - &cancel, + cancel, )?; // add pooled transactions to block - op_block_attributes.add_pooled_transactions(&pool, db, &cancel)?; + op_block_attributes.add_pooled_transactions(&pool, db, cancel)?; Ok(op_block_attributes) } @@ -288,28 +292,28 @@ impl OptimismPayloadBuilder { trie: Arc::new(trie_output), }; - let payload = OptimismBuiltPayload::new( + OptimismBuiltPayload::new( op_block_attributes.payload_attributes.payload_id(), sealed_block, op_block_attributes.total_fees, chain_spec, op_block_attributes.payload_attributes, Some(executed), - ); - - payload + ) } /// Constructs the execution and withdrawal outcomes resulting from transactions in the block. /// /// # Arguments /// - /// * `op_block_attributes` - Reference to the `OptimismBlockAttributes` containing block details + /// * `op_block_attributes` - Reference to the `OptimismBlockAttributes` containing block + /// details /// * `db` - Mutable reference to the database /// /// # Returns /// - /// * `Ok((WithdrawalsOutcome, ExecutionOutcome))` containing the outcomes of withdrawals and transaction executions. + /// * `Ok((WithdrawalsOutcome, ExecutionOutcome))` containing the outcomes of withdrawals and + /// transaction executions. /// * `Err(ProviderError)` if processing withdrawals fails or state merging encounters an error. pub fn construct_outcome( &self, @@ -337,7 +341,7 @@ impl OptimismPayloadBuilder { let execution_outcome = ExecutionOutcome::new( db.take_bundle(), - vec![op_block_attributes.receipts.to_owned()].into(), + op_block_attributes.receipts.clone(), block_number, Vec::new(), ); @@ -386,10 +390,7 @@ impl OptimismPayloadBuilder { // check if we have a better block if !is_better_payload(best_payload.as_ref(), op_block_attributes.total_fees) { // can skip building the block - return Ok(BuildOutcome::Aborted { - fees: op_block_attributes.total_fees, - cached_reads, - }); + return Ok(BuildOutcome::Aborted { fees: op_block_attributes.total_fees, cached_reads }); } let (withdrawals_outcome, execution_outcome) = @@ -519,11 +520,10 @@ where /// /// # Arguments /// - /// * `payload_config` - The `PayloadConfig` containing - /// `OptimismPayloadBuilderAttributes`, initialized block environment, configuration - /// environment, and chain specification. - /// * `evm_config` - The EVM configuration implementing the `ConfigureEvm` trait, - /// which dictates how the EVM should behave during transaction execution. + /// * `payload_config` - The `PayloadConfig` containing `OptimismPayloadBuilderAttributes`, + /// initialized block environment, configuration environment, and chain specification. + /// * `evm_config` - The EVM configuration implementing the `ConfigureEvm` trait, which dictates + /// how the EVM should behave during transaction execution. pub fn new( payload_config: &PayloadConfig, evm_config: EvmConfig, @@ -575,8 +575,10 @@ where /// * `Ok(())` on successful addition of all valid sequencer transactions /// * `Err(PayloadBuilderError::BuildOutcomeCancelled)` if the operation was cancelled /// * `Err(PayloadBuilderError::BlobTransactionRejected)` if a blob transaction is encountered - /// * `Err(PayloadBuilderError::TransactionEcRecoverFailed)` if EC recovery fails for a transaction - /// * `Err(PayloadBuilderError::EvmExecutionError(EVMError::Transaction(_)))` if an EVM execution error occurs + /// * `Err(PayloadBuilderError::TransactionEcRecoverFailed)` if EC recovery fails for a + /// transaction + /// * `Err(PayloadBuilderError::EvmExecutionError(EVMError::Transaction(_)))` if an EVM + /// execution error occurs pub fn add_sequencer_transactions( &mut self, transactions: &[WithEncoded], @@ -688,7 +690,8 @@ where Ok(()) } - /// Insert a transaction derived from payload attributes into the block, committing state to the db + /// Insert a transaction derived from payload attributes into the block, committing state to the + /// db /// /// # Arguments /// From e4c7911cfb28c3e49452801e47f03876c88aa18f Mon Sep 17 00:00:00 2001 From: 0xKitsune <0xkitsune@protonmail.com> Date: Sat, 14 Sep 2024 00:21:51 -0400 Subject: [PATCH 20/32] codespell --- crates/optimism/payload/src/builder.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/optimism/payload/src/builder.rs b/crates/optimism/payload/src/builder.rs index 6b81c6d8d316..84878e11b553 100644 --- a/crates/optimism/payload/src/builder.rs +++ b/crates/optimism/payload/src/builder.rs @@ -567,7 +567,7 @@ where /// # Arguments /// /// * `transactions` - Slice of encoded, signed transactions - /// * `db` - Mutable reference to the databsae + /// * `db` - Mutable reference to the database /// * `cancel` - Marker used to signal that the payload builder should cancel building the block /// /// # Returns From 91c8a4123b7152d819df60603a92bc053e3f5d3f Mon Sep 17 00:00:00 2001 From: 0xKitsune <0xkitsune@protonmail.com> Date: Sat, 14 Sep 2024 00:30:41 -0400 Subject: [PATCH 21/32] fix receipts --- crates/optimism/payload/src/builder.rs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/crates/optimism/payload/src/builder.rs b/crates/optimism/payload/src/builder.rs index 84878e11b553..91d23cc67a3b 100644 --- a/crates/optimism/payload/src/builder.rs +++ b/crates/optimism/payload/src/builder.rs @@ -14,7 +14,7 @@ use reth_payload_builder::error::PayloadBuilderError; use reth_payload_primitives::PayloadBuilderAttributes; use reth_primitives::{ constants::BEACON_NONCE, eip4844::calculate_excess_blob_gas, proofs, transaction::WithEncoded, - Address, Block, Bytes, Header, IntoRecoveredTransaction, Receipt, SealedBlock, + Address, Block, Bytes, Header, IntoRecoveredTransaction, Receipt, Receipts, SealedBlock, TransactionSigned, TransactionSignedEcRecovered, TxType, B256, EMPTY_OMMER_ROOT_HASH, }; use reth_provider::{ProviderError, StateProviderFactory}; @@ -338,13 +338,9 @@ impl OptimismPayloadBuilder { db.merge_transitions(BundleRetention::PlainState); let block_number = op_block_attributes.initialized_block_env.number.to::(); - - let execution_outcome = ExecutionOutcome::new( - db.take_bundle(), - op_block_attributes.receipts.clone(), - block_number, - Vec::new(), - ); + let receipts = Receipts { receipt_vec: vec![op_block_attributes.receipts.clone()] }; + let execution_outcome = + ExecutionOutcome::new(db.take_bundle(), receipts, block_number, Vec::new()); Ok((withdrawals_outcome, execution_outcome)) } From af84547dfc5dbeca60793d43b632cf253dc9b497 Mon Sep 17 00:00:00 2001 From: 0xKitsune <0xkitsune@protonmail.com> Date: Sat, 14 Sep 2024 00:38:41 -0400 Subject: [PATCH 22/32] fix arg count --- crates/optimism/payload/src/builder.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/crates/optimism/payload/src/builder.rs b/crates/optimism/payload/src/builder.rs index 91d23cc67a3b..0e9097837065 100644 --- a/crates/optimism/payload/src/builder.rs +++ b/crates/optimism/payload/src/builder.rs @@ -202,13 +202,11 @@ impl OptimismPayloadBuilder { pub fn construct_built_payload( &self, op_block_attributes: OptimismBlockAttributes, - parent_block: Arc, execution_outcome: ExecutionOutcome, state_root: B256, withdrawals_outcome: WithdrawalsOutcome, hashed_state: HashedPostState, trie_output: TrieUpdates, - extra_data: Bytes, ) -> OptimismBuiltPayload where EvmConfig: ConfigureEvm, @@ -232,6 +230,7 @@ impl OptimismPayloadBuilder { let mut blob_gas_used = None; // only determine cancun fields when active + let parent_block = op_block_attributes.parent_block; if chain_spec.is_cancun_active_at_timestamp(timestamp) { excess_blob_gas = if chain_spec.is_cancun_active_at_timestamp(parent_block.timestamp) { let parent_excess_blob_gas = parent_block.excess_blob_gas.unwrap_or_default(); @@ -263,7 +262,7 @@ impl OptimismPayloadBuilder { gas_limit: op_block_attributes.block_gas_limit, difficulty: U256::ZERO, gas_used: op_block_attributes.cumulative_gas_used, - extra_data, + extra_data: op_block_attributes.extra_data, parent_beacon_block_root: op_block_attributes .payload_attributes .parent_beacon_block_root(), @@ -408,13 +407,11 @@ impl OptimismPayloadBuilder { let payload = self.construct_built_payload( op_block_attributes, - parent_block, execution_outcome, state_root, withdrawals_outcome, hashed_state, trie_output, - config.extra_data, ); Ok(BuildOutcome::Better { payload, cached_reads }) @@ -503,6 +500,10 @@ pub struct OptimismBlockAttributes { pub chain_spec: Arc, /// Payload attributes sent from op-node for block construction pub payload_attributes: OptimismPayloadBuilderAttributes, + /// Reference to the parent block + pub parent_block: Arc, + /// Extra data to include in the block header + pub extra_data: Bytes, } impl OptimismBlockAttributes @@ -555,6 +556,8 @@ where initialized_cfg: payload_config.initialized_cfg.clone(), chain_spec: payload_config.chain_spec.clone(), payload_attributes: attributes.clone(), + parent_block: payload_config.parent_block.clone(), + extra_data: payload_config.extra_data.clone(), } } From cd6563d3ad2a896e500a18a4c96d297a17aee7e8 Mon Sep 17 00:00:00 2001 From: 0xKitsune <0xkitsune@protonmail.com> Date: Sat, 14 Sep 2024 00:42:59 -0400 Subject: [PATCH 23/32] simplify doc comments --- crates/optimism/payload/src/builder.rs | 77 +------------------------- 1 file changed, 3 insertions(+), 74 deletions(-) diff --git a/crates/optimism/payload/src/builder.rs b/crates/optimism/payload/src/builder.rs index 0e9097837065..42c55741a6fa 100644 --- a/crates/optimism/payload/src/builder.rs +++ b/crates/optimism/payload/src/builder.rs @@ -65,13 +65,6 @@ impl OptimismPayloadBuilder { /// Initializes the pre-block state for payload building. /// - /// # Arguments - /// - /// * `payload_config` - Payload configuration containing attributes and environments for the - /// block - /// * `evm_config` - EVM configuration for the execution environment - /// * `db` - Mutable reference to the database - /// /// # Returns /// /// * `Ok(())` if the pre-block state is successfully initialized. @@ -135,14 +128,6 @@ impl OptimismPayloadBuilder { /// This method uses the provided payload attributes and transaction pool to populate the /// block attributes. /// - /// # Arguments - /// - /// * `pool` - The transaction pool from which to retrieve pooled transactions - /// * `payload_config` - Reference to the payload configuration containing attributes and - /// environments - /// * `db` - Mutable reference to the database - /// * `cancel` - Marker used to signal that the payload builder should cancel building the block - /// /// # Returns /// /// * `Ok(OptimismBlockAttributes)` with populated block attributes on success @@ -179,23 +164,11 @@ impl OptimismPayloadBuilder { Ok(op_block_attributes) } - /// Constructs a built payload from the block attributes and execution outcomes - /// + /// Constructs a built payload from the block attributes and execution outcomes. /// This method assembles the final `OptimismBuiltPayload` by combining the block attributes, /// execution outcomes, state roots, and other necessary components. It ensures that all /// parts of the payload are correctly integrated and sealed for submission. /// - /// # Arguments - /// - /// * `op_block_attributes` - The populated `OptimismBlockAttributes` containing block details - /// * `parent_block` - Reference to the parent sealed block - /// * `execution_outcome` - The outcome of executing transactions within the block - /// * `state_root` - The resulting state root after transaction execution - /// * `withdrawals_outcome` - The outcome of processing withdrawals - /// * `hashed_state` - The hashed post-state after applying all transitions - /// * `trie_output` - Updates to the trie based on state changes - /// * `extra_data` - Additional data to include in the block header - /// /// # Returns /// /// An `OptimismBuiltPayload` representing the fully constructed block payload. @@ -303,12 +276,6 @@ impl OptimismPayloadBuilder { /// Constructs the execution and withdrawal outcomes resulting from transactions in the block. /// - /// # Arguments - /// - /// * `op_block_attributes` - Reference to the `OptimismBlockAttributes` containing block - /// details - /// * `db` - Mutable reference to the database - /// /// # Returns /// /// * `Ok((WithdrawalsOutcome, ExecutionOutcome))` containing the outcomes of withdrawals and @@ -511,16 +478,8 @@ where EvmConfig: ConfigureEvm, { /// Creates a new `OptimismBlockAttributes` instance. - /// /// Initializes the block attributes based on the provided payload configuration /// and EVM configuration. - /// - /// # Arguments - /// - /// * `payload_config` - The `PayloadConfig` containing `OptimismPayloadBuilderAttributes`, - /// initialized block environment, configuration environment, and chain specification. - /// * `evm_config` - The EVM configuration implementing the `ConfigureEvm` trait, which dictates - /// how the EVM should behave during transaction execution. pub fn new( payload_config: &PayloadConfig, evm_config: EvmConfig, @@ -563,12 +522,6 @@ where /// Adds sequencer transactions to the block /// - /// # Arguments - /// - /// * `transactions` - Slice of encoded, signed transactions - /// * `db` - Mutable reference to the database - /// * `cancel` - Marker used to signal that the payload builder should cancel building the block - /// /// # Returns /// /// * `Ok(())` on successful addition of all valid sequencer transactions @@ -630,12 +583,6 @@ where /// Adds transactions to the block from the transaction pool /// - /// # Arguments - /// - /// * `pool` - Reference to the transaction pool to retrieve transactions from - /// * `db` - Mutable reference to the database - /// * `cancel` - Marker used to signal that the payload builder should cancel building the block - /// /// # Returns /// /// * `Ok(())` on successful addition of all valid pooled transactions @@ -689,13 +636,8 @@ where Ok(()) } - /// Insert a transaction derived from payload attributes into the block, committing state to the - /// db - /// - /// # Arguments - /// - /// * `tx` - Reference to the signed and recovered sequencer transaction - /// * `db` - Mutable reference to the database + /// Insert a transaction derived from payload attributes into the block, + /// committing state to the db /// /// # Returns /// @@ -761,11 +703,6 @@ where /// Inserts a transaction from the transaction pool into the block, committing state to the db /// - /// # Arguments - /// - /// * `tx` - Reference to the signed and recovered pooled transaction - /// * `db` - Mutable reference to the database - /// /// # Returns /// /// * `Ok(())` on successful execution and insertion of the pooled transaction @@ -815,14 +752,6 @@ where /// Executes a transaction within the EVM and commits the state changes to the db /// -/// # Arguments -/// -/// * `transaction` - Reference to the recovered transaction to execute -/// * `initialized_cfg` - Initialized configuration environment with handler settings -/// * `initialized_block_env` - Initialized block environment containing block-specific information -/// * `evm_config` - EVM configuration -/// * `db` - Mutable reference to the database -/// /// # Returns /// /// * `Ok(ExecutionResult)` with the result of the transaction execution on success From 8f11f0bde5d31cd01d31327a6f050150a9dfd1eb Mon Sep 17 00:00:00 2001 From: 0xKitsune <0xkitsune@protonmail.com> Date: Sat, 14 Sep 2024 01:01:33 -0400 Subject: [PATCH 24/32] reorder function --- crates/optimism/payload/src/builder.rs | 146 ++++++++++++------------- 1 file changed, 73 insertions(+), 73 deletions(-) diff --git a/crates/optimism/payload/src/builder.rs b/crates/optimism/payload/src/builder.rs index 42c55741a6fa..7d7d0cb24859 100644 --- a/crates/optimism/payload/src/builder.rs +++ b/crates/optimism/payload/src/builder.rs @@ -63,6 +63,79 @@ impl OptimismPayloadBuilder { self.compute_pending_block } + /// Constructs an Ethereum transaction payload from the transactions sent through the + /// Payload attributes by the sequencer. If the `no_tx_pool` argument is passed in + /// the payload attributes, the transaction pool will be ignored and the only transactions + /// included in the payload will be those sent through the attributes. + /// + /// Given build arguments including an Ethereum client, transaction pool, + /// and configuration, this function creates a transaction payload. Returns + /// a result indicating success with the payload or an error in case of failure. + fn build_payload( + &self, + evm_config: EvmConfig, + args: BuildArguments, + _compute_pending_block: bool, + ) -> Result, PayloadBuilderError> + where + EvmConfig: ConfigureEvm, + Client: StateProviderFactory, + Pool: TransactionPool, + { + let BuildArguments { client, pool, mut cached_reads, config, cancel, best_payload } = args; + + let state_provider = client.state_by_block_hash(config.parent_block.hash())?; + let state = StateProviderDatabase::new(state_provider); + let mut db = State::builder() + .with_database_ref(cached_reads.as_db(state)) + .with_bundle_update() + .build(); + + self.init_pre_block_state(&config, evm_config, &mut db)?; + + let op_block_attributes = match self + .construct_block_attributes(pool, &config, &mut db, &cancel) + { + Ok(outcome) => Ok(outcome), + Err(PayloadBuilderError::BuildOutcomeCancelled) => return Ok(BuildOutcome::Cancelled), + Err(err) => Err(err), + }?; + + // check if we have a better block + if !is_better_payload(best_payload.as_ref(), op_block_attributes.total_fees) { + // can skip building the block + return Ok(BuildOutcome::Aborted { fees: op_block_attributes.total_fees, cached_reads }); + } + + let (withdrawals_outcome, execution_outcome) = + self.construct_outcome(&op_block_attributes, &mut db)?; + + // calculate the state root + let parent_block = config.parent_block; + let hashed_state = HashedPostState::from_bundle_state(&execution_outcome.state().state); + let (state_root, trie_output) = { + let state_provider = db.database.0.inner.borrow_mut(); + state_provider.db.state_root_with_updates(hashed_state.clone()).inspect_err(|err| { + warn!(target: "payload_builder", + parent_hash=%parent_block.hash(), + %err, + "failed to calculate state root for empty payload" + ); + })? + }; + + let payload = self.construct_built_payload( + op_block_attributes, + execution_outcome, + state_root, + withdrawals_outcome, + hashed_state, + trie_output, + ); + + Ok(BuildOutcome::Better { payload, cached_reads }) + } + /// Initializes the pre-block state for payload building. /// /// # Returns @@ -310,79 +383,6 @@ impl OptimismPayloadBuilder { Ok((withdrawals_outcome, execution_outcome)) } - - /// Constructs an Ethereum transaction payload from the transactions sent through the - /// Payload attributes by the sequencer. If the `no_tx_pool` argument is passed in - /// the payload attributes, the transaction pool will be ignored and the only transactions - /// included in the payload will be those sent through the attributes. - /// - /// Given build arguments including an Ethereum client, transaction pool, - /// and configuration, this function creates a transaction payload. Returns - /// a result indicating success with the payload or an error in case of failure. - fn build_payload( - &self, - evm_config: EvmConfig, - args: BuildArguments, - _compute_pending_block: bool, - ) -> Result, PayloadBuilderError> - where - EvmConfig: ConfigureEvm, - Client: StateProviderFactory, - Pool: TransactionPool, - { - let BuildArguments { client, pool, mut cached_reads, config, cancel, best_payload } = args; - - let state_provider = client.state_by_block_hash(config.parent_block.hash())?; - let state = StateProviderDatabase::new(state_provider); - let mut db = State::builder() - .with_database_ref(cached_reads.as_db(state)) - .with_bundle_update() - .build(); - - self.init_pre_block_state(&config, evm_config, &mut db)?; - - let op_block_attributes = match self - .construct_block_attributes(pool, &config, &mut db, &cancel) - { - Ok(outcome) => Ok(outcome), - Err(PayloadBuilderError::BuildOutcomeCancelled) => return Ok(BuildOutcome::Cancelled), - Err(err) => Err(err), - }?; - - // check if we have a better block - if !is_better_payload(best_payload.as_ref(), op_block_attributes.total_fees) { - // can skip building the block - return Ok(BuildOutcome::Aborted { fees: op_block_attributes.total_fees, cached_reads }); - } - - let (withdrawals_outcome, execution_outcome) = - self.construct_outcome(&op_block_attributes, &mut db)?; - - // calculate the state root - let parent_block = config.parent_block; - let hashed_state = HashedPostState::from_bundle_state(&execution_outcome.state().state); - let (state_root, trie_output) = { - let state_provider = db.database.0.inner.borrow_mut(); - state_provider.db.state_root_with_updates(hashed_state.clone()).inspect_err(|err| { - warn!(target: "payload_builder", - parent_hash=%parent_block.hash(), - %err, - "failed to calculate state root for empty payload" - ); - })? - }; - - let payload = self.construct_built_payload( - op_block_attributes, - execution_outcome, - state_root, - withdrawals_outcome, - hashed_state, - trie_output, - ); - - Ok(BuildOutcome::Better { payload, cached_reads }) - } } /// Implementation of the [`PayloadBuilder`] trait for [`OptimismPayloadBuilder`]. From d422762caa537117a8b473acbf09870c2899bf3d Mon Sep 17 00:00:00 2001 From: 0xKitsune <0xkitsune@protonmail.com> Date: Sat, 14 Sep 2024 01:06:42 -0400 Subject: [PATCH 25/32] fix comment --- crates/optimism/payload/src/builder.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/optimism/payload/src/builder.rs b/crates/optimism/payload/src/builder.rs index 7d7d0cb24859..f27ccbdb7bb1 100644 --- a/crates/optimism/payload/src/builder.rs +++ b/crates/optimism/payload/src/builder.rs @@ -436,8 +436,8 @@ where /// Represents the attributes and state required to build an Optimism block /// -/// This struct holds all necessary data for constructing a block on the Optimism -/// network, including executed transactions, receipts, gas usage, and EVM-specific +/// This struct holds all necessary data for constructing a block on Optimism +/// including executed transactions, receipts, gas usage, and EVM-specific /// configuration parameters #[derive(Debug)] pub struct OptimismBlockAttributes { From 6dc6c70b4a87c813f27a4738f624fac21305982b Mon Sep 17 00:00:00 2001 From: 0xKitsune <0xkitsune@protonmail.com> Date: Mon, 16 Sep 2024 10:16:12 -0400 Subject: [PATCH 26/32] remove unneeded functions --- crates/optimism/payload/src/builder.rs | 143 +++++++++++++++---------- 1 file changed, 86 insertions(+), 57 deletions(-) diff --git a/crates/optimism/payload/src/builder.rs b/crates/optimism/payload/src/builder.rs index f27ccbdb7bb1..841f2fa0bea6 100644 --- a/crates/optimism/payload/src/builder.rs +++ b/crates/optimism/payload/src/builder.rs @@ -441,7 +441,7 @@ where /// configuration parameters #[derive(Debug)] pub struct OptimismBlockAttributes { - /// Receipts corresponding to each transaction in the block + /// Receipts to each transaction in the block pub receipts: Vec>, /// Signed transactions executed in the block pub executed_txs: Vec, @@ -565,17 +565,64 @@ where ) })?; - if let Err(err) = self.insert_sequencer_transaction(&sequencer_tx, db) { - match err { - PayloadBuilderError::EvmExecutionError(EVMError::Transaction(err)) => { - trace!(target: "payload_builder", %err, ?sequencer_tx, "Error in sequencer transaction, skipping."); + // + // Note that this *only* needs to be done post-regolith hardfork, as deposit nonces + // were not introduced in Bedrock. In addition, regular transactions don't have deposit + // nonces, so we don't need to touch the DB for those. + let depositor = (self.is_regolith && sequencer_tx.is_deposit()) + .then(|| { + db.load_cache_account(sequencer_tx.signer()) + .map(|acc| acc.account_info().unwrap_or_default()) + }) + .transpose() + .map_err(|_| { + PayloadBuilderError::other(OptimismPayloadBuilderError::AccountLoadFailed( + sequencer_tx.signer(), + )) + })?; + + let execution_result = match evm_transact_commit( + &sequencer_tx, + self.initialized_cfg.clone(), + self.initialized_block_env.clone(), + self.evm_config.clone(), + db, + ) { + Ok(result) => result, + Err(err) => match err { + EVMError::Transaction(err) => { + trace!(target: "payload_builder", %err, ?sequencer_tx, "Error insequencer transaction, skipping."); continue; } other => { - return Err(other); + return Err(PayloadBuilderError::EvmExecutionError(other)); } - } - } + }, + }; + + self.cumulative_gas_used += execution_result.gas_used(); + + let receipt = Receipt { + tx_type: sequencer_tx.tx_type(), + success: execution_result.is_success(), + cumulative_gas_used: self.cumulative_gas_used, + logs: execution_result.into_logs().into_iter().map(Into::into).collect(), + deposit_nonce: depositor.map(|account| account.nonce), + // The deposit receipt version was introduced in Canyon to indicate an update to how + // receipt hashes should be computed when set. The state transition process + // ensures this is only set for post-Canyon deposit transactions. + deposit_receipt_version: self + .chain_spec + .is_fork_active_at_timestamp( + OptimismHardfork::Canyon, + self.payload_attributes.payload_attributes.timestamp, + ) + .then_some(1), + }; + + self.receipts.push(Some(receipt)); + self.executed_senders.push(sequencer_tx.signer()); + self.executed_txs.push(sequencer_tx.to_owned().into_signed()); } Ok(()) @@ -630,7 +677,37 @@ where // convert tx to a signed transaction let tx = pool_tx.to_recovered_transaction(); - self.insert_pooled_transaction(&tx, db)?; + + let execution_result = evm_transact_commit( + &tx, + self.initialized_cfg.clone(), + self.initialized_block_env.clone(), + self.evm_config.clone(), + db, + ) + .map_err(PayloadBuilderError::EvmExecutionError)?; + + let gas_used = execution_result.gas_used(); + self.cumulative_gas_used += gas_used; + + let receipt = Receipt { + tx_type: tx.tx_type(), + success: execution_result.is_success(), + cumulative_gas_used: self.cumulative_gas_used, + logs: execution_result.into_logs().into_iter().map(Into::into).collect(), + deposit_nonce: None, + deposit_receipt_version: None, + }; + + // update add to total fees + let miner_fee = tx + .effective_tip_per_gas(Some(self.base_fee)) + .expect("fee is always valid; execution succeeded"); + self.total_fees += U256::from(miner_fee) * U256::from(gas_used); + + self.receipts.push(Some(receipt)); + self.executed_senders.push(tx.signer()); + self.executed_txs.push(tx.to_owned().into_signed()); } Ok(()) @@ -700,54 +777,6 @@ where Ok(()) } - - /// Inserts a transaction from the transaction pool into the block, committing state to the db - /// - /// # Returns - /// - /// * `Ok(())` on successful execution and insertion of the pooled transaction - /// * `Err(PayloadBuilderError::EvmExecutionError(_))` if an EVM execution error occurs - pub fn insert_pooled_transaction( - &mut self, - tx: &TransactionSignedEcRecovered, - db: &mut revm::State, - ) -> Result<(), PayloadBuilderError> - where - DB: revm::Database, - { - let execution_result = evm_transact_commit( - tx, - self.initialized_cfg.clone(), - self.initialized_block_env.clone(), - self.evm_config.clone(), - db, - ) - .map_err(PayloadBuilderError::EvmExecutionError)?; - - let gas_used = execution_result.gas_used(); - self.cumulative_gas_used += gas_used; - - let receipt = Receipt { - tx_type: tx.tx_type(), - success: execution_result.is_success(), - cumulative_gas_used: self.cumulative_gas_used, - logs: execution_result.into_logs().into_iter().map(Into::into).collect(), - deposit_nonce: None, - deposit_receipt_version: None, - }; - - // update add to total fees - let miner_fee = tx - .effective_tip_per_gas(Some(self.base_fee)) - .expect("fee is always valid; execution succeeded"); - self.total_fees += U256::from(miner_fee) * U256::from(gas_used); - - self.receipts.push(Some(receipt)); - self.executed_senders.push(tx.signer()); - self.executed_txs.push(tx.to_owned().into_signed()); - - Ok(()) - } } /// Executes a transaction within the EVM and commits the state changes to the db From 47d0c8d734ffba017f59f6f006209dcdccb4079d Mon Sep 17 00:00:00 2001 From: 0xKitsune <0xkitsune@protonmail.com> Date: Mon, 16 Sep 2024 10:38:19 -0400 Subject: [PATCH 27/32] error handling for pooled transactions --- crates/optimism/payload/src/builder.rs | 33 ++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/crates/optimism/payload/src/builder.rs b/crates/optimism/payload/src/builder.rs index 841f2fa0bea6..b0b81213c497 100644 --- a/crates/optimism/payload/src/builder.rs +++ b/crates/optimism/payload/src/builder.rs @@ -25,7 +25,10 @@ use reth_transaction_pool::{ use reth_trie::{updates::TrieUpdates, HashedPostState}; use revm::{ db::states::bundle_state::BundleRetention, - primitives::{BlockEnv, CfgEnvWithHandlerCfg, EVMError, EnvWithHandlerCfg, ExecutionResult}, + primitives::{ + BlockEnv, CfgEnvWithHandlerCfg, EVMError, EnvWithHandlerCfg, ExecutionResult, + InvalidTransaction, + }, State, }; use std::sync::Arc; @@ -678,14 +681,36 @@ where // convert tx to a signed transaction let tx = pool_tx.to_recovered_transaction(); - let execution_result = evm_transact_commit( + let execution_result = match evm_transact_commit( &tx, self.initialized_cfg.clone(), self.initialized_block_env.clone(), self.evm_config.clone(), db, - ) - .map_err(PayloadBuilderError::EvmExecutionError)?; + ) { + Ok(res) => res, + Err(err) => { + match err { + EVMError::Transaction(err) => { + if matches!(err, InvalidTransaction::NonceTooLow { .. }) { + // if the nonce is too low, we can skip this transaction + trace!(target: "payload_builder", %err, ?tx, "skipping nonce too low transaction"); + } else { + // if the transaction is invalid, we can skip it and all of its + // descendants + trace!(target: "payload_builder", %err, ?tx, "skipping invalid transaction and its descendants"); + best_txs.mark_invalid(&pool_tx); + } + + continue; + } + err => { + // this is an error that we should treat as fatal for this attempt + return Err(PayloadBuilderError::EvmExecutionError(err)); + } + } + } + }; let gas_used = execution_result.gas_used(); self.cumulative_gas_used += gas_used; From f96f8dbddb4480e825ee9284b164c59a7be65de3 Mon Sep 17 00:00:00 2001 From: 0xKitsune <0xkitsune@protonmail.com> Date: Mon, 16 Sep 2024 18:45:38 -0400 Subject: [PATCH 28/32] clippy --- crates/optimism/payload/src/builder.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/optimism/payload/src/builder.rs b/crates/optimism/payload/src/builder.rs index b0b81213c497..cc97ee23e180 100644 --- a/crates/optimism/payload/src/builder.rs +++ b/crates/optimism/payload/src/builder.rs @@ -625,7 +625,7 @@ where self.receipts.push(Some(receipt)); self.executed_senders.push(sequencer_tx.signer()); - self.executed_txs.push(sequencer_tx.to_owned().into_signed()); + self.executed_txs.push(sequencer_tx.clone().into_signed()); } Ok(()) @@ -732,7 +732,7 @@ where self.receipts.push(Some(receipt)); self.executed_senders.push(tx.signer()); - self.executed_txs.push(tx.to_owned().into_signed()); + self.executed_txs.push(tx.clone().into_signed()); } Ok(()) From a012e9c2dc8bd70e39e8f2836d14b839bc338872 Mon Sep 17 00:00:00 2001 From: 0xKitsune <0xkitsune@protonmail.com> Date: Mon, 16 Sep 2024 23:51:13 -0400 Subject: [PATCH 29/32] add inline --- crates/optimism/payload/src/builder.rs | 73 +++----------------------- 1 file changed, 8 insertions(+), 65 deletions(-) diff --git a/crates/optimism/payload/src/builder.rs b/crates/optimism/payload/src/builder.rs index cc97ee23e180..095b27d8afe2 100644 --- a/crates/optimism/payload/src/builder.rs +++ b/crates/optimism/payload/src/builder.rs @@ -74,6 +74,7 @@ impl OptimismPayloadBuilder { /// Given build arguments including an Ethereum client, transaction pool, /// and configuration, this function creates a transaction payload. Returns /// a result indicating success with the payload or an error in case of failure. + #[inline] fn build_payload( &self, evm_config: EvmConfig, @@ -147,6 +148,7 @@ impl OptimismPayloadBuilder { /// * `Err(PayloadBuilderError::Internal(_))` if applying the beacon root contract call fails. /// * `Err(PayloadBuilderError::other(OptimismPayloadBuilderError::ForceCreate2DeployerFail))` /// if ensuring the create2 deployer fails. + #[inline] pub fn init_pre_block_state( &self, payload_config: &PayloadConfig, @@ -212,6 +214,7 @@ impl OptimismPayloadBuilder { /// * `Err(PayloadBuilderError::TransactionEcRecoverFailed)` if EC recovery fails for a /// transaction /// * `Err(PayloadBuilderError::EvmExecutionError(_))` if an EVM execution error occurs + #[inline] pub fn construct_block_attributes( &self, pool: Pool, @@ -248,6 +251,7 @@ impl OptimismPayloadBuilder { /// # Returns /// /// An `OptimismBuiltPayload` representing the fully constructed block payload. + #[inline] pub fn construct_built_payload( &self, op_block_attributes: OptimismBlockAttributes, @@ -357,6 +361,7 @@ impl OptimismPayloadBuilder { /// * `Ok((WithdrawalsOutcome, ExecutionOutcome))` containing the outcomes of withdrawals and /// transaction executions. /// * `Err(ProviderError)` if processing withdrawals fails or state merging encounters an error. + #[inline] pub fn construct_outcome( &self, op_block_attributes: &OptimismBlockAttributes, @@ -534,6 +539,7 @@ where /// transaction /// * `Err(PayloadBuilderError::EvmExecutionError(EVMError::Transaction(_)))` if an EVM /// execution error occurs + #[inline] pub fn add_sequencer_transactions( &mut self, transactions: &[WithEncoded], @@ -638,6 +644,7 @@ where /// * `Ok(())` on successful addition of all valid pooled transactions /// * `Err(PayloadBuilderError::BuildOutcomeCancelled)` if the operation was cancelled /// * `Err(PayloadBuilderError::EvmExecutionError(_))` if an EVM execution error occurs + #[inline] pub fn add_pooled_transactions( &mut self, pool: &Pool, @@ -737,71 +744,6 @@ where Ok(()) } - - /// Insert a transaction derived from payload attributes into the block, - /// committing state to the db - /// - /// # Returns - /// - /// * `Ok(())` on successful execution and insertion of the sequencer transaction - /// * `Err(PayloadBuilderError::AccountLoadFailed(_))` if loading the depositor account fails - /// * `Err(PayloadBuilderError::EvmExecutionError(_))` if an EVM execution error occurs - pub fn insert_sequencer_transaction( - &mut self, - tx: &TransactionSignedEcRecovered, - db: &mut revm::State, - ) -> Result<(), PayloadBuilderError> - where - DB: revm::Database, - { - // Cache the depositor account prior to the state transition for the deposit nonce. - // - // Note that this *only* needs to be done post-regolith hardfork, as deposit nonces - // were not introduced in Bedrock. In addition, regular transactions don't have deposit - // nonces, so we don't need to touch the DB for those. - let depositor = (self.is_regolith && tx.is_deposit()) - .then(|| { - db.load_cache_account(tx.signer()).map(|acc| acc.account_info().unwrap_or_default()) - }) - .transpose() - .map_err(|_| { - PayloadBuilderError::other(OptimismPayloadBuilderError::AccountLoadFailed( - tx.signer(), - )) - })?; - - let execution_result = evm_transact_commit( - tx, - self.initialized_cfg.clone(), - self.initialized_block_env.clone(), - self.evm_config.clone(), - db, - ) - .map_err(PayloadBuilderError::EvmExecutionError)?; - - self.cumulative_gas_used += execution_result.gas_used(); - - let receipt = Receipt { - tx_type: tx.tx_type(), - success: execution_result.is_success(), - cumulative_gas_used: self.cumulative_gas_used, - logs: execution_result.into_logs().into_iter().map(Into::into).collect(), - deposit_nonce: depositor.map(|account| account.nonce), - deposit_receipt_version: self - .chain_spec - .is_fork_active_at_timestamp( - OptimismHardfork::Canyon, - self.payload_attributes.payload_attributes.timestamp, - ) - .then_some(1), - }; - - self.receipts.push(Some(receipt)); - self.executed_senders.push(tx.signer()); - self.executed_txs.push(tx.to_owned().into_signed()); - - Ok(()) - } } /// Executes a transaction within the EVM and commits the state changes to the db @@ -810,6 +752,7 @@ where /// /// * `Ok(ExecutionResult)` with the result of the transaction execution on success /// * `Err(EVMError)` if the transaction execution fails due to EVM-related errors +#[inline] pub fn evm_transact_commit( transaction: &TransactionSignedEcRecovered, initialized_cfg: CfgEnvWithHandlerCfg, From b4334dca692bfa392f671e8ab8c0ccba7d50cb91 Mon Sep 17 00:00:00 2001 From: 0xKitsune <0xKitsune@protonmail.com> Date: Tue, 17 Sep 2024 13:34:13 -0400 Subject: [PATCH 30/32] fix compilation and imports --- crates/optimism/payload/src/builder.rs | 29 ++++++++++++++++---------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/crates/optimism/payload/src/builder.rs b/crates/optimism/payload/src/builder.rs index d056773de04c..eddca5b88b02 100644 --- a/crates/optimism/payload/src/builder.rs +++ b/crates/optimism/payload/src/builder.rs @@ -7,10 +7,9 @@ use crate::{ use alloy_primitives::U256; use reth_basic_payload_builder::*; use reth_chain_state::ExecutedBlock; -use reth_chainspec::{EthereumHardforks, OptimismHardfork}; +use reth_chainspec::{ChainSpec, EthereumHardforks, OptimismHardfork}; use reth_evm::{ - system_calls::pre_block_beacon_root_contract_call, ConfigureEvm, ConfigureEvmEnv, - NextBlockEnvAttributes, + system_calls::pre_block_beacon_root_contract_call, ConfigureEvm, NextBlockEnvAttributes, }; use reth_execution_types::ExecutionOutcome; use reth_payload_builder::error::PayloadBuilderError; @@ -20,7 +19,9 @@ use reth_primitives::{ eip4844::calculate_excess_blob_gas, proofs, revm_primitives::{BlockEnv, CfgEnvWithHandlerCfg}, - Block, Header, IntoRecoveredTransaction, Receipt, TxType, EMPTY_OMMER_ROOT_HASH, + transaction::WithEncoded, + Address, Block, Bytes, Header, IntoRecoveredTransaction, Receipt, Receipts, SealedBlock, + TransactionSigned, TransactionSignedEcRecovered, TxType, B256, EMPTY_OMMER_ROOT_HASH, }; use reth_provider::{ProviderError, StateProviderFactory}; use reth_revm::database::StateProviderDatabase; @@ -30,14 +31,14 @@ use reth_transaction_pool::{ use reth_trie::{updates::TrieUpdates, HashedPostState}; use revm::{ db::states::bundle_state::BundleRetention, - primitives::{ - BlobExcessGasAndPrice, BlockEnv, CfgEnv, CfgEnvWithHandlerCfg, EVMError, EnvWithHandlerCfg, - ExecutionResult, HandlerCfg, InvalidTransaction, SpecId, - }, + primitives::{EVMError, EnvWithHandlerCfg, ExecutionResult, InvalidTransaction}, State, }; +use std::sync::Arc; +use tracing::{debug, trace, warn}; /// Optimism's payload builder +#[derive(Clone)] pub struct OptimismPayloadBuilder { /// The rollup's compute pending block configuration option. // TODO(clabby): Implement this feature. @@ -46,7 +47,10 @@ pub struct OptimismPayloadBuilder { evm_config: EvmConfig, } -impl OptimismPayloadBuilder { +impl OptimismPayloadBuilder +where + EvmConfig: ConfigureEvm, +{ /// `OptimismPayloadBuilder` constructor. pub const fn new(evm_config: EvmConfig) -> Self { Self { compute_pending_block: true, evm_config } @@ -123,7 +127,10 @@ impl OptimismPayloadBuilder { // check if we have a better block if !is_better_payload(best_payload.as_ref(), op_block_attributes.total_fees) { // can skip building the block - return Ok(BuildOutcome::Aborted { fees: op_block_attributes.total_fees, cached_reads }); + return Ok(BuildOutcome::Aborted { + fees: op_block_attributes.total_fees, + cached_reads, + }); } let (withdrawals_outcome, execution_outcome) = @@ -419,7 +426,7 @@ impl OptimismPayloadBuilder { suggested_fee_recipient: config.attributes.suggested_fee_recipient(), prev_randao: config.attributes.prev_randao(), }; - self.evm_config.next_cfg_and_block_env(parent, next_attributes) + self.evm_config.next_cfg_and_block_env(&config.parent_block, next_attributes) } } From f6a3d682be2d1c1f5c901f0bc177d0c00a7ed21a Mon Sep 17 00:00:00 2001 From: 0xKitsune <0xKitsune@protonmail.com> Date: Tue, 17 Sep 2024 13:51:09 -0400 Subject: [PATCH 31/32] clippy --- crates/optimism/payload/src/builder.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/optimism/payload/src/builder.rs b/crates/optimism/payload/src/builder.rs index eddca5b88b02..ede170f455ac 100644 --- a/crates/optimism/payload/src/builder.rs +++ b/crates/optimism/payload/src/builder.rs @@ -36,9 +36,8 @@ use revm::{ }; use std::sync::Arc; use tracing::{debug, trace, warn}; - /// Optimism's payload builder -#[derive(Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct OptimismPayloadBuilder { /// The rollup's compute pending block configuration option. // TODO(clabby): Implement this feature. From 16a997b4600ca3be1caa2b756e75a92eb6529dc1 Mon Sep 17 00:00:00 2001 From: 0xKitsune <0xkitsune@protonmail.com> Date: Tue, 17 Sep 2024 14:00:37 -0400 Subject: [PATCH 32/32] fmt --- crates/optimism/payload/src/builder.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/crates/optimism/payload/src/builder.rs b/crates/optimism/payload/src/builder.rs index ede170f455ac..24a4d0b0ceac 100644 --- a/crates/optimism/payload/src/builder.rs +++ b/crates/optimism/payload/src/builder.rs @@ -126,10 +126,7 @@ where // check if we have a better block if !is_better_payload(best_payload.as_ref(), op_block_attributes.total_fees) { // can skip building the block - return Ok(BuildOutcome::Aborted { - fees: op_block_attributes.total_fees, - cached_reads, - }); + return Ok(BuildOutcome::Aborted { fees: op_block_attributes.total_fees, cached_reads }); } let (withdrawals_outcome, execution_outcome) =