Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: add execute_single_transaction to BlockExecutionStrategy #13803

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 58 additions & 1 deletion crates/ethereum/evm/src/execute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use crate::{
use alloc::{boxed::Box, sync::Arc, vec::Vec};
use alloy_consensus::{BlockHeader, Transaction};
use alloy_eips::{eip6110, eip7685::Requests};
use alloy_primitives::Address;
use core::fmt::Display;
use reth_chainspec::{ChainSpec, EthereumHardfork, EthereumHardforks, MAINNET};
use reth_consensus::ConsensusError;
Expand All @@ -21,7 +22,7 @@ use reth_evm::{
system_calls::{OnStateHook, SystemCaller},
ConfigureEvm, TxEnvOverrides,
};
use reth_primitives::{EthPrimitives, Receipt, RecoveredBlock};
use reth_primitives::{EthPrimitives, NodePrimitives, Receipt, RecoveredBlock};
use reth_primitives_traits::{BlockBody, SignedTransaction};
use reth_revm::db::State;
use revm_primitives::{
Expand Down Expand Up @@ -146,6 +147,62 @@ where
Ok(())
}

fn execute_single_transaction(
&mut self,
header: &<Self::Primitives as NodePrimitives>::BlockHeader,
transaction: &<Self::Primitives as NodePrimitives>::SignedTx,
sender: Address,
) -> Result<(Receipt, u64), Self::Error> {
let mut evm = self.evm_config.evm_for_block(&mut self.state, header);

// The sum of the transaction’s gas limit, Tg, and the gas utilized in this block prior,
// must be no greater than the block’s gasLimit.
if transaction.gas_limit() > header.gas_limit {
Comment on lines +158 to +160
Copy link
Member

@fgimenez fgimenez Jan 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this comment seems out of place, we are executing a single transaction?

return Err(BlockValidationError::TransactionGasLimitMoreThanAvailableBlockGas {
transaction_gas_limit: transaction.gas_limit(),
block_available_gas: header.gas_limit,
}
.into())
}

self.evm_config.fill_tx_env(evm.tx_mut(), transaction, sender);

if let Some(tx_env_overrides) = &mut self.tx_env_overrides {
tx_env_overrides.apply(evm.tx_mut());
}

// Execute transaction.
let result_and_state = evm.transact().map_err(move |err| {
let new_err = err.map_db_err(|e| e.into());
// Ensure hash is calculated for error log, if not already done
BlockValidationError::EVM {
hash: transaction.recalculate_hash(),
error: Box::new(new_err),
}
})?;
self.system_caller.on_state(&result_and_state.state);
let ResultAndState { result, state } = result_and_state;
evm.db_mut().commit(state);

// record gas used
let gas_used = result.gas_used();

// Push transaction changeset and calculate header bloom filter for receipt.
#[allow(clippy::needless_update)] // side-effect of optimism fields
let receipt = Receipt {
tx_type: transaction.tx_type(),
// Success flag was added in `EIP-658: Embedding transaction status code in
// receipts`.
success: result.is_success(),
cumulative_gas_used: gas_used,
// convert to reth log
logs: result.into_logs(),
..Default::default()
};

Ok((receipt, gas_used))
}

fn execute_transactions(
&mut self,
block: &RecoveredBlock<reth_primitives::Block>,
Expand Down
23 changes: 23 additions & 0 deletions crates/evm/src/execute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,14 @@ pub trait BlockExecutionStrategy {
block: &RecoveredBlock<<Self::Primitives as NodePrimitives>::Block>,
) -> Result<ExecuteOutput<<Self::Primitives as NodePrimitives>::Receipt>, Self::Error>;

/// Executes a single transaction in the block.
fn execute_single_transaction(
&mut self,
header: &<Self::Primitives as NodePrimitives>::BlockHeader,
transaction: &<Self::Primitives as NodePrimitives>::SignedTx,
sender: Address,
) -> Result<(<Self::Primitives as NodePrimitives>::Receipt, u64), Self::Error>;

/// Applies any necessary changes after executing the block's transactions.
fn apply_post_execution_changes(
&mut self,
Expand Down Expand Up @@ -611,13 +619,15 @@ mod tests {
_evm_config: EvmConfig,
state: State<DB>,
execute_transactions_result: ExecuteOutput<Receipt>,
execute_single_transaction_result: (Receipt, u64),
apply_post_execution_changes_result: Requests,
finish_result: BundleState,
}

#[derive(Clone)]
struct TestExecutorStrategyFactory {
execute_transactions_result: ExecuteOutput<Receipt>,
execute_single_transaction_result: (Receipt, u64),
apply_post_execution_changes_result: Requests,
finish_result: BundleState,
}
Expand All @@ -641,6 +651,7 @@ mod tests {
_chain_spec: MAINNET.clone(),
_evm_config: TestEvmConfig {},
execute_transactions_result: self.execute_transactions_result.clone(),
execute_single_transaction_result: self.execute_single_transaction_result.clone(),
apply_post_execution_changes_result: self
.apply_post_execution_changes_result
.clone(),
Expand Down Expand Up @@ -672,6 +683,15 @@ mod tests {
Ok(self.execute_transactions_result.clone())
}

fn execute_single_transaction(
&mut self,
_header: &<Self::Primitives as NodePrimitives>::BlockHeader,
_transaction: &<Self::Primitives as NodePrimitives>::SignedTx,
_sender: Address,
) -> Result<(<Self::Primitives as NodePrimitives>::Receipt, u64), Self::Error> {
Ok(self.execute_single_transaction_result.clone())
}

fn apply_post_execution_changes(
&mut self,
_block: &RecoveredBlock<reth_primitives::Block>,
Expand Down Expand Up @@ -723,11 +743,13 @@ mod tests {
receipts: expected_receipts.clone(),
gas_used: expected_gas_used,
};
let expected_execute_single_transaction_result = (Receipt::default(), 10);
let expected_apply_post_execution_changes_result = Requests::new(vec![bytes!("deadbeef")]);
let expected_finish_result = BundleState::default();

let strategy_factory = TestExecutorStrategyFactory {
execute_transactions_result: expected_execute_transactions_result,
execute_single_transaction_result: expected_execute_single_transaction_result,
apply_post_execution_changes_result: expected_apply_post_execution_changes_result
.clone(),
finish_result: expected_finish_result.clone(),
Expand All @@ -752,6 +774,7 @@ mod tests {
receipts: vec![Receipt::default()],
gas_used: 10,
},
execute_single_transaction_result: (Receipt::default(), 10),
apply_post_execution_changes_result: Requests::new(vec![bytes!("deadbeef")]),
finish_result: BundleState::default(),
};
Expand Down
102 changes: 102 additions & 0 deletions crates/optimism/evm/src/execute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use crate::{
use alloc::{boxed::Box, sync::Arc, vec::Vec};
use alloy_consensus::{BlockHeader, Eip658Value, Receipt, Transaction as _};
use alloy_eips::eip7685::Requests;
use alloy_primitives::Address;
use core::fmt::Display;
use op_alloy_consensus::{DepositTransaction, OpDepositReceipt};
use reth_chainspec::EthereumHardforks;
Expand Down Expand Up @@ -187,6 +188,107 @@ where
Ok(())
}

/// Executes a single transaction in the block.
fn execute_single_transaction(
&mut self,
header: &<Self::Primitives as NodePrimitives>::BlockHeader,
transaction: &<Self::Primitives as NodePrimitives>::SignedTx,
sender: Address,
) -> Result<(<Self::Primitives as NodePrimitives>::Receipt, u64), Self::Error> {
let mut evm = self.evm_config.evm_for_block(&mut self.state, header);

let is_regolith =
self.chain_spec.fork(OpHardfork::Regolith).active_at_timestamp(header.timestamp());

// The sum of the transaction’s gas limit, Tg, and the gas utilized in this block prior,
// must be no greater than the block’s gasLimit.
if transaction.gas_limit() > header.gas_limit() &&
Comment on lines +203 to +205
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as above

(is_regolith || !transaction.is_deposit())
{
return Err(BlockValidationError::TransactionGasLimitMoreThanAvailableBlockGas {
transaction_gas_limit: transaction.gas_limit(),
block_available_gas: header.gas_limit(),
}
.into())
}

// 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 && transaction.is_deposit())
.then(|| {
evm.db_mut()
.load_cache_account(sender)
.map(|acc| acc.account_info().unwrap_or_default())
})
.transpose()
.map_err(|_| OpBlockExecutionError::AccountLoadFailed(sender))?;

self.evm_config.fill_tx_env(evm.tx_mut(), transaction, sender);

if let Some(tx_env_overrides) = &mut self.tx_env_overrides {
tx_env_overrides.apply(evm.tx_mut());
}

// Execute transaction.
let result_and_state = evm.transact().map_err(move |err| {
let new_err = err.map_db_err(|e| e.into());
// Ensure hash is calculated for error log, if not already done
BlockValidationError::EVM {
hash: transaction.recalculate_hash(),
error: Box::new(new_err),
}
})?;

trace!(
target: "evm",
?transaction,
"Executed transaction"
);
self.system_caller.on_state(&result_and_state.state);
let ResultAndState { result, state } = result_and_state;
evm.db_mut().commit(state);

// append gas used
let gas_used = result.gas_used();

let receipt = match self.receipt_builder.build_receipt(ReceiptBuilderCtx {
header,
tx: transaction,
result,
cumulative_gas_used: gas_used,
}) {
Ok(receipt) => receipt,
Err(ctx) => {
let receipt = Receipt {
// Success flag was added in `EIP-658: Embedding transaction status code
// in receipts`.
status: Eip658Value::Eip658(ctx.result.is_success()),
cumulative_gas_used: gas_used,
logs: ctx.result.into_logs(),
};

self.receipt_builder.build_deposit_receipt(OpDepositReceipt {
inner: receipt,
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: (transaction.is_deposit() &&
self.chain_spec
.is_fork_active_at_timestamp(OpHardfork::Canyon, header.timestamp))
.then_some(1),
})
}
};

Ok((receipt, gas_used))
}

fn execute_transactions(
&mut self,
block: &RecoveredBlock<N::Block>,
Expand Down
11 changes: 10 additions & 1 deletion examples/custom-beacon-withdrawals/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ use reth_evm::execute::{
};
use reth_evm_ethereum::EthEvmConfig;
use reth_node_ethereum::{node::EthereumAddOns, BasicBlockExecutorProvider, EthereumNode};
use reth_primitives::{EthPrimitives, Receipt, RecoveredBlock};
use reth_primitives::{EthPrimitives, NodePrimitives, Receipt, RecoveredBlock};
use std::{fmt::Display, sync::Arc};

pub const SYSTEM_ADDRESS: Address = address!("fffffffffffffffffffffffffffffffffffffffe");
Expand Down Expand Up @@ -138,6 +138,15 @@ where
Ok(())
}

fn execute_single_transaction(
&mut self,
_header: &<Self::Primitives as NodePrimitives>::BlockHeader,
_transaction: &<Self::Primitives as NodePrimitives>::SignedTx,
_sender: Address,
) -> Result<(<Self::Primitives as NodePrimitives>::Receipt, u64), Self::Error> {
Ok(Default::default())
}

fn execute_transactions(
&mut self,
_block: &RecoveredBlock<reth_primitives::Block>,
Expand Down
Loading