From b4a1b733c93f7e262f1b774722670e08cdcb6276 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Tue, 28 May 2024 15:06:28 +0100 Subject: [PATCH] feat: implement EIP-7685 (#8424) Co-authored-by: Oliver Nordbjerg Co-authored-by: Dan Cline <6798349+Rjected@users.noreply.github.com> Co-authored-by: Matthias Seitz --- Cargo.lock | 32 ++++--- bin/reth/src/commands/db/diff.rs | 5 +- bin/reth/src/commands/db/stats.rs | 13 +-- bin/reth/src/commands/debug_cmd/execution.rs | 4 +- bin/reth/src/commands/debug_cmd/merkle.rs | 3 +- bin/reth/src/commands/stage/drop.rs | 1 + crates/blockchain-tree/src/blockchain_tree.rs | 1 + crates/blockchain-tree/src/chain.rs | 8 +- crates/consensus/auto-seal/src/lib.rs | 26 +++-- crates/consensus/auto-seal/src/task.rs | 6 +- crates/consensus/common/src/validation.rs | 34 ++++++- crates/consensus/consensus/Cargo.toml | 1 + crates/consensus/consensus/src/lib.rs | 38 +++++++- crates/consensus/consensus/src/test_utils.rs | 6 +- crates/engine-primitives/src/lib.rs | 27 +++++- crates/ethereum/consensus/src/lib.rs | 8 +- crates/ethereum/consensus/src/validation.rs | 17 +++- crates/ethereum/engine-primitives/src/lib.rs | 3 +- .../ethereum/engine-primitives/src/payload.rs | 28 +++++- crates/ethereum/evm/src/execute.rs | 67 +++++++++---- crates/ethereum/evm/src/lib.rs | 1 + crates/evm/Cargo.toml | 2 +- crates/evm/execution-types/src/bundle.rs | 5 +- crates/evm/src/execute.rs | 31 +++++- crates/evm/src/test_utils.rs | 3 +- crates/net/downloaders/src/bodies/bodies.rs | 7 +- .../net/downloaders/src/bodies/test_utils.rs | 1 + crates/net/downloaders/src/file_client.rs | 20 +--- crates/net/downloaders/src/test_utils/mod.rs | 1 + crates/net/eth-wire-types/src/blocks.rs | 6 ++ crates/net/network/src/eth_requests.rs | 1 + crates/net/network/tests/it/requests.rs | 8 +- crates/net/p2p/src/full_block.rs | 40 +++++--- crates/net/p2p/src/test_utils/generators.rs | 1 + crates/node-core/src/utils.rs | 1 + crates/optimism/consensus/src/lib.rs | 9 +- crates/optimism/evm/src/execute.rs | 10 +- crates/optimism/evm/src/l1.rs | 2 + crates/optimism/node/src/engine.rs | 4 +- crates/optimism/payload/src/builder.rs | 10 +- crates/optimism/payload/src/payload.rs | 32 ++++++- crates/payload/ethereum/Cargo.toml | 4 +- crates/payload/ethereum/src/lib.rs | 75 +++++++++++---- crates/primitives/Cargo.toml | 2 + crates/primitives/src/alloy_compat.rs | 5 + crates/primitives/src/block.rs | 47 ++++++++- crates/primitives/src/chain/spec.rs | 13 ++- crates/primitives/src/header.rs | 41 ++++++-- crates/primitives/src/lib.rs | 4 +- crates/primitives/src/proofs.rs | 12 ++- crates/primitives/src/request.rs | 55 +++++++++++ crates/primitives/src/revm/mod.rs | 2 + crates/primitives/src/transaction/mod.rs | 2 + crates/revm/src/batch.rs | 20 +++- crates/rpc/rpc-api/src/engine.rs | 25 ++++- crates/rpc/rpc-engine-api/src/engine_api.rs | 46 ++++++++- crates/rpc/rpc-engine-api/src/metrics.rs | 4 + crates/rpc/rpc-engine-api/tests/it/payload.rs | 1 + crates/rpc/rpc-types-compat/src/block.rs | 9 +- .../rpc-types-compat/src/engine/payload.rs | 66 ++++++++++--- crates/rpc/rpc/src/eth/api/pending_block.rs | 15 ++- crates/stages/src/stages/bodies.rs | 16 ++++ crates/stages/src/stages/execution.rs | 3 +- crates/stages/src/stages/merkle.rs | 5 +- crates/storage/codecs/Cargo.toml | 15 ++- crates/storage/codecs/src/alloy/mod.rs | 1 + crates/storage/codecs/src/alloy/request.rs | 39 ++++++++ .../storage/db/src/tables/codecs/compact.rs | 1 + crates/storage/db/src/tables/mod.rs | 5 +- .../src/providers/database/metrics.rs | 2 + .../provider/src/providers/database/mod.rs | 18 +++- .../src/providers/database/provider.rs | 95 ++++++++++++++++--- crates/storage/provider/src/providers/mod.rs | 19 +++- .../src/providers/static_file/manager.rs | 13 ++- .../storage/provider/src/test_utils/blocks.rs | 6 +- .../storage/provider/src/test_utils/mock.rs | 15 ++- .../storage/provider/src/test_utils/noop.rs | 12 ++- crates/storage/storage-api/src/block.rs | 3 +- crates/storage/storage-api/src/lib.rs | 3 + crates/storage/storage-api/src/requests.rs | 13 +++ .../src/mined_sidecar.rs | 6 +- examples/custom-engine-types/src/main.rs | 3 +- testing/ef-tests/src/models.rs | 3 + 83 files changed, 1053 insertions(+), 214 deletions(-) create mode 100644 crates/primitives/src/request.rs create mode 100644 crates/storage/codecs/src/alloy/request.rs create mode 100644 crates/storage/storage-api/src/requests.rs diff --git a/Cargo.lock b/Cargo.lock index 813e595dd..031ff1475 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -139,14 +139,17 @@ dependencies = [ "alloy-primitives", "alloy-rlp", "alloy-serde 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=64feb9b)", + "arbitrary", "c-kzg", + "proptest", + "proptest-derive", "serde", ] [[package]] name = "alloy-consensus" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy#0f9711692743d0444a887a589ca6786df77568be" +source = "git+https://github.com/alloy-rs/alloy#dd7a999d9efe259c47a34dde046952de795a8f6a" dependencies = [ "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy)", "alloy-primitives", @@ -195,7 +198,7 @@ dependencies = [ [[package]] name = "alloy-eips" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy#0f9711692743d0444a887a589ca6786df77568be" +source = "git+https://github.com/alloy-rs/alloy#dd7a999d9efe259c47a34dde046952de795a8f6a" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -220,7 +223,7 @@ dependencies = [ [[package]] name = "alloy-genesis" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy#0f9711692743d0444a887a589ca6786df77568be" +source = "git+https://github.com/alloy-rs/alloy#dd7a999d9efe259c47a34dde046952de795a8f6a" dependencies = [ "alloy-primitives", "alloy-serde 0.1.0 (git+https://github.com/alloy-rs/alloy)", @@ -342,9 +345,9 @@ dependencies = [ [[package]] name = "alloy-rlp" -version = "0.3.5" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b155716bab55763c95ba212806cf43d05bcc70e5f35b02bad20cf5ec7fe11fed" +checksum = "8d58d9f5da7b40e9bfff0b7e7816700be4019db97d4b6359fe7f94a9e22e42ac" dependencies = [ "alloy-rlp-derive", "arrayvec", @@ -353,9 +356,9 @@ dependencies = [ [[package]] name = "alloy-rlp-derive" -version = "0.3.5" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8037e03c7f462a063f28daec9fda285a9a89da003c552f8637a80b9c8fd96241" +checksum = "1a047897373be4bbb0224c1afdabca92648dc57a9c9ef6e7b0be3aff7a859c83" dependencies = [ "proc-macro2", "quote", @@ -407,7 +410,7 @@ dependencies = [ [[package]] name = "alloy-rpc-types" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy#0f9711692743d0444a887a589ca6786df77568be" +source = "git+https://github.com/alloy-rs/alloy#dd7a999d9efe259c47a34dde046952de795a8f6a" dependencies = [ "alloy-consensus 0.1.0 (git+https://github.com/alloy-rs/alloy)", "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy)", @@ -488,7 +491,7 @@ dependencies = [ [[package]] name = "alloy-serde" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy#0f9711692743d0444a887a589ca6786df77568be" +source = "git+https://github.com/alloy-rs/alloy#dd7a999d9efe259c47a34dde046952de795a8f6a" dependencies = [ "alloy-primitives", "serde", @@ -909,11 +912,12 @@ dependencies = [ [[package]] name = "async-channel" -version = "2.3.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" +checksum = "9f2776ead772134d55b62dd45e59a79e21612d85d0af729b8b7d3967d601a62a" dependencies = [ "concurrent-queue", + "event-listener 5.3.0", "event-listener-strategy 0.5.2", "futures-core", "pin-project-lite", @@ -1275,7 +1279,7 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "495f7104e962b7356f0aeb34247aca1fe7d2e783b346582db7f2904cb5717e88" dependencies = [ - "async-channel 2.3.1", + "async-channel 2.3.0", "async-lock", "async-task", "futures-io", @@ -6585,6 +6589,7 @@ dependencies = [ name = "reth-codecs" version = "0.2.0-beta.7" dependencies = [ + "alloy-consensus 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=64feb9b)", "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=64feb9b)", "alloy-genesis 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=64feb9b)", "alloy-primitives", @@ -6982,6 +6987,8 @@ name = "reth-ethereum-payload-builder" version = "0.2.0-beta.7" dependencies = [ "reth-basic-payload-builder", + "reth-evm", + "reth-evm-ethereum", "reth-payload-builder", "reth-primitives", "reth-provider", @@ -7608,6 +7615,7 @@ name = "reth-primitives" version = "0.2.0-beta.7" dependencies = [ "alloy-chains", + "alloy-consensus 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=64feb9b)", "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=64feb9b)", "alloy-genesis 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=64feb9b)", "alloy-primitives", diff --git a/bin/reth/src/commands/db/diff.rs b/bin/reth/src/commands/db/diff.rs index 9c098a50b..3c7bfb8c0 100644 --- a/bin/reth/src/commands/db/diff.rs +++ b/bin/reth/src/commands/db/diff.rs @@ -6,7 +6,7 @@ use crate::{ use clap::Parser; use reth_db::{ cursor::DbCursorRO, database::Database, open_db_read_only, table::Table, transaction::DbTx, - AccountChangeSets, AccountsHistory, AccountsTrie, BlockBodyIndices, BlockOmmers, + AccountChangeSets, AccountsHistory, AccountsTrie, BlockBodyIndices, BlockOmmers, BlockRequests, BlockWithdrawals, Bytecodes, CanonicalHeaders, DatabaseEnv, HashedAccounts, HashedStorages, HeaderNumbers, HeaderTerminalDifficulties, Headers, PlainAccountState, PlainStorageState, PruneCheckpoints, Receipts, StageCheckpointProgresses, StageCheckpoints, StorageChangeSets, @@ -98,6 +98,9 @@ impl Command { Tables::BlockWithdrawals => { find_diffs::(primary_tx, secondary_tx, output_dir)? } + Tables::BlockRequests => { + find_diffs::(primary_tx, secondary_tx, output_dir)? + } Tables::TransactionBlocks => { find_diffs::(primary_tx, secondary_tx, output_dir)? } diff --git a/bin/reth/src/commands/db/stats.rs b/bin/reth/src/commands/db/stats.rs index 03c384b2f..d38c0e21a 100644 --- a/bin/reth/src/commands/db/stats.rs +++ b/bin/reth/src/commands/db/stats.rs @@ -8,12 +8,12 @@ use human_bytes::human_bytes; use itertools::Itertools; use reth_db::{ database::Database, mdbx, static_file::iter_static_files, AccountChangeSets, AccountsHistory, - AccountsTrie, BlockBodyIndices, BlockOmmers, BlockWithdrawals, Bytecodes, CanonicalHeaders, - DatabaseEnv, HashedAccounts, HashedStorages, HeaderNumbers, HeaderTerminalDifficulties, - Headers, PlainAccountState, PlainStorageState, PruneCheckpoints, Receipts, - StageCheckpointProgresses, StageCheckpoints, StorageChangeSets, StoragesHistory, StoragesTrie, - Tables, TransactionBlocks, TransactionHashNumbers, TransactionSenders, Transactions, - VersionHistory, + AccountsTrie, BlockBodyIndices, BlockOmmers, BlockRequests, BlockWithdrawals, Bytecodes, + CanonicalHeaders, DatabaseEnv, HashedAccounts, HashedStorages, HeaderNumbers, + HeaderTerminalDifficulties, Headers, PlainAccountState, PlainStorageState, PruneCheckpoints, + Receipts, StageCheckpointProgresses, StageCheckpoints, StorageChangeSets, StoragesHistory, + StoragesTrie, Tables, TransactionBlocks, TransactionHashNumbers, TransactionSenders, + Transactions, VersionHistory, }; use reth_fs_util as fs; use reth_node_core::dirs::{ChainPath, DataDirPath}; @@ -333,6 +333,7 @@ impl Command { Tables::BlockBodyIndices => viewer.get_checksum::().unwrap(), Tables::BlockOmmers => viewer.get_checksum::().unwrap(), Tables::BlockWithdrawals => viewer.get_checksum::().unwrap(), + Tables::BlockRequests => viewer.get_checksum::().unwrap(), Tables::Bytecodes => viewer.get_checksum::().unwrap(), Tables::CanonicalHeaders => viewer.get_checksum::().unwrap(), Tables::HashedAccounts => viewer.get_checksum::().unwrap(), diff --git a/bin/reth/src/commands/debug_cmd/execution.rs b/bin/reth/src/commands/debug_cmd/execution.rs index c07efab2b..628a6cd26 100644 --- a/bin/reth/src/commands/debug_cmd/execution.rs +++ b/bin/reth/src/commands/debug_cmd/execution.rs @@ -187,7 +187,7 @@ impl Command { match get_single_header(&client, BlockHashOrNumber::Number(block)).await { Ok(tip_header) => { info!(target: "reth::cli", ?block, "Successfully fetched block"); - return Ok(tip_header.hash()); + return Ok(tip_header.hash()) } Err(error) => { error!(target: "reth::cli", ?block, %error, "Failed to fetch the block. Retrying..."); @@ -255,7 +255,7 @@ impl Command { provider.get_stage_checkpoint(StageId::Finish)?.map(|ch| ch.block_number); if latest_block_number.unwrap_or_default() >= self.to { info!(target: "reth::cli", latest = latest_block_number, "Nothing to run"); - return Ok(()); + return Ok(()) } let pipeline_events = pipeline.events(); diff --git a/bin/reth/src/commands/debug_cmd/merkle.rs b/bin/reth/src/commands/debug_cmd/merkle.rs index 291788bad..bfbca46f4 100644 --- a/bin/reth/src/commands/debug_cmd/merkle.rs +++ b/bin/reth/src/commands/debug_cmd/merkle.rs @@ -198,7 +198,8 @@ impl Command { PruneModes::none(), ); executor.execute_and_verify_one((&sealed_block.clone().unseal(), td).into())?; - let BatchBlockExecutionOutput { bundle, receipts, first_block } = executor.finalize(); + let BatchBlockExecutionOutput { bundle, receipts, requests: _, first_block } = + executor.finalize(); BundleStateWithReceipts::new(bundle, receipts, first_block).write_to_storage( provider_rw.tx_ref(), None, diff --git a/bin/reth/src/commands/stage/drop.rs b/bin/reth/src/commands/stage/drop.rs index fc3ef5768..47af150ce 100644 --- a/bin/reth/src/commands/stage/drop.rs +++ b/bin/reth/src/commands/stage/drop.rs @@ -110,6 +110,7 @@ impl Command { tx.clear::()?; tx.clear::()?; tx.clear::()?; + tx.clear::()?; tx.put::( StageId::Bodies.to_string(), Default::default(), diff --git a/crates/blockchain-tree/src/blockchain_tree.rs b/crates/blockchain-tree/src/blockchain_tree.rs index 42ba451ab..0f5a249a4 100644 --- a/crates/blockchain-tree/src/blockchain_tree.rs +++ b/crates/blockchain-tree/src/blockchain_tree.rs @@ -1631,6 +1631,7 @@ mod tests { body: body.clone().into_iter().map(|tx| tx.into_signed()).collect(), ommers: Vec::new(), withdrawals: Some(Withdrawals::default()), + requests: None, }, body.iter().map(|tx| tx.signer()).collect(), ) diff --git a/crates/blockchain-tree/src/chain.rs b/crates/blockchain-tree/src/chain.rs index e8e40cb41..38806b9da 100644 --- a/crates/blockchain-tree/src/chain.rs +++ b/crates/blockchain-tree/src/chain.rs @@ -9,7 +9,7 @@ use reth_blockchain_tree_api::{ error::{BlockchainTreeError, InsertBlockErrorKind}, BlockAttachment, BlockValidationKind, }; -use reth_consensus::{Consensus, ConsensusError}; +use reth_consensus::{Consensus, ConsensusError, PostExecutionInput}; use reth_db::database::Database; use reth_evm::execute::{BlockExecutionOutput, BlockExecutorProvider, Executor}; use reth_execution_errors::BlockExecutionError; @@ -210,8 +210,10 @@ impl AppendableChain { let block = block.unseal(); let state = executor.execute((&block, U256::MAX).into())?; - let BlockExecutionOutput { state, receipts, .. } = state; - externals.consensus.validate_block_post_execution(&block, &receipts)?; + let BlockExecutionOutput { state, receipts, requests, .. } = state; + externals + .consensus + .validate_block_post_execution(&block, PostExecutionInput::new(&receipts, &requests))?; let bundle_state = BundleStateWithReceipts::new( state, diff --git a/crates/consensus/auto-seal/src/lib.rs b/crates/consensus/auto-seal/src/lib.rs index f318b7ade..69ca048e1 100644 --- a/crates/consensus/auto-seal/src/lib.rs +++ b/crates/consensus/auto-seal/src/lib.rs @@ -16,14 +16,14 @@ #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] use reth_beacon_consensus::BeaconEngineMessage; -use reth_consensus::{Consensus, ConsensusError}; +use reth_consensus::{Consensus, ConsensusError, PostExecutionInput}; use reth_engine_primitives::EngineTypes; use reth_interfaces::executor::{BlockExecutionError, BlockValidationError}; use reth_primitives::{ constants::{EMPTY_TRANSACTIONS, ETHEREUM_BLOCK_GAS_LIMIT}, eip4844::calculate_excess_blob_gas, proofs, Block, BlockBody, BlockHash, BlockHashOrNumber, BlockNumber, BlockWithSenders, - ChainSpec, Header, Receipt, Receipts, SealedBlock, SealedHeader, TransactionSigned, + ChainSpec, Header, Receipts, Requests, SealedBlock, SealedHeader, TransactionSigned, Withdrawals, B256, U256, }; use reth_provider::{ @@ -92,7 +92,7 @@ impl Consensus for AutoSealConsensus { fn validate_block_post_execution( &self, _block: &BlockWithSenders, - _receipts: &[Receipt], + _input: PostExecutionInput<'_>, ) -> Result<(), ConsensusError> { Ok(()) } @@ -277,6 +277,7 @@ impl StorageInner { transactions: &[TransactionSigned], ommers: &[Header], withdrawals: Option<&Withdrawals>, + requests: Option<&Requests>, chain_spec: Arc, ) -> Header { let timestamp = SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_secs(); @@ -319,6 +320,7 @@ impl StorageInner { excess_blob_gas: None, extra_data: Default::default(), parent_beacon_block_root: None, + requests_root: requests.map(|r| proofs::calculate_requests_root(&r.0)), }; if chain_spec.is_cancun_active_at_timestamp(timestamp) { @@ -354,11 +356,13 @@ impl StorageInner { /// Builds and executes a new block with the given transactions, on the provided executor. /// /// This returns the header of the executed block, as well as the poststate from execution. + #[allow(clippy::too_many_arguments)] pub(crate) fn build_and_execute( &mut self, transactions: Vec, ommers: Vec
, withdrawals: Option, + requests: Option, provider: &Provider, chain_spec: Arc, executor: &Executor, @@ -367,14 +371,20 @@ impl StorageInner { Executor: BlockExecutorProvider, Provider: StateProviderFactory, { - let header = - self.build_header_template(&transactions, &ommers, withdrawals.as_ref(), chain_spec); + let header = self.build_header_template( + &transactions, + &ommers, + withdrawals.as_ref(), + requests.as_ref(), + chain_spec, + ); let block = Block { header, body: transactions, ommers: ommers.clone(), withdrawals: withdrawals.clone(), + requests: requests.clone(), } .with_recovered_senders() .ok_or(BlockExecutionError::Validation(BlockValidationError::SenderRecoveryError))?; @@ -394,8 +404,12 @@ impl StorageInner { block.number, ); + // todo(onbjerg): we should not pass requests around as this is building a block, which + // means we need to extract the requests from the execution output and compute the requests + // root here + let Block { mut header, body, .. } = block.block; - let body = BlockBody { transactions: body, ommers, withdrawals }; + let body = BlockBody { transactions: body, ommers, withdrawals, requests }; trace!(target: "consensus::auto", ?bundle_state, ?header, ?body, "executed block, calculating state root and completing header"); diff --git a/crates/consensus/auto-seal/src/task.rs b/crates/consensus/auto-seal/src/task.rs index 2a5ec4433..f047f0a5b 100644 --- a/crates/consensus/auto-seal/src/task.rs +++ b/crates/consensus/auto-seal/src/task.rs @@ -4,7 +4,7 @@ use reth_beacon_consensus::{BeaconEngineMessage, ForkchoiceStatus}; use reth_engine_primitives::EngineTypes; use reth_evm::execute::BlockExecutorProvider; use reth_primitives::{ - Block, ChainSpec, IntoRecoveredTransaction, SealedBlockWithSenders, Withdrawals, + Block, ChainSpec, IntoRecoveredTransaction, Requests, SealedBlockWithSenders, Withdrawals, }; use reth_provider::{CanonChainTracker, CanonStateNotificationSender, Chain, StateProviderFactory}; use reth_rpc_types::engine::ForkchoiceState; @@ -137,12 +137,15 @@ where }) .unzip(); let ommers = vec![]; + // todo(onbjerg): these two dont respect chainspec let withdrawals = Some(Withdrawals::default()); + let requests = Some(Requests::default()); match storage.build_and_execute( transactions.clone(), ommers.clone(), withdrawals.clone(), + requests.clone(), &client, chain_spec, &executor, @@ -201,6 +204,7 @@ where body: transactions, ommers, withdrawals, + requests, }; let sealed_block = block.seal_slow(); diff --git a/crates/consensus/common/src/validation.rs b/crates/consensus/common/src/validation.rs index ffa48e771..ad1e11643 100644 --- a/crates/consensus/common/src/validation.rs +++ b/crates/consensus/common/src/validation.rs @@ -52,6 +52,14 @@ pub fn validate_header_standalone( return Err(ConsensusError::ParentBeaconBlockRootUnexpected) } + if chain_spec.is_prague_active_at_timestamp(header.timestamp) { + if header.requests_root.is_none() { + return Err(ConsensusError::RequestsRootMissing) + } + } else if header.requests_root.is_some() { + return Err(ConsensusError::RequestsRootUnexpected) + } + Ok(()) } @@ -108,6 +116,19 @@ pub fn validate_block_pre_execution( } } + // EIP-7685: General purpose execution layer requests + if chain_spec.is_prague_active_at_timestamp(block.timestamp) { + let requests = block.requests.as_ref().ok_or(ConsensusError::BodyRequestsMissing)?; + let requests_root = reth_primitives::proofs::calculate_requests_root(&requests.0); + let header_requests_root = + block.requests_root.as_ref().ok_or(ConsensusError::RequestsRootMissing)?; + if requests_root != *header_requests_root { + return Err(ConsensusError::BodyRequestsRootDiff( + GotExpected { got: requests_root, expected: *header_requests_root }.into(), + )) + } + } + Ok(()) } @@ -326,6 +347,7 @@ mod tests { blob_gas_used: None, excess_blob_gas: None, parent_beacon_block_root: None, + requests_root: None }; // size: 0x9b5 @@ -339,7 +361,16 @@ mod tests { let ommers = Vec::new(); let body = Vec::new(); - (SealedBlock { header: header.seal_slow(), body, ommers, withdrawals: None }, parent) + ( + SealedBlock { + header: header.seal_slow(), + body, + ommers, + withdrawals: None, + requests: None, + }, + parent, + ) } #[test] @@ -419,6 +450,7 @@ mod tests { transactions: vec![transaction], ommers: vec![], withdrawals: Some(Withdrawals::default()), + requests: None, }; let block = SealedBlock::new(header, body); diff --git a/crates/consensus/consensus/Cargo.toml b/crates/consensus/consensus/Cargo.toml index 43264872e..8ea4236bf 100644 --- a/crates/consensus/consensus/Cargo.toml +++ b/crates/consensus/consensus/Cargo.toml @@ -11,6 +11,7 @@ repository.workspace = true workspace = true [dependencies] +# reth reth-primitives.workspace = true # misc diff --git a/crates/consensus/consensus/src/lib.rs b/crates/consensus/consensus/src/lib.rs index 46fce6d02..d117b2ea2 100644 --- a/crates/consensus/consensus/src/lib.rs +++ b/crates/consensus/consensus/src/lib.rs @@ -10,7 +10,8 @@ use reth_primitives::{ BlockHash, BlockNumber, BlockWithSenders, Bloom, GotExpected, GotExpectedBoxed, Header, - HeaderValidationError, InvalidTransactionError, Receipt, SealedBlock, SealedHeader, B256, U256, + HeaderValidationError, InvalidTransactionError, Receipt, Request, SealedBlock, SealedHeader, + B256, U256, }; use std::fmt::Debug; @@ -18,6 +19,22 @@ use std::fmt::Debug; /// test helpers for mocking consensus pub mod test_utils; +/// Post execution input passed to [Consensus::validate_block_post_execution]. +#[derive(Debug)] +pub struct PostExecutionInput<'a> { + /// Receipts of the block. + pub receipts: &'a [Receipt], + /// EIP-7685 requests of the block. + pub requests: &'a [Request], +} + +impl<'a> PostExecutionInput<'a> { + /// Creates a new instance of `PostExecutionInput`. + pub fn new(receipts: &'a [Receipt], requests: &'a [Request]) -> Self { + Self { receipts, requests } + } +} + /// Consensus is a protocol that chooses canonical chain. #[auto_impl::auto_impl(&, Arc)] pub trait Consensus: Debug + Send + Sync { @@ -94,7 +111,7 @@ pub trait Consensus: Debug + Send + Sync { fn validate_block_post_execution( &self, block: &BlockWithSenders, - receipts: &[Receipt], + input: PostExecutionInput<'_>, ) -> Result<(), ConsensusError>; } @@ -145,6 +162,11 @@ pub enum ConsensusError { #[error("mismatched block withdrawals root: {0}")] BodyWithdrawalsRootDiff(GotExpectedBoxed), + /// Error when the requests root in the block is different from the expected requests + /// root. + #[error("mismatched block requests root: {0}")] + BodyRequestsRootDiff(GotExpectedBoxed), + /// Error when a block with a specific hash and number is already known. #[error("block with [hash={hash}, number={number}] is already known")] BlockKnown { @@ -212,14 +234,26 @@ pub enum ConsensusError { #[error("missing withdrawals root")] WithdrawalsRootMissing, + /// Error when the requests root is missing. + #[error("missing requests root")] + RequestsRootMissing, + /// Error when an unexpected withdrawals root is encountered. #[error("unexpected withdrawals root")] WithdrawalsRootUnexpected, + /// Error when an unexpected requests root is encountered. + #[error("unexpected requests root")] + RequestsRootUnexpected, + /// Error when withdrawals are missing. #[error("missing withdrawals")] BodyWithdrawalsMissing, + /// Error when requests are missing. + #[error("missing requests")] + BodyRequestsMissing, + /// Error when blob gas used is missing. #[error("missing blob gas used")] BlobGasUsedMissing, diff --git a/crates/consensus/consensus/src/test_utils.rs b/crates/consensus/consensus/src/test_utils.rs index a616d4f43..546dffab1 100644 --- a/crates/consensus/consensus/src/test_utils.rs +++ b/crates/consensus/consensus/src/test_utils.rs @@ -1,5 +1,5 @@ -use crate::{Consensus, ConsensusError}; -use reth_primitives::{BlockWithSenders, Header, Receipt, SealedBlock, SealedHeader, U256}; +use crate::{Consensus, ConsensusError, PostExecutionInput}; +use reth_primitives::{BlockWithSenders, Header, SealedBlock, SealedHeader, U256}; use std::sync::atomic::{AtomicBool, Ordering}; /// Consensus engine implementation for testing @@ -71,7 +71,7 @@ impl Consensus for TestConsensus { fn validate_block_post_execution( &self, _block: &BlockWithSenders, - _receipts: &[Receipt], + _input: PostExecutionInput<'_>, ) -> Result<(), ConsensusError> { if self.fail_validation() { Err(ConsensusError::BaseFeeMissing) diff --git a/crates/engine-primitives/src/lib.rs b/crates/engine-primitives/src/lib.rs index aa2c44681..d9cd340a8 100644 --- a/crates/engine-primitives/src/lib.rs +++ b/crates/engine-primitives/src/lib.rs @@ -27,7 +27,7 @@ pub use payload::PayloadOrAttributes; /// The types that are used by the engine API. pub trait EngineTypes: - serde::de::DeserializeOwned + Serialize + fmt::Debug + Unpin + Send + Sync + Clone + DeserializeOwned + Serialize + fmt::Debug + Unpin + Send + Sync + Clone { /// The RPC payload attributes type the CL node emits via the engine API. type PayloadAttributes: PayloadAttributes + Unpin; @@ -43,7 +43,8 @@ pub trait EngineTypes: + Unpin + TryInto + TryInto - + TryInto; + + TryInto + + TryInto; /// Execution Payload V1 type. type ExecutionPayloadV1: DeserializeOwned + Serialize + Clone + Unpin + Send + Sync + 'static; @@ -51,6 +52,8 @@ pub trait EngineTypes: type ExecutionPayloadV2: DeserializeOwned + Serialize + Clone + Unpin + Send + Sync + 'static; /// Execution Payload V3 type. type ExecutionPayloadV3: DeserializeOwned + Serialize + Clone + Unpin + Send + Sync + 'static; + /// Execution Payload V4 type. + type ExecutionPayloadV4: DeserializeOwned + Serialize + Clone + Unpin + Send + Sync + 'static; /// Validates the presence or exclusion of fork-specific fields based on the payload attributes /// and the message version. @@ -63,8 +66,9 @@ pub trait EngineTypes: /// Validates the timestamp depending on the version called: /// -/// * If V2, this ensure that the payload timestamp is pre-Cancun. +/// * If V2, this ensures that the payload timestamp is pre-Cancun. /// * If V3, this ensures that the payload timestamp is within the Cancun timestamp. +/// * If V4, this ensures that the payload timestamp is within the Prague timestamp. /// /// Otherwise, this will return [EngineObjectValidationError::UnsupportedFork]. pub fn validate_payload_timestamp( @@ -219,6 +223,11 @@ pub fn validate_withdrawals_presence( /// 2. Client software **MUST** return `-38005: Unsupported fork` error if the `timestamp` of the /// payload does not fall within the time frame of the Cancun fork. /// +/// For `engine_newPayloadV4`: +/// +/// 2. Client software **MUST** return `-38005: Unsupported fork` error if the `timestamp` of the +/// payload does not fall within the time frame of the Prague fork. +/// /// Returning the right error code (ie, if the client should return `-38003: Invalid payload /// attributes` is handled by the `message_validation_kind` parameter. If the parameter is /// `MessageValidationKind::Payload`, then the error code will be `-32602: Invalid params`. If the @@ -334,7 +343,7 @@ where } /// The version of Engine API message. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub enum EngineApiMessageVersion { /// Version 1 V1, @@ -351,3 +360,13 @@ pub enum EngineApiMessageVersion { /// Added in the Prague hardfork. V4, } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn version_ord() { + assert!(EngineApiMessageVersion::V4 > EngineApiMessageVersion::V3); + } +} diff --git a/crates/ethereum/consensus/src/lib.rs b/crates/ethereum/consensus/src/lib.rs index 026408947..3783063b4 100644 --- a/crates/ethereum/consensus/src/lib.rs +++ b/crates/ethereum/consensus/src/lib.rs @@ -8,12 +8,12 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] -use reth_consensus::{Consensus, ConsensusError}; +use reth_consensus::{Consensus, ConsensusError, PostExecutionInput}; use reth_consensus_common::validation::{ validate_block_pre_execution, validate_header_extradata, validate_header_standalone, }; use reth_primitives::{ - BlockWithSenders, Chain, ChainSpec, Hardfork, Header, Receipt, SealedBlock, SealedHeader, + BlockWithSenders, Chain, ChainSpec, Hardfork, Header, SealedBlock, SealedHeader, EMPTY_OMMER_ROOT_HASH, U256, }; use std::{sync::Arc, time::SystemTime}; @@ -131,8 +131,8 @@ impl Consensus for EthBeaconConsensus { fn validate_block_post_execution( &self, block: &BlockWithSenders, - receipts: &[Receipt], + input: PostExecutionInput<'_>, ) -> Result<(), ConsensusError> { - validate_block_post_execution(block, &self.chain_spec, receipts) + validate_block_post_execution(block, &self.chain_spec, input.receipts, input.requests) } } diff --git a/crates/ethereum/consensus/src/validation.rs b/crates/ethereum/consensus/src/validation.rs index 11fe54406..50d01a0d1 100644 --- a/crates/ethereum/consensus/src/validation.rs +++ b/crates/ethereum/consensus/src/validation.rs @@ -1,6 +1,7 @@ use reth_consensus::ConsensusError; use reth_primitives::{ - gas_spent_by_transactions, BlockWithSenders, Bloom, ChainSpec, GotExpected, Receipt, B256, + gas_spent_by_transactions, BlockWithSenders, Bloom, ChainSpec, GotExpected, Receipt, Request, + B256, }; /// Validate a block with regard to execution results: @@ -11,6 +12,7 @@ pub fn validate_block_post_execution( block: &BlockWithSenders, chain_spec: &ChainSpec, receipts: &[Receipt], + requests: &[Request], ) -> Result<(), ConsensusError> { // Before Byzantium, receipts contained state root that would mean that expensive // operation as hashing that is required for state root got calculated in every @@ -30,6 +32,19 @@ pub fn validate_block_post_execution( }) } + // Validate that the header requests root matches the calculated requests root + if chain_spec.is_prague_active_at_timestamp(block.timestamp) { + let Some(header_requests_root) = block.header.requests_root else { + return Err(ConsensusError::RequestsRootMissing) + }; + let requests_root = reth_primitives::proofs::calculate_requests_root(requests); + if requests_root != header_requests_root { + return Err(ConsensusError::BodyRequestsRootDiff( + GotExpected::new(requests_root, header_requests_root).into(), + )) + } + } + Ok(()) } diff --git a/crates/ethereum/engine-primitives/src/lib.rs b/crates/ethereum/engine-primitives/src/lib.rs index cb6d0231e..4cfdf70a3 100644 --- a/crates/ethereum/engine-primitives/src/lib.rs +++ b/crates/ethereum/engine-primitives/src/lib.rs @@ -18,7 +18,7 @@ use reth_engine_primitives::{ use reth_primitives::ChainSpec; use reth_rpc_types::{ engine::{ - ExecutionPayloadEnvelopeV2, ExecutionPayloadEnvelopeV3, + ExecutionPayloadEnvelopeV2, ExecutionPayloadEnvelopeV3, ExecutionPayloadEnvelopeV4, PayloadAttributes as EthPayloadAttributes, }, ExecutionPayloadV1, @@ -36,6 +36,7 @@ impl EngineTypes for EthEngineTypes { type ExecutionPayloadV1 = ExecutionPayloadV1; type ExecutionPayloadV2 = ExecutionPayloadEnvelopeV2; type ExecutionPayloadV3 = ExecutionPayloadEnvelopeV3; + type ExecutionPayloadV4 = ExecutionPayloadEnvelopeV4; fn validate_version_specific_fields( chain_spec: &ChainSpec, diff --git a/crates/ethereum/engine-primitives/src/payload.rs b/crates/ethereum/engine-primitives/src/payload.rs index 6e753dac9..37261d995 100644 --- a/crates/ethereum/engine-primitives/src/payload.rs +++ b/crates/ethereum/engine-primitives/src/payload.rs @@ -7,11 +7,12 @@ use reth_primitives::{ BlobTransactionSidecar, ChainSpec, Hardfork, Header, SealedBlock, Withdrawals, B256, U256, }; use reth_rpc_types::engine::{ - ExecutionPayloadEnvelopeV2, ExecutionPayloadEnvelopeV3, ExecutionPayloadV1, PayloadAttributes, - PayloadId, + ExecutionPayloadEnvelopeV2, ExecutionPayloadEnvelopeV3, ExecutionPayloadEnvelopeV4, + ExecutionPayloadV1, PayloadAttributes, PayloadId, }; use reth_rpc_types_compat::engine::payload::{ - block_to_payload_v1, block_to_payload_v3, convert_block_to_payload_field_v2, + block_to_payload_v1, block_to_payload_v3, block_to_payload_v4, + convert_block_to_payload_field_v2, }; use revm_primitives::{BlobExcessGasAndPrice, BlockEnv, CfgEnv, CfgEnvWithHandlerCfg, SpecId}; use std::convert::Infallible; @@ -128,6 +129,27 @@ impl From for ExecutionPayloadEnvelopeV3 { } } +impl From for ExecutionPayloadEnvelopeV4 { + fn from(value: EthBuiltPayload) -> Self { + let EthBuiltPayload { block, fees, sidecars, .. } = value; + + ExecutionPayloadEnvelopeV4 { + execution_payload: block_to_payload_v4(block), + block_value: fees, + // From the engine API spec: + // + // > Client software **MAY** use any heuristics to decide whether to set + // `shouldOverrideBuilder` flag or not. If client software does not implement any + // heuristic this flag **SHOULD** be set to `false`. + // + // Spec: + // + should_override_builder: false, + blobs_bundle: sidecars.into_iter().map(Into::into).collect::>().into(), + } + } +} + /// Container type for all components required to build a payload. #[derive(Debug, Clone, PartialEq, Eq)] pub struct EthPayloadBuilderAttributes { diff --git a/crates/ethereum/evm/src/execute.rs b/crates/ethereum/evm/src/execute.rs index 999892706..8ae5d3c50 100644 --- a/crates/ethereum/evm/src/execute.rs +++ b/crates/ethereum/evm/src/execute.rs @@ -13,8 +13,8 @@ use reth_evm::{ ConfigureEvm, }; use reth_primitives::{ - BlockNumber, BlockWithSenders, ChainSpec, Hardfork, Header, PruneModes, Receipt, Withdrawals, - MAINNET, U256, + BlockNumber, BlockWithSenders, ChainSpec, Hardfork, Header, PruneModes, Receipt, Request, + Withdrawals, MAINNET, U256, }; use reth_revm::{ batch::{BlockBatchRecord, BlockExecutorStats}, @@ -98,6 +98,14 @@ where } } +/// Helper type for the output of executing a block. +#[derive(Debug, Clone)] +struct EthExecuteOutput { + receipts: Vec, + requests: Vec, + gas_used: u64, +} + /// Helper container type for EVM with chain spec. #[derive(Debug, Clone)] struct EthEvmExecutor { @@ -118,11 +126,11 @@ where /// # Note /// /// It does __not__ apply post-execution changes. - fn execute_pre_and_transactions( + fn execute_state_transitions( &self, block: &BlockWithSenders, mut evm: Evm<'_, Ext, &mut State>, - ) -> Result<(Vec, u64), BlockExecutionError> + ) -> Result where DB: Database, { @@ -182,7 +190,7 @@ where } drop(evm); - Ok((receipts, cumulative_gas_used)) + Ok(EthExecuteOutput { receipts, requests: vec![], gas_used: cumulative_gas_used }) } } @@ -250,22 +258,22 @@ where &mut self, block: &BlockWithSenders, total_difficulty: U256, - ) -> Result<(Vec, u64), BlockExecutionError> { + ) -> Result { // 1. prepare state on new block self.on_new_block(&block.header); // 2. configure the evm and execute let env = self.evm_env_for_block(&block.header, total_difficulty); - let (receipts, gas_used) = { + let output = { let evm = self.executor.evm_config.evm_with_env(&mut self.state, env); - self.executor.execute_pre_and_transactions(block, evm) + self.executor.execute_state_transitions(block, evm) }?; // 3. apply post execution changes self.post_execution(block, total_difficulty)?; - Ok((receipts, gas_used)) + Ok(output) } /// Apply settings before a new block is executed. @@ -333,12 +341,13 @@ where /// State changes are committed to the database. fn execute(mut self, input: Self::Input<'_>) -> Result { let BlockExecutionInput { block, total_difficulty } = input; - let (receipts, gas_used) = self.execute_without_verification(block, total_difficulty)?; + let EthExecuteOutput { receipts, requests, 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); - Ok(BlockExecutionOutput { state: self.state.take_bundle(), receipts, gas_used }) + Ok(BlockExecutionOutput { state: self.state.take_bundle(), receipts, requests, gas_used }) } } @@ -375,10 +384,10 @@ where fn execute_and_verify_one(&mut self, input: Self::Input<'_>) -> Result<(), Self::Error> { let BlockExecutionInput { block, total_difficulty } = input; - let (receipts, _gas_used) = + let EthExecuteOutput { receipts, requests, gas_used: _ } = self.executor.execute_without_verification(block, total_difficulty)?; - validate_block_post_execution(block, self.executor.chain_spec(), &receipts)?; + 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); @@ -387,6 +396,9 @@ where // store receipts in the set self.batch_record.save_receipts(receipts)?; + // store requests in the set + self.batch_record.save_requests(requests); + if self.batch_record.first_block().is_none() { self.batch_record.set_first_block(block.number); } @@ -400,6 +412,7 @@ where BatchBlockExecutionOutput::new( self.executor.state.take_bundle(), self.batch_record.take_receipts(), + self.batch_record.take_requests(), self.batch_record.first_block().unwrap_or_default(), ) } @@ -473,6 +486,7 @@ mod tests { body: vec![], ommers: vec![], withdrawals: None, + requests: None, }, senders: vec![], }, @@ -503,6 +517,7 @@ mod tests { body: vec![], ommers: vec![], withdrawals: None, + requests: None, }, senders: vec![], }, @@ -563,7 +578,13 @@ mod tests { .execute( ( &BlockWithSenders { - block: Block { header, body: vec![], ommers: vec![], withdrawals: None }, + block: Block { + header, + body: vec![], + ommers: vec![], + withdrawals: None, + requests: None, + }, senders: vec![], }, U256::ZERO, @@ -609,7 +630,13 @@ mod tests { executor .execute_without_verification( &BlockWithSenders { - block: Block { header, body: vec![], ommers: vec![], withdrawals: None }, + block: Block { + header, + body: vec![], + ommers: vec![], + withdrawals: None, + requests: None, + }, senders: vec![], }, U256::ZERO, @@ -653,6 +680,7 @@ mod tests { body: vec![], ommers: vec![], withdrawals: None, + requests: None, }, senders: vec![], }, @@ -674,7 +702,13 @@ mod tests { .execute_and_verify_one( ( &BlockWithSenders { - block: Block { header, body: vec![], ommers: vec![], withdrawals: None }, + block: Block { + header, + body: vec![], + ommers: vec![], + withdrawals: None, + requests: None, + }, senders: vec![], }, U256::ZERO, @@ -733,6 +767,7 @@ mod tests { body: vec![], ommers: vec![], withdrawals: None, + requests: None, }, senders: vec![], }, diff --git a/crates/ethereum/evm/src/lib.rs b/crates/ethereum/evm/src/lib.rs index 9e5db6bc2..d94ebc968 100644 --- a/crates/ethereum/evm/src/lib.rs +++ b/crates/ethereum/evm/src/lib.rs @@ -15,6 +15,7 @@ use reth_primitives::{ Address, ChainSpec, Head, Header, TransactionSigned, U256, }; use reth_revm::{Database, EvmBuilder}; + pub mod execute; /// Ethereum DAO hardfork state change data. diff --git a/crates/evm/Cargo.toml b/crates/evm/Cargo.toml index bc94dcd17..6ac91ca8d 100644 --- a/crates/evm/Cargo.toml +++ b/crates/evm/Cargo.toml @@ -27,4 +27,4 @@ parking_lot = { workspace = true, optional = true } parking_lot.workspace = true [features] -test-utils = ["dep:parking_lot"] \ No newline at end of file +test-utils = ["dep:parking_lot"] diff --git a/crates/evm/execution-types/src/bundle.rs b/crates/evm/execution-types/src/bundle.rs index 2bc7eda45..2b823e731 100644 --- a/crates/evm/execution-types/src/bundle.rs +++ b/crates/evm/execution-types/src/bundle.rs @@ -32,7 +32,7 @@ pub struct BundleStateWithReceipts { // TODO(mattsse): unify the types, currently there's a cyclic dependency between impl From for BundleStateWithReceipts { fn from(value: BatchBlockExecutionOutput) -> Self { - let BatchBlockExecutionOutput { bundle, receipts, first_block } = value; + let BatchBlockExecutionOutput { bundle, receipts, requests: _, first_block } = value; Self { bundle, receipts, first_block } } } @@ -41,7 +41,8 @@ impl From for BundleStateWithReceipts { impl From for BatchBlockExecutionOutput { fn from(value: BundleStateWithReceipts) -> Self { let BundleStateWithReceipts { bundle, receipts, first_block } = value; - Self { bundle, receipts, first_block } + // TODO(alexey): add requests + Self { bundle, receipts, requests: Vec::default(), first_block } } } diff --git a/crates/evm/src/execute.rs b/crates/evm/src/execute.rs index 6fdd6ebfd..f459eceb1 100644 --- a/crates/evm/src/execute.rs +++ b/crates/evm/src/execute.rs @@ -1,6 +1,8 @@ //! Traits for execution. -use reth_primitives::{BlockNumber, BlockWithSenders, PruneModes, Receipt, Receipts, U256}; +use reth_primitives::{ + BlockNumber, BlockWithSenders, PruneModes, Receipt, Receipts, Request, Requests, U256, +}; use revm::db::BundleState; use revm_primitives::db::Database; @@ -96,6 +98,8 @@ pub struct BlockExecutionOutput { pub state: BundleState, /// All the receipts of the transactions in the block. pub receipts: Vec, + /// All the EIP-7685 requests of the transactions in the block. + pub requests: Vec, /// The total gas used by the block. pub gas_used: u64, } @@ -111,14 +115,26 @@ pub struct BatchBlockExecutionOutput { /// /// If receipt is None it means it is pruned. pub receipts: Receipts, + /// The collection of EIP-7685 requests. + /// Outer vector stores requests for each block sequentially. + /// The inner vector stores requests ordered by transaction number. + /// + /// A transaction may have zero or more requests, so the length of the inner vector is not + /// guaranteed to be the same as the number of transactions. + pub requests: Vec, /// First block of bundle state. pub first_block: BlockNumber, } impl BatchBlockExecutionOutput { /// Create Bundle State. - pub fn new(bundle: BundleState, receipts: Receipts, first_block: BlockNumber) -> Self { - Self { bundle, receipts, first_block } + pub fn new( + bundle: BundleState, + receipts: Receipts, + requests: Vec, + first_block: BlockNumber, + ) -> Self { + Self { bundle, receipts, requests, first_block } } } @@ -260,8 +276,13 @@ mod tests { let provider = TestExecutorProvider; let db = CacheDB::>::default(); let executor = provider.executor(db); - let block = - Block { header: Default::default(), body: vec![], ommers: vec![], withdrawals: None }; + let block = Block { + header: Default::default(), + body: vec![], + ommers: vec![], + withdrawals: None, + requests: None, + }; let block = BlockWithSenders::new(block, Default::default()).unwrap(); let _ = executor.execute(BlockExecutionInput::new(&block, U256::ZERO)); } diff --git a/crates/evm/src/test_utils.rs b/crates/evm/src/test_utils.rs index 910f9d08b..d898165a5 100644 --- a/crates/evm/src/test_utils.rs +++ b/crates/evm/src/test_utils.rs @@ -50,11 +50,12 @@ impl Executor for MockExecutorProvider { type Error = BlockExecutionError; fn execute(self, _: Self::Input<'_>) -> Result { - let BatchBlockExecutionOutput { bundle, receipts, .. } = + let BatchBlockExecutionOutput { bundle, receipts, requests, first_block: _ } = self.exec_results.lock().pop().unwrap(); Ok(BlockExecutionOutput { state: bundle, receipts: receipts.into_iter().flatten().flatten().collect(), + requests: requests.into_iter().flatten().collect(), gas_used: 0, }) } diff --git a/crates/net/downloaders/src/bodies/bodies.rs b/crates/net/downloaders/src/bodies/bodies.rs index a806f2fa6..33139ab50 100644 --- a/crates/net/downloaders/src/bodies/bodies.rs +++ b/crates/net/downloaders/src/bodies/bodies.rs @@ -655,7 +655,12 @@ mod tests { .map(|block| { ( block.hash(), - BlockBody { transactions: block.body, ommers: block.ommers, withdrawals: None }, + BlockBody { + transactions: block.body, + ommers: block.ommers, + withdrawals: None, + requests: None, + }, ) }) .collect::>(); diff --git a/crates/net/downloaders/src/bodies/test_utils.rs b/crates/net/downloaders/src/bodies/test_utils.rs index 40e530129..dadd4b3bd 100644 --- a/crates/net/downloaders/src/bodies/test_utils.rs +++ b/crates/net/downloaders/src/bodies/test_utils.rs @@ -23,6 +23,7 @@ pub(crate) fn zip_blocks<'a>( body: body.transactions, ommers: body.ommers, withdrawals: body.withdrawals, + requests: body.requests, }) } }) diff --git a/crates/net/downloaders/src/file_client.rs b/crates/net/downloaders/src/file_client.rs index f79b8744f..6dc07c0da 100644 --- a/crates/net/downloaders/src/file_client.rs +++ b/crates/net/downloaders/src/file_client.rs @@ -234,6 +234,7 @@ impl FromReader for FileClient { transactions: block.body, ommers: block.ommers, withdrawals: block.withdrawals, + requests: block.requests, }, ); @@ -487,7 +488,7 @@ mod tests { headers::downloader::{HeaderDownloader, SyncTarget}, }; use reth_provider::test_utils::create_test_provider_factory; - use std::{mem, sync::Arc}; + use std::sync::Arc; #[tokio::test] async fn streams_bodies_from_buffer() { @@ -601,23 +602,11 @@ mod tests { async fn test_chunk_download_headers_from_file() { reth_tracing::init_test_tracing(); - // rig - - const MAX_BYTE_SIZE_HEADER: usize = 720; - // Generate some random blocks - let (file, headers, bodies) = generate_bodies_file(0..=14).await; - // now try to read them back in chunks. - for header in &headers { - assert_eq!(720, mem::size_of_val(header)) - } + let (file, headers, _) = generate_bodies_file(0..=14).await; // calculate min for chunk byte length range - let mut bodies_sizes = bodies.values().map(|body| body.size()).collect::>(); - bodies_sizes.sort(); - let max_block_size = MAX_BYTE_SIZE_HEADER + bodies_sizes.last().unwrap(); - let chunk_byte_len = rand::thread_rng().gen_range(max_block_size..=max_block_size + 10_000); - + let chunk_byte_len = rand::thread_rng().gen_range(1..=10_000); trace!(target: "downloaders::file::test", chunk_byte_len); // init reader @@ -628,7 +617,6 @@ mod tests { let mut local_header = headers.first().unwrap().clone(); // test - while let Some(client) = reader.next_chunk::().await.unwrap() { let sync_target = client.tip_header().unwrap(); let sync_target_hash = sync_target.hash(); diff --git a/crates/net/downloaders/src/test_utils/mod.rs b/crates/net/downloaders/src/test_utils/mod.rs index b84c5282b..97e30a02d 100644 --- a/crates/net/downloaders/src/test_utils/mod.rs +++ b/crates/net/downloaders/src/test_utils/mod.rs @@ -33,6 +33,7 @@ pub(crate) fn generate_bodies( transactions: block.body, ommers: block.ommers, withdrawals: block.withdrawals, + requests: block.requests, }, ) }) diff --git a/crates/net/eth-wire-types/src/blocks.rs b/crates/net/eth-wire-types/src/blocks.rs index d8c13062d..ae5cfee9e 100644 --- a/crates/net/eth-wire-types/src/blocks.rs +++ b/crates/net/eth-wire-types/src/blocks.rs @@ -296,6 +296,7 @@ mod tests { blob_gas_used: None, excess_blob_gas: None, parent_beacon_block_root: None, + requests_root: None }, ]), }.encode(&mut data); @@ -330,6 +331,7 @@ mod tests { blob_gas_used: None, excess_blob_gas: None, parent_beacon_block_root: None, + requests_root: None }, ]), }; @@ -430,9 +432,11 @@ mod tests { blob_gas_used: None, excess_blob_gas: None, parent_beacon_block_root: None, + requests_root: None }, ], withdrawals: None, + requests: None } ]), }; @@ -504,9 +508,11 @@ mod tests { blob_gas_used: None, excess_blob_gas: None, parent_beacon_block_root: None, + requests_root: None }, ], withdrawals: None, + requests: None } ]), }; diff --git a/crates/net/network/src/eth_requests.rs b/crates/net/network/src/eth_requests.rs index 7cca37995..10821dd80 100644 --- a/crates/net/network/src/eth_requests.rs +++ b/crates/net/network/src/eth_requests.rs @@ -166,6 +166,7 @@ where transactions: block.body, ommers: block.ommers, withdrawals: block.withdrawals, + requests: block.requests, }; total_bytes += body.length(); diff --git a/crates/net/network/tests/it/requests.rs b/crates/net/network/tests/it/requests.rs index 4e36f191c..45c86cb64 100644 --- a/crates/net/network/tests/it/requests.rs +++ b/crates/net/network/tests/it/requests.rs @@ -73,8 +73,12 @@ async fn test_get_body() { let blocks = res.unwrap().1; assert_eq!(blocks.len(), 1); - let expected = - BlockBody { transactions: block.body, ommers: block.ommers, withdrawals: None }; + let expected = BlockBody { + transactions: block.body, + ommers: block.ommers, + withdrawals: None, + requests: None, + }; assert_eq!(blocks[0], expected); } } diff --git a/crates/net/p2p/src/full_block.rs b/crates/net/p2p/src/full_block.rs index 997ab74bb..54c4731ff 100644 --- a/crates/net/p2p/src/full_block.rs +++ b/crates/net/p2p/src/full_block.rs @@ -313,22 +313,36 @@ fn ensure_valid_body_response( )) } - let withdrawals = match &block.withdrawals { - Some(withdrawals) => withdrawals.as_slice(), - None => &[][..], - }; - if let Some(header_withdrawals_root) = header.withdrawals_root { - let withdrawals_root = reth_primitives::proofs::calculate_withdrawals_root(withdrawals); - if withdrawals_root != header_withdrawals_root { - return Err(ConsensusError::BodyWithdrawalsRootDiff( - GotExpected { got: withdrawals_root, expected: header_withdrawals_root }.into(), - )) + match (header.withdrawals_root, &block.withdrawals) { + (Some(header_withdrawals_root), Some(withdrawals)) => { + let withdrawals = withdrawals.as_slice(); + let withdrawals_root = reth_primitives::proofs::calculate_withdrawals_root(withdrawals); + if withdrawals_root != header_withdrawals_root { + return Err(ConsensusError::BodyWithdrawalsRootDiff( + GotExpected { got: withdrawals_root, expected: header_withdrawals_root }.into(), + )) + } } - return Ok(()) + (None, None) => { + // this is ok because we assume the fork is not active in this case + } + _ => return Err(ConsensusError::WithdrawalsRootUnexpected), } - if !withdrawals.is_empty() { - return Err(ConsensusError::WithdrawalsRootUnexpected) + match (header.requests_root, &block.requests) { + (Some(header_requests_root), Some(requests)) => { + let requests = requests.0.as_slice(); + let requests_root = reth_primitives::proofs::calculate_requests_root(requests); + if requests_root != header_requests_root { + return Err(ConsensusError::BodyRequestsRootDiff( + GotExpected { got: requests_root, expected: header_requests_root }.into(), + )) + } + } + (None, None) => { + // this is ok because we assume the fork is not active in this case + } + _ => return Err(ConsensusError::RequestsRootUnexpected), } Ok(()) diff --git a/crates/net/p2p/src/test_utils/generators.rs b/crates/net/p2p/src/test_utils/generators.rs index 9da1429ea..8056e90ff 100644 --- a/crates/net/p2p/src/test_utils/generators.rs +++ b/crates/net/p2p/src/test_utils/generators.rs @@ -157,6 +157,7 @@ pub fn random_block( body: transactions, ommers, withdrawals: None, + requests: None, } } diff --git a/crates/node-core/src/utils.rs b/crates/node-core/src/utils.rs index 963f863c5..13ed6a247 100644 --- a/crates/node-core/src/utils.rs +++ b/crates/node-core/src/utils.rs @@ -119,6 +119,7 @@ where body: block.transactions, ommers: block.ommers, withdrawals: block.withdrawals, + requests: block.requests, }; validate_block_pre_execution(&block, &chain_spec)?; diff --git a/crates/optimism/consensus/src/lib.rs b/crates/optimism/consensus/src/lib.rs index 09f9c1f38..a9f07f23c 100644 --- a/crates/optimism/consensus/src/lib.rs +++ b/crates/optimism/consensus/src/lib.rs @@ -9,13 +9,12 @@ // The `optimism` feature must be enabled to use this crate. #![cfg(feature = "optimism")] -use reth_consensus::{Consensus, ConsensusError}; +use reth_consensus::{Consensus, ConsensusError, PostExecutionInput}; use reth_consensus_common::validation::{ validate_block_pre_execution, validate_header_extradata, validate_header_standalone, }; use reth_primitives::{ - BlockWithSenders, ChainSpec, Header, Receipt, SealedBlock, SealedHeader, EMPTY_OMMER_ROOT_HASH, - U256, + BlockWithSenders, ChainSpec, Header, SealedBlock, SealedHeader, EMPTY_OMMER_ROOT_HASH, U256, }; use std::{sync::Arc, time::SystemTime}; @@ -111,8 +110,8 @@ impl Consensus for OptimismBeaconConsensus { fn validate_block_post_execution( &self, block: &BlockWithSenders, - receipts: &[Receipt], + input: PostExecutionInput<'_>, ) -> Result<(), ConsensusError> { - validate_block_post_execution(block, &self.chain_spec, receipts) + validate_block_post_execution(block, &self.chain_spec, input.receipts) } } diff --git a/crates/optimism/evm/src/execute.rs b/crates/optimism/evm/src/execute.rs index 7df033dc5..aa99b018c 100644 --- a/crates/optimism/evm/src/execute.rs +++ b/crates/optimism/evm/src/execute.rs @@ -355,7 +355,12 @@ where // NOTE: we need to merge keep the reverts for the bundle retention self.state.merge_transitions(BundleRetention::Reverts); - Ok(BlockExecutionOutput { state: self.state.take_bundle(), receipts, gas_used }) + Ok(BlockExecutionOutput { + state: self.state.take_bundle(), + receipts, + requests: vec![], + gas_used, + }) } } @@ -420,6 +425,7 @@ where BatchBlockExecutionOutput::new( self.executor.state.take_bundle(), self.batch_record.take_receipts(), + self.batch_record.take_requests(), self.batch_record.first_block().unwrap_or_default(), ) } @@ -535,6 +541,7 @@ mod tests { body: vec![tx, tx_deposit], ommers: vec![], withdrawals: None, + requests: None, }, senders: vec![addr, addr], }, @@ -616,6 +623,7 @@ mod tests { body: vec![tx, tx_deposit], ommers: vec![], withdrawals: None, + requests: None, }, senders: vec![addr, addr], }, diff --git a/crates/optimism/evm/src/l1.rs b/crates/optimism/evm/src/l1.rs index 82fbb06e9..bd0313087 100644 --- a/crates/optimism/evm/src/l1.rs +++ b/crates/optimism/evm/src/l1.rs @@ -280,6 +280,7 @@ mod tests { body: vec![l1_info_tx], ommers: Vec::default(), withdrawals: None, + requests: None, }; let l1_info: L1BlockInfo = extract_l1_info(&mock_block).unwrap(); @@ -301,6 +302,7 @@ mod tests { body: vec![l1_info_tx], ommers: Vec::default(), withdrawals: None, + requests: None, }; let l1_info: L1BlockInfo = extract_l1_info(&mock_block).unwrap(); diff --git a/crates/optimism/node/src/engine.rs b/crates/optimism/node/src/engine.rs index 7382d2184..a01a84d3c 100644 --- a/crates/optimism/node/src/engine.rs +++ b/crates/optimism/node/src/engine.rs @@ -7,7 +7,8 @@ use reth_optimism_payload_builder::{OptimismBuiltPayload, OptimismPayloadBuilder use reth_primitives::{ChainSpec, Hardfork}; use reth_rpc_types::{ engine::{ - ExecutionPayloadEnvelopeV2, OptimismExecutionPayloadEnvelopeV3, OptimismPayloadAttributes, + ExecutionPayloadEnvelopeV2, OptimismExecutionPayloadEnvelopeV3, + OptimismExecutionPayloadEnvelopeV4, OptimismPayloadAttributes, }, ExecutionPayloadV1, }; @@ -24,6 +25,7 @@ impl EngineTypes for OptimismEngineTypes { type ExecutionPayloadV1 = ExecutionPayloadV1; type ExecutionPayloadV2 = ExecutionPayloadEnvelopeV2; type ExecutionPayloadV3 = OptimismExecutionPayloadEnvelopeV3; + type ExecutionPayloadV4 = OptimismExecutionPayloadEnvelopeV4; fn validate_version_specific_fields( chain_spec: &ChainSpec, diff --git a/crates/optimism/payload/src/builder.rs b/crates/optimism/payload/src/builder.rs index 2794ad968..a6f3d83c4 100644 --- a/crates/optimism/payload/src/builder.rs +++ b/crates/optimism/payload/src/builder.rs @@ -34,13 +34,13 @@ pub struct OptimismPayloadBuilder { compute_pending_block: bool, /// The rollup's chain spec. chain_spec: Arc, - + /// The type responsible for creating the evm. evm_config: EvmConfig, } impl OptimismPayloadBuilder { /// OptimismPayloadBuilder constructor. - pub fn new(chain_spec: Arc, evm_config: EvmConfig) -> Self { + pub const fn new(chain_spec: Arc, evm_config: EvmConfig) -> Self { Self { compute_pending_block: true, chain_spec, evm_config } } @@ -217,9 +217,10 @@ where blob_gas_used, excess_blob_gas, parent_beacon_block_root: attributes.payload_attributes.parent_beacon_block_root, + requests_root: None, }; - let block = Block { header, body: vec![], ommers: vec![], withdrawals }; + let block = Block { header, body: vec![], ommers: vec![], withdrawals, requests: None }; let sealed_block = block.seal_slow(); Ok(OptimismBuiltPayload::new( @@ -577,10 +578,11 @@ where parent_beacon_block_root: attributes.payload_attributes.parent_beacon_block_root, blob_gas_used, excess_blob_gas, + requests_root: None, }; // seal the block - let block = Block { header, body: executed_txs, ommers: vec![], withdrawals }; + 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"); diff --git a/crates/optimism/payload/src/payload.rs b/crates/optimism/payload/src/payload.rs index 41a3eec9b..738246bf8 100644 --- a/crates/optimism/payload/src/payload.rs +++ b/crates/optimism/payload/src/payload.rs @@ -13,10 +13,11 @@ use reth_primitives::{ }; use reth_rpc_types::engine::{ ExecutionPayloadEnvelopeV2, ExecutionPayloadV1, OptimismExecutionPayloadEnvelopeV3, - OptimismPayloadAttributes, PayloadId, + OptimismExecutionPayloadEnvelopeV4, OptimismPayloadAttributes, PayloadId, }; use reth_rpc_types_compat::engine::payload::{ - block_to_payload_v1, block_to_payload_v3, convert_block_to_payload_field_v2, + block_to_payload_v1, block_to_payload_v3, block_to_payload_v4, + convert_block_to_payload_field_v2, }; use revm::primitives::HandlerCfg; use std::sync::Arc; @@ -272,6 +273,33 @@ impl From for OptimismExecutionPayloadEnvelopeV3 { } } } +impl From for OptimismExecutionPayloadEnvelopeV4 { + fn from(value: OptimismBuiltPayload) -> Self { + let OptimismBuiltPayload { block, fees, sidecars, chain_spec, attributes, .. } = value; + + let parent_beacon_block_root = + if chain_spec.is_cancun_active_at_timestamp(attributes.timestamp()) { + attributes.parent_beacon_block_root().unwrap_or(B256::ZERO) + } else { + B256::ZERO + }; + OptimismExecutionPayloadEnvelopeV4 { + execution_payload: block_to_payload_v4(block), + block_value: fees, + // From the engine API spec: + // + // > Client software **MAY** use any heuristics to decide whether to set + // `shouldOverrideBuilder` flag or not. If client software does not implement any + // heuristic this flag **SHOULD** be set to `false`. + // + // Spec: + // + should_override_builder: false, + blobs_bundle: sidecars.into_iter().map(Into::into).collect::>().into(), + parent_beacon_block_root, + } + } +} /// Generates the payload id for the configured payload from the [OptimismPayloadAttributes]. /// diff --git a/crates/payload/ethereum/Cargo.toml b/crates/payload/ethereum/Cargo.toml index 6598023d7..245bc8ebf 100644 --- a/crates/payload/ethereum/Cargo.toml +++ b/crates/payload/ethereum/Cargo.toml @@ -19,9 +19,11 @@ reth-transaction-pool.workspace = true reth-provider.workspace = true reth-payload-builder.workspace = true reth-basic-payload-builder.workspace = true +reth-evm.workspace = true +reth-evm-ethereum.workspace = true # ethereum revm.workspace = true # misc -tracing.workspace = true \ No newline at end of file +tracing.workspace = true diff --git a/crates/payload/ethereum/src/lib.rs b/crates/payload/ethereum/src/lib.rs index e34287f76..582283056 100644 --- a/crates/payload/ethereum/src/lib.rs +++ b/crates/payload/ethereum/src/lib.rs @@ -13,17 +13,21 @@ use reth_basic_payload_builder::{ commit_withdrawals, is_better_payload, pre_block_beacon_root_contract_call, BuildArguments, BuildOutcome, PayloadBuilder, PayloadConfig, WithdrawalsOutcome, }; +use reth_evm::ConfigureEvm; +use reth_evm_ethereum::EthEvmConfig; use reth_payload_builder::{ error::PayloadBuilderError, EthBuiltPayload, EthPayloadBuilderAttributes, }; use reth_primitives::{ constants::{ - eip4844::MAX_DATA_GAS_PER_BLOCK, BEACON_NONCE, EMPTY_RECEIPTS, EMPTY_TRANSACTIONS, + eip4844::MAX_DATA_GAS_PER_BLOCK, BEACON_NONCE, EMPTY_RECEIPTS, EMPTY_ROOT_HASH, + EMPTY_TRANSACTIONS, }, eip4844::calculate_excess_blob_gas, proofs, revm::env::tx_env_with_recovered, - Block, Header, IntoRecoveredTransaction, Receipt, Receipts, EMPTY_OMMER_ROOT_HASH, U256, + Block, Header, IntoRecoveredTransaction, Receipt, Receipts, Requests, EMPTY_OMMER_ROOT_HASH, + U256, }; use reth_provider::{BundleStateWithReceipts, StateProviderFactory}; use reth_revm::database::StateProviderDatabase; @@ -36,13 +40,29 @@ use revm::{ use tracing::{debug, trace, warn}; /// Ethereum payload builder -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] -#[non_exhaustive] -pub struct EthereumPayloadBuilder; +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct EthereumPayloadBuilder { + /// The type responsible for creating the evm. + evm_config: EvmConfig, +} + +impl EthereumPayloadBuilder { + /// EthereumPayloadBuilder constructor. + pub const fn new(evm_config: EvmConfig) -> Self { + Self { evm_config } + } +} + +impl Default for EthereumPayloadBuilder { + fn default() -> Self { + Self::new(EthEvmConfig::default()) + } +} // Default implementation of [PayloadBuilder] for unit type -impl PayloadBuilder for EthereumPayloadBuilder +impl PayloadBuilder for EthereumPayloadBuilder where + EvmConfig: ConfigureEvm, Client: StateProviderFactory, Pool: TransactionPool, { @@ -53,7 +73,7 @@ where &self, args: BuildArguments, ) -> Result, PayloadBuilderError> { - default_ethereum_payload_builder(args) + default_ethereum_payload_builder(self.evm_config.clone(), args) } fn build_empty_payload( @@ -122,6 +142,14 @@ where err })?; + // Calculate the requests and the requests root. + let (requests, requests_root) = + if chain_spec.is_prague_active_at_timestamp(attributes.timestamp) { + (Some(Requests::default()), Some(EMPTY_ROOT_HASH)) + } else { + (None, None) + }; + // merge all transitions into bundle state, this would apply the withdrawal balance // changes and 4788 contract call db.merge_transitions(BundleRetention::PlainState); @@ -175,9 +203,10 @@ where blob_gas_used, excess_blob_gas, parent_beacon_block_root: attributes.parent_beacon_block_root, + requests_root, }; - let block = Block { header, body: vec![], ommers: vec![], withdrawals }; + let block = Block { header, body: vec![], ommers: vec![], withdrawals, requests }; let sealed_block = block.seal_slow(); Ok(EthBuiltPayload::new(attributes.payload_id(), sealed_block, U256::ZERO)) @@ -190,10 +219,12 @@ where /// 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 fn default_ethereum_payload_builder( +pub fn default_ethereum_payload_builder( + evm_config: EvmConfig, args: BuildArguments, ) -> Result, PayloadBuilderError> where + EvmConfig: ConfigureEvm, Client: StateProviderFactory, Pool: TransactionPool, { @@ -274,15 +305,14 @@ where } } + let env = EnvWithHandlerCfg::new_with_cfg_env( + initialized_cfg.clone(), + initialized_block_env.clone(), + tx_env_with_recovered(&tx), + ); + // Configure the environment for the block. - let mut evm = revm::Evm::builder() - .with_db(&mut db) - .with_env_with_handler_cfg(EnvWithHandlerCfg::new_with_cfg_env( - initialized_cfg.clone(), - initialized_block_env.clone(), - tx_env_with_recovered(&tx), - )) - .build(); + let mut evm = evm_config.evm_with_env(&mut db, env); let ResultAndState { result, state } = match evm.transact() { Ok(res) => res, @@ -404,6 +434,14 @@ where blob_gas_used = Some(sum_blob_gas_used); } + // todo: compute requests and requests root + let (requests, requests_root) = + if chain_spec.is_prague_active_at_timestamp(attributes.timestamp) { + (Some(Requests::default()), Some(EMPTY_ROOT_HASH)) + } else { + (None, None) + }; + let header = Header { parent_hash: parent_block.hash(), ommers_hash: EMPTY_OMMER_ROOT_HASH, @@ -425,10 +463,11 @@ where parent_beacon_block_root: attributes.parent_beacon_block_root, blob_gas_used, excess_blob_gas, + requests_root, }; // seal the block - let block = Block { header, body: executed_txs, ommers: vec![], withdrawals }; + let block = Block { header, body: executed_txs, ommers: vec![], withdrawals, requests }; let sealed_block = block.seal_slow(); debug!(target: "payload_builder", ?sealed_block, "sealed built block"); diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index f44db3cae..8660eb5ac 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -22,6 +22,7 @@ revm-primitives = { workspace = true, features = ["serde"] } # ethereum alloy-chains = { workspace = true, features = ["serde", "rlp"] } +alloy-consensus = { workspace = true, features = ["arbitrary", "serde"] } alloy-primitives = { workspace = true, features = ["rand", "rlp"] } alloy-rlp = { workspace = true, features = ["arrayvec"] } alloy-trie = { workspace = true, features = ["serde"] } @@ -91,6 +92,7 @@ pprof = { workspace = true, features = [ "frame-pointer", "criterion", ] } +secp256k1.workspace = true [features] default = ["c-kzg", "zstd-codec"] diff --git a/crates/primitives/src/alloy_compat.rs b/crates/primitives/src/alloy_compat.rs index 2cdaee72d..bd68860d2 100644 --- a/crates/primitives/src/alloy_compat.rs +++ b/crates/primitives/src/alloy_compat.rs @@ -47,6 +47,9 @@ impl TryFrom for Block { body, ommers: Default::default(), withdrawals: block.withdrawals.map(Into::into), + // todo(onbjerg): we don't know if this is added to rpc yet, so for now we leave it as + // empty. + requests: None, }) } } @@ -93,6 +96,8 @@ impl TryFrom for Header { timestamp: header.timestamp, transactions_root: header.transactions_root, withdrawals_root: header.withdrawals_root, + // TODO: requests_root: header.requests_root, + requests_root: None, }) } } diff --git a/crates/primitives/src/block.rs b/crates/primitives/src/block.rs index 467bca816..b37d5146f 100644 --- a/crates/primitives/src/block.rs +++ b/crates/primitives/src/block.rs @@ -1,5 +1,5 @@ use crate::{ - Address, Bytes, GotExpected, Header, SealedHeader, TransactionSigned, + Address, Bytes, GotExpected, Header, Requests, SealedHeader, TransactionSigned, TransactionSignedEcRecovered, Withdrawals, B256, }; use alloy_rlp::{RlpDecodable, RlpEncodable}; @@ -13,6 +13,16 @@ pub use alloy_eips::eip1898::{ BlockHashOrNumber, BlockId, BlockNumHash, BlockNumberOrTag, ForkBlock, RpcBlockHash, }; +// HACK(onbjerg): we need this to always set `requests` to `None` since we might otherwise generate +// a block with `None` withdrawals and `Some` requests, in which case we end up trying to decode the +// requests as withdrawals +#[cfg(any(feature = "arbitrary", test))] +prop_compose! { + pub fn empty_requests_strategy()(_ in 0..1) -> Option { + None + } +} + /// Ethereum full block. /// /// Withdrawals can be optionally included at the end of the RLP encoded message. @@ -45,6 +55,9 @@ pub struct Block { proptest(strategy = "proptest::option::of(proptest::arbitrary::any::())") )] pub withdrawals: Option, + /// Block requests. + #[cfg_attr(any(test, feature = "arbitrary"), proptest(strategy = "empty_requests_strategy()"))] + pub requests: Option, } impl Block { @@ -55,6 +68,7 @@ impl Block { body: self.body, ommers: self.ommers, withdrawals: self.withdrawals, + requests: self.requests, } } @@ -67,6 +81,7 @@ impl Block { body: self.body, ommers: self.ommers, withdrawals: self.withdrawals, + requests: self.requests, } } @@ -257,14 +272,17 @@ pub struct SealedBlock { proptest(strategy = "proptest::option::of(proptest::arbitrary::any::())") )] pub withdrawals: Option, + /// Block requests. + #[cfg_attr(any(test, feature = "arbitrary"), proptest(strategy = "empty_requests_strategy()"))] + pub requests: Option, } impl SealedBlock { /// Create a new sealed block instance using the sealed header and block body. #[inline] pub fn new(header: SealedHeader, body: BlockBody) -> Self { - let BlockBody { transactions, ommers, withdrawals } = body; - Self { header, body: transactions, ommers, withdrawals } + let BlockBody { transactions, ommers, withdrawals, requests } = body; + Self { header, body: transactions, ommers, withdrawals, requests } } /// Header hash. @@ -288,6 +306,7 @@ impl SealedBlock { transactions: self.body, ommers: self.ommers, withdrawals: self.withdrawals, + requests: self.requests, }, ) } @@ -343,6 +362,7 @@ impl SealedBlock { body: self.body, ommers: self.ommers, withdrawals: self.withdrawals, + requests: self.requests, } } @@ -514,16 +534,21 @@ pub struct BlockBody { pub ommers: Vec
, /// Withdrawals in the block. pub withdrawals: Option, + /// Requests in the block. + #[cfg_attr(any(test, feature = "arbitrary"), proptest(strategy = "empty_requests_strategy()"))] + pub requests: Option, } impl BlockBody { /// Create a [`Block`] from the body and its header. + // todo(onbjerg): should this not just take `self`? its used in one place pub fn create_block(&self, header: Header) -> Block { Block { header, body: self.transactions.clone(), ommers: self.ommers.clone(), withdrawals: self.withdrawals.clone(), + requests: self.requests.clone(), } } @@ -543,6 +568,12 @@ impl BlockBody { self.withdrawals.as_ref().map(|w| crate::proofs::calculate_withdrawals_root(w)) } + /// Calculate the requests root for the block body, if requests exist. If there are no + /// requests, this will return `None`. + pub fn calculate_requests_root(&self) -> Option { + self.requests.as_ref().map(|r| crate::proofs::calculate_requests_root(&r.0)) + } + /// Calculates a heuristic for the in-memory size of the [BlockBody]. #[inline] pub fn size(&self) -> usize { @@ -558,7 +589,12 @@ impl BlockBody { impl From for BlockBody { fn from(block: Block) -> Self { - Self { transactions: block.body, ommers: block.ommers, withdrawals: block.withdrawals } + Self { + transactions: block.body, + ommers: block.ommers, + withdrawals: block.withdrawals, + requests: block.requests, + } } } @@ -602,6 +638,9 @@ pub fn generate_valid_header( header.parent_beacon_block_root = None; } + // todo(onbjerg): adjust this for eip-7589 + header.requests_root = None; + header } diff --git a/crates/primitives/src/chain/spec.rs b/crates/primitives/src/chain/spec.rs index 98ec3d972..3c166b2c7 100644 --- a/crates/primitives/src/chain/spec.rs +++ b/crates/primitives/src/chain/spec.rs @@ -1,5 +1,8 @@ use crate::{ - constants::{EIP1559_INITIAL_BASE_FEE, EMPTY_RECEIPTS, EMPTY_TRANSACTIONS, EMPTY_WITHDRAWALS}, + constants::{ + EIP1559_INITIAL_BASE_FEE, EMPTY_RECEIPTS, EMPTY_ROOT_HASH, EMPTY_TRANSACTIONS, + EMPTY_WITHDRAWALS, + }, holesky_nodes, net::{goerli_nodes, mainnet_nodes, sepolia_nodes}, proofs::state_root_ref_unhashed, @@ -612,6 +615,13 @@ impl ChainSpec { (None, None, None) }; + // If Prague is activated at genesis we set requests root to an empty trie root. + let requests_root = if self.is_prague_active_at_timestamp(self.genesis.timestamp) { + Some(EMPTY_ROOT_HASH) + } else { + None + }; + Header { parent_hash: B256::ZERO, number: 0, @@ -633,6 +643,7 @@ impl ChainSpec { parent_beacon_block_root, blob_gas_used, excess_blob_gas, + requests_root, } } diff --git a/crates/primitives/src/header.rs b/crates/primitives/src/header.rs index db75e1b6d..b9574785a 100644 --- a/crates/primitives/src/header.rs +++ b/crates/primitives/src/header.rs @@ -50,7 +50,8 @@ pub struct Header { /// of each transaction in the transactions list portion of the block; formally He. pub receipts_root: B256, /// The Keccak 256-bit hash of the withdrawals list portion of this block. - /// + /// + /// See [EIP-4895](https://eips.ethereum.org/EIPS/eip-4895). pub withdrawals_root: Option, /// The Bloom filter composed from indexable information (logger address and log topics) /// contained in each log entry from the receipt of each transaction in the transactions list; @@ -98,6 +99,11 @@ pub struct Header { /// /// The beacon roots contract handles root storage, enhancing Ethereum's functionalities. pub parent_beacon_block_root: Option, + /// The Keccak 256-bit hash of the root node of the trie structure populated with each + /// [EIP-7685] request in the block body. + /// + /// [EIP-7685]: https://eips.ethereum.org/EIPS/eip-7685 + pub requests_root: Option, /// An arbitrary byte array containing data relevant to this block. This must be 32 bytes or /// fewer; formally Hx. pub extra_data: Bytes, @@ -126,6 +132,7 @@ impl Default for Header { blob_gas_used: None, excess_blob_gas: None, parent_beacon_block_root: None, + requests_root: None, } } } @@ -343,6 +350,10 @@ impl Header { length += parent_beacon_block_root.length(); } + if let Some(requests_root) = self.requests_root { + length += requests_root.length(); + } + length } } @@ -396,15 +407,22 @@ impl Encodable for Header { U256::from(*excess_blob_gas).encode(out); } - // Encode parent beacon block root. If new fields are added, the above pattern will need to + // Encode parent beacon block root. + if let Some(ref parent_beacon_block_root) = self.parent_beacon_block_root { + parent_beacon_block_root.encode(out); + } + + // Encode EIP-7685 requests root + // + // If new fields are added, the above pattern will need to // be repeated and placeholders added. Otherwise, it's impossible to tell _which_ // fields are missing. This is mainly relevant for contrived cases where a header is // created at random, for example: // * A header is created with a withdrawals root, but no base fee. Shanghai blocks are // post-London, so this is technically not valid. However, a tool like proptest would // generate a block like this. - if let Some(ref parent_beacon_block_root) = self.parent_beacon_block_root { - parent_beacon_block_root.encode(out); + if let Some(ref requests_root) = self.requests_root { + requests_root.encode(out); } } @@ -444,6 +462,7 @@ impl Decodable for Header { blob_gas_used: None, excess_blob_gas: None, parent_beacon_block_root: None, + requests_root: None, }; if started_len - buf.len() < rlp_head.payload_length { this.base_fee_per_gas = Some(u64::decode(buf)?); @@ -463,7 +482,14 @@ impl Decodable for Header { this.excess_blob_gas = Some(u64::decode(buf)?); } - // Decode parent beacon block root. If new fields are added, the above pattern will need to + // Decode parent beacon block root. + if started_len - buf.len() < rlp_head.payload_length { + this.parent_beacon_block_root = Some(B256::decode(buf)?); + } + + // Decode requests root. + // + // If new fields are added, the above pattern will need to // be repeated and placeholders decoded. Otherwise, it's impossible to tell _which_ // fields are missing. This is mainly relevant for contrived cases where a header is // created at random, for example: @@ -471,7 +497,7 @@ impl Decodable for Header { // post-London, so this is technically not valid. However, a tool like proptest would // generate a block like this. if started_len - buf.len() < rlp_head.payload_length { - this.parent_beacon_block_root = Some(B256::decode(buf)?); + this.requests_root = Some(B256::decode(buf)?); } let consumed = started_len - buf.len(); @@ -1059,6 +1085,7 @@ mod tests { blob_gas_used: None, excess_blob_gas: None, parent_beacon_block_root: None, + requests_root: None }; assert_eq!(header.hash_slow(), expected_hash); } @@ -1183,6 +1210,7 @@ mod tests { blob_gas_used: Some(0x020000), excess_blob_gas: Some(0), parent_beacon_block_root: None, + requests_root: None, }; let header = Header::decode(&mut data.as_slice()).unwrap(); @@ -1228,6 +1256,7 @@ mod tests { parent_beacon_block_root: None, blob_gas_used: Some(0), excess_blob_gas: Some(0x1600000), + requests_root: None, }; let header = Header::decode(&mut data.as_slice()).unwrap(); diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index b10582cf9..7681ddb9b 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -39,12 +39,12 @@ pub mod op_mainnet; pub mod proofs; mod prune; mod receipt; +mod request; /// Helpers for working with revm pub mod revm; pub mod stage; pub use reth_static_file_types as static_file; mod storage; -/// Helpers for working with transactions pub mod transaction; pub mod trie; mod withdrawal; @@ -85,6 +85,7 @@ pub use prune::{ pub use receipt::{ gas_spent_by_transactions, Receipt, ReceiptWithBloom, ReceiptWithBloomRef, Receipts, }; +pub use request::Requests; pub use static_file::StaticFileSegment; pub use storage::StorageEntry; @@ -109,6 +110,7 @@ pub use withdrawal::{Withdrawal, Withdrawals}; // Re-exports pub use self::ruint::UintTryTo; +pub use alloy_consensus::Request; pub use alloy_primitives::{ self, address, b256, bloom, bytes, bytes::{Buf, BufMut, BytesMut}, diff --git a/crates/primitives/src/proofs.rs b/crates/primitives/src/proofs.rs index b16fa6879..e8015a276 100644 --- a/crates/primitives/src/proofs.rs +++ b/crates/primitives/src/proofs.rs @@ -4,9 +4,10 @@ use crate::{ constants::EMPTY_OMMER_ROOT_HASH, keccak256, trie::{HashBuilder, Nibbles, TrieAccount}, - Address, Header, Receipt, ReceiptWithBloom, ReceiptWithBloomRef, TransactionSigned, Withdrawal, - B256, U256, + Address, Header, Receipt, ReceiptWithBloom, ReceiptWithBloomRef, Request, TransactionSigned, + Withdrawal, B256, U256, }; +use alloy_eips::eip7685::Encodable7685; use alloy_rlp::Encodable; use itertools::Itertools; @@ -69,6 +70,13 @@ pub fn calculate_receipt_root(receipts: &[ReceiptWithBloom]) -> B256 { ordered_trie_root_with_encoder(receipts, |r, buf| r.encode_inner(buf, false)) } +/// Calculate [EIP-7685](https://eips.ethereum.org/EIPS/eip-7685) requests root. +/// +/// NOTE: The requests are encoded as `id + request` +pub fn calculate_requests_root(requests: &[Request]) -> B256 { + ordered_trie_root_with_encoder(requests, |item, buf| item.encode_7685(buf)) +} + /// Calculates the receipt root for a header. #[cfg(feature = "optimism")] pub fn calculate_receipt_root_optimism( diff --git a/crates/primitives/src/request.rs b/crates/primitives/src/request.rs new file mode 100644 index 000000000..ef0f75d54 --- /dev/null +++ b/crates/primitives/src/request.rs @@ -0,0 +1,55 @@ +//! EIP-7685 requests. + +use crate::Request; +use alloy_eips::eip7685::{Decodable7685, Encodable7685}; +use alloy_rlp::{Decodable, Encodable}; +use reth_codecs::{main_codec, Compact}; +use revm_primitives::Bytes; + +/// A list of EIP-7685 requests. +#[main_codec] +#[derive(Debug, Clone, PartialEq, Eq, Default, Hash)] +pub struct Requests(pub Vec); + +impl From> for Requests { + fn from(requests: Vec) -> Self { + Self(requests) + } +} + +impl IntoIterator for Requests { + type Item = Request; + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +impl Encodable for Requests { + fn encode(&self, out: &mut dyn bytes::BufMut) { + let mut h = alloy_rlp::Header { list: true, payload_length: 0 }; + + let mut encoded = Vec::new(); + for req in self.0.iter() { + let encoded_req = req.encoded_7685(); + h.payload_length += encoded_req.len(); + encoded.push(Bytes::from(encoded_req)); + } + + h.encode(out); + for req in encoded { + req.encode(out); + } + } +} + +impl Decodable for Requests { + fn decode(buf: &mut &[u8]) -> alloy_rlp::Result { + Ok( as Decodable>::decode(buf)? + .into_iter() + .map(|bytes| Request::decode_7685(&mut bytes.as_ref())) + .collect::, alloy_eips::eip7685::Eip7685Error>>() + .map(Self)?) + } +} diff --git a/crates/primitives/src/revm/mod.rs b/crates/primitives/src/revm/mod.rs index 311c5e2b5..1d527d28c 100644 --- a/crates/primitives/src/revm/mod.rs +++ b/crates/primitives/src/revm/mod.rs @@ -1,3 +1,5 @@ +//! Helpers for working with revm. + /// The `compat` module contains utility functions that perform conversions between reth and revm, /// compare analogous types from the two implementations, and calculate intrinsic gas usage. /// diff --git a/crates/primitives/src/transaction/mod.rs b/crates/primitives/src/transaction/mod.rs index d481bed16..f53405d08 100644 --- a/crates/primitives/src/transaction/mod.rs +++ b/crates/primitives/src/transaction/mod.rs @@ -1,3 +1,5 @@ +//! Transaction types. + #[cfg(any(feature = "arbitrary", feature = "zstd-codec"))] use crate::compression::{TRANSACTION_COMPRESSOR, TRANSACTION_DECOMPRESSOR}; use crate::{keccak256, Address, BlockHashOrNumber, Bytes, TxHash, TxKind, B256, U256}; diff --git a/crates/revm/src/batch.rs b/crates/revm/src/batch.rs index 77f747cd9..4eb29eee7 100644 --- a/crates/revm/src/batch.rs +++ b/crates/revm/src/batch.rs @@ -3,7 +3,8 @@ use crate::{precompile::Address, primitives::alloy_primitives::BlockNumber}; use reth_execution_errors::BlockExecutionError; use reth_primitives::{ - PruneMode, PruneModes, PruneSegmentError, Receipt, Receipts, MINIMUM_PRUNING_DISTANCE, + PruneMode, PruneModes, PruneSegmentError, Receipt, Receipts, Request, Requests, + MINIMUM_PRUNING_DISTANCE, }; use revm::db::states::bundle_state::BundleRetention; use std::time::Duration; @@ -23,6 +24,13 @@ pub struct BlockBatchRecord { /// /// If receipt is None it means it is pruned. receipts: Receipts, + /// The collection of EIP-7685 requests. + /// Outer vector stores requests for each block sequentially. + /// The inner vector stores requests ordered by transaction number. + /// + /// A transaction may have zero or more requests, so the length of the inner vector is not + /// guaranteed to be the same as the number of transactions. + requests: Vec, /// Memoized address pruning filter. /// Empty implies that there is going to be addresses to include in the filter in a future /// block. None means there isn't any kind of configuration. @@ -75,6 +83,11 @@ impl BlockBatchRecord { std::mem::take(&mut self.receipts) } + /// Returns all recorded requests. + pub fn take_requests(&mut self) -> Vec { + std::mem::take(&mut self.requests) + } + /// Returns the [BundleRetention] for the given block based on the configured prune modes. pub fn bundle_retention(&self, block_number: BlockNumber) -> BundleRetention { if self.tip.map_or(true, |tip| { @@ -155,6 +168,11 @@ impl BlockBatchRecord { Ok(()) } + + /// Save EIP-7685 requests to the executor. + pub fn save_requests(&mut self, requests: Vec) { + self.requests.push(requests.into()); + } } /// Block execution statistics. Contains duration of each step of block execution. diff --git a/crates/rpc/rpc-api/src/engine.rs b/crates/rpc/rpc-api/src/engine.rs index be20a4fbe..e858f62df 100644 --- a/crates/rpc/rpc-api/src/engine.rs +++ b/crates/rpc/rpc-api/src/engine.rs @@ -9,8 +9,8 @@ use reth_primitives::{Address, BlockHash, BlockId, BlockNumberOrTag, Bytes, B256 use reth_rpc_types::{ engine::{ ClientVersionV1, ExecutionPayloadBodiesV1, ExecutionPayloadInputV2, ExecutionPayloadV1, - ExecutionPayloadV3, ForkchoiceState, ForkchoiceUpdated, PayloadId, PayloadStatus, - TransitionConfiguration, + ExecutionPayloadV3, ExecutionPayloadV4, ForkchoiceState, ForkchoiceUpdated, PayloadId, + PayloadStatus, TransitionConfiguration, }, state::StateOverride, BlockOverrides, Filter, Log, RichBlock, SyncStatus, TransactionRequest, @@ -46,6 +46,17 @@ pub trait EngineApi { parent_beacon_block_root: B256, ) -> RpcResult; + /// Post Prague payload handler + /// + /// See also + #[method(name = "newPayloadV4")] + async fn new_payload_v4( + &self, + payload: ExecutionPayloadV4, + versioned_hashes: Vec, + parent_beacon_block_root: B256, + ) -> RpcResult; + /// See also /// /// Caution: This should not accept the `withdrawals` field in the payload attributes. @@ -116,6 +127,16 @@ pub trait EngineApi { #[method(name = "getPayloadV3")] async fn get_payload_v3(&self, payload_id: PayloadId) -> RpcResult; + /// Post Prague payload handler. + /// + /// See also + /// + /// Returns the most recent version of the payload that is available in the corresponding + /// payload build process at the time of receiving this call. Note: + /// > Provider software MAY stop the corresponding build process after serving this call. + #[method(name = "getPayloadV4")] + async fn get_payload_v4(&self, payload_id: PayloadId) -> RpcResult; + /// See also #[method(name = "getPayloadBodiesByHashV1")] async fn get_payload_bodies_by_hash_v1( diff --git a/crates/rpc/rpc-engine-api/src/engine_api.rs b/crates/rpc/rpc-engine-api/src/engine_api.rs index 8d51884d5..c3713f731 100644 --- a/crates/rpc/rpc-engine-api/src/engine_api.rs +++ b/crates/rpc/rpc-engine-api/src/engine_api.rs @@ -24,6 +24,10 @@ use std::{sync::Arc, time::Instant}; use tokio::sync::oneshot; use tracing::{trace, warn}; +/// The list of additional V4 caps +// TODO(mattsse): move to alloy +const V4_CAPABILITIES: [&str; 2] = ["engine_getPayloadV4", "engine_newPayloadV4"]; + /// The Engine API response sender. pub type EngineApiSender = oneshot::Sender>; @@ -331,7 +335,7 @@ where pub async fn get_payload_v4( &self, payload_id: PayloadId, - ) -> EngineApiResult { + ) -> EngineApiResult { // First we fetch the payload attributes to check the timestamp let attributes = self.get_payload_attributes(payload_id).await?; @@ -508,7 +512,7 @@ where /// validated according to the Shanghai rules, as well as the validity changes from cancun: /// /// - /// * If the version is [EngineApiMessageVersion::V3], then the payload attributes will be + /// * If the version above [EngineApiMessageVersion::V3], then the payload attributes will be /// validated according to the Cancun rules. async fn validate_and_execute_forkchoice( &self, @@ -595,6 +599,22 @@ where Ok(res?) } + async fn new_payload_v4( + &self, + payload: ExecutionPayloadV4, + versioned_hashes: Vec, + parent_beacon_block_root: B256, + ) -> RpcResult { + trace!(target: "rpc::engine", "Serving engine_newPayloadV4"); + let start = Instant::now(); + let res = + EngineApi::new_payload_v4(self, payload, versioned_hashes, parent_beacon_block_root) + .await; + self.inner.metrics.latency.new_payload_v4.record(start.elapsed()); + self.inner.metrics.new_payload_response.update_response_metrics(&res); + Ok(res?) + } + /// Handler for `engine_forkchoiceUpdatedV1` /// See also /// @@ -708,6 +728,26 @@ where Ok(res?) } + /// Handler for `engine_getPayloadV4` + /// + /// Returns the most recent version of the payload that is available in the corresponding + /// payload build process at the time of receiving this call. + /// + /// See also + /// + /// Note: + /// > Provider software MAY stop the corresponding build process after serving this call. + async fn get_payload_v4( + &self, + payload_id: PayloadId, + ) -> RpcResult { + trace!(target: "rpc::engine", "Serving engine_getPayloadV4"); + let start = Instant::now(); + let res = EngineApi::get_payload_v4(self, payload_id).await; + self.inner.metrics.latency.get_payload_v4.record(start.elapsed()); + Ok(res?) + } + /// Handler for `engine_getPayloadBodiesByHashV1` /// See also async fn get_payload_bodies_by_hash_v1( @@ -777,7 +817,7 @@ where /// Handler for `engine_exchangeCapabilitiesV1` /// See also async fn exchange_capabilities(&self, _capabilities: Vec) -> RpcResult> { - Ok(CAPABILITIES.into_iter().map(str::to_owned).collect()) + Ok(CAPABILITIES.into_iter().chain(V4_CAPABILITIES.into_iter()).map(str::to_owned).collect()) } } diff --git a/crates/rpc/rpc-engine-api/src/metrics.rs b/crates/rpc/rpc-engine-api/src/metrics.rs index d63611f7d..b8679c84e 100644 --- a/crates/rpc/rpc-engine-api/src/metrics.rs +++ b/crates/rpc/rpc-engine-api/src/metrics.rs @@ -24,6 +24,8 @@ pub(crate) struct EngineApiLatencyMetrics { pub(crate) new_payload_v2: Histogram, /// Latency for `engine_newPayloadV3` pub(crate) new_payload_v3: Histogram, + /// Latency for `engine_newPayloadV4` + pub(crate) new_payload_v4: Histogram, /// Latency for `engine_forkchoiceUpdatedV1` pub(crate) fork_choice_updated_v1: Histogram, /// Latency for `engine_forkchoiceUpdatedV2` @@ -36,6 +38,8 @@ pub(crate) struct EngineApiLatencyMetrics { pub(crate) get_payload_v2: Histogram, /// Latency for `engine_getPayloadV3` pub(crate) get_payload_v3: Histogram, + /// Latency for `engine_getPayloadV4` + pub(crate) get_payload_v4: Histogram, /// Latency for `engine_getPayloadBodiesByRangeV1` pub(crate) get_payload_bodies_by_range_v1: Histogram, /// Latency for `engine_getPayloadBodiesByHashV1` diff --git a/crates/rpc/rpc-engine-api/tests/it/payload.rs b/crates/rpc/rpc-engine-api/tests/it/payload.rs index e2b39691a..a68eef639 100644 --- a/crates/rpc/rpc-engine-api/tests/it/payload.rs +++ b/crates/rpc/rpc-engine-api/tests/it/payload.rs @@ -28,6 +28,7 @@ fn transform_block Block>(src: SealedBlock, f: F) -> Executi body: transformed.body, ommers: transformed.ommers, withdrawals: transformed.withdrawals, + requests: transformed.requests, }) .0 } diff --git a/crates/rpc/rpc-types-compat/src/block.rs b/crates/rpc/rpc-types-compat/src/block.rs index 1c2a44ebb..d3840b24e 100644 --- a/crates/rpc/rpc-types-compat/src/block.rs +++ b/crates/rpc/rpc-types-compat/src/block.rs @@ -90,9 +90,11 @@ pub fn from_block_full( )) } -/// Converts from a [reth_primitives::SealedHeader] to a [reth_rpc_types::BlockNumberOrTag] +/// Converts from a [reth_primitives::SealedHeader] to a [reth_rpc_types::Header] /// -/// Note: This does not set the `totalDifficulty` field. +/// # Note +/// +/// This does not set the `totalDifficulty` field. pub fn from_primitive_with_hash(primitive_header: reth_primitives::SealedHeader) -> Header { let (header, hash) = primitive_header.split(); let PrimitiveHeader { @@ -116,6 +118,7 @@ pub fn from_primitive_with_hash(primitive_header: reth_primitives::SealedHeader) blob_gas_used, excess_blob_gas, parent_beacon_block_root, + requests_root, } = header; Header { @@ -141,7 +144,7 @@ pub fn from_primitive_with_hash(primitive_header: reth_primitives::SealedHeader) excess_blob_gas: excess_blob_gas.map(u128::from), parent_beacon_block_root, total_difficulty: None, - requests_root: None, + requests_root, } } diff --git a/crates/rpc/rpc-types-compat/src/engine/payload.rs b/crates/rpc/rpc-types-compat/src/engine/payload.rs index 9f968a1a4..e68cee2bb 100644 --- a/crates/rpc/rpc-types-compat/src/engine/payload.rs +++ b/crates/rpc/rpc-types-compat/src/engine/payload.rs @@ -4,7 +4,7 @@ use reth_primitives::{ constants::{EMPTY_OMMER_ROOT_HASH, MAXIMUM_EXTRA_DATA_SIZE, MIN_PROTOCOL_BASE_FEE_U256}, proofs::{self}, - Block, Header, SealedBlock, TransactionSigned, UintTryTo, Withdrawals, B256, U256, + Block, Header, Request, SealedBlock, TransactionSigned, UintTryTo, Withdrawals, B256, U256, }; use reth_rpc_types::engine::{ payload::{ExecutionPayloadBodyV1, ExecutionPayloadFieldV2, ExecutionPayloadInputV2}, @@ -55,6 +55,7 @@ pub fn try_payload_v1_to_block(payload: ExecutionPayloadV1) -> Result Result Result Result { - // this performs the same conversion as the underlying V3 payload. - // - // the new request lists (`deposit_requests`, `withdrawal_requests`) are EL -> CL only, so we do - // not do anything special here to handle them - try_payload_v3_to_block(payload.payload_inner) + let ExecutionPayloadV4 { payload_inner, deposit_requests, withdrawal_requests } = payload; + let mut block = try_payload_v3_to_block(payload_inner)?; + + // attach requests with asc type identifiers + let requests = deposit_requests + .into_iter() + .map(Request::DepositRequest) + .chain(withdrawal_requests.into_iter().map(Request::WithdrawalRequest)) + .collect::>(); + + let requests_root = proofs::calculate_requests_root(&requests); + block.header.requests_root = Some(requests_root); + block.requests = Some(requests.into()); + + Ok(block) } -/// Converts [SealedBlock] to [ExecutionPayload], returning additional data (the parent beacon block -/// root) if the block is a V3 payload +/// Converts [SealedBlock] to [ExecutionPayload] pub fn block_to_payload(value: SealedBlock) -> (ExecutionPayload, Option) { - // todo(onbjerg): check for requests_root here and return payload v4 - if value.header.parent_beacon_block_root.is_some() { + if value.header.requests_root.is_some() { + (ExecutionPayload::V4(block_to_payload_v4(value)), None) + } else if value.header.parent_beacon_block_root.is_some() { // block with parent beacon block root: V3 let (payload, beacon_block_root) = block_to_payload_v3(value); (ExecutionPayload::V3(payload), beacon_block_root) @@ -192,6 +209,33 @@ pub fn block_to_payload_v3(value: SealedBlock) -> (ExecutionPayloadV3, Option ExecutionPayloadV4 { + let (deposit_requests, withdrawal_requests) = + value.requests.take().unwrap_or_default().into_iter().fold( + (Vec::new(), Vec::new()), + |(mut deposits, mut withdrawals), request| { + match request { + Request::DepositRequest(r) => { + deposits.push(r); + } + Request::WithdrawalRequest(r) => { + withdrawals.push(r); + } + _ => {} + }; + + (deposits, withdrawals) + }, + ); + + ExecutionPayloadV4 { + deposit_requests, + withdrawal_requests, + payload_inner: block_to_payload_v3(value).0, + } +} + /// Converts [SealedBlock] to [ExecutionPayloadFieldV2] pub fn convert_block_to_payload_field_v2(value: SealedBlock) -> ExecutionPayloadFieldV2 { // if there are withdrawals, return V2 diff --git a/crates/rpc/rpc/src/eth/api/pending_block.rs b/crates/rpc/rpc/src/eth/api/pending_block.rs index dbb148981..b58949a67 100644 --- a/crates/rpc/rpc/src/eth/api/pending_block.rs +++ b/crates/rpc/rpc/src/eth/api/pending_block.rs @@ -8,8 +8,9 @@ use reth_primitives::{ revm_primitives::{ BlockEnv, CfgEnvWithHandlerCfg, EVMError, Env, InvalidTransaction, ResultAndState, SpecId, }, + trie::EMPTY_ROOT_HASH, Block, BlockId, BlockNumberOrTag, ChainSpec, Header, IntoRecoveredTransaction, Receipt, - Receipts, SealedBlockWithSenders, SealedHeader, B256, EMPTY_OMMER_ROOT_HASH, U256, + Receipts, Requests, SealedBlockWithSenders, SealedHeader, B256, EMPTY_OMMER_ROOT_HASH, U256, }; use reth_provider::{BundleStateWithReceipts, ChainSpecProvider, StateProviderFactory}; use reth_revm::{ @@ -240,6 +241,15 @@ impl PendingBlockEnv { let blob_gas_used = if cfg.handler_cfg.spec_id >= SpecId::CANCUN { Some(sum_blob_gas_used) } else { None }; + // note(onbjerg): the rpc spec has not been changed to include requests, so for now we just + // set these to empty + let (requests, requests_root) = + if chain_spec.is_prague_active_at_timestamp(block_env.timestamp.to::()) { + (Some(Requests::default()), Some(EMPTY_ROOT_HASH)) + } else { + (None, None) + }; + let header = Header { parent_hash, ommers_hash: EMPTY_OMMER_ROOT_HASH, @@ -261,10 +271,11 @@ impl PendingBlockEnv { excess_blob_gas: block_env.get_blob_excess_gas(), extra_data: Default::default(), parent_beacon_block_root, + requests_root, }; // seal the block - let block = Block { header, body: executed_txs, ommers: vec![], withdrawals }; + let block = Block { header, body: executed_txs, ommers: vec![], withdrawals, requests }; Ok(SealedBlockWithSenders { block: block.seal_slow(), senders }) } } diff --git a/crates/stages/src/stages/bodies.rs b/crates/stages/src/stages/bodies.rs index bce56880a..cc5a291fe 100644 --- a/crates/stages/src/stages/bodies.rs +++ b/crates/stages/src/stages/bodies.rs @@ -127,6 +127,7 @@ impl Stage for BodyStage { let mut tx_block_cursor = tx.cursor_write::()?; let mut ommers_cursor = tx.cursor_write::()?; let mut withdrawals_cursor = tx.cursor_write::()?; + let mut requests_cursor = tx.cursor_write::()?; // Get id for the next tx_num of zero if there are no transactions. let mut next_tx_num = tx_block_cursor.last()?.map(|(id, _)| id + 1).unwrap_or_default(); @@ -238,6 +239,13 @@ impl Stage for BodyStage { .append(block_number, StoredBlockWithdrawals { withdrawals })?; } } + + // Write requests if any + if let Some(requests) = block.requests { + if !requests.0.is_empty() { + requests_cursor.append(block_number, requests)?; + } + } } BlockResponse::Empty(_) => {} }; @@ -273,6 +281,7 @@ impl Stage for BodyStage { let mut body_cursor = tx.cursor_write::()?; let mut ommers_cursor = tx.cursor_write::()?; let mut withdrawals_cursor = tx.cursor_write::()?; + let mut requests_cursor = tx.cursor_write::()?; // Cursors to unwind transitions let mut tx_block_cursor = tx.cursor_write::()?; @@ -292,6 +301,11 @@ impl Stage for BodyStage { withdrawals_cursor.delete_current()?; } + // Delete the requests entry if any + if requests_cursor.seek_exact(number)?.is_some() { + requests_cursor.delete_current()?; + } + // Delete all transaction to block values. if !block_meta.is_empty() && tx_block_cursor.seek_exact(block_meta.last_tx_num())?.is_some() @@ -663,6 +677,7 @@ mod tests { transactions: block.body.clone(), ommers: block.ommers.clone(), withdrawals: block.withdrawals.clone(), + requests: block.requests.clone(), }, ) } @@ -941,6 +956,7 @@ mod tests { body: body.transactions, ommers: body.ommers, withdrawals: body.withdrawals, + requests: body.requests, })); } diff --git a/crates/stages/src/stages/execution.rs b/crates/stages/src/stages/execution.rs index d3bcfba17..2c03e34a4 100644 --- a/crates/stages/src/stages/execution.rs +++ b/crates/stages/src/stages/execution.rs @@ -273,7 +273,8 @@ where } } let time = Instant::now(); - let BatchBlockExecutionOutput { bundle, receipts, first_block } = executor.finalize(); + let BatchBlockExecutionOutput { bundle, receipts, requests: _, first_block } = + executor.finalize(); let state = BundleStateWithReceipts::new(bundle, receipts, first_block); let write_preparation_duration = time.elapsed(); diff --git a/crates/stages/src/stages/merkle.rs b/crates/stages/src/stages/merkle.rs index cdf33b40f..7590f9d06 100644 --- a/crates/stages/src/stages/merkle.rs +++ b/crates/stages/src/stages/merkle.rs @@ -516,7 +516,7 @@ mod tests { accounts.iter().map(|(addr, acc)| (*addr, (*acc, std::iter::empty()))), )?; - let SealedBlock { header, body, ommers, withdrawals } = random_block( + let SealedBlock { header, body, ommers, withdrawals, requests } = random_block( &mut rng, stage_progress, preblocks.last().map(|b| b.hash()), @@ -531,7 +531,8 @@ mod tests { .into_iter() .map(|(address, account)| (address, (account, std::iter::empty()))), ); - let sealed_head = SealedBlock { header: header.seal_slow(), body, ommers, withdrawals }; + let sealed_head = + SealedBlock { header: header.seal_slow(), body, ommers, withdrawals, requests }; let head_hash = sealed_head.hash(); let mut blocks = vec![sealed_head]; diff --git a/crates/storage/codecs/Cargo.toml b/crates/storage/codecs/Cargo.toml index 958ccf917..e370233d1 100644 --- a/crates/storage/codecs/Cargo.toml +++ b/crates/storage/codecs/Cargo.toml @@ -15,8 +15,9 @@ workspace = true reth-codecs-derive = { path = "./derive", default-features = false } # eth +alloy-consensus = { workspace = true, optional = true } alloy-eips = { workspace = true, optional = true } -alloy-genesis = { workspace = true, optional = true } +alloy-genesis = { workspace = true, optional = true } alloy-primitives.workspace = true # misc @@ -25,7 +26,10 @@ modular-bitfield = { workspace = true, optional = true } serde.workspace = true [dev-dependencies] -alloy-eips = { workspace = true, default-features = false, features = ["arbitrary", "serde"] } +alloy-eips = { workspace = true, default-features = false, features = [ + "arbitrary", + "serde", +] } alloy-primitives = { workspace = true, features = ["arbitrary", "serde"] } test-fuzz.workspace = true serde_json.workspace = true @@ -37,5 +41,10 @@ proptest-derive.workspace = true [features] default = ["std", "alloy"] std = ["alloy-primitives/std", "bytes/std"] -alloy = ["dep:alloy-eips", "dep:alloy-genesis", "dep:modular-bitfield"] +alloy = [ + "dep:alloy-consensus", + "dep:alloy-eips", + "dep:alloy-genesis", + "dep:modular-bitfield", +] optimism = ["reth-codecs-derive/optimism"] diff --git a/crates/storage/codecs/src/alloy/mod.rs b/crates/storage/codecs/src/alloy/mod.rs index 664ab2607..b36f4c943 100644 --- a/crates/storage/codecs/src/alloy/mod.rs +++ b/crates/storage/codecs/src/alloy/mod.rs @@ -1,5 +1,6 @@ mod access_list; mod genesis_account; mod log; +mod request; mod txkind; mod withdrawal; diff --git a/crates/storage/codecs/src/alloy/request.rs b/crates/storage/codecs/src/alloy/request.rs new file mode 100644 index 000000000..d5d4daa4a --- /dev/null +++ b/crates/storage/codecs/src/alloy/request.rs @@ -0,0 +1,39 @@ +//! Native Compact codec impl for EIP-7685 requests. + +use crate::Compact; +use alloy_consensus::Request; +use alloy_eips::eip7685::{Decodable7685, Encodable7685}; +use alloy_primitives::Bytes; +use bytes::BufMut; + +impl Compact for Request { + fn to_compact(self, buf: &mut B) -> usize + where + B: BufMut + AsMut<[u8]>, + { + let encoded: Bytes = self.encoded_7685().into(); + encoded.to_compact(buf) + } + + fn from_compact(buf: &[u8], _: usize) -> (Self, &[u8]) { + let (raw, buf) = Bytes::from_compact(buf, buf.len()); + + (Request::decode_7685(&mut raw.as_ref()).expect("invalid eip-7685 request in db"), buf) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use proptest::proptest; + + proptest! { + #[test] + fn roundtrip(request: Request) { + let mut buf = Vec::::new(); + request.to_compact(&mut buf); + let (decoded, _) = Request::from_compact(&buf, buf.len()); + assert_eq!(request, decoded); + } + } +} diff --git a/crates/storage/db/src/tables/codecs/compact.rs b/crates/storage/db/src/tables/codecs/compact.rs index aed8d97ef..907fb2146 100644 --- a/crates/storage/db/src/tables/codecs/compact.rs +++ b/crates/storage/db/src/tables/codecs/compact.rs @@ -51,6 +51,7 @@ impl_compression_for_compact!( StageCheckpoint, PruneCheckpoint, ClientVersion, + Requests, // Non-DB GenesisAccount ); diff --git a/crates/storage/db/src/tables/mod.rs b/crates/storage/db/src/tables/mod.rs index b10662325..cc420fabc 100644 --- a/crates/storage/db/src/tables/mod.rs +++ b/crates/storage/db/src/tables/mod.rs @@ -41,7 +41,7 @@ use reth_primitives::{ stage::StageCheckpoint, trie::{StorageTrieEntry, StoredBranchNode, StoredNibbles, StoredNibblesSubKey}, Account, Address, BlockHash, BlockNumber, Bytecode, Header, IntegerList, PruneCheckpoint, - PruneSegment, Receipt, StorageEntry, TransactionSignedNoHash, TxHash, TxNumber, B256, + PruneSegment, Receipt, Requests, StorageEntry, TransactionSignedNoHash, TxHash, TxNumber, B256, }; use std::fmt; @@ -377,6 +377,9 @@ tables! { /// Stores the history of client versions that have accessed the database with write privileges by unix timestamp in seconds. table VersionHistory; + + /// Stores EIP-7685 EL -> CL requests, indexed by block number. + table BlockRequests; } // Alias types. diff --git a/crates/storage/provider/src/providers/database/metrics.rs b/crates/storage/provider/src/providers/database/metrics.rs index c103ae5f6..0eb21aed8 100644 --- a/crates/storage/provider/src/providers/database/metrics.rs +++ b/crates/storage/provider/src/providers/database/metrics.rs @@ -56,6 +56,7 @@ pub(crate) enum Action { InsertTransactions, InsertTransactionHashNumbers, InsertBlockWithdrawals, + InsertBlockRequests, InsertBlockBodyIndices, InsertTransactionBlocks, @@ -83,6 +84,7 @@ impl Action { Action::InsertTransactions => "insert transactions", Action::InsertTransactionHashNumbers => "insert transaction hash numbers", Action::InsertBlockWithdrawals => "insert block withdrawals", + Action::InsertBlockRequests => "insert block withdrawals", Action::InsertBlockBodyIndices => "insert block body indices", Action::InsertTransactionBlocks => "insert transaction blocks", Action::GetNextTxNum => "get next tx num", diff --git a/crates/storage/provider/src/providers/database/mod.rs b/crates/storage/provider/src/providers/database/mod.rs index e8987b7d4..354b56937 100644 --- a/crates/storage/provider/src/providers/database/mod.rs +++ b/crates/storage/provider/src/providers/database/mod.rs @@ -4,8 +4,9 @@ use crate::{ traits::{BlockSource, ReceiptProvider}, BlockHashReader, BlockNumReader, BlockReader, ChainSpecProvider, DatabaseProviderFactory, EvmEnvProvider, HeaderProvider, HeaderSyncGap, HeaderSyncGapProvider, HeaderSyncMode, - ProviderError, PruneCheckpointReader, StageCheckpointReader, StateProviderBox, - StaticFileProviderFactory, TransactionVariant, TransactionsProvider, WithdrawalsProvider, + ProviderError, PruneCheckpointReader, RequestsProvider, StageCheckpointReader, + StateProviderBox, StaticFileProviderFactory, TransactionVariant, TransactionsProvider, + WithdrawalsProvider, }; use reth_db::{ database::Database, init_db, mdbx::DatabaseArguments, models::StoredBlockBodyIndices, @@ -465,6 +466,19 @@ impl WithdrawalsProvider for ProviderFactory { } } +impl RequestsProvider for ProviderFactory +where + DB: Database, +{ + fn requests_by_block( + &self, + id: BlockHashOrNumber, + timestamp: u64, + ) -> ProviderResult> { + self.provider()?.requests_by_block(id, timestamp) + } +} + impl StageCheckpointReader for ProviderFactory { fn get_stage_checkpoint(&self, id: StageId) -> ProviderResult> { self.provider()?.get_stage_checkpoint(id) diff --git a/crates/storage/provider/src/providers/database/provider.rs b/crates/storage/provider/src/providers/database/provider.rs index b3c2608bf..3a571bbd0 100644 --- a/crates/storage/provider/src/providers/database/provider.rs +++ b/crates/storage/provider/src/providers/database/provider.rs @@ -9,8 +9,9 @@ use crate::{ Chain, EvmEnvProvider, HashingWriter, HeaderProvider, HeaderSyncGap, HeaderSyncGapProvider, HeaderSyncMode, HistoricalStateProvider, HistoryWriter, LatestStateProvider, OriginalValuesKnown, ProviderError, PruneCheckpointReader, PruneCheckpointWriter, - StageCheckpointReader, StateProviderBox, StateWriter, StatsReader, StorageReader, - TransactionVariant, TransactionsProvider, TransactionsProviderExt, WithdrawalsProvider, + RequestsProvider, StageCheckpointReader, StateProviderBox, StateWriter, StatsReader, + StorageReader, TransactionVariant, TransactionsProvider, TransactionsProviderExt, + WithdrawalsProvider, }; use itertools::{izip, Itertools}; use reth_db::{ @@ -35,9 +36,10 @@ use reth_primitives::{ trie::Nibbles, Account, Address, Block, BlockHash, BlockHashOrNumber, BlockNumber, BlockWithSenders, ChainInfo, ChainSpec, GotExpected, Head, Header, PruneCheckpoint, PruneLimiter, PruneModes, - PruneSegment, Receipt, SealedBlock, SealedBlockWithSenders, SealedHeader, StaticFileSegment, - StorageEntry, TransactionMeta, TransactionSigned, TransactionSignedEcRecovered, - TransactionSignedNoHash, TxHash, TxNumber, Withdrawal, Withdrawals, B256, U256, + PruneSegment, Receipt, Requests, SealedBlock, SealedBlockWithSenders, SealedHeader, + StaticFileSegment, StorageEntry, TransactionMeta, TransactionSigned, + TransactionSignedEcRecovered, TransactionSignedNoHash, TxHash, TxNumber, Withdrawal, + Withdrawals, B256, U256, }; use reth_storage_errors::provider::{ProviderResult, RootMismatch}; use reth_trie::{ @@ -712,7 +714,14 @@ impl DatabaseProvider { &self, range: impl RangeBounds + Clone, ) -> ProviderResult> { - // For block we need Headers, Bodies, Uncles, withdrawals, Transactions, Signers + // For blocks we need: + // + // - Headers + // - Bodies (transactions) + // - Uncles/ommers + // - Withdrawals + // - Requests + // - Signers let block_headers = self.get_or_take::(range.clone())?; if block_headers.is_empty() { @@ -724,6 +733,7 @@ impl DatabaseProvider { let block_ommers = self.get_or_take::(range.clone())?; let block_withdrawals = self.get_or_take::(range.clone())?; + let block_requests = self.get_or_take::(range.clone())?; let block_tx = self.get_take_block_transaction_range::(range.clone())?; @@ -747,8 +757,10 @@ impl DatabaseProvider { // Ommers can be empty for some blocks let mut block_ommers_iter = block_ommers.into_iter(); let mut block_withdrawals_iter = block_withdrawals.into_iter(); + let mut block_requests_iter = block_requests.into_iter(); let mut block_ommers = block_ommers_iter.next(); let mut block_withdrawals = block_withdrawals_iter.next(); + let mut block_requests = block_requests_iter.next(); let mut blocks = Vec::new(); for ((main_block_number, header), (_, header_hash), (_, tx)) in @@ -782,8 +794,22 @@ impl DatabaseProvider { withdrawals = None } + // requests can be missing + let prague_is_active = self.chain_spec.is_prague_active_at_timestamp(header.timestamp); + let mut requests = Some(Requests::default()); + if prague_is_active { + if let Some((block_number, _)) = block_requests.as_ref() { + if *block_number == main_block_number { + requests = Some(block_requests.take().unwrap().1); + block_requests = block_requests_iter.next(); + } + } + } else { + requests = None; + } + blocks.push(SealedBlockWithSenders { - block: SealedBlock { header, body, ommers, withdrawals }, + block: SealedBlock { header, body, ommers, withdrawals, requests }, senders, }) } @@ -1297,7 +1323,13 @@ impl DatabaseProvider { mut assemble_block: F, ) -> ProviderResult> where - F: FnMut(Range, Header, Vec
, Option) -> ProviderResult, + F: FnMut( + Range, + Header, + Vec
, + Option, + Option, + ) -> ProviderResult, { if range.is_empty() { return Ok(Vec::new()) @@ -1309,6 +1341,7 @@ impl DatabaseProvider { let headers = self.headers_range(range)?; let mut ommers_cursor = self.tx.cursor_read::()?; let mut withdrawals_cursor = self.tx.cursor_read::()?; + let mut requests_cursor = self.tx.cursor_read::()?; let mut block_body_cursor = self.tx.cursor_read::()?; for header in headers { @@ -1333,6 +1366,11 @@ impl DatabaseProvider { } else { None }; + let requests = if self.chain_spec.is_prague_active_at_timestamp(header.timestamp) { + Some(requests_cursor.seek_exact(header.number)?.unwrap_or_default().1) + } else { + None + }; let ommers = if self.chain_spec.final_paris_total_difficulty(header.number).is_some() { Vec::new() @@ -1342,7 +1380,7 @@ impl DatabaseProvider { .map(|(_, o)| o.ommers) .unwrap_or_default() }; - if let Ok(b) = assemble_block(tx_range, header, ommers, withdrawals) { + if let Ok(b) = assemble_block(tx_range, header, ommers, withdrawals, requests) { blocks.push(b); } } @@ -1370,6 +1408,7 @@ impl BlockReader for DatabaseProvider { if let Some(header) = self.header_by_number(number)? { let withdrawals = self.withdrawals_by_block(number.into(), header.timestamp)?; let ommers = self.ommers(number.into())?.unwrap_or_default(); + let requests = self.requests_by_block(number.into(), header.timestamp)?; // If the body indices are not found, this means that the transactions either do not // exist in the database yet, or they do exit but are not indexed. // If they exist but are not indexed, we don't have enough @@ -1379,7 +1418,7 @@ impl BlockReader for DatabaseProvider { None => return Ok(None), }; - return Ok(Some(Block { header, body: transactions, ommers, withdrawals })) + return Ok(Some(Block { header, body: transactions, ommers, withdrawals, requests })) } } @@ -1435,6 +1474,7 @@ impl BlockReader for DatabaseProvider { let ommers = self.ommers(block_number.into())?.unwrap_or_default(); let withdrawals = self.withdrawals_by_block(block_number.into(), header.timestamp)?; + let requests = self.requests_by_block(block_number.into(), header.timestamp)?; // Get the block body // @@ -1465,7 +1505,7 @@ impl BlockReader for DatabaseProvider { }) .collect(); - Block { header, body, ommers, withdrawals } + Block { header, body, ommers, withdrawals, requests } // Note: we're using unchecked here because we know the block contains valid txs wrt to // its height and can ignore the s value check so pre EIP-2 txs are allowed .try_with_senders_unchecked(senders) @@ -1475,7 +1515,7 @@ impl BlockReader for DatabaseProvider { fn block_range(&self, range: RangeInclusive) -> ProviderResult> { let mut tx_cursor = self.tx.cursor_read::()?; - self.process_block_range(range, |tx_range, header, ommers, withdrawals| { + self.process_block_range(range, |tx_range, header, ommers, withdrawals, requests| { let body = if tx_range.is_empty() { Vec::new() } else { @@ -1484,7 +1524,7 @@ impl BlockReader for DatabaseProvider { .map(Into::into) .collect() }; - Ok(Block { header, body, ommers, withdrawals }) + Ok(Block { header, body, ommers, withdrawals, requests }) }) } @@ -1495,7 +1535,7 @@ impl BlockReader for DatabaseProvider { let mut tx_cursor = self.tx.cursor_read::()?; let mut senders_cursor = self.tx.cursor_read::()?; - self.process_block_range(range, |tx_range, header, ommers, withdrawals| { + self.process_block_range(range, |tx_range, header, ommers, withdrawals, requests| { let (body, senders) = if tx_range.is_empty() { (Vec::new(), Vec::new()) } else { @@ -1527,7 +1567,7 @@ impl BlockReader for DatabaseProvider { (body, senders) }; - Block { header, body, ommers, withdrawals } + Block { header, body, ommers, withdrawals, requests } .try_with_senders_unchecked(senders) .map_err(|_| ProviderError::SenderRecoveryError) }) @@ -1835,6 +1875,24 @@ impl WithdrawalsProvider for DatabaseProvider { } } +impl RequestsProvider for DatabaseProvider { + fn requests_by_block( + &self, + id: BlockHashOrNumber, + timestamp: u64, + ) -> ProviderResult> { + if self.chain_spec.is_prague_active_at_timestamp(timestamp) { + if let Some(number) = self.convert_hash_or_number(id)? { + // If we are past Prague, then all blocks should have a requests list, even if + // empty + let requests = self.tx.get::(number)?.unwrap_or_default(); + return Ok(Some(requests)) + } + } + Ok(None) + } +} + impl EvmEnvProvider for DatabaseProvider { fn fill_env_at( &self, @@ -2587,6 +2645,13 @@ impl BlockWriter for DatabaseProvider { } } + if let Some(requests) = block.block.requests { + if !requests.0.is_empty() { + self.tx.put::(block_number, requests)?; + durations_recorder.record_relative(metrics::Action::InsertBlockRequests); + } + } + let block_indices = StoredBlockBodyIndices { first_tx_num, tx_count }; self.tx.put::(block_number, block_indices.clone())?; durations_recorder.record_relative(metrics::Action::InsertBlockBodyIndices); diff --git a/crates/storage/provider/src/providers/mod.rs b/crates/storage/provider/src/providers/mod.rs index d6a7d34c8..a8db59b06 100644 --- a/crates/storage/provider/src/providers/mod.rs +++ b/crates/storage/provider/src/providers/mod.rs @@ -3,9 +3,9 @@ use crate::{ BlockSource, BlockchainTreePendingStateProvider, CanonChainTracker, CanonStateNotifications, CanonStateSubscriptions, ChainSpecProvider, ChangeSetReader, DatabaseProviderFactory, EvmEnvProvider, FullBundleStateDataProvider, HeaderProvider, ProviderError, - PruneCheckpointReader, ReceiptProvider, ReceiptProviderIdExt, StageCheckpointReader, - StateProviderBox, StateProviderFactory, StaticFileProviderFactory, TransactionVariant, - TransactionsProvider, TreeViewer, WithdrawalsProvider, + PruneCheckpointReader, ReceiptProvider, ReceiptProviderIdExt, RequestsProvider, + StageCheckpointReader, StateProviderBox, StateProviderFactory, StaticFileProviderFactory, + TransactionVariant, TransactionsProvider, TreeViewer, WithdrawalsProvider, }; use reth_blockchain_tree_api::{ error::{CanonicalError, InsertBlockError}, @@ -465,6 +465,19 @@ where } } +impl RequestsProvider for BlockchainProvider +where + DB: Database, +{ + fn requests_by_block( + &self, + id: BlockHashOrNumber, + timestamp: u64, + ) -> ProviderResult> { + self.database.requests_by_block(id, timestamp) + } +} + impl StageCheckpointReader for BlockchainProvider where DB: Database, diff --git a/crates/storage/provider/src/providers/static_file/manager.rs b/crates/storage/provider/src/providers/static_file/manager.rs index 275c8935e..1dfd15cd7 100644 --- a/crates/storage/provider/src/providers/static_file/manager.rs +++ b/crates/storage/provider/src/providers/static_file/manager.rs @@ -4,7 +4,7 @@ use super::{ }; use crate::{ to_range, BlockHashReader, BlockNumReader, BlockReader, BlockSource, HeaderProvider, - ReceiptProvider, StatsReader, TransactionVariant, TransactionsProvider, + ReceiptProvider, RequestsProvider, StatsReader, TransactionVariant, TransactionsProvider, TransactionsProviderExt, WithdrawalsProvider, }; use dashmap::{mapref::entry::Entry as DashMapEntry, DashMap}; @@ -1128,6 +1128,17 @@ impl WithdrawalsProvider for StaticFileProvider { } } +impl RequestsProvider for StaticFileProvider { + fn requests_by_block( + &self, + _id: BlockHashOrNumber, + _timestamp: u64, + ) -> ProviderResult> { + // Required data not present in static_files + Err(ProviderError::UnsupportedProvider) + } +} + impl StatsReader for StaticFileProvider { fn count_entries(&self) -> ProviderResult { match T::NAME { diff --git a/crates/storage/provider/src/test_utils/blocks.rs b/crates/storage/provider/src/test_utils/blocks.rs index 32ecb4897..a1e1134d3 100644 --- a/crates/storage/provider/src/test_utils/blocks.rs +++ b/crates/storage/provider/src/test_utils/blocks.rs @@ -8,8 +8,8 @@ use reth_primitives::{ hex_literal::hex, proofs::{state_root_unhashed, storage_root_unhashed}, revm::compat::into_reth_acc, - Address, BlockNumber, Bytes, Header, Receipt, Receipts, SealedBlock, SealedBlockWithSenders, - TxType, Withdrawal, Withdrawals, B256, U256, + Address, BlockNumber, Bytes, Header, Receipt, Receipts, Requests, SealedBlock, + SealedBlockWithSenders, TxType, Withdrawal, Withdrawals, B256, U256, }; use revm::{ db::BundleState, @@ -37,6 +37,7 @@ pub fn assert_genesis_block(provider: &DatabaseProviderRW, g: ); assert_eq!(tx.table::().unwrap(), vec![]); assert_eq!(tx.table::().unwrap(), vec![]); + assert_eq!(tx.table::().unwrap(), vec![]); assert_eq!(tx.table::().unwrap(), vec![]); assert_eq!(tx.table::().unwrap(), vec![]); assert_eq!(tx.table::().unwrap(), vec![]); @@ -108,6 +109,7 @@ pub fn genesis() -> SealedBlock { body: vec![], ommers: vec![], withdrawals: Some(Withdrawals::default()), + requests: Some(Requests::default()), } } diff --git a/crates/storage/provider/src/test_utils/mock.rs b/crates/storage/provider/src/test_utils/mock.rs index 7dd7c5b4d..3498677c0 100644 --- a/crates/storage/provider/src/test_utils/mock.rs +++ b/crates/storage/provider/src/test_utils/mock.rs @@ -2,8 +2,9 @@ use crate::{ traits::{BlockSource, ReceiptProvider}, AccountReader, BlockHashReader, BlockIdReader, BlockNumReader, BlockReader, BlockReaderIdExt, ChainSpecProvider, ChangeSetReader, EvmEnvProvider, FullBundleStateDataProvider, - HeaderProvider, ReceiptProviderIdExt, StateProvider, StateProviderBox, StateProviderFactory, - StateRootProvider, TransactionVariant, TransactionsProvider, WithdrawalsProvider, + HeaderProvider, ReceiptProviderIdExt, RequestsProvider, StateProvider, StateProviderBox, + StateProviderFactory, StateRootProvider, TransactionVariant, TransactionsProvider, + WithdrawalsProvider, }; use parking_lot::Mutex; use reth_db::models::{AccountBeforeTx, StoredBlockBodyIndices}; @@ -679,6 +680,16 @@ impl WithdrawalsProvider for MockEthProvider { } } +impl RequestsProvider for MockEthProvider { + fn requests_by_block( + &self, + _id: BlockHashOrNumber, + _timestamp: u64, + ) -> ProviderResult> { + Ok(None) + } +} + impl ChangeSetReader for MockEthProvider { fn account_block_changeset( &self, diff --git a/crates/storage/provider/src/test_utils/noop.rs b/crates/storage/provider/src/test_utils/noop.rs index 6593b74cc..c97195f94 100644 --- a/crates/storage/provider/src/test_utils/noop.rs +++ b/crates/storage/provider/src/test_utils/noop.rs @@ -2,7 +2,7 @@ use crate::{ traits::{BlockSource, ReceiptProvider}, AccountReader, BlockHashReader, BlockIdReader, BlockNumReader, BlockReader, BlockReaderIdExt, ChainSpecProvider, ChangeSetReader, EvmEnvProvider, HeaderProvider, PruneCheckpointReader, - ReceiptProviderIdExt, StageCheckpointReader, StateProvider, StateProviderBox, + ReceiptProviderIdExt, RequestsProvider, StageCheckpointReader, StateProvider, StateProviderBox, StateProviderFactory, StateRootProvider, TransactionVariant, TransactionsProvider, WithdrawalsProvider, }; @@ -448,6 +448,16 @@ impl WithdrawalsProvider for NoopProvider { } } +impl RequestsProvider for NoopProvider { + fn requests_by_block( + &self, + _id: BlockHashOrNumber, + _timestamp: u64, + ) -> ProviderResult> { + Ok(None) + } +} + impl PruneCheckpointReader for NoopProvider { fn get_prune_checkpoint( &self, diff --git a/crates/storage/storage-api/src/block.rs b/crates/storage/storage-api/src/block.rs index 070e31ed9..bac6053f7 100644 --- a/crates/storage/storage-api/src/block.rs +++ b/crates/storage/storage-api/src/block.rs @@ -1,6 +1,6 @@ use crate::{ BlockIdReader, BlockNumReader, HeaderProvider, ReceiptProvider, ReceiptProviderIdExt, - TransactionsProvider, WithdrawalsProvider, + RequestsProvider, TransactionsProvider, WithdrawalsProvider, }; use reth_db::models::StoredBlockBodyIndices; use reth_primitives::{ @@ -61,6 +61,7 @@ pub trait BlockReader: + HeaderProvider + TransactionsProvider + ReceiptProvider + + RequestsProvider + WithdrawalsProvider + Send + Sync diff --git a/crates/storage/storage-api/src/lib.rs b/crates/storage/storage-api/src/lib.rs index fdbb5e9a0..814b9ac7b 100644 --- a/crates/storage/storage-api/src/lib.rs +++ b/crates/storage/storage-api/src/lib.rs @@ -26,6 +26,9 @@ pub use header::*; mod receipts; pub use receipts::*; +mod requests; +pub use requests::*; + mod state; pub use state::*; diff --git a/crates/storage/storage-api/src/requests.rs b/crates/storage/storage-api/src/requests.rs new file mode 100644 index 000000000..c8b13dc05 --- /dev/null +++ b/crates/storage/storage-api/src/requests.rs @@ -0,0 +1,13 @@ +use reth_primitives::{BlockHashOrNumber, Requests}; +use reth_storage_errors::provider::ProviderResult; + +/// Client trait for fetching EIP-7685 [Requests] for blocks. +#[auto_impl::auto_impl(&, Arc)] +pub trait RequestsProvider: Send + Sync { + /// Get withdrawals by block id. + fn requests_by_block( + &self, + id: BlockHashOrNumber, + timestamp: u64, + ) -> ProviderResult>; +} diff --git a/examples/beacon-api-sidecar-fetcher/src/mined_sidecar.rs b/examples/beacon-api-sidecar-fetcher/src/mined_sidecar.rs index 5f5f4cbf1..70c6c98a9 100644 --- a/examples/beacon-api-sidecar-fetcher/src/mined_sidecar.rs +++ b/examples/beacon-api-sidecar-fetcher/src/mined_sidecar.rs @@ -103,7 +103,7 @@ where let mut actions_to_queue: Vec = Vec::new(); if txs.is_empty() { - return; + return } match self.pool.get_all_blobs_exact(txs.iter().map(|(tx, _)| tx.hash()).collect()) { @@ -157,7 +157,7 @@ where // Request locally first, otherwise request from CL loop { if let Some(mined_sidecar) = this.queued_actions.pop_front() { - return Poll::Ready(Some(Ok(mined_sidecar))); + return Poll::Ready(Some(Ok(mined_sidecar))) } // Check if any pending requests are ready and append to buffer @@ -243,7 +243,7 @@ async fn fetch_blobs_for_block( response.status().as_u16(), "Unhandled HTTP status.".to_string(), )), - }; + } } let bytes = match response.bytes().await { diff --git a/examples/custom-engine-types/src/main.rs b/examples/custom-engine-types/src/main.rs index d16146420..7156440db 100644 --- a/examples/custom-engine-types/src/main.rs +++ b/examples/custom-engine-types/src/main.rs @@ -47,7 +47,7 @@ use reth_payload_builder::{ use reth_primitives::{Address, Chain, ChainSpec, Genesis, Header, Withdrawals, B256}; use reth_rpc_types::{ engine::{ - ExecutionPayloadEnvelopeV2, ExecutionPayloadEnvelopeV3, + ExecutionPayloadEnvelopeV2, ExecutionPayloadEnvelopeV3, ExecutionPayloadEnvelopeV4, PayloadAttributes as EthPayloadAttributes, PayloadId, }, ExecutionPayloadV1, Withdrawal, @@ -167,6 +167,7 @@ impl EngineTypes for CustomEngineTypes { type ExecutionPayloadV1 = ExecutionPayloadV1; type ExecutionPayloadV2 = ExecutionPayloadEnvelopeV2; type ExecutionPayloadV3 = ExecutionPayloadEnvelopeV3; + type ExecutionPayloadV4 = ExecutionPayloadEnvelopeV4; fn validate_version_specific_fields( chain_spec: &ChainSpec, diff --git a/testing/ef-tests/src/models.rs b/testing/ef-tests/src/models.rs index a30aa3d93..c40f89b13 100644 --- a/testing/ef-tests/src/models.rs +++ b/testing/ef-tests/src/models.rs @@ -86,6 +86,8 @@ pub struct Header { pub excess_blob_gas: Option, /// Parent beacon block root. pub parent_beacon_block_root: Option, + /// Requests root. + pub requests_root: Option, } impl From
for SealedHeader { @@ -111,6 +113,7 @@ impl From
for SealedHeader { blob_gas_used: value.blob_gas_used.map(|v| v.to::()), excess_blob_gas: value.excess_blob_gas.map(|v| v.to::()), parent_beacon_block_root: value.parent_beacon_block_root, + requests_root: value.requests_root, }; header.seal(value.hash) }