From b60225fcfbdd3d208c2170dda1c71565e2eaeacb Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Mon, 13 May 2024 15:37:48 +0300 Subject: [PATCH] feat: implement EIP-7685 (#8053) Co-authored-by: Alexey Shekhirin --- Cargo.lock | 38 ++++---- Cargo.toml | 5 +- bin/reth/src/commands/db/diff.rs | 5 +- bin/reth/src/commands/db/stats.rs | 13 +-- crates/blockchain-tree/src/blockchain_tree.rs | 1 + crates/consensus/auto-seal/src/lib.rs | 23 ++++- crates/consensus/auto-seal/src/task.rs | 6 +- crates/consensus/common/src/validation.rs | 15 +++- crates/ethereum/evm/src/execute.rs | 28 +++++- crates/evm/src/execute.rs | 13 ++- .../interfaces/src/test_utils/generators.rs | 1 + 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/node-core/src/utils.rs | 1 + crates/optimism/evm/src/execute.rs | 2 + crates/optimism/evm/src/l1.rs | 2 + crates/optimism/payload/src/builder.rs | 6 +- crates/payload/ethereum/src/lib.rs | 27 +++++- crates/primitives/Cargo.toml | 23 ++++- crates/primitives/src/alloy_compat.rs | 5 ++ crates/primitives/src/block.rs | 41 ++++++++- crates/primitives/src/chain/spec.rs | 13 ++- crates/primitives/src/header.rs | 41 +++++++-- crates/primitives/src/lib.rs | 4 +- crates/primitives/src/proofs.rs | 6 ++ crates/primitives/src/request.rs | 34 +++---- crates/primitives/src/revm/mod.rs | 2 + crates/primitives/src/transaction/mod.rs | 2 + crates/revm/src/batch.rs | 6 +- 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 | 32 +++++-- crates/rpc/rpc/src/eth/api/pending_block.rs | 15 +++- crates/stages/src/stages/bodies.rs | 2 + 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 +- .../bundle_state_with_receipts.rs | 6 +- .../provider/src/providers/database/mod.rs | 18 +++- .../src/providers/database/provider.rs | 88 +++++++++++++++---- crates/storage/provider/src/providers/mod.rs | 19 +++- .../src/providers/static_file/manager.rs | 13 ++- .../storage/provider/src/test_utils/blocks.rs | 5 +- .../storage/provider/src/test_utils/mock.rs | 14 ++- .../storage/provider/src/test_utils/noop.rs | 12 ++- crates/storage/provider/src/traits/block.rs | 3 +- crates/storage/provider/src/traits/mod.rs | 3 + .../storage/provider/src/traits/requests.rs | 13 +++ testing/ef-tests/src/models.rs | 3 + 57 files changed, 580 insertions(+), 149 deletions(-) create mode 100644 crates/storage/codecs/src/alloy/request.rs create mode 100644 crates/storage/provider/src/traits/requests.rs diff --git a/Cargo.lock b/Cargo.lock index 487aaf8ed296..71035cb3e3a9 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=dd7a999)", + "arbitrary", "c-kzg", + "proptest", + "proptest-derive", "serde", ] [[package]] name = "alloy-consensus" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy#af25c53f99a4549ece47625b7d65f088c3487864" +source = "git+https://github.com/alloy-rs/alloy#dd7a999d9efe259c47a34dde046952de795a8f6a" dependencies = [ "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy)", "alloy-primitives", @@ -197,7 +200,7 @@ dependencies = [ [[package]] name = "alloy-eips" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy#af25c53f99a4549ece47625b7d65f088c3487864" +source = "git+https://github.com/alloy-rs/alloy#dd7a999d9efe259c47a34dde046952de795a8f6a" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -222,7 +225,7 @@ dependencies = [ [[package]] name = "alloy-genesis" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy#af25c53f99a4549ece47625b7d65f088c3487864" +source = "git+https://github.com/alloy-rs/alloy#dd7a999d9efe259c47a34dde046952de795a8f6a" dependencies = [ "alloy-primitives", "alloy-serde 0.1.0 (git+https://github.com/alloy-rs/alloy)", @@ -408,7 +411,7 @@ dependencies = [ [[package]] name = "alloy-rpc-types" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy#af25c53f99a4549ece47625b7d65f088c3487864" +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)", @@ -490,7 +493,7 @@ dependencies = [ [[package]] name = "alloy-serde" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy#af25c53f99a4549ece47625b7d65f088c3487864" +source = "git+https://github.com/alloy-rs/alloy#dd7a999d9efe259c47a34dde046952de795a8f6a" dependencies = [ "alloy-primitives", "serde", @@ -897,9 +900,9 @@ dependencies = [ [[package]] name = "async-channel" -version = "2.2.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "136d4d23bcc79e27423727b36823d86233aad06dfea531837b038394d11e9928" +checksum = "9f2776ead772134d55b62dd45e59a79e21612d85d0af729b8b7d3967d601a62a" dependencies = [ "concurrent-queue", "event-listener 5.3.0", @@ -1248,7 +1251,7 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "495f7104e962b7356f0aeb34247aca1fe7d2e783b346582db7f2904cb5717e88" dependencies = [ - "async-channel 2.2.1", + "async-channel 2.3.0", "async-lock", "async-task", "futures-io", @@ -3078,9 +3081,9 @@ dependencies = [ [[package]] name = "fiat-crypto" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38793c55593b33412e3ae40c2c9781ffaa6f438f6f8c10f24e71846fbd7ae01e" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" [[package]] name = "findshlibs" @@ -3493,9 +3496,9 @@ dependencies = [ [[package]] name = "hashlink" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "692eaaf7f7607518dd3cef090f1474b61edc5301d8012f09579920df68b725ee" +checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" dependencies = [ "hashbrown 0.14.5", ] @@ -5700,9 +5703,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "piper" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "668d31b1c4eba19242f2088b2bf3316b82ca31082a8335764db4e083db7485d4" +checksum = "464db0c665917b13ebb5d453ccdec4add5658ee1adc7affc7677615356a8afaf" dependencies = [ "atomic-waker", "fastrand 2.1.0", @@ -6578,6 +6581,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=dd7a999)", "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=dd7a999)", "alloy-genesis 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=dd7a999)", "alloy-primitives", @@ -8318,7 +8322,7 @@ dependencies = [ "bitflags 2.5.0", "fallible-iterator", "fallible-streaming-iterator", - "hashlink 0.9.0", + "hashlink 0.9.1", "libsqlite3-sys", "smallvec", ] @@ -10151,9 +10155,9 @@ dependencies = [ [[package]] name = "waker-fn" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c4517f54858c779bbcbf228f4fca63d121bf85fbecb2dc578cdf4a39395690" +checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" [[package]] name = "walkdir" diff --git a/Cargo.toml b/Cargo.toml index 9f4b29d7c874..fff9d6bfb202 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -382,7 +382,10 @@ secp256k1 = { version = "0.28", default-features = false, features = [ "recovery", ] } # TODO: Remove `k256` feature: https://github.com/sigp/enr/pull/74 -enr = { version = "0.12.0", default-features = false, features = ["k256", "rust-secp256k1"] } +enr = { version = "0.12.0", default-features = false, features = [ + "k256", + "rust-secp256k1", +] } # for eip-4844 c-kzg = "1.0.0" diff --git a/bin/reth/src/commands/db/diff.rs b/bin/reth/src/commands/db/diff.rs index 9c098a50b574..3c7bfb8c07ea 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 b47e7980b02e..0f22b8bad4e0 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_node_core::dirs::{ChainPath, DataDirPath}; use reth_primitives::static_file::{find_fixed_range, SegmentRangeInclusive}; @@ -332,6 +332,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/crates/blockchain-tree/src/blockchain_tree.rs b/crates/blockchain-tree/src/blockchain_tree.rs index 689994471200..cbd40c717c1e 100644 --- a/crates/blockchain-tree/src/blockchain_tree.rs +++ b/crates/blockchain-tree/src/blockchain_tree.rs @@ -1577,6 +1577,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/consensus/auto-seal/src/lib.rs b/crates/consensus/auto-seal/src/lib.rs index e954108c8c40..1520cdd20e25 100644 --- a/crates/consensus/auto-seal/src/lib.rs +++ b/crates/consensus/auto-seal/src/lib.rs @@ -23,7 +23,7 @@ use reth_primitives::{ constants::{EMPTY_TRANSACTIONS, ETHEREUM_BLOCK_GAS_LIMIT}, eip4844::calculate_excess_blob_gas, proofs, Block, BlockBody, BlockHash, BlockHashOrNumber, BlockNumber, ChainSpec, Header, - Receipts, SealedBlock, SealedHeader, TransactionSigned, Withdrawals, B256, U256, + Receipts, Requests, SealedBlock, SealedHeader, TransactionSigned, Withdrawals, B256, U256, }; use reth_provider::{ BlockReaderIdExt, BundleStateWithReceipts, CanonStateNotificationSender, StateProviderFactory, @@ -268,6 +268,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(); @@ -310,6 +311,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) { @@ -345,11 +347,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, @@ -358,14 +362,21 @@ 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, + ); + // todo(onbjerg): when we rewrite this, add support for requests let mut block = Block { header, body: transactions, ommers: ommers.clone(), withdrawals: withdrawals.clone(), + requests: requests.clone(), } .with_recovered_senders() .ok_or(BlockExecutionError::Validation(BlockValidationError::SenderRecoveryError))?; @@ -405,8 +416,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 42f1268f3312..80af743f11eb 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 b67d40e98533..afaeb0c917f5 100644 --- a/crates/consensus/common/src/validation.rs +++ b/crates/consensus/common/src/validation.rs @@ -11,6 +11,8 @@ use reth_primitives::{ }; use reth_provider::{HeaderProvider, WithdrawalsProvider}; +// todo: validate requests root + /// Validate header standalone pub fn validate_header_standalone( header: &SealedHeader, @@ -351,6 +353,7 @@ mod tests { blob_gas_used: None, excess_blob_gas: None, parent_beacon_block_root: None, + requests_root: None }; // size: 0x9b5 @@ -364,7 +367,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] @@ -452,6 +464,7 @@ mod tests { transactions: vec![transaction], ommers: vec![], withdrawals: Some(Withdrawals::default()), + requests: None, }; let block = SealedBlock::new(header, body); diff --git a/crates/ethereum/evm/src/execute.rs b/crates/ethereum/evm/src/execute.rs index f9f53313a524..6f177402b923 100644 --- a/crates/ethereum/evm/src/execute.rs +++ b/crates/ethereum/evm/src/execute.rs @@ -526,6 +526,7 @@ mod tests { body: vec![], ommers: vec![], withdrawals: None, + requests: None, }, senders: vec![], }, @@ -556,6 +557,7 @@ mod tests { body: vec![], ommers: vec![], withdrawals: None, + requests: None, }, senders: vec![], }, @@ -616,7 +618,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, @@ -662,7 +670,13 @@ mod tests { executor .execute_and_verify( &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, @@ -706,6 +720,7 @@ mod tests { body: vec![], ommers: vec![], withdrawals: None, + requests: None, }, senders: vec![], }, @@ -727,7 +742,13 @@ mod tests { .execute_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, @@ -786,6 +807,7 @@ mod tests { body: vec![], ommers: vec![], withdrawals: None, + requests: None, }, senders: vec![], }, diff --git a/crates/evm/src/execute.rs b/crates/evm/src/execute.rs index 3c9750e125ae..53c0ffb16dc5 100644 --- a/crates/evm/src/execute.rs +++ b/crates/evm/src/execute.rs @@ -106,7 +106,7 @@ pub struct BatchBlockExecutionOutput { /// /// 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: Requests, + pub requests: Vec, /// First block of bundle state. pub first_block: BlockNumber, } @@ -116,7 +116,7 @@ impl BatchBlockExecutionOutput { pub fn new( bundle: BundleState, receipts: Receipts, - requests: Requests, + requests: Vec, first_block: BlockNumber, ) -> Self { Self { bundle, receipts, requests, first_block } @@ -261,8 +261,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/interfaces/src/test_utils/generators.rs b/crates/interfaces/src/test_utils/generators.rs index 506358276c74..312ceeb9e296 100644 --- a/crates/interfaces/src/test_utils/generators.rs +++ b/crates/interfaces/src/test_utils/generators.rs @@ -160,6 +160,7 @@ pub fn random_block( body: transactions, ommers, withdrawals: None, + requests: None, } } diff --git a/crates/net/downloaders/src/bodies/bodies.rs b/crates/net/downloaders/src/bodies/bodies.rs index a806f2fa62e4..33139ab501d5 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 40e5301293d8..dadd4b3bdd7b 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 85fac4642822..035a36b903dd 100644 --- a/crates/net/downloaders/src/file_client.rs +++ b/crates/net/downloaders/src/file_client.rs @@ -232,6 +232,7 @@ impl FromReader for FileClient { transactions: block.body, ommers: block.ommers, withdrawals: block.withdrawals, + requests: block.requests, }, ); @@ -485,7 +486,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() { @@ -599,23 +600,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 @@ -626,7 +615,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 b84c5282baca..97e30a02dd8b 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 36b8e6e8ca9e..a8ecd74a24f1 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 3268ff8987af..8820dd67b1a8 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 4e36f191c81e..45c86cb647e8 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/node-core/src/utils.rs b/crates/node-core/src/utils.rs index 32dc509fa140..e72fc792e214 100644 --- a/crates/node-core/src/utils.rs +++ b/crates/node-core/src/utils.rs @@ -120,6 +120,7 @@ where body: block.transactions, ommers: block.ommers, withdrawals: block.withdrawals, + requests: block.requests, }; validate_block_standalone(&block, &chain_spec)?; diff --git a/crates/optimism/evm/src/execute.rs b/crates/optimism/evm/src/execute.rs index 3b3b5bf2305d..b6e51fd28a32 100644 --- a/crates/optimism/evm/src/execute.rs +++ b/crates/optimism/evm/src/execute.rs @@ -571,6 +571,7 @@ mod tests { body: vec![tx, tx_deposit], ommers: vec![], withdrawals: None, + requests: None, }, senders: vec![addr, addr], }, @@ -652,6 +653,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 7b605448ff30..b18f9b890e7d 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/payload/src/builder.rs b/crates/optimism/payload/src/builder.rs index 2794ad96892a..7de11c08bce8 100644 --- a/crates/optimism/payload/src/builder.rs +++ b/crates/optimism/payload/src/builder.rs @@ -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/payload/ethereum/src/lib.rs b/crates/payload/ethereum/src/lib.rs index ac9dafb11432..67477270edb0 100644 --- a/crates/payload/ethereum/src/lib.rs +++ b/crates/payload/ethereum/src/lib.rs @@ -18,12 +18,14 @@ use reth_payload_builder::{ }; 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, state_change::apply_blockhashes_update}; @@ -165,6 +167,13 @@ where blob_gas_used = Some(0); } + 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, @@ -186,9 +195,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)) @@ -424,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, @@ -445,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 d5c339c12511..975c5aa3503c 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -21,7 +21,7 @@ revm-primitives = { workspace = true, features = ["serde"] } # ethereum alloy-chains = { workspace = true, features = ["serde", "rlp"] } -alloy-consensus.workspace = true +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"] } @@ -31,7 +31,11 @@ alloy-eips = { workspace = true, features = ["serde"] } nybbles = { workspace = true, features = ["serde", "rlp"] } # crypto -secp256k1 = { workspace = true, features = ["global-context", "recovery", "rand"] } +secp256k1 = { workspace = true, features = [ + "global-context", + "recovery", + "rand", +] } # for eip-4844 c-kzg = { workspace = true, features = ["serde"], optional = true } @@ -84,7 +88,12 @@ plain_hasher = "0.2" sucds = "0.8.1" criterion.workspace = true -pprof = { workspace = true, features = ["flamegraph", "frame-pointer", "criterion"] } +pprof = { workspace = true, features = [ + "flamegraph", + "frame-pointer", + "criterion", +] } +secp256k1.workspace = true [features] default = ["c-kzg", "zstd-codec"] @@ -101,7 +110,13 @@ arbitrary = [ "dep:proptest-derive", "zstd-codec", ] -c-kzg = ["dep:c-kzg", "revm/c-kzg", "revm-primitives/c-kzg", "dep:tempfile", "alloy-eips/kzg"] +c-kzg = [ + "dep:c-kzg", + "revm/c-kzg", + "revm-primitives/c-kzg", + "dep:tempfile", + "alloy-eips/kzg", +] zstd-codec = ["dep:zstd"] clap = ["dep:clap"] optimism = [ diff --git a/crates/primitives/src/alloy_compat.rs b/crates/primitives/src/alloy_compat.rs index 8b4368a1211b..8a7e058bba56 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 467bca8166ec..efee694933e7 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(), } } @@ -558,7 +583,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 +632,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 98ec3d972dcc..3c166b2c7206 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 db75e1b6d4c0..e7d3f3f9ed64 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-4895 + 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 178236d023b4..78845fc98e7d 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -45,7 +45,6 @@ pub mod revm; pub mod stage; pub mod static_file; mod storage; -/// Helpers for working with transactions pub mod transaction; pub mod trie; mod withdrawal; @@ -84,7 +83,6 @@ pub use prune::{ MINIMUM_PRUNING_DISTANCE, }; pub use receipt::{Receipt, ReceiptWithBloom, ReceiptWithBloomRef, Receipts}; -pub use request::Requests; pub use static_file::StaticFileSegment; pub use storage::StorageEntry; @@ -105,10 +103,12 @@ pub use transaction::{ LEGACY_TX_TYPE_ID, }; +pub use request::Requests; 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 d08fc10a63c8..b366eff27295 100644 --- a/crates/primitives/src/proofs.rs +++ b/crates/primitives/src/proofs.rs @@ -7,6 +7,7 @@ use crate::{ Address, Header, Receipt, ReceiptWithBloom, ReceiptWithBloomRef, TransactionSigned, Withdrawal, B256, U256, }; +use alloy_consensus::Request; use alloy_rlp::Encodable; use bytes::BufMut; use itertools::Itertools; @@ -72,6 +73,11 @@ pub fn calculate_receipt_root(receipts: &[ReceiptWithBloom]) -> B256 { ordered_trie_root_with_encoder(receipts, |r, buf| r.encode_inner(buf, false)) } +/// Calculate EIP-7685 requests root. +pub fn calculate_requests_root(requests: &[Request]) -> B256 { + ordered_trie_root(requests) +} + /// 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 index 1eb6d94accd8..3dda25db959b 100644 --- a/crates/primitives/src/request.rs +++ b/crates/primitives/src/request.rs @@ -1,25 +1,25 @@ +//! EIP-7685 requests. + use alloy_consensus::Request; +use alloy_rlp::{RlpDecodableWrapper, RlpEncodableWrapper}; +use reth_codecs::{main_codec, Compact}; -/// A collection of requests organized as a two-dimensional vector. -#[derive(Clone, Debug, PartialEq, Eq, Default)] -pub struct Requests { - /// A two-dimensional vector of [Request] instances. - pub request_vec: Vec>, -} +/// A list of EIP-7685 requests. +#[main_codec] +#[derive(Debug, Clone, PartialEq, Eq, Default, Hash, RlpEncodableWrapper, RlpDecodableWrapper)] +pub struct Requests(pub Vec); -impl Requests { - /// Create a new [Requests] instance with an empty vector. - pub fn new() -> Self { - Self { request_vec: vec![] } +impl From> for Requests { + fn from(requests: Vec) -> Self { + Self(requests) } +} - /// Create a new [Requests] instance from an existing vector. - pub fn from_vec(vec: Vec>) -> Self { - Self { request_vec: vec } - } +impl IntoIterator for Requests { + type Item = Request; + type IntoIter = std::vec::IntoIter; - /// Push a new vector of [requests](Request) into the [Requests] collection. - pub fn push(&mut self, requests: Vec) { - self.request_vec.push(requests); + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() } } diff --git a/crates/primitives/src/revm/mod.rs b/crates/primitives/src/revm/mod.rs index 311c5e2b5007..1d527d28ca55 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 af80c8271bbd..55965d29816f 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 f30fcd8d6cd3..08ee65bbcba5 100644 --- a/crates/revm/src/batch.rs +++ b/crates/revm/src/batch.rs @@ -30,7 +30,7 @@ pub struct BlockBatchRecord { /// /// 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: Requests, + 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. @@ -84,7 +84,7 @@ impl BlockBatchRecord { } /// Returns all recorded requests. - pub fn take_requests(&mut self) -> Requests { + pub fn take_requests(&mut self) -> Vec { std::mem::take(&mut self.requests) } @@ -171,7 +171,7 @@ impl BlockBatchRecord { /// Save EIP-7685 requests to the executor. pub fn save_requests(&mut self, requests: Vec) { - self.requests.push(requests); + self.requests.push(requests.into()); } } diff --git a/crates/rpc/rpc-engine-api/tests/it/payload.rs b/crates/rpc/rpc-engine-api/tests/it/payload.rs index 22219584c7e1..3a020b8c1836 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, }) } diff --git a/crates/rpc/rpc-types-compat/src/block.rs b/crates/rpc/rpc-types-compat/src/block.rs index 1c2a44ebb6b1..d3840b24e4ee 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 f504c169cb7b..21d6f155dee0 100644 --- a/crates/rpc/rpc-types-compat/src/engine/payload.rs +++ b/crates/rpc/rpc-types-compat/src/engine/payload.rs @@ -51,6 +51,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) + // this performs the same conversion as the underlying V3 payload, but inserts the blob gas + // used and excess blob gas + let mut base_block = try_payload_v3_to_block(payload.payload_inner)?; + + base_block.header.requests_root = None; + base_block.requests = None; + + todo!() } /// Converts [SealedBlock] to [ExecutionPayload] pub fn block_to_payload(value: SealedBlock) -> ExecutionPayload { - // 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)) + } else if value.header.parent_beacon_block_root.is_some() { // block with parent beacon block root: V3 ExecutionPayload::V3(block_to_payload_v3(value)) } else if value.withdrawals.is_some() { @@ -183,6 +194,11 @@ pub fn block_to_payload_v3(value: SealedBlock) -> ExecutionPayloadV3 { } } +/// Converts [SealedBlock] to [ExecutionPayloadV4] +pub fn block_to_payload_v4(_value: SealedBlock) -> ExecutionPayloadV4 { + todo!() +} + /// 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 60bba2a8d1fb..8a4cbff110af 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::{ @@ -244,6 +245,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, @@ -265,10 +275,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 bce56880a953..eb15d55a98bb 100644 --- a/crates/stages/src/stages/bodies.rs +++ b/crates/stages/src/stages/bodies.rs @@ -663,6 +663,7 @@ mod tests { transactions: block.body.clone(), ommers: block.ommers.clone(), withdrawals: block.withdrawals.clone(), + requests: block.requests.clone(), }, ) } @@ -941,6 +942,7 @@ mod tests { body: body.transactions, ommers: body.ommers, withdrawals: body.withdrawals, + requests: body.requests, })); } diff --git a/crates/stages/src/stages/merkle.rs b/crates/stages/src/stages/merkle.rs index cdf33b40f2f0..7590f9d066aa 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 958ccf9174ea..e370233d1c7d 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 664ab26077cd..b36f4c94312b 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 000000000000..d5d4daa4af94 --- /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 aed8d97efee9..907fb2146528 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 b106623259c8..cc420fabcc93 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/bundle_state/bundle_state_with_receipts.rs b/crates/storage/provider/src/bundle_state/bundle_state_with_receipts.rs index b7ce4d176c7a..a2b23a086d87 100644 --- a/crates/storage/provider/src/bundle_state/bundle_state_with_receipts.rs +++ b/crates/storage/provider/src/bundle_state/bundle_state_with_receipts.rs @@ -9,8 +9,8 @@ use reth_interfaces::provider::{ProviderError, ProviderResult}; use reth_primitives::{ logs_bloom, revm::compat::{into_reth_acc, into_revm_acc}, - Account, Address, BlockNumber, Bloom, Bytecode, Log, Receipt, Receipts, Requests, - StaticFileSegment, StorageEntry, B256, U256, + Account, Address, BlockNumber, Bloom, Bytecode, Log, Receipt, Receipts, StaticFileSegment, + StorageEntry, B256, U256, }; use reth_trie::HashedPostState; pub use revm::db::states::OriginalValuesKnown; @@ -49,7 +49,7 @@ impl From for BatchBlockExecutionOutput { fn from(value: BundleStateWithReceipts) -> Self { let BundleStateWithReceipts { bundle, receipts, first_block } = value; // TODO(alexey): add requests - Self { bundle, receipts, requests: Requests::default(), first_block } + Self { bundle, receipts, requests: Vec::default(), first_block } } } diff --git a/crates/storage/provider/src/providers/database/mod.rs b/crates/storage/provider/src/providers/database/mod.rs index c84e9d8cec23..15ef489023ef 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, models::StoredBlockBodyIndices, DatabaseEnv}; use reth_evm::ConfigureEvmEnv; @@ -462,6 +463,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 6e07b7c46a1b..5ff656c800cd 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::{ @@ -39,9 +40,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_trie::{ prefix_set::{PrefixSet, PrefixSetMut, TriePrefixSets}, @@ -715,7 +717,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() { @@ -727,6 +736,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())?; @@ -750,8 +760,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 @@ -785,8 +797,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, }) } @@ -1300,7 +1326,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()) @@ -1312,6 +1344,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 { @@ -1336,6 +1369,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() @@ -1345,7 +1383,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); } } @@ -1373,6 +1411,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 @@ -1382,7 +1421,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 })) } } @@ -1438,6 +1477,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 // @@ -1468,7 +1508,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) @@ -1478,7 +1518,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 { @@ -1487,7 +1527,7 @@ impl BlockReader for DatabaseProvider { .map(Into::into) .collect() }; - Ok(Block { header, body, ommers, withdrawals }) + Ok(Block { header, body, ommers, withdrawals, requests }) }) } @@ -1498,7 +1538,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 { @@ -1530,7 +1570,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) }) @@ -1838,6 +1878,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, diff --git a/crates/storage/provider/src/providers/mod.rs b/crates/storage/provider/src/providers/mod.rs index 8a06f0c0d204..d0ff13a8ec7d 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, BundleStateDataProvider, CanonChainTracker, CanonStateNotifications, CanonStateSubscriptions, ChainSpecProvider, ChangeSetReader, DatabaseProviderFactory, EvmEnvProvider, HeaderProvider, ProviderError, PruneCheckpointReader, - ReceiptProvider, ReceiptProviderIdExt, StageCheckpointReader, StateProviderBox, - StateProviderFactory, StaticFileProviderFactory, TransactionVariant, TransactionsProvider, - TreeViewer, WithdrawalsProvider, + ReceiptProvider, ReceiptProviderIdExt, RequestsProvider, StageCheckpointReader, + StateProviderBox, StateProviderFactory, StaticFileProviderFactory, TransactionVariant, + TransactionsProvider, TreeViewer, WithdrawalsProvider, }; use reth_db::{ database::Database, @@ -468,6 +468,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 7814a709768c..8ababccef427 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 32ecb489758a..c2b228f3be3d 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, @@ -108,6 +108,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 db490bd37ca7..58c7e986ecbe 100644 --- a/crates/storage/provider/src/test_utils/mock.rs +++ b/crates/storage/provider/src/test_utils/mock.rs @@ -2,8 +2,8 @@ use crate::{ traits::{BlockSource, ReceiptProvider}, AccountReader, BlockHashReader, BlockIdReader, BlockNumReader, BlockReader, BlockReaderIdExt, BundleStateDataProvider, ChainSpecProvider, ChangeSetReader, EvmEnvProvider, HeaderProvider, - ReceiptProviderIdExt, StateProvider, StateProviderBox, StateProviderFactory, StateRootProvider, - TransactionVariant, TransactionsProvider, WithdrawalsProvider, + ReceiptProviderIdExt, RequestsProvider, StateProvider, StateProviderBox, StateProviderFactory, + StateRootProvider, TransactionVariant, TransactionsProvider, WithdrawalsProvider, }; use parking_lot::Mutex; use reth_db::models::{AccountBeforeTx, StoredBlockBodyIndices}; @@ -679,6 +679,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 626bd535115e..88ad63ef63a7 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/provider/src/traits/block.rs b/crates/storage/provider/src/traits/block.rs index 1b767f350a39..7ab712619a72 100644 --- a/crates/storage/provider/src/traits/block.rs +++ b/crates/storage/provider/src/traits/block.rs @@ -1,6 +1,6 @@ use crate::{ BlockIdReader, BlockNumReader, BundleStateWithReceipts, Chain, HeaderProvider, ReceiptProvider, - ReceiptProviderIdExt, TransactionsProvider, WithdrawalsProvider, + ReceiptProviderIdExt, RequestsProvider, TransactionsProvider, WithdrawalsProvider, }; use auto_impl::auto_impl; use reth_db::models::StoredBlockBodyIndices; @@ -64,6 +64,7 @@ pub trait BlockReader: + TransactionsProvider + ReceiptProvider + WithdrawalsProvider + + RequestsProvider + Send + Sync { diff --git a/crates/storage/provider/src/traits/mod.rs b/crates/storage/provider/src/traits/mod.rs index c966cd9efa09..f2757d58ba98 100644 --- a/crates/storage/provider/src/traits/mod.rs +++ b/crates/storage/provider/src/traits/mod.rs @@ -48,6 +48,9 @@ pub use transactions::{TransactionsProvider, TransactionsProviderExt}; mod withdrawals; pub use withdrawals::WithdrawalsProvider; +mod requests; +pub use requests::RequestsProvider; + mod chain; pub use chain::{ CanonStateNotification, CanonStateNotificationSender, CanonStateNotificationStream, diff --git a/crates/storage/provider/src/traits/requests.rs b/crates/storage/provider/src/traits/requests.rs new file mode 100644 index 000000000000..fcc6100b804f --- /dev/null +++ b/crates/storage/provider/src/traits/requests.rs @@ -0,0 +1,13 @@ +use reth_interfaces::provider::ProviderResult; +use reth_primitives::{BlockHashOrNumber, Requests}; + +/// 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/testing/ef-tests/src/models.rs b/testing/ef-tests/src/models.rs index a30aa3d930f2..c40f89b13f6c 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) }