refactor(consensus, evm): move post-execution validation to consensus (#8321)

This commit is contained in:
Alexey Shekhirin
2024-05-22 18:20:14 +01:00
committed by GitHub
parent 90713300bf
commit f45ca74772
52 changed files with 424 additions and 346 deletions

View File

@ -17,8 +17,12 @@ reth-primitives.workspace = true
reth-revm.workspace = true
reth-interfaces.workspace = true
reth-provider.workspace = true
reth-consensus-common.workspace = true
# Optimism
reth-optimism-consensus.workspace = true
# revm
revm.workspace = true
revm-primitives.workspace = true
@ -35,4 +39,5 @@ optimism = [
"reth-provider/optimism",
"reth-interfaces/optimism",
"revm-primitives/optimism",
"reth-optimism-consensus/optimism",
]

View File

@ -1,9 +1,6 @@
//! Optimism block executor.
use crate::{
l1::ensure_create2_deployer, verify::verify_receipts, OptimismBlockExecutionError,
OptimismEvmConfig,
};
use crate::{l1::ensure_create2_deployer, OptimismBlockExecutionError, OptimismEvmConfig};
use reth_evm::{
execute::{
BatchBlockExecutionOutput, BatchExecutor, BlockExecutionInput, BlockExecutionOutput,
@ -15,9 +12,10 @@ use reth_interfaces::{
executor::{BlockExecutionError, BlockValidationError},
provider::ProviderError,
};
use reth_optimism_consensus::validate_block_post_execution;
use reth_primitives::{
BlockNumber, BlockWithSenders, ChainSpec, GotExpected, Hardfork, Header, PruneModes, Receipt,
Receipts, TxType, Withdrawals, U256,
BlockNumber, BlockWithSenders, ChainSpec, Hardfork, Header, PruneModes, Receipt, Receipts,
TxType, Withdrawals, U256,
};
use reth_revm::{
batch::{BlockBatchRecord, BlockExecutorStats},
@ -30,7 +28,7 @@ use revm_primitives::{
BlockEnv, CfgEnvWithHandlerCfg, EnvWithHandlerCfg, ResultAndState,
};
use std::sync::Arc;
use tracing::{debug, trace};
use tracing::trace;
/// Provides executors to execute regular ethereum blocks
#[derive(Debug, Clone)]
@ -157,12 +155,12 @@ where
transaction_gas_limit: transaction.gas_limit(),
block_available_gas,
}
.into());
.into())
}
// An optimism block should never contain blob transactions.
if matches!(transaction.tx_type(), TxType::Eip4844) {
return Err(OptimismBlockExecutionError::BlobTransactionRejected.into());
return Err(OptimismBlockExecutionError::BlobTransactionRejected.into())
}
// Cache the depositor account prior to the state transition for the deposit nonce.
@ -221,16 +219,6 @@ where
}
drop(evm);
// Check if gas used matches the value set in header.
if block.gas_used != cumulative_gas_used {
let receipts = Receipts::from_block_receipt(receipts);
return Err(BlockValidationError::BlockGasUsed {
gas: GotExpected { got: cumulative_gas_used, expected: block.gas_used },
gas_spent_by_tx: receipts.gas_spent_by_tx()?,
}
.into());
}
Ok((receipts, cumulative_gas_used))
}
}
@ -292,8 +280,8 @@ where
///
/// Returns the receipts of the transactions in the block and the total gas used.
///
/// Returns an error if execution fails or receipt verification fails.
fn execute_and_verify(
/// Returns an error if execution fails.
fn execute_without_verification(
&mut self,
block: &BlockWithSenders,
total_difficulty: U256,
@ -312,23 +300,6 @@ where
// 3. apply post execution changes
self.post_execution(block, total_difficulty)?;
// Before Byzantium, receipts contained state root that would mean that expensive
// operation as hashing that is required for state root got calculated in every
// transaction This was replaced with is_success flag.
// See more about EIP here: https://eips.ethereum.org/EIPS/eip-658
if self.chain_spec().is_byzantium_active_at_block(block.header.number) {
if let Err(error) = verify_receipts(
block.header.receipts_root,
block.header.logs_bloom,
receipts.iter(),
self.chain_spec(),
block.timestamp,
) {
debug!(target: "evm", %error, ?receipts, "receipts verification failed");
return Err(error);
};
}
Ok((receipts, gas_used))
}
@ -383,7 +354,7 @@ where
/// State changes are committed to the database.
fn execute(mut self, input: Self::Input<'_>) -> Result<Self::Output, Self::Error> {
let BlockExecutionInput { block, total_difficulty } = input;
let (receipts, gas_used) = self.execute_and_verify(block, total_difficulty)?;
let (receipts, gas_used) = self.execute_without_verification(block, total_difficulty)?;
// NOTE: we need to merge keep the reverts for the bundle retention
self.state.merge_transitions(BundleRetention::Reverts);
@ -426,9 +397,12 @@ where
type Output = BatchBlockExecutionOutput;
type Error = BlockExecutionError;
fn execute_one(&mut self, input: Self::Input<'_>) -> Result<(), Self::Error> {
fn execute_and_verify_one(&mut self, input: Self::Input<'_>) -> Result<(), Self::Error> {
let BlockExecutionInput { block, total_difficulty } = input;
let (receipts, _gas_used) = self.executor.execute_and_verify(block, total_difficulty)?;
let (receipts, _gas_used) =
self.executor.execute_without_verification(block, total_difficulty)?;
validate_block_post_execution(block, self.executor.chain_spec(), &receipts)?;
// prepare the state according to the prune mode
let retention = self.batch_record.bundle_retention(block.number);
@ -557,7 +531,7 @@ mod tests {
// Attempt to execute a block with one deposit and one non-deposit transaction
executor
.execute_one(
.execute_and_verify_one(
(
&BlockWithSenders {
block: Block {
@ -638,7 +612,7 @@ mod tests {
// attempt to execute an empty block with parent beacon block root, this should not fail
executor
.execute_one(
.execute_and_verify_one(
(
&BlockWithSenders {
block: Block {

View File

@ -23,7 +23,6 @@ pub mod l1;
pub use l1::*;
mod error;
pub mod verify;
pub use error::OptimismBlockExecutionError;
/// Optimism-related EVM configuration.

View File

@ -1,58 +0,0 @@
//! Helpers for verifying the receipts.
use reth_interfaces::executor::{BlockExecutionError, BlockValidationError};
use reth_primitives::{
proofs::calculate_receipt_root_optimism, Bloom, ChainSpec, GotExpected, Receipt,
ReceiptWithBloom, B256,
};
/// Verify the calculated receipts root against the expected receipts root.
pub fn verify_receipts<'a>(
expected_receipts_root: B256,
expected_logs_bloom: Bloom,
receipts: impl Iterator<Item = &'a Receipt> + Clone,
chain_spec: &ChainSpec,
timestamp: u64,
) -> Result<(), BlockExecutionError> {
// Calculate receipts root.
let receipts_with_bloom = receipts.map(|r| r.clone().into()).collect::<Vec<ReceiptWithBloom>>();
let receipts_root =
calculate_receipt_root_optimism(&receipts_with_bloom, chain_spec, timestamp);
// Create header log bloom.
let logs_bloom = receipts_with_bloom.iter().fold(Bloom::ZERO, |bloom, r| bloom | r.bloom);
compare_receipts_root_and_logs_bloom(
receipts_root,
logs_bloom,
expected_receipts_root,
expected_logs_bloom,
)?;
Ok(())
}
/// Compare the calculated receipts root with the expected receipts root, also compare
/// the calculated logs bloom with the expected logs bloom.
pub fn compare_receipts_root_and_logs_bloom(
calculated_receipts_root: B256,
calculated_logs_bloom: Bloom,
expected_receipts_root: B256,
expected_logs_bloom: Bloom,
) -> Result<(), BlockExecutionError> {
if calculated_receipts_root != expected_receipts_root {
return Err(BlockValidationError::ReceiptRootDiff(
GotExpected { got: calculated_receipts_root, expected: expected_receipts_root }.into(),
)
.into())
}
if calculated_logs_bloom != expected_logs_bloom {
return Err(BlockValidationError::BloomLogDiff(
GotExpected { got: calculated_logs_bloom, expected: expected_logs_bloom }.into(),
)
.into())
}
Ok(())
}